From 45ca67ce69c17976158c9de0d38ac4cccfe8bcf8 Mon Sep 17 00:00:00 2001 From: stephane brossier Date: Fri, 28 Oct 2011 15:06:57 -0700 Subject: [PATCH] Initial killbill proto --- .gitignore | 19 + LICENSE-2.0.txt | 202 ++++++ README.md | 0 account/pom.xml | 33 + analytics/pom.xml | 80 +++ .../billing/analytics/AnalyticsListener.java | 58 ++ .../analytics/BusinessSubscription.java | 333 ++++++++++ .../analytics/BusinessSubscriptionEvent.java | 174 +++++ .../BusinessSubscriptionTransition.java | 133 ++++ .../BusinessSubscriptionTransitionBinder.java | 163 +++++ .../BusinessSubscriptionTransitionMapper.java | 89 +++ .../ning/billing/analytics/dao/EventDao.java | 40 ++ .../analytics/dao/EventDaoProvider.java | 38 ++ .../analytics/setup/AnalyticsModule.java | 33 + .../billing/analytics/dao/EventDao.sql.stg | 100 +++ analytics/src/main/resources/ddl.sql | 32 + .../ning/billing/analytics/MockDuration.java | 77 +++ .../com/ning/billing/analytics/MockPhase.java | 112 ++++ .../com/ning/billing/analytics/MockPlan.java | 92 +++ .../ning/billing/analytics/MockProduct.java | 72 +++ .../billing/analytics/MockSubscription.java | 135 ++++ .../analytics/TestBusinessSubscription.java | 102 +++ .../TestBusinessSubscriptionEvent.java | 122 ++++ .../TestBusinessSubscriptionTransition.java | 121 ++++ .../analytics/dao/MysqlTestingHelper.java | 129 ++++ .../billing/analytics/dao/TestEventDao.java | 102 +++ analytics/src/test/resources/log4j.xml | 30 + api/pom.xml | 55 ++ .../ning/billing/BillingExceptionBase.java | 64 ++ .../main/java/com/ning/billing/ErrorCode.java | 63 ++ .../ning/billing/account/api/IAccount.java | 34 + .../billing/account/api/IAccountData.java | 28 + .../ning/billing/account/api/IUserApi.java | 31 + .../billing/catalog/api/ActionPolicy.java | 22 + .../billing/catalog/api/BillingAlignment.java | 23 + .../billing/catalog/api/BillingPeriod.java | 35 + .../ning/billing/catalog/api/Currency.java | 35 + .../ning/billing/catalog/api/ICatalog.java | 58 ++ .../billing/catalog/api/ICatalogUserApi.java | 22 + .../ning/billing/catalog/api/IDuration.java | 25 + .../catalog/api/IInternationalPrice.java | 28 + .../com/ning/billing/catalog/api/IPlan.java | 39 ++ .../ning/billing/catalog/api/IPlanPhase.java | 39 ++ .../com/ning/billing/catalog/api/IPrice.java | 28 + .../ning/billing/catalog/api/IPriceList.java | 30 + .../ning/billing/catalog/api/IProduct.java | 32 + .../billing/catalog/api/IProductTier.java | 23 + .../billing/catalog/api/IProductType.java | 23 + .../catalog/api/InvalidConfigException.java | 30 + .../ning/billing/catalog/api/PhaseType.java | 24 + .../billing/catalog/api/PlanAlignment.java | 24 + .../catalog/api/PlanPhaseSpecifier.java | 33 + .../billing/catalog/api/PlanSpecifier.java | 41 ++ .../billing/catalog/api/ProductCategory.java | 22 + .../ning/billing/catalog/api/TimeUnit.java | 27 + .../entitlement/api/billing/BillingMode.java | 21 + .../EntitlementBillingApiException.java | 40 ++ .../entitlement/api/billing/IBillingApi.java | 47 ++ .../api/billing/IBillingEvent.java | 87 +++ .../api/user/EntitlementUserApiException.java | 32 + .../entitlement/api/user/IApiListener.java | 33 + .../entitlement/api/user/IPrivateFields.java | 24 + .../entitlement/api/user/ISubscription.java | 70 ++ .../api/user/ISubscriptionBundle.java | 34 + .../api/user/ISubscriptionTransition.java | 48 ++ .../entitlement/api/user/IUserApi.java | 43 ++ .../billing/invoice/api/BillingEvent.java | 109 ++++ .../billing/invoice/api/BillingEventSet.java | 52 ++ catalog/pom.xml | 57 ++ .../com/ning/billing/catalog/Catalog.java | 303 +++++++++ .../ning/billing/catalog/CatalogUserApi.java | 38 ++ .../com/ning/billing/catalog/Duration.java | 65 ++ .../billing/catalog/InternationalPrice.java | 98 +++ .../java/com/ning/billing/catalog/Plan.java | 178 +++++ .../billing/catalog/PlanAlignmentCase.java | 117 ++++ .../ning/billing/catalog/PlanCancelCase.java | 117 ++++ .../ning/billing/catalog/PlanChangeCase.java | 130 ++++ .../ning/billing/catalog/PlanChangeRule.java | 95 +++ .../com/ning/billing/catalog/PlanPhase.java | 156 +++++ .../com/ning/billing/catalog/PlanRules.java | 158 +++++ .../java/com/ning/billing/catalog/Price.java | 61 ++ .../com/ning/billing/catalog/PriceList.java | 99 +++ .../com/ning/billing/catalog/Product.java | 121 ++++ .../com/ning/billing/catalog/ProductTier.java | 49 ++ .../com/ning/billing/catalog/ProductType.java | 58 ++ .../billing/catalog/ValidatingConfig.java | 60 ++ .../ning/billing/catalog/ValidationError.java | 47 ++ .../billing/catalog/VersionedCatalog.java | 210 ++++++ .../catalog/io/VersionedCatalogLoader.java | 134 ++++ .../ning/billing/catalog/io/XMLReader.java | 84 +++ .../catalog/io/XMLSchemaGenerator.java | 89 +++ .../.dont-let-git-remove-this-directory | 0 .../ning/billing/catalog/TestCancelCase.java | 123 ++++ .../billing/catalog/TestPlanChangeCase.java | 265 ++++++++ .../billing/catalog/TestPlanChangeRules.java | 226 +++++++ .../ning/billing/catalog/TestPlanRules.java | 69 ++ .../billing/catalog/TestVersionedCatalog.java | 67 ++ .../io/TestVersionedCatalogLoader.java | 146 +++++ catalog/src/test/resources/WeaponsHire.xml | 608 +++++++++++++++++ .../src/test/resources/WeaponsHireSmall.xml | 174 +++++ .../versionedCatalog/WeaponsHireSmall-1.xml | 174 +++++ .../versionedCatalog/WeaponsHireSmall-2.xml | 175 +++++ .../versionedCatalog/WeaponsHireSmall-3.xml | 174 +++++ entitlement/pom.xml | 110 ++++ .../alignment/EntitlementAlignment.java | 148 +++++ .../entitlement/api/billing/BillingApi.java | 81 +++ .../entitlement/api/user/PrivateFields.java | 36 ++ .../entitlement/api/user/Subscription.java | 521 +++++++++++++++ .../api/user/SubscriptionBundle.java | 66 ++ .../api/user/SubscriptionEvents.java | 67 ++ .../api/user/SubscriptionTransition.java | 138 ++++ .../billing/entitlement/api/user/UserApi.java | 161 +++++ .../engine/core/ApiEventProcessor.java | 56 ++ .../engine/core/ApiEventProcessorBase.java | 170 +++++ .../entitlement/engine/core/Engine.java | 166 +++++ .../engine/core/IApiEventProcessor.java | 22 + .../engine/core/IEventListener.java | 25 + .../engine/core/IEventNotifier.java | 25 + .../engine/dao/EntitlementDao.java | 316 +++++++++ .../entitlement/engine/dao/IBundleSqlDao.java | 92 +++ .../engine/dao/IEntitlementDao.java | 68 ++ .../entitlement/engine/dao/IEventSqlDao.java | 180 ++++++ .../engine/dao/ISubscriptionSqlDao.java | 109 ++++ .../billing/entitlement/events/EventBase.java | 235 +++++++ .../billing/entitlement/events/IEvent.java | 45 ++ .../entitlement/events/IEventLyfecycle.java | 54 ++ .../entitlement/events/phase/IPhaseEvent.java | 24 + .../entitlement/events/phase/PhaseEvent.java | 74 +++ .../entitlement/events/user/ApiEventBase.java | 111 ++++ .../events/user/ApiEventCancel.java | 38 ++ .../events/user/ApiEventChange.java | 40 ++ .../events/user/ApiEventCreate.java | 42 ++ .../events/user/ApiEventPause.java | 39 ++ .../events/user/ApiEventResume.java | 41 ++ .../entitlement/events/user/ApiEventType.java | 31 + .../events/user/ApiEventUncancel.java | 36 ++ .../entitlement/events/user/IUserEvent.java | 32 + .../exceptions/EntitlementError.java | 38 ++ .../entitlement/glue/EngineModule.java | 98 +++ .../entitlement/glue/IEngineConfig.java | 34 + entitlement/src/main/resources/ddl.sql | 59 ++ .../entitlement/api/ApiTestListener.java | 143 ++++ .../entitlement/api/user/TestUserApiBase.java | 321 +++++++++ .../api/user/TestUserApiCancel.java | 237 +++++++ .../api/user/TestUserApiCancelMemory.java | 54 ++ .../api/user/TestUserApiCancelSql.java | 71 ++ .../api/user/TestUserApiChangePlan.java | 413 ++++++++++++ .../api/user/TestUserApiChangePlanMemory.java | 77 +++ .../api/user/TestUserApiChangePlanSql.java | 85 +++ .../api/user/TestUserApiCreate.java | 180 ++++++ .../api/user/TestUserApiCreateMemory.java | 49 ++ .../api/user/TestUserApiCreateSql.java | 48 ++ .../api/user/TestUserApiError.java | 193 ++++++ .../api/user/TestUserApiPriceList.java | 36 ++ .../api/user/TestUserApiScenarios.java | 99 +++ .../core/ApiEventProcessorMemoryMock.java | 50 ++ .../engine/dao/EntitlementDaoMemoryMock.java | 333 ++++++++++ .../engine/dao/EntitlementDaoSqlMock.java | 71 ++ .../engine/dao/IEntitlementDaoMock.java | 21 + .../engine/dao/TestEntitlementDao.java | 130 ++++ .../glue/EngineModuleMemoryMock.java | 49 ++ .../entitlement/glue/EngineModuleSqlMock.java | 35 + .../src/test/resources/entitlement.properties | 6 + entitlement/src/test/resources/log4j.xml | 36 ++ entitlement/src/test/resources/testInput.xml | 612 ++++++++++++++++++ invoice/pom.xml | 43 ++ .../invoice/model/BillingModeBase.java | 84 +++ .../model/DefaultInvoiceGenerator.java | 119 ++++ .../billing/invoice/model/IBillingMode.java | 28 + .../invoice/model/IInvoiceGenerator.java | 24 + .../invoice/model/InAdvanceBillingMode.java | 148 +++++ .../model/InvalidDateSequenceException.java | 21 + .../ning/billing/invoice/model/Invoice.java | 60 ++ .../billing/invoice/model/InvoiceItem.java | 41 ++ .../invoice/model/InvoicingConfiguration.java | 32 + .../billing/invoice/model/TestConsole.java | 145 +++++ .../tests/DefaultInvoiceGeneratorTests.java | 136 ++++ .../invoice/tests/InternationalPriceMock.java | 45 ++ .../invoice/tests/InvoicingTestBase.java | 71 ++ .../invoice/tests/ProRationTestBase.java | 58 ++ .../inAdvance/GenericProRationTestBase.java | 183 ++++++ .../inAdvance/ProRationInAdvanceTestBase.java | 30 + .../inAdvance/ValidationProRationTests.java | 102 +++ .../annual/DoubleProRationTests.java | 145 +++++ .../annual/GenericProRationTests.java | 36 ++ .../annual/LeadingProRationTests.java | 149 +++++ .../inAdvance/annual/ProRationTests.java | 61 ++ .../annual/TrailingProRationTests.java | 90 +++ .../monthly/DoubleProRationTests.java | 145 +++++ .../monthly/GenericProRationTests.java | 36 ++ .../monthly/LeadingProRationTests.java | 149 +++++ .../inAdvance/monthly/ProRationTests.java | 235 +++++++ .../monthly/TrailingProRationTests.java | 90 +++ .../quarterly/DoubleProRationTests.java | 145 +++++ .../quarterly/GenericProRationTests.java | 36 ++ .../quarterly/LeadingProRationTests.java | 149 +++++ .../inAdvance/quarterly/ProRationTests.java | 253 ++++++++ .../quarterly/TrailingProRationTests.java | 90 +++ payment/pom.xml | 33 + pom.xml | 459 +++++++++++++ util/pom.xml | 67 ++ .../com/ning/billing/dbi/DBIProvider.java | 71 ++ .../java/com/ning/billing/dbi/DbiConfig.java | 55 ++ .../java/com/ning/billing/util/Hostname.java | 33 + .../com/ning/billing/util/clock/Clock.java | 70 ++ .../ning/billing/util/clock/ClockMock.java | 134 ++++ .../com/ning/billing/util/clock/IClock.java | 30 + .../util/notification/INotification.java | 21 + .../notification/INotificationHandler.java | 22 + .../util/notification/NotificationSystem.java | 51 ++ .../notification/TestNotificationSystem.java | 55 ++ 211 files changed, 20181 insertions(+) create mode 100644 .gitignore create mode 100644 LICENSE-2.0.txt create mode 100644 README.md create mode 100644 account/pom.xml create mode 100644 analytics/pom.xml create mode 100644 analytics/src/main/java/com/ning/billing/analytics/AnalyticsListener.java create mode 100644 analytics/src/main/java/com/ning/billing/analytics/BusinessSubscription.java create mode 100644 analytics/src/main/java/com/ning/billing/analytics/BusinessSubscriptionEvent.java create mode 100644 analytics/src/main/java/com/ning/billing/analytics/BusinessSubscriptionTransition.java create mode 100644 analytics/src/main/java/com/ning/billing/analytics/dao/BusinessSubscriptionTransitionBinder.java create mode 100644 analytics/src/main/java/com/ning/billing/analytics/dao/BusinessSubscriptionTransitionMapper.java create mode 100644 analytics/src/main/java/com/ning/billing/analytics/dao/EventDao.java create mode 100644 analytics/src/main/java/com/ning/billing/analytics/dao/EventDaoProvider.java create mode 100644 analytics/src/main/java/com/ning/billing/analytics/setup/AnalyticsModule.java create mode 100644 analytics/src/main/resources/com/ning/billing/analytics/dao/EventDao.sql.stg create mode 100644 analytics/src/main/resources/ddl.sql create mode 100644 analytics/src/test/java/com/ning/billing/analytics/MockDuration.java create mode 100644 analytics/src/test/java/com/ning/billing/analytics/MockPhase.java create mode 100644 analytics/src/test/java/com/ning/billing/analytics/MockPlan.java create mode 100644 analytics/src/test/java/com/ning/billing/analytics/MockProduct.java create mode 100644 analytics/src/test/java/com/ning/billing/analytics/MockSubscription.java create mode 100644 analytics/src/test/java/com/ning/billing/analytics/TestBusinessSubscription.java create mode 100644 analytics/src/test/java/com/ning/billing/analytics/TestBusinessSubscriptionEvent.java create mode 100644 analytics/src/test/java/com/ning/billing/analytics/TestBusinessSubscriptionTransition.java create mode 100644 analytics/src/test/java/com/ning/billing/analytics/dao/MysqlTestingHelper.java create mode 100644 analytics/src/test/java/com/ning/billing/analytics/dao/TestEventDao.java create mode 100644 analytics/src/test/resources/log4j.xml create mode 100644 api/pom.xml create mode 100644 api/src/main/java/com/ning/billing/BillingExceptionBase.java create mode 100644 api/src/main/java/com/ning/billing/ErrorCode.java create mode 100644 api/src/main/java/com/ning/billing/account/api/IAccount.java create mode 100644 api/src/main/java/com/ning/billing/account/api/IAccountData.java create mode 100644 api/src/main/java/com/ning/billing/account/api/IUserApi.java create mode 100644 api/src/main/java/com/ning/billing/catalog/api/ActionPolicy.java create mode 100644 api/src/main/java/com/ning/billing/catalog/api/BillingAlignment.java create mode 100644 api/src/main/java/com/ning/billing/catalog/api/BillingPeriod.java create mode 100644 api/src/main/java/com/ning/billing/catalog/api/Currency.java create mode 100644 api/src/main/java/com/ning/billing/catalog/api/ICatalog.java create mode 100644 api/src/main/java/com/ning/billing/catalog/api/ICatalogUserApi.java create mode 100644 api/src/main/java/com/ning/billing/catalog/api/IDuration.java create mode 100644 api/src/main/java/com/ning/billing/catalog/api/IInternationalPrice.java create mode 100644 api/src/main/java/com/ning/billing/catalog/api/IPlan.java create mode 100644 api/src/main/java/com/ning/billing/catalog/api/IPlanPhase.java create mode 100644 api/src/main/java/com/ning/billing/catalog/api/IPrice.java create mode 100644 api/src/main/java/com/ning/billing/catalog/api/IPriceList.java create mode 100644 api/src/main/java/com/ning/billing/catalog/api/IProduct.java create mode 100644 api/src/main/java/com/ning/billing/catalog/api/IProductTier.java create mode 100644 api/src/main/java/com/ning/billing/catalog/api/IProductType.java create mode 100644 api/src/main/java/com/ning/billing/catalog/api/InvalidConfigException.java create mode 100644 api/src/main/java/com/ning/billing/catalog/api/PhaseType.java create mode 100644 api/src/main/java/com/ning/billing/catalog/api/PlanAlignment.java create mode 100644 api/src/main/java/com/ning/billing/catalog/api/PlanPhaseSpecifier.java create mode 100644 api/src/main/java/com/ning/billing/catalog/api/PlanSpecifier.java create mode 100644 api/src/main/java/com/ning/billing/catalog/api/ProductCategory.java create mode 100644 api/src/main/java/com/ning/billing/catalog/api/TimeUnit.java create mode 100644 api/src/main/java/com/ning/billing/entitlement/api/billing/BillingMode.java create mode 100644 api/src/main/java/com/ning/billing/entitlement/api/billing/EntitlementBillingApiException.java create mode 100644 api/src/main/java/com/ning/billing/entitlement/api/billing/IBillingApi.java create mode 100644 api/src/main/java/com/ning/billing/entitlement/api/billing/IBillingEvent.java create mode 100644 api/src/main/java/com/ning/billing/entitlement/api/user/EntitlementUserApiException.java create mode 100644 api/src/main/java/com/ning/billing/entitlement/api/user/IApiListener.java create mode 100644 api/src/main/java/com/ning/billing/entitlement/api/user/IPrivateFields.java create mode 100644 api/src/main/java/com/ning/billing/entitlement/api/user/ISubscription.java create mode 100644 api/src/main/java/com/ning/billing/entitlement/api/user/ISubscriptionBundle.java create mode 100644 api/src/main/java/com/ning/billing/entitlement/api/user/ISubscriptionTransition.java create mode 100644 api/src/main/java/com/ning/billing/entitlement/api/user/IUserApi.java create mode 100644 api/src/main/java/com/ning/billing/invoice/api/BillingEvent.java create mode 100644 api/src/main/java/com/ning/billing/invoice/api/BillingEventSet.java create mode 100644 catalog/pom.xml create mode 100644 catalog/src/main/java/com/ning/billing/catalog/Catalog.java create mode 100644 catalog/src/main/java/com/ning/billing/catalog/CatalogUserApi.java create mode 100644 catalog/src/main/java/com/ning/billing/catalog/Duration.java create mode 100644 catalog/src/main/java/com/ning/billing/catalog/InternationalPrice.java create mode 100644 catalog/src/main/java/com/ning/billing/catalog/Plan.java create mode 100644 catalog/src/main/java/com/ning/billing/catalog/PlanAlignmentCase.java create mode 100644 catalog/src/main/java/com/ning/billing/catalog/PlanCancelCase.java create mode 100644 catalog/src/main/java/com/ning/billing/catalog/PlanChangeCase.java create mode 100644 catalog/src/main/java/com/ning/billing/catalog/PlanChangeRule.java create mode 100644 catalog/src/main/java/com/ning/billing/catalog/PlanPhase.java create mode 100644 catalog/src/main/java/com/ning/billing/catalog/PlanRules.java create mode 100644 catalog/src/main/java/com/ning/billing/catalog/Price.java create mode 100644 catalog/src/main/java/com/ning/billing/catalog/PriceList.java create mode 100644 catalog/src/main/java/com/ning/billing/catalog/Product.java create mode 100644 catalog/src/main/java/com/ning/billing/catalog/ProductTier.java create mode 100644 catalog/src/main/java/com/ning/billing/catalog/ProductType.java create mode 100644 catalog/src/main/java/com/ning/billing/catalog/ValidatingConfig.java create mode 100644 catalog/src/main/java/com/ning/billing/catalog/ValidationError.java create mode 100644 catalog/src/main/java/com/ning/billing/catalog/VersionedCatalog.java create mode 100644 catalog/src/main/java/com/ning/billing/catalog/io/VersionedCatalogLoader.java create mode 100644 catalog/src/main/java/com/ning/billing/catalog/io/XMLReader.java create mode 100644 catalog/src/main/java/com/ning/billing/catalog/io/XMLSchemaGenerator.java create mode 100644 catalog/src/main/resources/.dont-let-git-remove-this-directory create mode 100644 catalog/src/test/java/com/ning/billing/catalog/TestCancelCase.java create mode 100644 catalog/src/test/java/com/ning/billing/catalog/TestPlanChangeCase.java create mode 100644 catalog/src/test/java/com/ning/billing/catalog/TestPlanChangeRules.java create mode 100644 catalog/src/test/java/com/ning/billing/catalog/TestPlanRules.java create mode 100644 catalog/src/test/java/com/ning/billing/catalog/TestVersionedCatalog.java create mode 100644 catalog/src/test/java/com/ning/billing/catalog/io/TestVersionedCatalogLoader.java create mode 100644 catalog/src/test/resources/WeaponsHire.xml create mode 100644 catalog/src/test/resources/WeaponsHireSmall.xml create mode 100644 catalog/src/test/resources/versionedCatalog/WeaponsHireSmall-1.xml create mode 100644 catalog/src/test/resources/versionedCatalog/WeaponsHireSmall-2.xml create mode 100644 catalog/src/test/resources/versionedCatalog/WeaponsHireSmall-3.xml create mode 100644 entitlement/pom.xml create mode 100644 entitlement/src/main/java/com/ning/billing/entitlement/alignment/EntitlementAlignment.java create mode 100644 entitlement/src/main/java/com/ning/billing/entitlement/api/billing/BillingApi.java create mode 100644 entitlement/src/main/java/com/ning/billing/entitlement/api/user/PrivateFields.java create mode 100644 entitlement/src/main/java/com/ning/billing/entitlement/api/user/Subscription.java create mode 100644 entitlement/src/main/java/com/ning/billing/entitlement/api/user/SubscriptionBundle.java create mode 100644 entitlement/src/main/java/com/ning/billing/entitlement/api/user/SubscriptionEvents.java create mode 100644 entitlement/src/main/java/com/ning/billing/entitlement/api/user/SubscriptionTransition.java create mode 100644 entitlement/src/main/java/com/ning/billing/entitlement/api/user/UserApi.java create mode 100644 entitlement/src/main/java/com/ning/billing/entitlement/engine/core/ApiEventProcessor.java create mode 100644 entitlement/src/main/java/com/ning/billing/entitlement/engine/core/ApiEventProcessorBase.java create mode 100644 entitlement/src/main/java/com/ning/billing/entitlement/engine/core/Engine.java create mode 100644 entitlement/src/main/java/com/ning/billing/entitlement/engine/core/IApiEventProcessor.java create mode 100644 entitlement/src/main/java/com/ning/billing/entitlement/engine/core/IEventListener.java create mode 100644 entitlement/src/main/java/com/ning/billing/entitlement/engine/core/IEventNotifier.java create mode 100644 entitlement/src/main/java/com/ning/billing/entitlement/engine/dao/EntitlementDao.java create mode 100644 entitlement/src/main/java/com/ning/billing/entitlement/engine/dao/IBundleSqlDao.java create mode 100644 entitlement/src/main/java/com/ning/billing/entitlement/engine/dao/IEntitlementDao.java create mode 100644 entitlement/src/main/java/com/ning/billing/entitlement/engine/dao/IEventSqlDao.java create mode 100644 entitlement/src/main/java/com/ning/billing/entitlement/engine/dao/ISubscriptionSqlDao.java create mode 100644 entitlement/src/main/java/com/ning/billing/entitlement/events/EventBase.java create mode 100644 entitlement/src/main/java/com/ning/billing/entitlement/events/IEvent.java create mode 100644 entitlement/src/main/java/com/ning/billing/entitlement/events/IEventLyfecycle.java create mode 100644 entitlement/src/main/java/com/ning/billing/entitlement/events/phase/IPhaseEvent.java create mode 100644 entitlement/src/main/java/com/ning/billing/entitlement/events/phase/PhaseEvent.java create mode 100644 entitlement/src/main/java/com/ning/billing/entitlement/events/user/ApiEventBase.java create mode 100644 entitlement/src/main/java/com/ning/billing/entitlement/events/user/ApiEventCancel.java create mode 100644 entitlement/src/main/java/com/ning/billing/entitlement/events/user/ApiEventChange.java create mode 100644 entitlement/src/main/java/com/ning/billing/entitlement/events/user/ApiEventCreate.java create mode 100644 entitlement/src/main/java/com/ning/billing/entitlement/events/user/ApiEventPause.java create mode 100644 entitlement/src/main/java/com/ning/billing/entitlement/events/user/ApiEventResume.java create mode 100644 entitlement/src/main/java/com/ning/billing/entitlement/events/user/ApiEventType.java create mode 100644 entitlement/src/main/java/com/ning/billing/entitlement/events/user/ApiEventUncancel.java create mode 100644 entitlement/src/main/java/com/ning/billing/entitlement/events/user/IUserEvent.java create mode 100644 entitlement/src/main/java/com/ning/billing/entitlement/exceptions/EntitlementError.java create mode 100644 entitlement/src/main/java/com/ning/billing/entitlement/glue/EngineModule.java create mode 100644 entitlement/src/main/java/com/ning/billing/entitlement/glue/IEngineConfig.java create mode 100644 entitlement/src/main/resources/ddl.sql create mode 100644 entitlement/src/test/java/com/ning/billing/entitlement/api/ApiTestListener.java create mode 100644 entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiBase.java create mode 100644 entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiCancel.java create mode 100644 entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiCancelMemory.java create mode 100644 entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiCancelSql.java create mode 100644 entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiChangePlan.java create mode 100644 entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiChangePlanMemory.java create mode 100644 entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiChangePlanSql.java create mode 100644 entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiCreate.java create mode 100644 entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiCreateMemory.java create mode 100644 entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiCreateSql.java create mode 100644 entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiError.java create mode 100644 entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiPriceList.java create mode 100644 entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiScenarios.java create mode 100644 entitlement/src/test/java/com/ning/billing/entitlement/engine/core/ApiEventProcessorMemoryMock.java create mode 100644 entitlement/src/test/java/com/ning/billing/entitlement/engine/dao/EntitlementDaoMemoryMock.java create mode 100644 entitlement/src/test/java/com/ning/billing/entitlement/engine/dao/EntitlementDaoSqlMock.java create mode 100644 entitlement/src/test/java/com/ning/billing/entitlement/engine/dao/IEntitlementDaoMock.java create mode 100644 entitlement/src/test/java/com/ning/billing/entitlement/engine/dao/TestEntitlementDao.java create mode 100644 entitlement/src/test/java/com/ning/billing/entitlement/glue/EngineModuleMemoryMock.java create mode 100644 entitlement/src/test/java/com/ning/billing/entitlement/glue/EngineModuleSqlMock.java create mode 100644 entitlement/src/test/resources/entitlement.properties create mode 100644 entitlement/src/test/resources/log4j.xml create mode 100644 entitlement/src/test/resources/testInput.xml create mode 100644 invoice/pom.xml create mode 100644 invoice/src/main/java/com/ning/billing/invoice/model/BillingModeBase.java create mode 100644 invoice/src/main/java/com/ning/billing/invoice/model/DefaultInvoiceGenerator.java create mode 100644 invoice/src/main/java/com/ning/billing/invoice/model/IBillingMode.java create mode 100644 invoice/src/main/java/com/ning/billing/invoice/model/IInvoiceGenerator.java create mode 100644 invoice/src/main/java/com/ning/billing/invoice/model/InAdvanceBillingMode.java create mode 100644 invoice/src/main/java/com/ning/billing/invoice/model/InvalidDateSequenceException.java create mode 100644 invoice/src/main/java/com/ning/billing/invoice/model/Invoice.java create mode 100644 invoice/src/main/java/com/ning/billing/invoice/model/InvoiceItem.java create mode 100644 invoice/src/main/java/com/ning/billing/invoice/model/InvoicingConfiguration.java create mode 100644 invoice/src/main/java/com/ning/billing/invoice/model/TestConsole.java create mode 100644 invoice/src/test/java/com/ning/billing/invoice/tests/DefaultInvoiceGeneratorTests.java create mode 100644 invoice/src/test/java/com/ning/billing/invoice/tests/InternationalPriceMock.java create mode 100644 invoice/src/test/java/com/ning/billing/invoice/tests/InvoicingTestBase.java create mode 100644 invoice/src/test/java/com/ning/billing/invoice/tests/ProRationTestBase.java create mode 100644 invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/GenericProRationTestBase.java create mode 100644 invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/ProRationInAdvanceTestBase.java create mode 100644 invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/ValidationProRationTests.java create mode 100644 invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/annual/DoubleProRationTests.java create mode 100644 invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/annual/GenericProRationTests.java create mode 100644 invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/annual/LeadingProRationTests.java create mode 100644 invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/annual/ProRationTests.java create mode 100644 invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/annual/TrailingProRationTests.java create mode 100644 invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/monthly/DoubleProRationTests.java create mode 100644 invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/monthly/GenericProRationTests.java create mode 100644 invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/monthly/LeadingProRationTests.java create mode 100644 invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/monthly/ProRationTests.java create mode 100644 invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/monthly/TrailingProRationTests.java create mode 100644 invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/quarterly/DoubleProRationTests.java create mode 100644 invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/quarterly/GenericProRationTests.java create mode 100644 invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/quarterly/LeadingProRationTests.java create mode 100644 invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/quarterly/ProRationTests.java create mode 100644 invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/quarterly/TrailingProRationTests.java create mode 100644 payment/pom.xml create mode 100644 pom.xml create mode 100644 util/pom.xml create mode 100644 util/src/main/java/com/ning/billing/dbi/DBIProvider.java create mode 100644 util/src/main/java/com/ning/billing/dbi/DbiConfig.java create mode 100644 util/src/main/java/com/ning/billing/util/Hostname.java create mode 100644 util/src/main/java/com/ning/billing/util/clock/Clock.java create mode 100644 util/src/main/java/com/ning/billing/util/clock/ClockMock.java create mode 100644 util/src/main/java/com/ning/billing/util/clock/IClock.java create mode 100644 util/src/main/java/com/ning/billing/util/notification/INotification.java create mode 100644 util/src/main/java/com/ning/billing/util/notification/INotificationHandler.java create mode 100644 util/src/main/java/com/ning/billing/util/notification/NotificationSystem.java create mode 100644 util/src/test/java/com/ning/billing/util/notification/TestNotificationSystem.java diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000000..2f47d40bfc --- /dev/null +++ b/.gitignore @@ -0,0 +1,19 @@ +*.idea +*.iml +*.ipr +*.iws +*.DS_Store +target +staging +overlays +examples +pom.xml.releaseBackup +release.properties +logs/ +.diskspool +*.classpath +*.settings +*.project +*/bin/ +catalog/src/main/resources/CatalogSchema.xsd +*/test-output/ \ No newline at end of file diff --git a/LICENSE-2.0.txt b/LICENSE-2.0.txt new file mode 100644 index 0000000000..b08ca99953 --- /dev/null +++ b/LICENSE-2.0.txt @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2010-2011 Ning, 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. diff --git a/README.md b/README.md new file mode 100644 index 0000000000..e69de29bb2 diff --git a/account/pom.xml b/account/pom.xml new file mode 100644 index 0000000000..fda1f245ae --- /dev/null +++ b/account/pom.xml @@ -0,0 +1,33 @@ + + + + + 4.0.0 + + com.ning.billing + killbill + 0.0.3-SNAPSHOT + ../pom.xml + + killbill-account + killbill-account + jar + + + + + diff --git a/analytics/pom.xml b/analytics/pom.xml new file mode 100644 index 0000000000..4bda36b8e7 --- /dev/null +++ b/analytics/pom.xml @@ -0,0 +1,80 @@ + + + + + 4.0.0 + + com.ning.billing + killbill + 0.0.3-SNAPSHOT + ../pom.xml + + killbill-analytics + killbill-analytics + jar + + + com.google.inject + guice + provided + + + com.mysql + management + test + + + com.mysql + management-dbfiles + test + + + com.ning.billing + killbill-api + + + com.ning.jetty + ning-service-skeleton-core + + + commons-io + commons-io + test + + + joda-time + joda-time + + + mysql + mysql-connector-java + runtime + + + org.antlr + stringtemplate + runtime + + + org.testng + testng + test + + + + + diff --git a/analytics/src/main/java/com/ning/billing/analytics/AnalyticsListener.java b/analytics/src/main/java/com/ning/billing/analytics/AnalyticsListener.java new file mode 100644 index 0000000000..e5cd82689d --- /dev/null +++ b/analytics/src/main/java/com/ning/billing/analytics/AnalyticsListener.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.analytics; + +import com.google.inject.Inject; +import com.ning.billing.analytics.dao.EventDao; +import com.ning.billing.entitlement.api.user.ISubscription; +import org.joda.time.DateTime; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class AnalyticsListener// implements IApiListener +{ + private static final Logger log = LoggerFactory.getLogger(AnalyticsListener.class); + + private final EventDao dao; + + @Inject + public AnalyticsListener(final EventDao dao) + { + this.dao = dao; + } + + public void recordTransition(final String key, final DateTime requestedDateTime, final BusinessSubscriptionEvent event, final ISubscription prev, final ISubscription next) + { + final BusinessSubscription prevSubscription = new BusinessSubscription(prev); + final BusinessSubscription nextSubscription = new BusinessSubscription(next); + recordTransition(key, requestedDateTime, event, prevSubscription, nextSubscription); + } + + public void recordTransition(final String key, final DateTime requestedDateTime, final BusinessSubscriptionEvent event, final BusinessSubscription prevSubscription, final BusinessSubscription nextSubscription) + { + final BusinessSubscriptionTransition transition = new BusinessSubscriptionTransition( + key, + requestedDateTime, + event, + prevSubscription, + nextSubscription + ); + + log.info(transition.getEvent() + " " + transition); + dao.createTransition(transition); + } +} diff --git a/analytics/src/main/java/com/ning/billing/analytics/BusinessSubscription.java b/analytics/src/main/java/com/ning/billing/analytics/BusinessSubscription.java new file mode 100644 index 0000000000..0940d13236 --- /dev/null +++ b/analytics/src/main/java/com/ning/billing/analytics/BusinessSubscription.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.analytics; + +import com.ning.billing.account.api.IAccount; +import com.ning.billing.catalog.api.Currency; +import com.ning.billing.catalog.api.IDuration; +import com.ning.billing.catalog.api.IPlan; +import com.ning.billing.catalog.api.IPlanPhase; +import com.ning.billing.catalog.api.IProduct; +import com.ning.billing.catalog.api.ProductCategory; +import com.ning.billing.catalog.api.TimeUnit; +import com.ning.billing.entitlement.api.user.ISubscription; +import org.joda.time.DateTime; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.math.BigDecimal; +import java.math.RoundingMode; +import java.util.UUID; + +/** + * Describe a subscription for Analytics purposes + */ +public class BusinessSubscription +{ + private static final Logger log = LoggerFactory.getLogger(BusinessSubscription.class); + + private static final BigDecimal DAYS_IN_MONTH = BigDecimal.valueOf(30); + private static final BigDecimal MONTHS_IN_YEAR = BigDecimal.valueOf(12); + private static final Currency USD = Currency.valueOf("USD"); + private static final int SCALE = 4; + + private final String productName; + private final String productType; + private final ProductCategory productCategory; + private final String slug; + private final String phase; + private final BigDecimal price; + private final BigDecimal mrr; + private final String currency; + private final DateTime startDate; + private final ISubscription.SubscriptionState state; + private final UUID subscriptionId; + private final UUID bundleId; + + public BusinessSubscription(final String productName, final String productType, final ProductCategory productCategory, final String slug, final String phase, final BigDecimal price, final BigDecimal mrr, final String currency, final DateTime startDate, final ISubscription.SubscriptionState state, final UUID subscriptionId, final UUID bundleId) + { + this.productName = productName; + this.productType = productType; + this.productCategory = productCategory; + this.slug = slug; + this.phase = phase; + this.price = price; + this.mrr = mrr; + this.currency = currency; + this.startDate = startDate; + this.state = state; + this.subscriptionId = subscriptionId; + this.bundleId = bundleId; + } + + public BusinessSubscription(final ISubscription subscription) + { + // Record plan information + final IPlan currentPlan = subscription.getCurrentPlan(); + if (currentPlan != null && currentPlan.getProduct() != null) { + final IProduct product = currentPlan.getProduct(); + productName = product.getName(); + productCategory = product.getCategory(); + if (product.getType() != null) { + productType = product.getType().getName(); + } + else { + productType = null; + } + } + else { + productName = null; + productCategory = null; + productType = null; + } + + // Record phase information + final IPlanPhase currentPhase = subscription.getCurrentPhase(); + if (currentPhase != null) { + slug = currentPhase.getName(); + + if (currentPhase.getPhaseType() != null) { + phase = currentPhase.getPhaseType().toString(); + } + else { + phase = null; + } + + if (currentPhase.getRecurringPrice() != null) { + price = currentPhase.getRecurringPrice().getPrice(USD); + mrr = getMrrFromISubscription(currentPhase.getDuration(), price); + } + else { + price = null; + mrr = null; + } + } + else { + slug = null; + phase = null; + price = null; + mrr = null; + } + + final IAccount account = subscription.getAccount(); + if (account != null && account.getCurrency() != null) { + currency = account.getCurrency().toString(); + } + else { + currency = null; + } + startDate = subscription.getStartDate(); + state = subscription.getState(); + subscriptionId = subscription.getId(); + bundleId = subscription.getBundleId(); + } + + public UUID getBundleId() + { + return bundleId; + } + + public String getCurrency() + { + return currency; + } + + public BigDecimal getMrr() + { + return mrr; + } + + public double getRoundedMrr() + { + return round(mrr); + } + + public String getPhase() + { + return phase; + } + + public BigDecimal getPrice() + { + return price; + } + + public double getRoundedPrice() + { + return round(price); + } + + public ProductCategory getProductCategory() + { + return productCategory; + } + + public String getProductName() + { + return productName; + } + + public String getProductType() + { + return productType; + } + + public String getSlug() + { + return slug; + } + + public DateTime getStartDate() + { + return startDate; + } + + public ISubscription.SubscriptionState getState() + { + return state; + } + + public UUID getSubscriptionId() + { + return subscriptionId; + } + + static BigDecimal getMrrFromISubscription(final IDuration duration, final BigDecimal price) + { + if (duration == null || duration.getUnit() == null || duration.getLength() == 0) { + return null; + } + + if (duration.getUnit().equals(TimeUnit.UNLIMITED)) { + return BigDecimal.ZERO; + } + else if (duration.getUnit().equals(TimeUnit.DAYS)) { + return price.multiply(DAYS_IN_MONTH).multiply(BigDecimal.valueOf(duration.getLength())); + } + else if (duration.getUnit().equals(TimeUnit.MONTHS)) { + return price.divide(BigDecimal.valueOf(duration.getLength()), SCALE, BigDecimal.ROUND_HALF_UP); + } + else if (duration.getUnit().equals(TimeUnit.YEARS)) { + return price.divide(BigDecimal.valueOf(duration.getLength()), SCALE, RoundingMode.HALF_UP).divide(MONTHS_IN_YEAR, SCALE, RoundingMode.HALF_UP); + } + else { + log.error("Unknown duration [" + duration + "], can't compute mrr"); + return null; + } + } + + public static double round(final BigDecimal decimal) + { + if (decimal == null) { + return 0; + } + else { + return decimal.setScale(SCALE, BigDecimal.ROUND_HALF_UP).doubleValue(); + } + } + + @Override + public String toString() + { + final StringBuilder sb = new StringBuilder(); + sb.append("BusinessSubscription"); + sb.append("{bundleId=").append(bundleId); + sb.append(", productName='").append(productName).append('\''); + sb.append(", productType='").append(productType).append('\''); + sb.append(", productCategory='").append(productCategory).append('\''); + sb.append(", slug='").append(slug).append('\''); + sb.append(", phase='").append(phase).append('\''); + sb.append(", price=").append(price); + sb.append(", mrr=").append(mrr); + sb.append(", currency='").append(currency).append('\''); + sb.append(", startDate=").append(startDate); + sb.append(", state=").append(state); + sb.append(", subscriptionId=").append(subscriptionId); + sb.append('}'); + return sb.toString(); + } + + @Override + public boolean equals(final Object o) + { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + final BusinessSubscription that = (BusinessSubscription) o; + + if (bundleId != null ? !bundleId.equals(that.bundleId) : that.bundleId != null) { + return false; + } + if (currency != null ? !currency.equals(that.currency) : that.currency != null) { + return false; + } + if (mrr != null ? !(round(mrr) == round(that.mrr)) : that.mrr != null) { + return false; + } + if (phase != null ? !phase.equals(that.phase) : that.phase != null) { + return false; + } + if (price != null ? !(round(price) == round(that.price)) : that.price != null) { + return false; + } + if (productCategory != null ? !productCategory.equals(that.productCategory) : that.productCategory != null) { + return false; + } + if (productName != null ? !productName.equals(that.productName) : that.productName != null) { + return false; + } + if (productType != null ? !productType.equals(that.productType) : that.productType != null) { + return false; + } + if (slug != null ? !slug.equals(that.slug) : that.slug != null) { + return false; + } + if (startDate != null ? !startDate.equals(that.startDate) : that.startDate != null) { + return false; + } + if (state != that.state) { + return false; + } + if (subscriptionId != null ? !subscriptionId.equals(that.subscriptionId) : that.subscriptionId != null) { + return false; + } + + return true; + } + + @Override + public int hashCode() + { + int result = productName != null ? productName.hashCode() : 0; + result = 31 * result + (productType != null ? productType.hashCode() : 0); + result = 31 * result + (productCategory != null ? productCategory.hashCode() : 0); + result = 31 * result + (slug != null ? slug.hashCode() : 0); + result = 31 * result + (phase != null ? phase.hashCode() : 0); + result = 31 * result + (price != null ? price.hashCode() : 0); + result = 31 * result + (mrr != null ? mrr.hashCode() : 0); + result = 31 * result + (currency != null ? currency.hashCode() : 0); + result = 31 * result + (startDate != null ? startDate.hashCode() : 0); + result = 31 * result + (state != null ? state.hashCode() : 0); + result = 31 * result + (subscriptionId != null ? subscriptionId.hashCode() : 0); + result = 31 * result + (bundleId != null ? bundleId.hashCode() : 0); + return result; + } +} diff --git a/analytics/src/main/java/com/ning/billing/analytics/BusinessSubscriptionEvent.java b/analytics/src/main/java/com/ning/billing/analytics/BusinessSubscriptionEvent.java new file mode 100644 index 0000000000..956339e8c3 --- /dev/null +++ b/analytics/src/main/java/com/ning/billing/analytics/BusinessSubscriptionEvent.java @@ -0,0 +1,174 @@ +/* + * Copyright 2010-2011 Ning, Inc. + * + * Ning licenses this file to you under the Apache License, version 2.0 + * (the "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package com.ning.billing.analytics; + +import com.ning.billing.catalog.api.IPlan; +import com.ning.billing.catalog.api.IProduct; +import com.ning.billing.catalog.api.ProductCategory; +import com.ning.billing.entitlement.api.user.ISubscription; + +/** + * Describe an event associated with a transition between two BusinessSubscription + */ +public class BusinessSubscriptionEvent +{ + private static final String MISC = "MISC"; + + public enum EventType + { + ADD, + CANCEL, + CHANGE, + PAUSE, + RESUME, + SYSTEM_CANCEL, + SYSTEM_CHANGE + } + + private final EventType eventType; + private final ProductCategory category; + + public static BusinessSubscriptionEvent valueOf(final String eventString) + { + for (final EventType possibleEventType : EventType.values()) { + if (!eventString.startsWith(possibleEventType.toString().toUpperCase())) { + continue; + } + + final String categoryString = eventString.substring(possibleEventType.toString().length() + 1, eventString.length()); + + if (categoryString.equals(MISC)) { + return new BusinessSubscriptionEvent(possibleEventType, null); + } + else { + return new BusinessSubscriptionEvent(possibleEventType, ProductCategory.valueOf(categoryString)); + } + } + + throw new IllegalArgumentException("Unable to parse event string: " + eventString); + } + + public BusinessSubscriptionEvent(final EventType eventType, final ProductCategory category) + { + this.eventType = eventType; + this.category = category; + } + + public ProductCategory getCategory() + { + return category; + } + + public EventType getEventType() + { + return eventType; + } + + public static BusinessSubscriptionEvent subscriptionCreated(final ISubscription subscription) + { + return eventFromType(EventType.ADD, subscription); + } + + public static BusinessSubscriptionEvent subscriptionCancelled(final ISubscription subscription) + { + return eventFromType(EventType.CANCEL, subscription); + } + + public static BusinessSubscriptionEvent subscriptionChanged(final ISubscription subscription) + { + return eventFromType(EventType.CHANGE, subscription); + } + + public static BusinessSubscriptionEvent subscriptionPaused(final ISubscription subscription) + { + return eventFromType(EventType.PAUSE, subscription); + } + + public static BusinessSubscriptionEvent subscriptionResumed(final ISubscription subscription) + { + return eventFromType(EventType.RESUME, subscription); + } + + public static BusinessSubscriptionEvent subscriptionPhaseChanged(final ISubscription subscription) + { + if (subscription.getState() != null && subscription.getState().equals(ISubscription.SubscriptionState.CANCELLED)) { + return eventFromType(EventType.SYSTEM_CANCEL, subscription); + } + else { + return eventFromType(EventType.SYSTEM_CHANGE, subscription); + } + } + + private static BusinessSubscriptionEvent eventFromType(final EventType eventType, final ISubscription subscription) + { + final ProductCategory category = getTypeFromSubscription(subscription); + return new BusinessSubscriptionEvent(eventType, category); + } + + private static ProductCategory getTypeFromSubscription(final ISubscription subscription) + { + if (subscription == null) { + return null; + } + + final IPlan currentPlan = subscription.getCurrentPlan(); + if (currentPlan != null && currentPlan.getProduct() != null) { + final IProduct product = currentPlan.getProduct(); + if (product.getType() != null && product.getCategory() != null) { + return product.getCategory(); + } + } + + return null; + } + + @Override + public String toString() + { + return eventType.toString() + "_" + (category == null ? MISC : category.toString().toUpperCase()); + } + + @Override + public boolean equals(final Object o) + { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + final BusinessSubscriptionEvent that = (BusinessSubscriptionEvent) o; + + if (category != that.category) { + return false; + } + if (eventType != null ? !eventType.equals(that.eventType) : that.eventType != null) { + return false; + } + + return true; + } + + @Override + public int hashCode() + { + int result = eventType != null ? eventType.hashCode() : 0; + result = 31 * result + (category != null ? category.hashCode() : 0); + return result; + } +} \ No newline at end of file diff --git a/analytics/src/main/java/com/ning/billing/analytics/BusinessSubscriptionTransition.java b/analytics/src/main/java/com/ning/billing/analytics/BusinessSubscriptionTransition.java new file mode 100644 index 0000000000..b37e45af4c --- /dev/null +++ b/analytics/src/main/java/com/ning/billing/analytics/BusinessSubscriptionTransition.java @@ -0,0 +1,133 @@ +/* + * Copyright 2010-2011 Ning, Inc. + * + * Ning licenses this file to you under the Apache License, version 2.0 + * (the "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package com.ning.billing.analytics; + +import org.joda.time.DateTime; + +/** + * Describe a state change between two BusinessSubscription + *

+ * The key is unique identifier that ties sets of subscriptions together. + */ +public class BusinessSubscriptionTransition +{ + private final String key; + private final DateTime requestedTimestamp; + private final BusinessSubscriptionEvent event; + private final BusinessSubscription previousSubscription; + private final BusinessSubscription nextSubscription; + + public BusinessSubscriptionTransition(final String key, final DateTime requestedTimestamp, final BusinessSubscriptionEvent event, final BusinessSubscription previousSubscription, final BusinessSubscription nextsubscription) + { + if (key == null) { + throw new IllegalArgumentException("An event must have an key"); + } + if (requestedTimestamp == null) { + throw new IllegalArgumentException("An event must have a requestedTimestamp"); + } + if (event == null) { + throw new IllegalArgumentException("No event specified"); + } + + this.key = key; + this.requestedTimestamp = requestedTimestamp; + this.event = event; + this.previousSubscription = previousSubscription; + this.nextSubscription = nextsubscription; + } + + public BusinessSubscriptionEvent getEvent() + { + return event; + } + + public String getKey() + { + return key; + } + + public BusinessSubscription getNextSubscription() + { + return nextSubscription; + } + + public BusinessSubscription getPreviousSubscription() + { + return previousSubscription; + } + + public DateTime getRequestedTimestamp() + { + return requestedTimestamp; + } + + @Override + public String toString() + { + final StringBuilder sb = new StringBuilder(); + sb.append("BusinessSubscriptionTransition"); + sb.append("{event=").append(event); + sb.append(", key='").append(key).append('\''); + sb.append(", requestedTimestamp=").append(requestedTimestamp); + sb.append(", previousSubscription=").append(previousSubscription); + sb.append(", nextSubscription=").append(nextSubscription); + sb.append('}'); + return sb.toString(); + } + + @Override + public boolean equals(final Object o) + { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + final BusinessSubscriptionTransition that = (BusinessSubscriptionTransition) o; + + if (event != null ? !event.equals(that.event) : that.event != null) { + return false; + } + if (key != null ? !key.equals(that.key) : that.key != null) { + return false; + } + if (nextSubscription != null ? !nextSubscription.equals(that.nextSubscription) : that.nextSubscription != null) { + return false; + } + if (previousSubscription != null ? !previousSubscription.equals(that.previousSubscription) : that.previousSubscription != null) { + return false; + } + if (requestedTimestamp != null ? !requestedTimestamp.equals(that.requestedTimestamp) : that.requestedTimestamp != null) { + return false; + } + + return true; + } + + @Override + public int hashCode() + { + int result = key != null ? key.hashCode() : 0; + result = 31 * result + (requestedTimestamp != null ? requestedTimestamp.hashCode() : 0); + result = 31 * result + (event != null ? event.hashCode() : 0); + result = 31 * result + (previousSubscription != null ? previousSubscription.hashCode() : 0); + result = 31 * result + (nextSubscription != null ? nextSubscription.hashCode() : 0); + return result; + } +} diff --git a/analytics/src/main/java/com/ning/billing/analytics/dao/BusinessSubscriptionTransitionBinder.java b/analytics/src/main/java/com/ning/billing/analytics/dao/BusinessSubscriptionTransitionBinder.java new file mode 100644 index 0000000000..6a1074168e --- /dev/null +++ b/analytics/src/main/java/com/ning/billing/analytics/dao/BusinessSubscriptionTransitionBinder.java @@ -0,0 +1,163 @@ +/* + * Copyright 2010-2011 Ning, Inc. + * + * Ning licenses this file to you under the Apache License, version 2.0 + * (the "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package com.ning.billing.analytics.dao; + +import com.ning.billing.analytics.BusinessSubscription; +import com.ning.billing.analytics.BusinessSubscriptionTransition; +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; +import java.sql.Types; + +@BindingAnnotation(BusinessSubscriptionTransitionBinder.BstBinderFactory.class) +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.PARAMETER}) +public @interface BusinessSubscriptionTransitionBinder +{ + public static class BstBinderFactory implements BinderFactory + { + public Binder build(final Annotation annotation) + { + return new Binder() + { + public void bind(final SQLStatement q, final BusinessSubscriptionTransitionBinder bind, final BusinessSubscriptionTransition arg) + { + q.bind("event_key", arg.getKey()); + q.bind("requested_timestamp", arg.getRequestedTimestamp().getMillis()); + q.bind("event", arg.getEvent().toString()); + + final BusinessSubscription previousSubscription = arg.getPreviousSubscription(); + if (previousSubscription == null) { + q.bindNull("prev_product_name", Types.VARCHAR); + q.bindNull("prev_product_type", Types.VARCHAR); + q.bindNull("prev_product_category", Types.VARCHAR); + q.bindNull("prev_slug", Types.VARCHAR); + q.bindNull("prev_phase", Types.VARCHAR); + q.bindNull("prev_price", Types.NUMERIC); + q.bindNull("prev_mrr", Types.NUMERIC); + q.bindNull("prev_currency", Types.VARCHAR); + q.bindNull("prev_start_date", Types.BIGINT); + q.bindNull("prev_state", Types.VARCHAR); + q.bindNull("prev_subscription_id", Types.VARCHAR); + q.bindNull("prev_bundle_id", Types.VARCHAR); + } + else { + q.bind("prev_product_name", previousSubscription.getProductName()); + q.bind("prev_product_type", previousSubscription.getProductType()); + if (previousSubscription.getProductCategory() == null) { + q.bindNull("prev_product_category", Types.VARCHAR); + } + else { + q.bind("prev_product_category", previousSubscription.getProductCategory().toString()); + } + q.bind("prev_slug", previousSubscription.getSlug()); + q.bind("prev_phase", previousSubscription.getPhase()); + q.bind("prev_price", previousSubscription.getRoundedPrice()); + q.bind("prev_mrr", previousSubscription.getRoundedMrr()); + q.bind("prev_currency", previousSubscription.getCurrency()); + if (previousSubscription.getStartDate() == null) { + q.bindNull("prev_start_date", Types.BIGINT); + } + else { + q.bind("prev_start_date", previousSubscription.getStartDate().getMillis()); + } + if (previousSubscription.getState() == null) { + q.bindNull("prev_state", Types.VARCHAR); + } + else { + q.bind("prev_state", previousSubscription.getState().toString()); + } + if (previousSubscription.getSubscriptionId() == null) { + q.bindNull("prev_subscription_id", Types.VARCHAR); + } + else { + q.bind("prev_subscription_id", previousSubscription.getSubscriptionId().toString()); + } + if (previousSubscription.getBundleId() == null) { + q.bindNull("prev_bundle_id", Types.VARCHAR); + } + else { + q.bind("prev_bundle_id", previousSubscription.getBundleId().toString()); + } + } + + final BusinessSubscription nextSubscription = arg.getNextSubscription(); + if (nextSubscription == null) { + q.bindNull("next_product_name", Types.VARCHAR); + q.bindNull("next_product_type", Types.VARCHAR); + q.bindNull("next_product_category", Types.VARCHAR); + q.bindNull("next_slug", Types.VARCHAR); + q.bindNull("next_phase", Types.VARCHAR); + q.bindNull("next_price", Types.NUMERIC); + q.bindNull("next_mrr", Types.NUMERIC); + q.bindNull("next_currency", Types.VARCHAR); + q.bindNull("next_start_date", Types.BIGINT); + q.bindNull("next_state", Types.VARCHAR); + q.bindNull("next_subscription_id", Types.VARCHAR); + q.bindNull("next_bundle_id", Types.VARCHAR); + } + else { + q.bind("next_product_name", nextSubscription.getProductName()); + q.bind("next_product_type", nextSubscription.getProductType()); + if (nextSubscription.getProductCategory() == null) { + q.bindNull("next_product_category", Types.VARCHAR); + } + else { + q.bind("next_product_category", nextSubscription.getProductCategory().toString()); + } + q.bind("next_slug", nextSubscription.getSlug()); + q.bind("next_phase", nextSubscription.getPhase()); + q.bind("next_price", nextSubscription.getRoundedPrice()); + q.bind("next_mrr", nextSubscription.getRoundedMrr()); + q.bind("next_currency", nextSubscription.getCurrency()); + if (nextSubscription.getStartDate() == null) { + q.bindNull("next_start_date", Types.BIGINT); + } + else { + q.bind("next_start_date", nextSubscription.getStartDate().getMillis()); + } + if (nextSubscription.getState() == null) { + q.bindNull("next_state", Types.VARCHAR); + } + else { + q.bind("next_state", nextSubscription.getState().toString()); + } + if (nextSubscription.getSubscriptionId() == null) { + q.bindNull("next_subscription_id", Types.VARCHAR); + } + else { + q.bind("next_subscription_id", nextSubscription.getSubscriptionId().toString()); + } + if (nextSubscription.getBundleId() == null) { + q.bindNull("next_bundle_id", Types.VARCHAR); + } + else { + q.bind("next_bundle_id", nextSubscription.getBundleId().toString()); + } + } + } + }; + } + } +} diff --git a/analytics/src/main/java/com/ning/billing/analytics/dao/BusinessSubscriptionTransitionMapper.java b/analytics/src/main/java/com/ning/billing/analytics/dao/BusinessSubscriptionTransitionMapper.java new file mode 100644 index 0000000000..3b6f35356b --- /dev/null +++ b/analytics/src/main/java/com/ning/billing/analytics/dao/BusinessSubscriptionTransitionMapper.java @@ -0,0 +1,89 @@ +/* + * Copyright 2010-2011 Ning, Inc. + * + * Ning licenses this file to you under the Apache License, version 2.0 + * (the "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package com.ning.billing.analytics.dao; + +import com.ning.billing.analytics.BusinessSubscription; +import com.ning.billing.analytics.BusinessSubscriptionEvent; +import com.ning.billing.analytics.BusinessSubscriptionTransition; +import com.ning.billing.catalog.api.ProductCategory; +import com.ning.billing.entitlement.api.user.ISubscription; +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.math.BigDecimal; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.UUID; + +public class BusinessSubscriptionTransitionMapper implements ResultSetMapper +{ + @Override + public BusinessSubscriptionTransition map(final int index, final ResultSet r, final StatementContext ctx) throws SQLException + { + BusinessSubscription prev = new BusinessSubscription( + r.getString(4), // productName + r.getString(5), // productType + r.getString(6) == null ? null : ProductCategory.valueOf(r.getString(6)), // productCategory + r.getString(7), // slug + r.getString(8), // phase + BigDecimal.valueOf(r.getDouble(9)), // price + BigDecimal.valueOf(r.getDouble(10)), // mrr + r.getString(11), // currency + r.getLong(12) == 0 ? null : new DateTime(r.getLong(12), DateTimeZone.UTC), // startDate + r.getString(13) == null ? null : ISubscription.SubscriptionState.valueOf(r.getString(13)), // state + r.getString(14) == null ? null : UUID.fromString(r.getString(14)), // subscriptionId + r.getString(15) == null ? null : UUID.fromString(r.getString(15)) //bundleId + ); + + // Avoid creating a dummy subscriptions with all null fields + if (prev.getProductName() == null && prev.getSlug() == null) { + prev = null; + } + + BusinessSubscription next = new BusinessSubscription( + r.getString(16), // productName + r.getString(17), // productType + r.getString(18) == null ? null : ProductCategory.valueOf(r.getString(18)), // productCategory + r.getString(19), // slug8 + r.getString(20), // phase + BigDecimal.valueOf(r.getDouble(21)), // price + BigDecimal.valueOf(r.getDouble(22)), // mrr + r.getString(23), // currency + r.getLong(24) == 0 ? null : new DateTime(r.getLong(24), DateTimeZone.UTC), // startDate + r.getString(25) == null ? null : ISubscription.SubscriptionState.valueOf(r.getString(25)), // state + r.getString(26) == null ? null : UUID.fromString(r.getString(26)), // subscriptionId + r.getString(27) == null ? null : UUID.fromString(r.getString(27)) //bundleId + ); + + // Avoid creating a dummy subscriptions with all null fields + if (next.getProductName() == null && next.getSlug() == null) { + next = null; + } + + final BusinessSubscriptionEvent event = BusinessSubscriptionEvent.valueOf(r.getString(3)); + + return new BusinessSubscriptionTransition( + r.getString(1), + new DateTime(r.getLong(2), DateTimeZone.UTC), + event, + prev, + next + ); + } +} diff --git a/analytics/src/main/java/com/ning/billing/analytics/dao/EventDao.java b/analytics/src/main/java/com/ning/billing/analytics/dao/EventDao.java new file mode 100644 index 0000000000..b76e55ca01 --- /dev/null +++ b/analytics/src/main/java/com/ning/billing/analytics/dao/EventDao.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.analytics.dao; + +import com.ning.billing.analytics.BusinessSubscriptionTransition; +import org.skife.jdbi.v2.sqlobject.Bind; +import org.skife.jdbi.v2.sqlobject.SqlQuery; +import org.skife.jdbi.v2.sqlobject.SqlUpdate; +import org.skife.jdbi.v2.sqlobject.customizers.RegisterMapper; +import org.skife.jdbi.v2.sqlobject.stringtemplate.ExternalizedSqlViaStringTemplate3; + +import java.util.List; + +@ExternalizedSqlViaStringTemplate3() +@RegisterMapper(BusinessSubscriptionTransitionMapper.class) +public interface EventDao +{ + @SqlQuery + List getTransitions(@Bind("event_key") final String key); + + @SqlUpdate + int createTransition(@BusinessSubscriptionTransitionBinder final BusinessSubscriptionTransition transition); + + @SqlUpdate + void test(); +} diff --git a/analytics/src/main/java/com/ning/billing/analytics/dao/EventDaoProvider.java b/analytics/src/main/java/com/ning/billing/analytics/dao/EventDaoProvider.java new file mode 100644 index 0000000000..4986c5fef9 --- /dev/null +++ b/analytics/src/main/java/com/ning/billing/analytics/dao/EventDaoProvider.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.analytics.dao; + +import com.google.inject.Inject; +import com.google.inject.Provider; +import org.skife.jdbi.v2.DBI; + +public class EventDaoProvider implements Provider +{ + private final DBI dbi; + + @Inject + public EventDaoProvider(final DBI dbi) + { + this.dbi = dbi; + } + + @Override + public EventDao get() + { + return dbi.onDemand(EventDao.class); + } +} diff --git a/analytics/src/main/java/com/ning/billing/analytics/setup/AnalyticsModule.java b/analytics/src/main/java/com/ning/billing/analytics/setup/AnalyticsModule.java new file mode 100644 index 0000000000..b50ad8c1b9 --- /dev/null +++ b/analytics/src/main/java/com/ning/billing/analytics/setup/AnalyticsModule.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.analytics.setup; + +import com.google.inject.AbstractModule; +import com.ning.billing.analytics.dao.EventDao; +import com.ning.billing.analytics.dao.EventDaoProvider; +import com.ning.jetty.core.providers.DBIProvider; +import org.skife.jdbi.v2.DBI; + +public class AnalyticsModule extends AbstractModule +{ + @Override + protected void configure() + { + bind(DBI.class).toProvider(DBIProvider.class).asEagerSingleton(); + bind(EventDao.class).toProvider(EventDaoProvider.class).asEagerSingleton(); + } +} diff --git a/analytics/src/main/resources/com/ning/billing/analytics/dao/EventDao.sql.stg b/analytics/src/main/resources/com/ning/billing/analytics/dao/EventDao.sql.stg new file mode 100644 index 0000000000..b9044bc8aa --- /dev/null +++ b/analytics/src/main/resources/com/ning/billing/analytics/dao/EventDao.sql.stg @@ -0,0 +1,100 @@ +group EventDao; + +getTransitions(event_key) ::= << + select + event_key + , requested_timestamp + , event + , prev_product_name + , prev_product_type + , prev_product_category + , prev_slug + , prev_phase + , prev_price + , prev_mrr + , prev_currency + , prev_start_date + , prev_state + , prev_subscription_id + , prev_bundle_id + , next_product_name + , next_product_type + , next_product_category + , next_slug + , next_phase + , next_price + , next_mrr + , next_currency + , next_start_date + , next_state + , next_subscription_id + , next_bundle_id + from bst + where event_key=:event_key + order by requested_timestamp asc + ; +>> + +createTransition() ::= << + insert into bst( + event_key + , requested_timestamp + , event + , prev_product_name + , prev_product_type + , prev_product_category + , prev_slug + , prev_phase + , prev_price + , prev_mrr + , prev_currency + , prev_start_date + , prev_state + , prev_subscription_id + , prev_bundle_id + , next_product_name + , next_product_type + , next_product_category + , next_slug + , next_phase + , next_price + , next_mrr + , next_currency + , next_start_date + , next_state + , next_subscription_id + , next_bundle_id + ) values ( + :event_key + , :requested_timestamp + , :event + , :prev_product_name + , :prev_product_type + , :prev_product_category + , :prev_slug + , :prev_phase + , :prev_price + , :prev_mrr + , :prev_currency + , :prev_start_date + , :prev_state + , :prev_subscription_id + , :prev_bundle_id + , :next_product_name + , :next_product_type + , :next_product_category + , :next_slug + , :next_phase + , :next_price + , :next_mrr + , :next_currency + , :next_start_date + , :next_state + , :next_subscription_id + , :next_bundle_id + ); +>> + +test() ::= << + select 1 from bst; +>> diff --git a/analytics/src/main/resources/ddl.sql b/analytics/src/main/resources/ddl.sql new file mode 100644 index 0000000000..f7be5f4035 --- /dev/null +++ b/analytics/src/main/resources/ddl.sql @@ -0,0 +1,32 @@ +drop table if exists bst; +create table bst ( + event_key varchar(50) not null +, requested_timestamp bigint not null +, event varchar(50) not null +, prev_product_name varchar(32) default null +, prev_product_type varchar(32) default null +, prev_product_category varchar(32) default null +, prev_slug varchar(50) default null +, prev_phase varchar(32) default null +, prev_price numeric(10, 4) default 0 +, prev_mrr numeric(10, 4) default 0 +, prev_currency varchar(32) default null +, prev_start_date bigint default null +, prev_state varchar(32) default null +, prev_subscription_id varchar(100) default null +, prev_bundle_id varchar(100) default null +, next_product_name varchar(32) default null +, next_product_type varchar(32) default null +, next_product_category varchar(32) default null +, next_slug varchar(50) default null +, next_phase varchar(32) default null +, next_price numeric(10, 4) default 0 +, next_mrr numeric(10, 4) default 0 +, next_currency varchar(32) default null +, next_start_date bigint default null +, next_state varchar(32) default null +, next_subscription_id varchar(100) default null +, next_bundle_id varchar(100) default null +) engine=innodb; + +create index bst_key_index on bst (event_key, requested_timestamp asc); diff --git a/analytics/src/test/java/com/ning/billing/analytics/MockDuration.java b/analytics/src/test/java/com/ning/billing/analytics/MockDuration.java new file mode 100644 index 0000000000..a56a3a5834 --- /dev/null +++ b/analytics/src/test/java/com/ning/billing/analytics/MockDuration.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.analytics; + +import com.ning.billing.catalog.api.IDuration; +import com.ning.billing.catalog.api.TimeUnit; + +public class MockDuration +{ + public static IDuration MONHTLY() + { + return new IDuration() + { + @Override + public TimeUnit getUnit() + { + return TimeUnit.MONTHS; + } + + @Override + public int getLength() + { + return 1; + } + }; + } + + public static IDuration YEARLY() + { + return new IDuration() + { + @Override + public TimeUnit getUnit() + { + return TimeUnit.YEARS; + } + + @Override + public int getLength() + { + return 1; + } + }; + } + + public static IDuration UNLIMITED() + { + return new IDuration() + { + @Override + public TimeUnit getUnit() + { + return TimeUnit.UNLIMITED; + } + + @Override + public int getLength() + { + return 1; + } + }; + } +} diff --git a/analytics/src/test/java/com/ning/billing/analytics/MockPhase.java b/analytics/src/test/java/com/ning/billing/analytics/MockPhase.java new file mode 100644 index 0000000000..54c0773a59 --- /dev/null +++ b/analytics/src/test/java/com/ning/billing/analytics/MockPhase.java @@ -0,0 +1,112 @@ +/* + * Copyright 2010-2011 Ning, Inc. + * + * Ning licenses this file to you under the Apache License, version 2.0 + * (the "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package com.ning.billing.analytics; + +import com.ning.billing.catalog.api.BillingPeriod; +import com.ning.billing.catalog.api.Currency; +import com.ning.billing.catalog.api.IDuration; +import com.ning.billing.catalog.api.IInternationalPrice; +import com.ning.billing.catalog.api.IPlan; +import com.ning.billing.catalog.api.IPlanPhase; +import com.ning.billing.catalog.api.IPrice; +import com.ning.billing.catalog.api.PhaseType; + +import java.math.BigDecimal; + +public class MockPhase implements IPlanPhase +{ + private final PhaseType cohort; + private final IPlan plan; + private final IDuration duration; + private final double price; + + public MockPhase(final PhaseType cohort, final IPlan plan, final IDuration duration, final double price) + { + this.cohort = cohort; + this.plan = plan; + this.duration = duration; + this.price = price; + } + + @Override + public IInternationalPrice getRecurringPrice() + { + return new IInternationalPrice() + { + @Override + public IPrice[] getPrices() + { + throw new UnsupportedOperationException(); + } + + @Override + public BigDecimal getPrice(final Currency currency) + { + return BigDecimal.valueOf(price); + } + }; + } + + @Override + public IInternationalPrice getFixedPrice() + { + return new IInternationalPrice() + { + @Override + public IPrice[] getPrices() + { + throw new UnsupportedOperationException(); + } + + @Override + public BigDecimal getPrice(final Currency currency) + { + return BigDecimal.valueOf(price); + } + }; + } + + @Override + public BillingPeriod getBillingPeriod() + { + throw new UnsupportedOperationException(); + } + + @Override + public String getName() + { + return plan.getName() + "-" + cohort; + } + + @Override + public IPlan getPlan() + { + return plan; + } + + @Override + public IDuration getDuration() + { + return duration; + } + + @Override + public PhaseType getPhaseType() + { + return cohort; + } +} diff --git a/analytics/src/test/java/com/ning/billing/analytics/MockPlan.java b/analytics/src/test/java/com/ning/billing/analytics/MockPlan.java new file mode 100644 index 0000000000..fefa76cd33 --- /dev/null +++ b/analytics/src/test/java/com/ning/billing/analytics/MockPlan.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.analytics; + +import com.ning.billing.catalog.api.BillingAlignment; +import com.ning.billing.catalog.api.BillingPeriod; +import com.ning.billing.catalog.api.IPlan; +import com.ning.billing.catalog.api.IPlanPhase; +import com.ning.billing.catalog.api.IProduct; +import com.ning.billing.catalog.api.PlanAlignment; + +import java.util.Iterator; + +public class MockPlan implements IPlan +{ + private final String name; + private final IProduct product; + + public MockPlan(final String name, final IProduct product) + { + this.name = name; + this.product = product; + } + + @Override + public IPlanPhase[] getInitialPhases() + { + throw new UnsupportedOperationException(); + } + + @Override + public IProduct getProduct() + { + return product; + } + + @Override + public String getName() + { + return name; + } + + @Override + public Iterator getInitialPhaseIterator() + { + throw new UnsupportedOperationException(); + } + + @Override + public IPlanPhase getFinalPhase() + { + throw new UnsupportedOperationException(); + } + + @Override + public BillingPeriod getBillingPeriod() + { + throw new UnsupportedOperationException(); + } + + @Override + public BillingAlignment getBillingAlignment() + { + throw new UnsupportedOperationException(); + } + + @Override + public PlanAlignment getPlanAlignment() + { + throw new UnsupportedOperationException(); + } + + @Override + public int getPlansAllowedInBundle() + { + throw new UnsupportedOperationException(); + } +} diff --git a/analytics/src/test/java/com/ning/billing/analytics/MockProduct.java b/analytics/src/test/java/com/ning/billing/analytics/MockProduct.java new file mode 100644 index 0000000000..62d7b5adeb --- /dev/null +++ b/analytics/src/test/java/com/ning/billing/analytics/MockProduct.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.analytics; + +import com.ning.billing.catalog.api.IProduct; +import com.ning.billing.catalog.api.IProductType; +import com.ning.billing.catalog.api.ProductCategory; + +public class MockProduct implements IProduct +{ + private final String name; + private final String type; + private final ProductCategory category; + + public MockProduct(final String name, final String type, final ProductCategory category) + { + this.name = name; + this.type = type; + this.category = category; + } + + @Override + public IProductType getType() + { + return new IProductType() + { + @Override + public String getName() + { + return type; + } + }; + } + + @Override + public String getName() + { + return name; + } + + @Override + public ProductCategory getCategory() + { + return category; + } + + @Override + public IProduct[] getAvailable() { + // TODO Auto-generated method stub + return null; + } + + @Override + public IProduct[] getIncluded() { + // TODO Auto-generated method stub + return null; + } +} diff --git a/analytics/src/test/java/com/ning/billing/analytics/MockSubscription.java b/analytics/src/test/java/com/ning/billing/analytics/MockSubscription.java new file mode 100644 index 0000000000..a6dafc6c00 --- /dev/null +++ b/analytics/src/test/java/com/ning/billing/analytics/MockSubscription.java @@ -0,0 +1,135 @@ +/* + * Copyright 2010-2011 Ning, Inc. + * + * Ning licenses this file to you under the Apache License, version 2.0 + * (the "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package com.ning.billing.analytics; + +import com.ning.billing.account.api.IAccount; +import com.ning.billing.catalog.api.BillingPeriod; +import com.ning.billing.catalog.api.IPlan; +import com.ning.billing.catalog.api.IPlanPhase; +import com.ning.billing.entitlement.api.user.EntitlementUserApiException; +import com.ning.billing.entitlement.api.user.ISubscription; +import org.joda.time.DateTime; +import org.joda.time.DateTimeZone; + +import java.util.UUID; + +public class MockSubscription implements ISubscription +{ + private static final UUID ID = UUID.randomUUID(); + private static final UUID BUNDLE_ID = UUID.randomUUID(); + private static final DateTime START_DATE = new DateTime(DateTimeZone.UTC); + + private final SubscriptionState state; + private final IPlan plan; + private final IPlanPhase phase; + + public MockSubscription(final SubscriptionState state, final IPlan plan, final IPlanPhase phase) + { + this.state = state; + this.plan = plan; + this.phase = phase; + } + + @Override + public void cancel() + { + throw new UnsupportedOperationException(); + } + + @Override + public void changePlan(final String productName, final BillingPeriod term, final String planSet) + { + throw new UnsupportedOperationException(); + } + + @Override + public void pause() + { + throw new UnsupportedOperationException(); + } + + @Override + public void resume() + { + throw new UnsupportedOperationException(); + } + + @Override + public UUID getId() + { + return ID; + } + + @Override + public UUID getBundleId() + { + return BUNDLE_ID; + } + + @Override + public SubscriptionState getState() + { + return state; + } + + @Override + public DateTime getStartDate() + { + return START_DATE; + } + + @Override + public IPlan getCurrentPlan() + { + return plan; + } + + @Override + public IPlanPhase getCurrentPhase() + { + return phase; + } + + + @Override + public IAccount getAccount() + { + return null; + } + + @Override + public void setPrivate(final String name, final String value) + { + throw new UnsupportedOperationException(); + } + + @Override + public String getPrivate(final String name) + { + throw new UnsupportedOperationException(); + } + + @Override + public void uncancel() throws EntitlementUserApiException { + throw new UnsupportedOperationException(); + } + + @Override + public String getCurrentPriceList() { + throw new UnsupportedOperationException(); + } +} diff --git a/analytics/src/test/java/com/ning/billing/analytics/TestBusinessSubscription.java b/analytics/src/test/java/com/ning/billing/analytics/TestBusinessSubscription.java new file mode 100644 index 0000000000..79a81f1105 --- /dev/null +++ b/analytics/src/test/java/com/ning/billing/analytics/TestBusinessSubscription.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.analytics; + +import com.ning.billing.catalog.api.IDuration; +import com.ning.billing.catalog.api.IPlan; +import com.ning.billing.catalog.api.IPlanPhase; +import com.ning.billing.catalog.api.IProduct; +import com.ning.billing.catalog.api.PhaseType; +import com.ning.billing.catalog.api.ProductCategory; +import com.ning.billing.entitlement.api.user.ISubscription; +import org.testng.Assert; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +import java.math.BigDecimal; + +public class TestBusinessSubscription +{ + private final IDuration MONTHLY = MockDuration.MONHTLY(); + private final IDuration YEARLY = MockDuration.YEARLY(); + final Object[][] catalog = { + {MONTHLY, 229.0000, 229.0000}, + {MONTHLY, 19.9500, 19.9500}, + {MONTHLY, 14.9500, 14.9500}, + {MONTHLY, 12.9500, 12.9500}, + {YEARLY, 19.9500, 1.6625}, + {YEARLY, 399.0000, 33.2500}, + {YEARLY, 29.9500, 2.4958}, + {YEARLY, 59.0000, 4.9167}, + {YEARLY, 18.2900, 1.5242}, + {YEARLY, 49.0000, 4.0833}}; + + private IProduct product; + private IPlan plan; + private IPlanPhase phase; + private ISubscription isubscription; + private BusinessSubscription subscription; + + @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); + isubscription = new MockSubscription(ISubscription.SubscriptionState.ACTIVE, plan, phase); + subscription = new BusinessSubscription(isubscription); + } + + @Test(groups = "fast") + public void testMrrComputation() throws Exception + { + int i = 0; + for (final Object[] object : catalog) { + final IDuration duration = (IDuration) object[0]; + final double price = (Double) object[1]; + final double expectedMrr = (Double) object[2]; + + final BigDecimal computedMrr = BusinessSubscription.getMrrFromISubscription(duration, BigDecimal.valueOf(price)); + Assert.assertEquals(computedMrr.doubleValue(), expectedMrr, "Invalid mrr for product #" + i); + i++; + } + } + + @Test(groups = "fast") + public void testConstructor() throws Exception + { + Assert.assertEquals(subscription.getRoundedMrr(), 0.0); + Assert.assertEquals(subscription.getSlug(), phase.getName()); + Assert.assertEquals(subscription.getPhase(), phase.getPhaseType().toString()); + Assert.assertEquals(subscription.getPrice(), phase.getRecurringPrice().getPrice(null)); + Assert.assertEquals(subscription.getProductCategory(), product.getCategory()); + Assert.assertEquals(subscription.getProductName(), product.getName()); + Assert.assertEquals(subscription.getProductType(), product.getType().getName()); + Assert.assertEquals(subscription.getStartDate(), isubscription.getStartDate()); + } + + @Test(groups = "fast") + public void testEquals() throws Exception + { + Assert.assertSame(subscription, subscription); + Assert.assertEquals(subscription, subscription); + Assert.assertTrue(subscription.equals(subscription)); + + final ISubscription otherIsubscription = new MockSubscription(ISubscription.SubscriptionState.CANCELLED, plan, phase); + Assert.assertTrue(!subscription.equals(new BusinessSubscription(otherIsubscription))); + } +} diff --git a/analytics/src/test/java/com/ning/billing/analytics/TestBusinessSubscriptionEvent.java b/analytics/src/test/java/com/ning/billing/analytics/TestBusinessSubscriptionEvent.java new file mode 100644 index 0000000000..171dd9c941 --- /dev/null +++ b/analytics/src/test/java/com/ning/billing/analytics/TestBusinessSubscriptionEvent.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.analytics; + +import com.ning.billing.catalog.api.IPlan; +import com.ning.billing.catalog.api.IPlanPhase; +import com.ning.billing.catalog.api.IProduct; +import com.ning.billing.catalog.api.PhaseType; +import com.ning.billing.catalog.api.ProductCategory; +import com.ning.billing.entitlement.api.user.ISubscription; +import org.testng.Assert; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +public class TestBusinessSubscriptionEvent +{ + private IProduct product; + private IPlan plan; + private IPlanPhase phase; + private ISubscription isubscription; + + @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); + isubscription = new MockSubscription(ISubscription.SubscriptionState.ACTIVE, plan, phase); + } + + @Test(groups = "fast") + public void testValueOf() throws Exception + { + BusinessSubscriptionEvent event; + + event = BusinessSubscriptionEvent.valueOf("ADD_ADD_ON"); + Assert.assertEquals(event.getEventType(), BusinessSubscriptionEvent.EventType.ADD); + Assert.assertEquals(event.getCategory(), ProductCategory.ADD_ON); + + event = BusinessSubscriptionEvent.valueOf("CANCEL_BASE"); + Assert.assertEquals(event.getEventType(), BusinessSubscriptionEvent.EventType.CANCEL); + Assert.assertEquals(event.getCategory(), ProductCategory.BASE); + + event = BusinessSubscriptionEvent.valueOf("PAUSE_MISC"); + Assert.assertEquals(event.getEventType(), BusinessSubscriptionEvent.EventType.PAUSE); + Assert.assertNull(event.getCategory()); + + event = BusinessSubscriptionEvent.valueOf("SYSTEM_CANCEL_ADD_ON"); + Assert.assertEquals(event.getEventType(), BusinessSubscriptionEvent.EventType.SYSTEM_CANCEL); + Assert.assertEquals(event.getCategory(), ProductCategory.ADD_ON); + } + + @Test(groups = "fast") + public void testFromISubscription() throws Exception + { + BusinessSubscriptionEvent event; + + event = BusinessSubscriptionEvent.subscriptionCreated(isubscription); + Assert.assertEquals(event.getEventType(), BusinessSubscriptionEvent.EventType.ADD); + Assert.assertEquals(event.getCategory(), product.getCategory()); + Assert.assertEquals(event.toString(), "ADD_BASE"); + + event = BusinessSubscriptionEvent.subscriptionCancelled(isubscription); + Assert.assertEquals(event.getEventType(), BusinessSubscriptionEvent.EventType.CANCEL); + Assert.assertEquals(event.getCategory(), product.getCategory()); + Assert.assertEquals(event.toString(), "CANCEL_BASE"); + + event = BusinessSubscriptionEvent.subscriptionChanged(isubscription); + Assert.assertEquals(event.getEventType(), BusinessSubscriptionEvent.EventType.CHANGE); + Assert.assertEquals(event.getCategory(), product.getCategory()); + Assert.assertEquals(event.toString(), "CHANGE_BASE"); + + event = BusinessSubscriptionEvent.subscriptionPaused(isubscription); + Assert.assertEquals(event.getEventType(), BusinessSubscriptionEvent.EventType.PAUSE); + Assert.assertEquals(event.getCategory(), product.getCategory()); + Assert.assertEquals(event.toString(), "PAUSE_BASE"); + + event = BusinessSubscriptionEvent.subscriptionResumed(isubscription); + Assert.assertEquals(event.getEventType(), BusinessSubscriptionEvent.EventType.RESUME); + Assert.assertEquals(event.getCategory(), product.getCategory()); + Assert.assertEquals(event.toString(), "RESUME_BASE"); + + event = BusinessSubscriptionEvent.subscriptionPhaseChanged(isubscription); + // 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"); + + isubscription = new MockSubscription(ISubscription.SubscriptionState.CANCELLED, plan, phase); + event = BusinessSubscriptionEvent.subscriptionPhaseChanged(isubscription); + // The subscription is cancelled, it's a system cancellation + Assert.assertEquals(event.getEventType(), BusinessSubscriptionEvent.EventType.SYSTEM_CANCEL); + Assert.assertEquals(event.getCategory(), product.getCategory()); + Assert.assertEquals(event.toString(), "SYSTEM_CANCEL_BASE"); + } + + @Test(groups = "fast") + public void testEquals() throws Exception + { + final BusinessSubscriptionEvent event = BusinessSubscriptionEvent.subscriptionChanged(isubscription); + Assert.assertSame(event, event); + Assert.assertEquals(event, event); + Assert.assertTrue(event.equals(event)); + + final BusinessSubscriptionEvent otherEvent = BusinessSubscriptionEvent.subscriptionPaused(isubscription); + Assert.assertTrue(!event.equals(otherEvent)); + } +} diff --git a/analytics/src/test/java/com/ning/billing/analytics/TestBusinessSubscriptionTransition.java b/analytics/src/test/java/com/ning/billing/analytics/TestBusinessSubscriptionTransition.java new file mode 100644 index 0000000000..93758e281f --- /dev/null +++ b/analytics/src/test/java/com/ning/billing/analytics/TestBusinessSubscriptionTransition.java @@ -0,0 +1,121 @@ +/* + * Copyright 2010-2011 Ning, Inc. + * + * Ning licenses this file to you under the Apache License, version 2.0 + * (the "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package com.ning.billing.analytics; + +import com.ning.billing.catalog.api.IPlan; +import com.ning.billing.catalog.api.IPlanPhase; +import com.ning.billing.catalog.api.IProduct; +import com.ning.billing.catalog.api.PhaseType; +import com.ning.billing.catalog.api.ProductCategory; +import com.ning.billing.entitlement.api.user.ISubscription; +import org.joda.time.DateTime; +import org.joda.time.DateTimeZone; +import org.testng.Assert; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +public class TestBusinessSubscriptionTransition +{ + private BusinessSubscription prevSubscription; + private BusinessSubscription nextSubscription; + private BusinessSubscriptionEvent event; + private DateTime requestedTimestamp; + private String key; + private BusinessSubscriptionTransition transition; + + @BeforeMethod(alwaysRun = true) + public void setUp() throws Exception + { + final IProduct product = new MockProduct("platinium", "subscription", ProductCategory.BASE); + final IPlan plan = new MockPlan("platinum-monthly", product); + final IPlanPhase phase = new MockPhase(PhaseType.EVERGREEN, plan, MockDuration.UNLIMITED(), 25.95); + final ISubscription prevISubscription = new MockSubscription(ISubscription.SubscriptionState.ACTIVE, plan, phase); + final ISubscription nextISubscription = new MockSubscription(ISubscription.SubscriptionState.CANCELLED, plan, phase); + + prevSubscription = new BusinessSubscription(prevISubscription); + nextSubscription = new BusinessSubscription(nextISubscription); + event = BusinessSubscriptionEvent.subscriptionCancelled(prevISubscription); + requestedTimestamp = new DateTime(DateTimeZone.UTC); + key = "1234"; + transition = new BusinessSubscriptionTransition(key, requestedTimestamp, event, prevSubscription, nextSubscription); + } + + @Test(groups = "fast") + public void testConstructor() throws Exception + { + Assert.assertEquals(transition.getEvent(), event); + Assert.assertEquals(transition.getPreviousSubscription(), prevSubscription); + Assert.assertEquals(transition.getNextSubscription(), nextSubscription); + Assert.assertEquals(transition.getRequestedTimestamp(), requestedTimestamp); + } + + @Test(groups = "fast") + public void testEquals() throws Exception + { + Assert.assertSame(transition, transition); + Assert.assertEquals(transition, transition); + Assert.assertTrue(transition.equals(transition)); + + BusinessSubscriptionTransition otherTransition; + + otherTransition = new BusinessSubscriptionTransition(key, new DateTime(), event, prevSubscription, nextSubscription); + Assert.assertTrue(!transition.equals(otherTransition)); + + otherTransition = new BusinessSubscriptionTransition("12345", requestedTimestamp, event, prevSubscription, nextSubscription); + Assert.assertTrue(!transition.equals(otherTransition)); + + otherTransition = new BusinessSubscriptionTransition(key, requestedTimestamp, BusinessSubscriptionEvent.subscriptionPaused(null), prevSubscription, nextSubscription); + Assert.assertTrue(!transition.equals(otherTransition)); + + otherTransition = new BusinessSubscriptionTransition(key, requestedTimestamp, event, prevSubscription, prevSubscription); + Assert.assertTrue(!transition.equals(otherTransition)); + + otherTransition = new BusinessSubscriptionTransition(key, requestedTimestamp, event, nextSubscription, nextSubscription); + Assert.assertTrue(!transition.equals(otherTransition)); + + otherTransition = new BusinessSubscriptionTransition(key, requestedTimestamp, event, nextSubscription, prevSubscription); + Assert.assertTrue(!transition.equals(otherTransition)); + } + + @Test(groups = "fast") + public void testRejectInvalidTransitions() throws Exception + { + try { + new BusinessSubscriptionTransition(null, requestedTimestamp, event, prevSubscription, nextSubscription); + Assert.fail(); + } + catch (IllegalArgumentException e) { + Assert.assertTrue(true); + } + + try { + new BusinessSubscriptionTransition(key, null, event, prevSubscription, nextSubscription); + Assert.fail(); + } + catch (IllegalArgumentException e) { + Assert.assertTrue(true); + } + + try { + new BusinessSubscriptionTransition(key, requestedTimestamp, null, prevSubscription, nextSubscription); + Assert.fail(); + } + catch (IllegalArgumentException e) { + Assert.assertTrue(true); + } + } +} diff --git a/analytics/src/test/java/com/ning/billing/analytics/dao/MysqlTestingHelper.java b/analytics/src/test/java/com/ning/billing/analytics/dao/MysqlTestingHelper.java new file mode 100644 index 0000000000..1c51ae4890 --- /dev/null +++ b/analytics/src/test/java/com/ning/billing/analytics/dao/MysqlTestingHelper.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.analytics.dao; + +import com.mysql.management.MysqldResource; +import com.mysql.management.MysqldResourceI; +import org.apache.commons.io.FileUtils; +import org.apache.commons.io.IOUtils; +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.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.File; +import java.io.IOException; +import java.net.ServerSocket; +import java.util.HashMap; +import java.util.Map; + +/** + * Utility class to embed MySQL for testing purposes + */ +public class MysqlTestingHelper +{ + private static final Logger log = LoggerFactory.getLogger(MysqlTestingHelper.class); + + private static final String DB_NAME = "test_killbill_analytics"; + private static final String USERNAME = "root"; + private static final String PASSWORD = ""; + + private File dbDir; + private MysqldResource mysqldResource; + private int port = 0; + + public void startMysql() throws IOException + { + // New socket on any free port + final ServerSocket socket = new ServerSocket(0); + port = socket.getLocalPort(); + socket.close(); + + dbDir = File.createTempFile("mysql", ""); + dbDir.delete(); + dbDir.mkdir(); + mysqldResource = new MysqldResource(dbDir); + + final Map dbOpts = new HashMap(); + dbOpts.put(MysqldResourceI.PORT, Integer.toString(port)); + dbOpts.put(MysqldResourceI.INITIALIZE_USER, "true"); + dbOpts.put(MysqldResourceI.INITIALIZE_USER_NAME, USERNAME); + dbOpts.put(MysqldResourceI.INITIALIZE_PASSWORD, PASSWORD); + + mysqldResource.start("test-mysqld-thread", dbOpts); + if (!mysqldResource.isRunning()) { + throw new IllegalStateException("MySQL did not start."); + } + else { + log.info("MySQL running on port " + mysqldResource.getPort()); + } + } + + public void cleanupTable(final String table) + { + if (mysqldResource == null || mysqldResource.isRunning()) { + log.error("Asked to cleanup table " + table + " but MySQL is not running!"); + return; + } + + log.info("Deleting table: " + table); + final IDBI dbi = getDBI(); + dbi.withHandle(new HandleCallback() + { + @Override + public Void withHandle(final Handle handle) throws Exception + { + handle.createQuery("delete * from " + table); + return null; + } + }); + } + + public void stopMysql() + { + if (mysqldResource != null) { + mysqldResource.shutdown(); + FileUtils.deleteQuietly(dbDir); + log.info("MySQLd stopped"); + } + } + + public IDBI getDBI() + { + final String dbiString = "jdbc:mysql://localhost:" + port + "/" + DB_NAME + "?createDatabaseIfNotExist=true"; + return new DBI(dbiString, USERNAME, PASSWORD); + } + + public void initDb() throws IOException + { + final String ddl = IOUtils.toString(MysqlTestingHelper.class.getResourceAsStream("/ddl.sql")); + + final IDBI dbi = getDBI(); + dbi.withHandle(new HandleCallback() + { + @Override + public Void withHandle(final Handle handle) throws Exception + { + log.info("Executing DDL script: " + ddl); + handle.createScript(ddl).execute(); + return null; + } + }); + } +} diff --git a/analytics/src/test/java/com/ning/billing/analytics/dao/TestEventDao.java b/analytics/src/test/java/com/ning/billing/analytics/dao/TestEventDao.java new file mode 100644 index 0000000000..42f56bd0fe --- /dev/null +++ b/analytics/src/test/java/com/ning/billing/analytics/dao/TestEventDao.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.analytics.dao; + +import com.ning.billing.analytics.BusinessSubscription; +import com.ning.billing.analytics.BusinessSubscriptionEvent; +import com.ning.billing.analytics.BusinessSubscriptionTransition; +import com.ning.billing.analytics.MockDuration; +import com.ning.billing.analytics.MockPhase; +import com.ning.billing.analytics.MockPlan; +import com.ning.billing.analytics.MockProduct; +import com.ning.billing.analytics.MockSubscription; +import com.ning.billing.catalog.api.IPlan; +import com.ning.billing.catalog.api.IPlanPhase; +import com.ning.billing.catalog.api.IProduct; +import com.ning.billing.catalog.api.PhaseType; +import com.ning.billing.catalog.api.ProductCategory; +import com.ning.billing.entitlement.api.user.ISubscription; +import org.joda.time.DateTime; +import org.joda.time.DateTimeZone; +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 java.io.IOException; +import java.sql.SQLException; +import java.util.List; + +public class TestEventDao +{ + private static final String KEY = "12345"; + + private final MysqlTestingHelper helper = new MysqlTestingHelper(); + + private EventDao dao; + private BusinessSubscriptionTransition transition; + + @BeforeClass(alwaysRun = true) + public void startMysql() throws IOException, ClassNotFoundException, SQLException + { + helper.startMysql(); + helper.initDb(); + + final IProduct product = new MockProduct("platinium", "subscription", ProductCategory.BASE); + final IPlan plan = new MockPlan("platinum-monthly", product); + final IPlanPhase phase = new MockPhase(PhaseType.EVERGREEN, plan, MockDuration.UNLIMITED(), 25.95); + final ISubscription prevISubscription = new MockSubscription(ISubscription.SubscriptionState.ACTIVE, plan, phase); + final ISubscription nextISubscription = new MockSubscription(ISubscription.SubscriptionState.CANCELLED, plan, phase); + + final BusinessSubscription prevSubscription = new BusinessSubscription(prevISubscription); + final BusinessSubscription nextSubscription = new BusinessSubscription(nextISubscription); + final BusinessSubscriptionEvent event = BusinessSubscriptionEvent.subscriptionCancelled(prevISubscription); + final DateTime requestedTimestamp = new DateTime(DateTimeZone.UTC); + + transition = new BusinessSubscriptionTransition(KEY, requestedTimestamp, event, prevSubscription, nextSubscription); + + final IDBI dbi = helper.getDBI(); + dao = dbi.onDemand(EventDao.class); + + // Healthcheck test to make sure MySQL is setup properly + try { + dao.test(); + } + catch (Throwable t) { + Assert.fail(t.toString()); + } + } + + @AfterClass(alwaysRun = true) + public void stopMysql() + { + helper.stopMysql(); + } + + @Test(groups = "slow") + public void testCreateAndRetrieve() + { + dao.createTransition(transition); + + final List transitions = dao.getTransitions(KEY); + Assert.assertEquals(transitions.size(), 1); + Assert.assertEquals(transitions.get(0), transition); + + Assert.assertEquals(dao.getTransitions("Doesn't exist").size(), 0); + } +} diff --git a/analytics/src/test/resources/log4j.xml b/analytics/src/test/resources/log4j.xml new file mode 100644 index 0000000000..82b5a26051 --- /dev/null +++ b/analytics/src/test/resources/log4j.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + diff --git a/api/pom.xml b/api/pom.xml new file mode 100644 index 0000000000..d38ffe164e --- /dev/null +++ b/api/pom.xml @@ -0,0 +1,55 @@ + + + + + 4.0.0 + + com.ning.billing + killbill + 0.0.3-SNAPSHOT + ../pom.xml + + killbill-api + killbill-api + jar + + + com.google.guava + guava + provided + + + com.google.inject + guice + provided + + + joda-time + joda-time + + + org.slf4j + slf4j-api + + + org.slf4j + slf4j-log4j12 + + + + + diff --git a/api/src/main/java/com/ning/billing/BillingExceptionBase.java b/api/src/main/java/com/ning/billing/BillingExceptionBase.java new file mode 100644 index 0000000000..486e5dacc9 --- /dev/null +++ b/api/src/main/java/com/ning/billing/BillingExceptionBase.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; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + + +public class BillingExceptionBase extends Exception { + + private final static Logger log = LoggerFactory.getLogger(BillingExceptionBase.class); + + private static final long serialVersionUID = 165720101383L; + + private final Throwable cause; + private final int code; + private final String formattedMsg; + + public BillingExceptionBase(Throwable cause, ErrorCode code, final Object... args) { + String tmp = null; + try { + tmp = String.format(code.getFormat(), args); + } catch (RuntimeException e) { + log.error("Failed to format msg for error code " + code.getCode(), e); + throw e; + } + this.formattedMsg = tmp; + this.code = code.getCode(); + this.cause = cause; + } + + public BillingExceptionBase(ErrorCode code, final Object... args) { + this(null, code, args); + } + + @Override + public String getMessage() { + return formattedMsg; + } + + @Override + public Throwable getCause() { + return cause; + } + + public int getCode() { + return code; + } + +} diff --git a/api/src/main/java/com/ning/billing/ErrorCode.java b/api/src/main/java/com/ning/billing/ErrorCode.java new file mode 100644 index 0000000000..d89ff9708d --- /dev/null +++ b/api/src/main/java/com/ning/billing/ErrorCode.java @@ -0,0 +1,63 @@ +/* + * Copyright 2010-2011 Ning, Inc. + * + * Ning licenses this file to you under the Apache License, version 2.0 + * (the "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package com.ning.billing; + +public enum ErrorCode { + + /* + * Range 0 : COMMON EXCEPTIONS + */ + NOT_IMPLEMENTED(1, "Api not implemented yet"), + + /* + * + * Range 1000 : ENTITLEMENTS + * + */ + /* Not yet */ + + /* Creation */ + ENT_CREATE_BAD_CATALOG(1011, "Plan for product %s, term %s and set %s does not exist in the catalog"), + ENT_CREATE_NO_BUNDLE(1012, "Bundle %s does not exists"), + ENT_CREATE_NO_BP(1013, "Missing Base Subscription for bundle %s"), + ENT_CREATE_BP_EXISTS(1015, "Subscription bundle %s already has a base subscription"), + /* Change plan */ + ENT_CHANGE_NON_ACTIVE(1021, "Subscription %s is in state %s"), + ENT_CHANGE_FUTURE_CANCELLED(1022, "Subscription %s is future cancelled"), + /* Cancellation */ + ENT_CANCEL_BAD_STATE(1031, "Subscription %s is in state %s"), + /* Un-cancellation */ + ENT_UNCANCEL_BAD_STATE(1070, "Subscription %s was not in a cancelled state") + ; + + private int code; + private String format; + + ErrorCode(int code, String format) { + this.code = code; + this.format = format; + } + + public String getFormat() { + return format; + } + + public int getCode() { + return code; + } + +} diff --git a/api/src/main/java/com/ning/billing/account/api/IAccount.java b/api/src/main/java/com/ning/billing/account/api/IAccount.java new file mode 100644 index 0000000000..3af1bcf2ad --- /dev/null +++ b/api/src/main/java/com/ning/billing/account/api/IAccount.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.account.api; + +import com.ning.billing.catalog.api.Currency; + +import java.util.UUID; + +public interface IAccount extends IAccountData { + + public UUID getId(); + + public int getBillCycleDay(); + + public Currency getCurrency(); + + public void setPrivate(String name, String value); + + public String getPrivate(String name); +} diff --git a/api/src/main/java/com/ning/billing/account/api/IAccountData.java b/api/src/main/java/com/ning/billing/account/api/IAccountData.java new file mode 100644 index 0000000000..7565964eef --- /dev/null +++ b/api/src/main/java/com/ning/billing/account/api/IAccountData.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.account.api; + +public interface IAccountData { + + public String getKey(); + + public String getName(); + + public String getEmail(); + + public String getPhone(); +} diff --git a/api/src/main/java/com/ning/billing/account/api/IUserApi.java b/api/src/main/java/com/ning/billing/account/api/IUserApi.java new file mode 100644 index 0000000000..f0bf23aa2e --- /dev/null +++ b/api/src/main/java/com/ning/billing/account/api/IUserApi.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.account.api; + +import java.util.List; +import java.util.UUID; + +public interface IUserApi { + + public IAccount createAccount(IAccountData data); + + public IAccount getAccountByKey(String key); + + public IAccount getAccountFromId(UUID uid); + + public List getAccounts(); +} diff --git a/api/src/main/java/com/ning/billing/catalog/api/ActionPolicy.java b/api/src/main/java/com/ning/billing/catalog/api/ActionPolicy.java new file mode 100644 index 0000000000..86b8619d21 --- /dev/null +++ b/api/src/main/java/com/ning/billing/catalog/api/ActionPolicy.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 ActionPolicy { + END_OF_TERM, + IMMEDIATE +} diff --git a/api/src/main/java/com/ning/billing/catalog/api/BillingAlignment.java b/api/src/main/java/com/ning/billing/catalog/api/BillingAlignment.java new file mode 100644 index 0000000000..5dce39d561 --- /dev/null +++ b/api/src/main/java/com/ning/billing/catalog/api/BillingAlignment.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.catalog.api; + +public enum BillingAlignment { + ACCOUNT, + BUNDLE, + SUBSCRIPTION +} diff --git a/api/src/main/java/com/ning/billing/catalog/api/BillingPeriod.java b/api/src/main/java/com/ning/billing/catalog/api/BillingPeriod.java new file mode 100644 index 0000000000..aa8b20c1cc --- /dev/null +++ b/api/src/main/java/com/ning/billing/catalog/api/BillingPeriod.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.catalog.api; + +public enum BillingPeriod { + MONTHLY(1), + QUARTERLY(3), + //SEMI_ANNUAL(6), ** not yet supported + ANNUAL(12); + //BI_ANNUAL(24); ** not yet supported + + private final int numberOfMonths; + + BillingPeriod(int numberOfMonths) { + this.numberOfMonths = numberOfMonths; + } + + public int getNumberOfMonths() { + return numberOfMonths; + } +} \ No newline at end of file diff --git a/api/src/main/java/com/ning/billing/catalog/api/Currency.java b/api/src/main/java/com/ning/billing/catalog/api/Currency.java new file mode 100644 index 0000000000..029616add3 --- /dev/null +++ b/api/src/main/java/com/ning/billing/catalog/api/Currency.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.catalog.api; + +import javax.xml.bind.annotation.XmlEnum; + + +@XmlEnum +public enum Currency { + GBP, + MXN, + BRL, + EUR, + AUD, + USD + +// Unsupported +// CAD, +// JPY + +} diff --git a/api/src/main/java/com/ning/billing/catalog/api/ICatalog.java b/api/src/main/java/com/ning/billing/catalog/api/ICatalog.java new file mode 100644 index 0000000000..0e533b1fb0 --- /dev/null +++ b/api/src/main/java/com/ning/billing/catalog/api/ICatalog.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.catalog.api; + +import java.util.Date; +import java.util.List; + +public interface ICatalog { + + public abstract IProductType[] getProductTypes(); + + public abstract IProduct[] getProducts(); + + public abstract IPriceList[] getPriceLists(); + + public abstract IPriceList getPriceListFromName(String priceListName); + + public abstract List getProductsForType(IProductType productType); + + public abstract IPlan getPlan(String productName, BillingPeriod term, String priceList); + + public abstract Currency[] getSupportedCurrencies(); + + public abstract IPlan[] getPlans(); + + public abstract ActionPolicy getPlanChangePolicy(PlanPhaseSpecifier from, + PlanSpecifier to); + + public abstract IPlan getPlanFromName(String name); + + public abstract IPlanPhase getPhaseFromName(String name); + + public abstract Date getEffectiveDate(); + + public abstract IPlanPhase getPhaseFor(String name, Date date); + + public abstract IProduct getProductFromName(String name); + + public abstract ActionPolicy getPlanCancelPolicy(PlanPhaseSpecifier planPhase); + + public abstract PlanAlignment getPlanAlignment(PlanPhaseSpecifier from, PlanSpecifier to); + + +} \ No newline at end of file diff --git a/api/src/main/java/com/ning/billing/catalog/api/ICatalogUserApi.java b/api/src/main/java/com/ning/billing/catalog/api/ICatalogUserApi.java new file mode 100644 index 0000000000..b67e69c3c2 --- /dev/null +++ b/api/src/main/java/com/ning/billing/catalog/api/ICatalogUserApi.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 interface ICatalogUserApi { + + ICatalog getCatalog(String catalogName); +} diff --git a/api/src/main/java/com/ning/billing/catalog/api/IDuration.java b/api/src/main/java/com/ning/billing/catalog/api/IDuration.java new file mode 100644 index 0000000000..45973df4e9 --- /dev/null +++ b/api/src/main/java/com/ning/billing/catalog/api/IDuration.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.catalog.api; + +public interface IDuration { + + public abstract TimeUnit getUnit(); + + public abstract int getLength(); + +} \ No newline at end of file diff --git a/api/src/main/java/com/ning/billing/catalog/api/IInternationalPrice.java b/api/src/main/java/com/ning/billing/catalog/api/IInternationalPrice.java new file mode 100644 index 0000000000..e20ed970c1 --- /dev/null +++ b/api/src/main/java/com/ning/billing/catalog/api/IInternationalPrice.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.api; + +import java.math.BigDecimal; + + +public interface IInternationalPrice { + + public abstract IPrice[] getPrices(); + + public abstract BigDecimal getPrice(Currency currency); + +} \ No newline at end of file diff --git a/api/src/main/java/com/ning/billing/catalog/api/IPlan.java b/api/src/main/java/com/ning/billing/catalog/api/IPlan.java new file mode 100644 index 0000000000..e9efa979e2 --- /dev/null +++ b/api/src/main/java/com/ning/billing/catalog/api/IPlan.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.api; + +import java.util.Iterator; + +public interface IPlan { + + public abstract IPlanPhase[] getInitialPhases(); + + public abstract IProduct getProduct(); + + public abstract String getName(); + + public abstract Iterator getInitialPhaseIterator(); + + public abstract IPlanPhase getFinalPhase(); + + public abstract BillingPeriod getBillingPeriod(); + + public abstract BillingAlignment getBillingAlignment(); + + public abstract int getPlansAllowedInBundle(); + +} \ No newline at end of file diff --git a/api/src/main/java/com/ning/billing/catalog/api/IPlanPhase.java b/api/src/main/java/com/ning/billing/catalog/api/IPlanPhase.java new file mode 100644 index 0000000000..72383ec15e --- /dev/null +++ b/api/src/main/java/com/ning/billing/catalog/api/IPlanPhase.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.api; + + +public interface IPlanPhase { + + public abstract IInternationalPrice getRecurringPrice(); + + public abstract IInternationalPrice getFixedPrice(); + + public abstract BillingPeriod getBillingPeriod(); + + public abstract String getName(); + + public abstract IPlan getPlan(); + + public abstract IDuration getDuration(); + + public abstract PhaseType getPhaseType(); + + + + +} \ No newline at end of file diff --git a/api/src/main/java/com/ning/billing/catalog/api/IPrice.java b/api/src/main/java/com/ning/billing/catalog/api/IPrice.java new file mode 100644 index 0000000000..dce29cce4f --- /dev/null +++ b/api/src/main/java/com/ning/billing/catalog/api/IPrice.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.api; + +import java.math.BigDecimal; + + +public interface IPrice { + + public abstract Currency getCurrency(); + + public abstract BigDecimal getValue(); + +} \ No newline at end of file diff --git a/api/src/main/java/com/ning/billing/catalog/api/IPriceList.java b/api/src/main/java/com/ning/billing/catalog/api/IPriceList.java new file mode 100644 index 0000000000..f6a528a7d7 --- /dev/null +++ b/api/src/main/java/com/ning/billing/catalog/api/IPriceList.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.catalog.api; + + +public interface IPriceList { + + public abstract String getName(); + + public abstract IPlan[] getPlans(); + + public abstract IPlan findPlanByProductName(String productName); + + public abstract boolean isDefault(); + +} \ No newline at end of file diff --git a/api/src/main/java/com/ning/billing/catalog/api/IProduct.java b/api/src/main/java/com/ning/billing/catalog/api/IProduct.java new file mode 100644 index 0000000000..af82794aa0 --- /dev/null +++ b/api/src/main/java/com/ning/billing/catalog/api/IProduct.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.catalog.api; + + +public interface IProduct { + + public abstract IProductType getType(); + + public abstract String getName(); + + public abstract IProduct[] getAvailable(); + + public abstract IProduct[] getIncluded(); + + public abstract ProductCategory getCategory(); + +} \ No newline at end of file diff --git a/api/src/main/java/com/ning/billing/catalog/api/IProductTier.java b/api/src/main/java/com/ning/billing/catalog/api/IProductTier.java new file mode 100644 index 0000000000..634e7ab5cc --- /dev/null +++ b/api/src/main/java/com/ning/billing/catalog/api/IProductTier.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.catalog.api; + +public interface IProductTier { + + public abstract IProduct[] getProducts(); + +} \ No newline at end of file diff --git a/api/src/main/java/com/ning/billing/catalog/api/IProductType.java b/api/src/main/java/com/ning/billing/catalog/api/IProductType.java new file mode 100644 index 0000000000..9ff2e79ab8 --- /dev/null +++ b/api/src/main/java/com/ning/billing/catalog/api/IProductType.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.catalog.api; + +public interface IProductType { + + public abstract String getName(); + +} \ No newline at end of file diff --git a/api/src/main/java/com/ning/billing/catalog/api/InvalidConfigException.java b/api/src/main/java/com/ning/billing/catalog/api/InvalidConfigException.java new file mode 100644 index 0000000000..328abaa8ed --- /dev/null +++ b/api/src/main/java/com/ning/billing/catalog/api/InvalidConfigException.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.catalog.api; + +public class InvalidConfigException extends Exception { + private static final long serialVersionUID = 1L; + + public InvalidConfigException(String arg0, Throwable arg1) { + super(arg0, arg1); + } + + public InvalidConfigException(String arg0) { + super(arg0); + } + +} diff --git a/api/src/main/java/com/ning/billing/catalog/api/PhaseType.java b/api/src/main/java/com/ning/billing/catalog/api/PhaseType.java new file mode 100644 index 0000000000..06678c71c2 --- /dev/null +++ b/api/src/main/java/com/ning/billing/catalog/api/PhaseType.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.catalog.api; + +public enum PhaseType { + TRIAL, + DISCOUNT, + FIXEDTERM, + EVERGREEN +} diff --git a/api/src/main/java/com/ning/billing/catalog/api/PlanAlignment.java b/api/src/main/java/com/ning/billing/catalog/api/PlanAlignment.java new file mode 100644 index 0000000000..153e9d832a --- /dev/null +++ b/api/src/main/java/com/ning/billing/catalog/api/PlanAlignment.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.catalog.api; + +public enum PlanAlignment { + START_OF_BUNDLE, + START_OF_SUBSCRIPTION, + CHANGE_OF_PLAN, + CHANGE_OF_PRICELIST +} diff --git a/api/src/main/java/com/ning/billing/catalog/api/PlanPhaseSpecifier.java b/api/src/main/java/com/ning/billing/catalog/api/PlanPhaseSpecifier.java new file mode 100644 index 0000000000..281a81ceb3 --- /dev/null +++ b/api/src/main/java/com/ning/billing/catalog/api/PlanPhaseSpecifier.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.catalog.api; + +public class PlanPhaseSpecifier extends PlanSpecifier { + + private final PhaseType phaseType; + + public PlanPhaseSpecifier(String productName, BillingPeriod billingPeriod, + String priceListName, PhaseType phaseType) { + super(productName, billingPeriod, priceListName); + this.phaseType = phaseType; + } + + public PhaseType getPhaseType() { + return phaseType; + } + +} diff --git a/api/src/main/java/com/ning/billing/catalog/api/PlanSpecifier.java b/api/src/main/java/com/ning/billing/catalog/api/PlanSpecifier.java new file mode 100644 index 0000000000..face0da7b0 --- /dev/null +++ b/api/src/main/java/com/ning/billing/catalog/api/PlanSpecifier.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.catalog.api; + +public class PlanSpecifier { + private final String productName; + private final BillingPeriod billingPeriod; + private final String priceListName; + + public PlanSpecifier(String productName, BillingPeriod billingPeriod, + String priceListName) { + super(); + this.productName = productName; + this.billingPeriod = billingPeriod; + this.priceListName = priceListName; + } + + public String getProductName() { + return productName; + } + public BillingPeriod getBillingPeriod() { + return billingPeriod; + } + public String getPriceListName() { + return priceListName; + } +} diff --git a/api/src/main/java/com/ning/billing/catalog/api/ProductCategory.java b/api/src/main/java/com/ning/billing/catalog/api/ProductCategory.java new file mode 100644 index 0000000000..dca1f8d7d6 --- /dev/null +++ b/api/src/main/java/com/ning/billing/catalog/api/ProductCategory.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 ProductCategory { + BASE, + ADD_ON +} diff --git a/api/src/main/java/com/ning/billing/catalog/api/TimeUnit.java b/api/src/main/java/com/ning/billing/catalog/api/TimeUnit.java new file mode 100644 index 0000000000..5271e2998a --- /dev/null +++ b/api/src/main/java/com/ning/billing/catalog/api/TimeUnit.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.catalog.api; + +import javax.xml.bind.annotation.XmlEnum; + +@XmlEnum +public enum TimeUnit { + DAYS, + MONTHS, + YEARS, + UNLIMITED +} \ No newline at end of file diff --git a/api/src/main/java/com/ning/billing/entitlement/api/billing/BillingMode.java b/api/src/main/java/com/ning/billing/entitlement/api/billing/BillingMode.java new file mode 100644 index 0000000000..77ffbb7423 --- /dev/null +++ b/api/src/main/java/com/ning/billing/entitlement/api/billing/BillingMode.java @@ -0,0 +1,21 @@ +/* + * Copyright 2010-2011 Ning, Inc. + * + * Ning licenses this file to you under the Apache License, version 2.0 + * (the "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package com.ning.billing.entitlement.api.billing; + +public enum BillingMode { + IN_ADVANCE +} diff --git a/api/src/main/java/com/ning/billing/entitlement/api/billing/EntitlementBillingApiException.java b/api/src/main/java/com/ning/billing/entitlement/api/billing/EntitlementBillingApiException.java new file mode 100644 index 0000000000..9c22915f85 --- /dev/null +++ b/api/src/main/java/com/ning/billing/entitlement/api/billing/EntitlementBillingApiException.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.entitlement.api.billing; + +public class EntitlementBillingApiException extends Exception { + + private static final long serialVersionUID = 127392038L; + + public EntitlementBillingApiException() { + super(); + } + + public EntitlementBillingApiException(String msg, Throwable arg1) { + super(msg, arg1); + } + + public EntitlementBillingApiException(String msg) { + super(msg); + } + + public EntitlementBillingApiException(Throwable msg) { + super(msg); + } + + +} diff --git a/api/src/main/java/com/ning/billing/entitlement/api/billing/IBillingApi.java b/api/src/main/java/com/ning/billing/entitlement/api/billing/IBillingApi.java new file mode 100644 index 0000000000..cd55ca29b1 --- /dev/null +++ b/api/src/main/java/com/ning/billing/entitlement/api/billing/IBillingApi.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.entitlement.api.billing; + +import java.util.List; +import java.util.SortedSet; +import java.util.UUID; + +import org.joda.time.DateTime; + +import com.ning.billing.account.api.IAccount; + +public interface IBillingApi { + + /** + * + * @return the list of accounts which have active subscriptions + */ + public List getActiveAccounts(); + + /** + * + * @param subscriptionId the subscriptionId of interest for a gievn account + * @return an ordered list of billing event + * + * Note: The user api allows to get list of subscription bundle / subscriptions for an account + */ + public SortedSet getBillingEventsForSubscription(UUID subscriptionId); + + + public void setChargedThroughDate(UUID subscriptionId, DateTime ctd); + +} diff --git a/api/src/main/java/com/ning/billing/entitlement/api/billing/IBillingEvent.java b/api/src/main/java/com/ning/billing/entitlement/api/billing/IBillingEvent.java new file mode 100644 index 0000000000..5db481526e --- /dev/null +++ b/api/src/main/java/com/ning/billing/entitlement/api/billing/IBillingEvent.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.entitlement.api.billing; + +import com.ning.billing.catalog.api.BillingPeriod; +import com.ning.billing.catalog.api.Currency; +import com.ning.billing.catalog.api.IInternationalPrice; +import org.joda.time.DateTime; + +import java.math.BigDecimal; +import java.util.UUID; + +public interface IBillingEvent extends Comparable { + + /** + * + * @return the billCycleDay as seen for that subscription at that time + * + * Note: The billCycleDay may come from the Account, or the bundle or the subscription itself + */ + public int getBillCycleDay(); + + /** + * + * @return the id for the matching subscription + */ + public UUID getSubscriptionId(); + + /** + * + * @return the date for when that event became effective + */ + public DateTime getEffectiveDate(); + + /** + * + * @return the name of the plan phase + */ + public String getPlanPhaseName(); + + + /** + * + * @return the name of the plan + */ + public String getPlanName(); + + /** + * + * @return the international price for the event + * + */ + public IInternationalPrice getPrice(); + + /** + * + * @param currency the target currency for invoicing + * @return the price of the plan phase in the specified currency + */ + public BigDecimal getPrice(Currency currency); + + /** + * + * @return the billing period for the active phase + */ + public BillingPeriod getBillingPeriod(); + + /** + * + * @return the billing mode for the current event + */ + public BillingMode getBillingMode(); +} 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 new file mode 100644 index 0000000000..c4b8c42456 --- /dev/null +++ b/api/src/main/java/com/ning/billing/entitlement/api/user/EntitlementUserApiException.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.entitlement.api.user; + +import com.ning.billing.BillingExceptionBase; +import com.ning.billing.ErrorCode; + +public class EntitlementUserApiException extends BillingExceptionBase { + + public EntitlementUserApiException(Throwable e, ErrorCode code, Object...args) { + super(e, code, args); + } + + public EntitlementUserApiException(ErrorCode code, Object...args) { + super(code, args); + } + +} diff --git a/api/src/main/java/com/ning/billing/entitlement/api/user/IApiListener.java b/api/src/main/java/com/ning/billing/entitlement/api/user/IApiListener.java new file mode 100644 index 0000000000..9e9241adcd --- /dev/null +++ b/api/src/main/java/com/ning/billing/entitlement/api/user/IApiListener.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.entitlement.api.user; + + +public interface IApiListener { + + public void subscriptionCreated(ISubscriptionTransition created); + + public void subscriptionCancelled(ISubscriptionTransition cancelled); + + public void subscriptionChanged(ISubscriptionTransition changed); + + public void subscriptionPaused(ISubscriptionTransition paused); + + public void subscriptionResumed(ISubscriptionTransition resumed); + + public void subscriptionPhaseChanged(ISubscriptionTransition phaseChanged); +} diff --git a/api/src/main/java/com/ning/billing/entitlement/api/user/IPrivateFields.java b/api/src/main/java/com/ning/billing/entitlement/api/user/IPrivateFields.java new file mode 100644 index 0000000000..b31a8c25c3 --- /dev/null +++ b/api/src/main/java/com/ning/billing/entitlement/api/user/IPrivateFields.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.entitlement.api.user; + +public interface IPrivateFields { + + public void setPrivate(String name, String value); + + public String getPrivate(String name); +} diff --git a/api/src/main/java/com/ning/billing/entitlement/api/user/ISubscription.java b/api/src/main/java/com/ning/billing/entitlement/api/user/ISubscription.java new file mode 100644 index 0000000000..d0bcb441ea --- /dev/null +++ b/api/src/main/java/com/ning/billing/entitlement/api/user/ISubscription.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.entitlement.api.user; + +import java.util.UUID; + +import com.ning.billing.account.api.IAccount; +import org.joda.time.DateTime; + +import com.ning.billing.catalog.api.BillingPeriod; +import com.ning.billing.catalog.api.IPlan; +import com.ning.billing.catalog.api.IPlanPhase; +import com.ning.billing.catalog.api.ActionPolicy; + + +public interface ISubscription extends IPrivateFields { + + public void cancel() + throws EntitlementUserApiException; + + public void uncancel() + throws EntitlementUserApiException; + + public void changePlan(String productName, BillingPeriod term, String planSet) + throws EntitlementUserApiException ; + + public void pause() + throws EntitlementUserApiException ; + + public void resume() + throws EntitlementUserApiException ; + + + public enum SubscriptionState { + ACTIVE, + PAUSED, + CANCELLED + } + + public UUID getId(); + + public UUID getBundleId(); + + public SubscriptionState getState(); + + public DateTime getStartDate(); + + public IPlan getCurrentPlan(); + + public String getCurrentPriceList(); + + public IPlanPhase getCurrentPhase(); + + public IAccount getAccount(); + +} diff --git a/api/src/main/java/com/ning/billing/entitlement/api/user/ISubscriptionBundle.java b/api/src/main/java/com/ning/billing/entitlement/api/user/ISubscriptionBundle.java new file mode 100644 index 0000000000..7689cc3bc2 --- /dev/null +++ b/api/src/main/java/com/ning/billing/entitlement/api/user/ISubscriptionBundle.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.entitlement.api.user; + +import java.util.UUID; + +import org.joda.time.DateTime; + +import com.ning.billing.catalog.api.BillingPeriod; + +public interface ISubscriptionBundle extends IPrivateFields { + + public UUID getAccountId(); + + public String getName(); + + public UUID getId(); + + public DateTime getStartDate(); +} diff --git a/api/src/main/java/com/ning/billing/entitlement/api/user/ISubscriptionTransition.java b/api/src/main/java/com/ning/billing/entitlement/api/user/ISubscriptionTransition.java new file mode 100644 index 0000000000..74fdf3f019 --- /dev/null +++ b/api/src/main/java/com/ning/billing/entitlement/api/user/ISubscriptionTransition.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.entitlement.api.user; + +import java.util.UUID; + +import org.joda.time.DateTime; + +import com.ning.billing.catalog.api.IPlan; +import com.ning.billing.catalog.api.IPlanPhase; +import com.ning.billing.entitlement.api.user.ISubscription.SubscriptionState; + +public interface ISubscriptionTransition { + + UUID getSubscriptionId(); + + DateTime getTransitionTime(); + + SubscriptionState getPreviousState(); + + IPlan getPreviousPlan(); + + String getPreviousPriceList(); + + IPlanPhase getPreviousPhase(); + + IPlan getNextPlan(); + + IPlanPhase getNextPhase(); + + SubscriptionState getNextState(); + + String getNextPriceList(); +} diff --git a/api/src/main/java/com/ning/billing/entitlement/api/user/IUserApi.java b/api/src/main/java/com/ning/billing/entitlement/api/user/IUserApi.java new file mode 100644 index 0000000000..ee14f32dcd --- /dev/null +++ b/api/src/main/java/com/ning/billing/entitlement/api/user/IUserApi.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.entitlement.api.user; + +import java.util.List; +import java.util.UUID; + +import com.ning.billing.account.api.IAccount; +import com.ning.billing.catalog.api.BillingPeriod; + + +public interface IUserApi { + + public void initialize(List listeners); + + public ISubscriptionBundle getBundleFromId(UUID id); + + public ISubscription getSubscriptionFromId(UUID id); + + public List getBundlesForAccount(UUID accountId); + + public List getSubscriptionsForBundle(UUID bundleId); + + public ISubscriptionBundle createBundleForAccount(IAccount account, String bundleName) + throws EntitlementUserApiException; + + public ISubscription createSubscription(UUID bundleId, String productName, BillingPeriod term, String planSet) + throws EntitlementUserApiException; +} diff --git a/api/src/main/java/com/ning/billing/invoice/api/BillingEvent.java b/api/src/main/java/com/ning/billing/invoice/api/BillingEvent.java new file mode 100644 index 0000000000..6457466649 --- /dev/null +++ b/api/src/main/java/com/ning/billing/invoice/api/BillingEvent.java @@ -0,0 +1,109 @@ +/* + * Copyright 2010-2011 Ning, Inc. + * + * Ning licenses this file to you under the Apache License, version 2.0 + * (the "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF 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.catalog.api.BillingPeriod; +import com.ning.billing.catalog.api.Currency; +import com.ning.billing.catalog.api.IInternationalPrice; +import com.ning.billing.entitlement.api.billing.BillingMode; +import com.ning.billing.entitlement.api.billing.IBillingEvent; +import org.joda.time.DateTime; + +import java.math.BigDecimal; +import java.util.UUID; + +public class BillingEvent implements IBillingEvent { + private final UUID subscriptionId; + private final DateTime startDate; + private final String planName; + private final String planPhaseName; + private final IInternationalPrice price; + private final BillingPeriod billingPeriod; + private final int billCycleDay; + private final BillingMode billingMode; + + public BillingEvent(UUID subscriptionId, DateTime startDate, String planName, String planPhaseName, IInternationalPrice price, + BillingPeriod billingPeriod, int billCycleDay, BillingMode billingMode) { + this.subscriptionId = subscriptionId; + this.startDate = startDate; + this.planName = planName; + this.planPhaseName = planPhaseName; + this.price = price; + this.billingPeriod = billingPeriod; + this.billCycleDay = billCycleDay; + this.billingMode = billingMode; + } + + @Override + public DateTime getEffectiveDate() { + return startDate; + } + + @Override + public int getBillCycleDay() { + return billCycleDay; + } + + @Override + public UUID getSubscriptionId() { + return subscriptionId; + } + + @Override + public String getPlanName() { + return planName; + } + + @Override + public String getPlanPhaseName() { + return planPhaseName; + } + + @Override + public IInternationalPrice getPrice() { + return price; + } + + @Override + public BigDecimal getPrice(Currency currency) { + return price.getPrice(currency); + } + + @Override + public BillingPeriod getBillingPeriod() { + return billingPeriod; + } + + @Override + public BillingMode getBillingMode() { + return billingMode; + } + + @Override + public int compareTo(IBillingEvent billingEvent) { +// // strict date comparison here breaks SortedTree if multiple events occur on the same day +// return getEffectiveDate().compareTo(billingEvent.getEffectiveDate()) > 0 ? 1 : -1; + + int compareSubscriptions = getSubscriptionId().compareTo(billingEvent.getSubscriptionId()); + + if (compareSubscriptions == 0) { + return getEffectiveDate().compareTo(billingEvent.getEffectiveDate()); + } else { + return compareSubscriptions; + } + } +} \ No newline at end of file diff --git a/api/src/main/java/com/ning/billing/invoice/api/BillingEventSet.java b/api/src/main/java/com/ning/billing/invoice/api/BillingEventSet.java new file mode 100644 index 0000000000..37b536a5df --- /dev/null +++ b/api/src/main/java/com/ning/billing/invoice/api/BillingEventSet.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.invoice.api; + +import com.ning.billing.catalog.api.Currency; +import com.ning.billing.entitlement.api.billing.IBillingEvent; +import org.joda.time.DateTime; + +import java.util.ArrayList; + +public class BillingEventSet extends ArrayList { + private Currency targetCurrency; + private DateTime targetDate; + + public BillingEventSet(Currency targetCurrency) { + this.targetCurrency = targetCurrency; + this.targetDate = new DateTime(); + } + + public BillingEventSet(Currency targetCurrency, DateTime targetDate) { + this.targetCurrency = targetCurrency; + this.targetDate = targetDate; + } + + public Currency getTargetCurrency() { + return targetCurrency; + } + + public DateTime getTargetDate() { + return targetDate; + } + + public IBillingEvent getLast() { + if (this.size() == 0) {return null;} + + return this.get(this.size() - 1); + } +} \ No newline at end of file diff --git a/catalog/pom.xml b/catalog/pom.xml new file mode 100644 index 0000000000..d3786dabf3 --- /dev/null +++ b/catalog/pom.xml @@ -0,0 +1,57 @@ + + + + + 4.0.0 + + com.ning.billing + killbill + 0.0.3-SNAPSHOT + ../pom.xml + + killbill-catalog + killbill-catalog + jar + + + com.ning.billing + killbill-api + + + org.testng + testng + test + + + + + + org.codehaus.mojo + exec-maven-plugin + 1.1.1 + + + test + + java + + + com.ning.billing.catalog.io.XMLSchemaGenerator + + ${project.basedir}/src/main/resources + + + + + + + + diff --git a/catalog/src/main/java/com/ning/billing/catalog/Catalog.java b/catalog/src/main/java/com/ning/billing/catalog/Catalog.java new file mode 100644 index 0000000000..0618c8e662 --- /dev/null +++ b/catalog/src/main/java/com/ning/billing/catalog/Catalog.java @@ -0,0 +1,303 @@ +/* + * Copyright 2010-2011 Ning, Inc. + * + * Ning licenses this file to you under the Apache License, version 2.0 + * (the "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package com.ning.billing.catalog; + +import java.net.URL; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; + +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlElement; +import javax.xml.bind.annotation.XmlElementWrapper; +import javax.xml.bind.annotation.XmlRootElement; + +import com.ning.billing.catalog.api.ActionPolicy; +import com.ning.billing.catalog.api.BillingPeriod; +import com.ning.billing.catalog.api.Currency; +import com.ning.billing.catalog.api.ICatalog; +import com.ning.billing.catalog.api.IPlan; +import com.ning.billing.catalog.api.IProduct; +import com.ning.billing.catalog.api.IProductType; +import com.ning.billing.catalog.api.PlanAlignment; +import com.ning.billing.catalog.api.PlanPhaseSpecifier; +import com.ning.billing.catalog.api.PlanSpecifier; + +@XmlRootElement +@XmlAccessorType(XmlAccessType.NONE) +public class Catalog extends ValidatingConfig implements ICatalog { + + @XmlElement(required=true) + private Date effectiveDate; + + private URL catalogURL; + + @XmlElementWrapper(name="currencies", required=true) + @XmlElement(name="currency", required=true) + private Currency[] supportedCurrencies; + + @XmlElementWrapper(name="productTypes", required=true) + @XmlElement(name="productType", required=true) + private ProductType[] productTypes; + + @XmlElementWrapper(name="products", required=true) + @XmlElement(name="product", required=true) + private Product[] products; + + @XmlElement(name="rules", required=true) + private PlanRules planRules; + + @XmlElementWrapper(name="plans", required=true) + @XmlElement(name="plan", required=true) + private Plan[] plans; + + @XmlElementWrapper(name="priceLists", required=true) + @XmlElement(name="priceList", required=true) + private PriceList[] priceLists; + + public Catalog() {} + + protected Catalog (Date effectiveDate) { + this.effectiveDate = effectiveDate; + } + + @Override + public void initialize(Catalog catalog) { + super.initialize(catalog); + planRules.initialize(catalog); + for(Product p : products) { + p.initialize(catalog); + } + for(Plan p : plans) { + p.initialize(catalog); + } + for(PriceList p : priceLists) { + p.initialize(catalog); + } + + } + + /* (non-Javadoc) + * @see com.ning.billing.catalog.ICatalog#getProductTypes() + */ + @Override + public ProductType[] getProductTypes() { + return productTypes; + } + + /* (non-Javadoc) + * @see com.ning.billing.catalog.ICatalog#getProducts() + */ + @Override + public Product[] getProducts() { + return products; + } + + public void setProducts(Product[] products) { + this.products = products; + } + + /* (non-Javadoc) + * @see com.ning.billing.catalog.ICatalog#getPlanSets() + */ + @Override + public PriceList[] getPriceLists() { + return priceLists; + } + + public void setPlanSets(PriceList[] planSets) { + this.priceLists = planSets; + } + + /* (non-Javadoc) + * @see com.ning.billing.catalog.ICatalog#getPriceListFromName(java.lang.String) + */ + @Override + public PriceList getPriceListFromName(String priceListName) { + for(PriceList set : priceLists) { + if(set.getName().equals(priceListName)) { + return set; + } + } + return null; + } + + /* (non-Javadoc) + * @see com.ning.billing.catalog.ICatalog#getProductsForType(com.ning.billing.catalog.ProductType) + */ + @Override + public List getProductsForType(IProductType productType) { + ArrayList result = new ArrayList(); + for(Product p : products) { + if(p.getType().equals(productType)) { + result.add(p); + } + } + return result; + } + + /* (non-Javadoc) + * @see com.ning.billing.catalog.ICatalog#getPlan(java.lang.String, java.lang.String) + */ + @Override + public Plan getPlan(String productName, BillingPeriod term, String planSetName) { + + PriceList planSet = getPriceListFromName(planSetName); + if (planSet == null) { + return null; + } + + for (Plan cur : planSet.getPlans()) { + if (cur.getProduct().getName().equals(productName) && + cur.getBillingPeriod() == term) { + return cur; + } + } + return null; + } + + public void setProductTypes(ProductType[] productTypes) { + this.productTypes = productTypes; + } + + @Override + public Currency[] getSupportedCurrencies() { + return supportedCurrencies; + } + + public void setSupportedCurrencies(Currency[] supportedCurrencies) { + this.supportedCurrencies = supportedCurrencies; + } + + public void setPlanChangeRules(PlanRules planChangeRules) { + this.planRules = planChangeRules; + } + + @Override + public Plan[] getPlans() { + return plans; + } + + public void setPlans(Plan[] plans) { + this.plans = plans; + } + + @Override + public ValidationErrors validate(Catalog catalog, ValidationErrors errors) { + errors.addAll(validate(catalog,errors, products)); + errors.addAll(validate(catalog,errors, productTypes)); + errors.addAll(validate(catalog,errors, priceLists)); + errors.addAll(validate(catalog,errors, plans)); + errors.addAll(planRules.validate(catalog, errors)); + return errors; + } + + @Override + public ActionPolicy getPlanChangePolicy(PlanPhaseSpecifier from, PlanSpecifier to) { + return planRules.getPlanChangePolicy(from, to, this); + } + + @Override + public ActionPolicy getPlanCancelPolicy(PlanPhaseSpecifier planPhase) { + return planRules.getPlanCancelPolicy(planPhase, this); + } + + @Override + public PlanAlignment getPlanAlignment(PlanPhaseSpecifier from, PlanSpecifier to) { + return planRules.getPlanAlignment(from, to, this); + } + + @Override + public IPlan getPlanFromName(String name) { + if (name == null) { + return null; + } + for(Plan p : plans) { + if(p.getName().equals(name)) { + return p; + } + } + return null; + } + + @Override + public IProduct getProductFromName(String name) { + for(Product p : products) { + if (p.getName().equals(name)) { + return p; + } + } + return null; + } + + + @Override + public PlanPhase getPhaseFromName(String name) { + + if (name == null) { + return null; + } + for(Plan p : plans) { + + if(p.getFinalPhase().getName().equals(name)) { + return p.getFinalPhase(); + } + if (p.getInitialPhases() != null) { + for(PlanPhase pp : p.getInitialPhases()) { + if(pp.getName().equals(name)) { + return pp; + } + } + } + } + + return null; + } + + @Override + public Date getEffectiveDate() { + return effectiveDate; + } + + public void setEffectiveDate(Date effectiveDate) { + this.effectiveDate = effectiveDate; + } + + public URL getCatalogURL() { + return catalogURL; + } + + public void setCatalogURL(URL catalogURL) { + this.catalogURL = catalogURL; + } + + @Override + public PlanPhase getPhaseFor(String name, Date date) { + if(getEffectiveDate().getTime() >= date.getTime()){ + return getPhaseFromName(name); + } + return null; + } + + public void setPriceLists(PriceList[] priceLists) { + this.priceLists = priceLists; + } + + //TODO: MDW validation - only allow one default pricelist + +} diff --git a/catalog/src/main/java/com/ning/billing/catalog/CatalogUserApi.java b/catalog/src/main/java/com/ning/billing/catalog/CatalogUserApi.java new file mode 100644 index 0000000000..744bf5a376 --- /dev/null +++ b/catalog/src/main/java/com/ning/billing/catalog/CatalogUserApi.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.catalog; + +import java.io.File; + +import com.ning.billing.catalog.api.ICatalog; +import com.ning.billing.catalog.api.ICatalogUserApi; +import com.ning.billing.catalog.io.XMLReader; + +public class CatalogUserApi implements ICatalogUserApi { + + @Override + public ICatalog getCatalog(final String catalogName) { + String name = catalogName; + try { + return XMLReader.getCatalogFromName(new File(name).toURI().toURL()); + } catch (Exception e) { + e.printStackTrace(); + return null; + } + } + +} diff --git a/catalog/src/main/java/com/ning/billing/catalog/Duration.java b/catalog/src/main/java/com/ning/billing/catalog/Duration.java new file mode 100644 index 0000000000..5e7909a4ef --- /dev/null +++ b/catalog/src/main/java/com/ning/billing/catalog/Duration.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.catalog; + +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlElement; + +import com.ning.billing.catalog.api.IDuration; +import com.ning.billing.catalog.api.TimeUnit; + +@XmlAccessorType(XmlAccessType.NONE) +public class Duration extends ValidatingConfig implements IDuration { + @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 getLength() { + return number; + } + + public void setUnit(TimeUnit unit) { + this.unit = unit; + } + + public void setLength(int length) { + this.number = length; + } + + + + @Override + public ValidationErrors validate(Catalog catalog, ValidationErrors errors) { + return errors; + + } +} diff --git a/catalog/src/main/java/com/ning/billing/catalog/InternationalPrice.java b/catalog/src/main/java/com/ning/billing/catalog/InternationalPrice.java new file mode 100644 index 0000000000..48d97822bd --- /dev/null +++ b/catalog/src/main/java/com/ning/billing/catalog/InternationalPrice.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.catalog; + +import java.math.BigDecimal; +import java.util.Date; + +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlElement; + +import com.ning.billing.catalog.api.Currency; +import com.ning.billing.catalog.api.IInternationalPrice; +import com.ning.billing.catalog.api.IPrice; + +@XmlAccessorType(XmlAccessType.NONE) +public class InternationalPrice extends ValidatingConfig implements IInternationalPrice { + + //TODO MDW Validation - effectiveDateForExistingSubscriptons > catalog effectiveDate + @XmlElement(required=false) + private Date effectiveDateForExistingSubscriptons; + + //TODO: Must have a price point for every configured currency + //TODO: No prices is a zero cost plan + @XmlElement(name="price") + private Price[] prices; + + /* (non-Javadoc) + * @see com.ning.billing.catalog.IInternationalPrice#getPrices() + */ + @Override + public IPrice[] getPrices() { + return prices; + } + + public void setPrices(Price[] prices) { + this.prices = prices; + } + + /* (non-Javadoc) + * @see com.ning.billing.catalog.IInternationalPrice#getPrice(com.ning.billing.catalog.api.Currency) + */ + @Override + public BigDecimal getPrice(Currency currency) { + // Note if there are no prices specified we default to 0 for any currency + for(IPrice p : prices) { + if(p.getCurrency() == currency) { + return p.getValue(); + } + } + return new BigDecimal(0); + } + + @Override + public ValidationErrors validate(Catalog catalog, ValidationErrors errors) { + if(prices.length == 0) return errors; + Currency[] supportedCurrencies = catalog.getSupportedCurrencies(); + for (IPrice p : prices) { + Currency currency = p.getCurrency(); + if(!currencyIsSupported(currency, supportedCurrencies)) { + errors.add("Unsupported currency: " + currency, catalog.getCatalogURL(), this.getClass(), ""); + } + } + return errors; + } + + private boolean currencyIsSupported(Currency currency, Currency[] supportedCurrencies) { + for (Currency c : supportedCurrencies) { + if(c == currency) { + return true; + } + } + return false; + } + + public Date getEffectiveDateForExistingSubscriptons() { + return effectiveDateForExistingSubscriptons; + } + + public void setEffectiveDateForExistingSubscriptons( + Date effectiveDateForExistingSubscriptons) { + this.effectiveDateForExistingSubscriptons = effectiveDateForExistingSubscriptons; + } +} diff --git a/catalog/src/main/java/com/ning/billing/catalog/Plan.java b/catalog/src/main/java/com/ning/billing/catalog/Plan.java new file mode 100644 index 0000000000..10e80aa379 --- /dev/null +++ b/catalog/src/main/java/com/ning/billing/catalog/Plan.java @@ -0,0 +1,178 @@ +/* + * Copyright 2010-2011 Ning, Inc. + * + * Ning licenses this file to you under the Apache License, version 2.0 + * (the "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package com.ning.billing.catalog; + +import java.util.ArrayList; +import java.util.Iterator; + +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlAttribute; +import javax.xml.bind.annotation.XmlElement; +import javax.xml.bind.annotation.XmlElementWrapper; +import javax.xml.bind.annotation.XmlID; +import javax.xml.bind.annotation.XmlIDREF; + +import com.ning.billing.catalog.api.BillingAlignment; +import com.ning.billing.catalog.api.BillingPeriod; +import com.ning.billing.catalog.api.IPlan; +import com.ning.billing.catalog.api.IPlanPhase; +import com.ning.billing.catalog.api.IProduct; + +@XmlAccessorType(XmlAccessType.NONE) +public class Plan extends ValidatingConfig implements IPlan { + + + @XmlAttribute(required=true) + @XmlID + private String name; + + @XmlElement(required=true) + @XmlIDREF + private Product product; + + @XmlElementWrapper(name="initialPhases", required=false) + @XmlElement(name="phase", required=true) + private PlanPhase[] initialPhases; + + @XmlElement(name="finalPhase", required=true) + private PlanPhase finalPhase; + + @XmlElement(required=true) + private BillingAlignment billingAlignment; + + //If this is missing it defaults to 1 + //No other value is allowed for BASE plans. + //No other value is allowed for Tiered ADDONS + //A value of -1 means unlimited + @XmlElement(required=false) + private Integer plansAllowedInBundle = 1; + + public Plan(){} + + protected Plan(String name, Product product, PlanPhase finalPhase) { + this.name = name; + this.product = product; + this.finalPhase = finalPhase; + } + + @Override + public void initialize(Catalog catalog) { + super.initialize(catalog); + if(finalPhase != null) { + finalPhase.setPlan(this); + } + if(initialPhases != null) { + for(PlanPhase p : initialPhases) { + p.setPlan(this); + } + } + } + + /* (non-Javadoc) + * @see com.ning.billing.catalog.IPlan#getPhases() + */ + @Override + public PlanPhase[] getInitialPhases() { + return initialPhases; + } + + /* (non-Javadoc) + * @see com.ning.billing.catalog.IPlan#getProduct() + */ + @Override + public IProduct getProduct() { + return product; + } + + public void setInitialPhases(PlanPhase[] phases) { + this.initialPhases = phases; + + } + + public void setProduct(Product product) { + this.product = product; + } + + /* (non-Javadoc) + * @see com.ning.billing.catalog.IPlan#getName() + */ + @Override + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + /* (non-Javadoc) + * @see com.ning.billing.catalog.IPlan#getPhaseIterator() + */ + @Override + public Iterator getInitialPhaseIterator() { + ArrayList list = new ArrayList(); + for(PlanPhase p : initialPhases) { + list.add(p); + } + return list.iterator(); + } + + @Override + public ValidationErrors validate(Catalog catalog, ValidationErrors errors) { + return errors; + + } + + @Override + public PlanPhase getFinalPhase() { + return finalPhase; + } + + public void setFinalPhase(PlanPhase finalPhase) { + this.finalPhase = finalPhase; + } + + @Override + public BillingPeriod getBillingPeriod(){ + return finalPhase.getBillingPeriod(); + } + + /* (non-Javadoc) + * @see com.ning.billing.catalog.IPlan#getBillingAlignment() + */ + @Override + public BillingAlignment getBillingAlignment() { + return billingAlignment; + } + + public void setBillingAlignment(BillingAlignment billingAlignment) { + this.billingAlignment = billingAlignment; + } + + /* (non-Javadoc) + * @see com.ning.billing.catalog.IPlan#getPlansAllowedInBundle() + */ + @Override + public int getPlansAllowedInBundle() { + return plansAllowedInBundle; + } + + public void setPlansAllowedInBundle(int plansAllowedInBundle) { + this.plansAllowedInBundle = plansAllowedInBundle; + } +} diff --git a/catalog/src/main/java/com/ning/billing/catalog/PlanAlignmentCase.java b/catalog/src/main/java/com/ning/billing/catalog/PlanAlignmentCase.java new file mode 100644 index 0000000000..e5cb54a9da --- /dev/null +++ b/catalog/src/main/java/com/ning/billing/catalog/PlanAlignmentCase.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.catalog; + +import javax.xml.bind.annotation.XmlElement; +import javax.xml.bind.annotation.XmlIDREF; + +import com.ning.billing.catalog.api.BillingPeriod; +import com.ning.billing.catalog.api.PhaseType; +import com.ning.billing.catalog.api.PlanAlignment; +import com.ning.billing.catalog.api.PlanPhaseSpecifier; +import com.ning.billing.catalog.api.PlanSpecifier; + +public class PlanAlignmentCase extends ValidatingConfig { + + @XmlElement(required=false) + private PhaseType phaseType; + + @XmlElement(required=false) + @XmlIDREF + private Product fromProduct; + + @XmlElement(required=false) + private BillingPeriod fromBillingPeriod; + + @XmlElement(required=false) + @XmlIDREF + private Product toProduct; + + @XmlElement(required=false) + private BillingPeriod toBillingPeriod; + + @XmlElement(required=false) + @XmlIDREF + private PriceList fromPriceList; + + @XmlElement(required=false) + @XmlIDREF + private PriceList toPriceList; + + @XmlElement(required=true) + private PlanAlignment alignment; + + public PlanAlignmentCase(){} + + protected PlanAlignmentCase ( + Product from, Product to, + BillingPeriod fromBP, BillingPeriod toBP, + PhaseType fromType, PhaseType toType, + PriceList fromPriceList, PriceList toPriceList, + PlanAlignment alignment) { + this.fromProduct = from; + this.toProduct = to; + this.fromBillingPeriod = fromBP; + this.toBillingPeriod = toBP; + this.phaseType = fromType; + this.fromPriceList = fromPriceList; + this.toPriceList = toPriceList; + this.alignment = alignment; + } + + public Product getFromProduct() { + return fromProduct; + } + + public BillingPeriod getFromBillingPeriod() { + return fromBillingPeriod; + } + + public Product getToProduct() { + return toProduct; + } + + public BillingPeriod getToBillingPeriod() { + return toBillingPeriod; + } + + public PhaseType getFromPhaseType() { + return phaseType; + } + + public PlanAlignment getPlanAlignment(PlanPhaseSpecifier from, + PlanSpecifier to, Catalog catalog) { + if( + (phaseType == null || from.getPhaseType() == phaseType) && + (fromProduct == null || fromProduct.equals(catalog.getProductFromName(from.getProductName()))) && + (fromBillingPeriod == null || fromBillingPeriod.equals(from.getBillingPeriod())) && + (toProduct == null || toProduct.equals(catalog.getProductFromName(to.getProductName()))) && + (toBillingPeriod == null || toBillingPeriod.equals(to.getBillingPeriod())) && + (fromPriceList == null || fromPriceList.equals(catalog.getPriceListFromName(from.getPriceListName()))) && + (toPriceList == null || toPriceList.equals(catalog.getPriceListFromName(to.getPriceListName()))) + ) { + return alignment; + } + return null; + } + + @Override + public ValidationErrors validate(Catalog catalog, ValidationErrors errors) { + // TODO Auto-generated method stub + return errors; + } +} diff --git a/catalog/src/main/java/com/ning/billing/catalog/PlanCancelCase.java b/catalog/src/main/java/com/ning/billing/catalog/PlanCancelCase.java new file mode 100644 index 0000000000..26ddc8738c --- /dev/null +++ b/catalog/src/main/java/com/ning/billing/catalog/PlanCancelCase.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.catalog; + +import javax.xml.bind.annotation.XmlElement; +import javax.xml.bind.annotation.XmlIDREF; + +import com.ning.billing.catalog.api.ActionPolicy; +import com.ning.billing.catalog.api.BillingPeriod; +import com.ning.billing.catalog.api.PhaseType; +import com.ning.billing.catalog.api.PlanPhaseSpecifier; + +public class PlanCancelCase extends ValidatingConfig { + + @XmlElement(required=false) + private PhaseType phaseType; + + @XmlElement(required=false) + @XmlIDREF + private Product product; + + @XmlElement(required=false) + private BillingPeriod billingPeriod; + + @XmlElement(required=false) + private PriceList priceList; + + @XmlElement(required=true) + private ActionPolicy policy; + + public PlanCancelCase() {} + + protected PlanCancelCase ( + Product product, + BillingPeriod billingPeriod, + PhaseType phaseType, + ActionPolicy policy + ) { + this.product = product; + this.billingPeriod = billingPeriod; + this.phaseType = phaseType; + this.policy = policy; + } + + public Product getFromProduct() { + return product; + } + public void setFromProduct(Product product) { + this.product = product; + } + + public BillingPeriod getFromBillingPeriod() { + return billingPeriod; + } + public void setFromBillingPeriod(BillingPeriod billingPeriod) { + this.billingPeriod = billingPeriod; + } + + public Product getToProduct() { + return product; + } + public void setToProduct(Product product) { + this.product = product; + } + + public BillingPeriod getToBillingPeriod() { + return billingPeriod; + } + public void setToBillingPeriod(BillingPeriod billingPeriod) { + this.billingPeriod = billingPeriod; + } + + public ActionPolicy getPolicy() { + return policy; + } + public void setPolicy(ActionPolicy policy) { + this.policy = policy; + } + + public PhaseType getPhaseType() { + return phaseType; + } + + + public ActionPolicy getPlanCancelPolicy(PlanPhaseSpecifier planPhase, Catalog c) { + if( + (phaseType == null || planPhase.getPhaseType() == phaseType) && + (product == null || product.equals(c.getProductFromName(planPhase.getProductName()))) && + (billingPeriod == null || billingPeriod.equals(planPhase.getBillingPeriod())) && + (priceList == null || priceList.equals(c.getPriceListFromName(planPhase.getPriceListName()))) + ) { + return getPolicy(); + } + return null; + } + + @Override + public ValidationErrors validate(Catalog catalog, ValidationErrors errors) { + // TODO Auto-generated method stub + return null; + } + +} diff --git a/catalog/src/main/java/com/ning/billing/catalog/PlanChangeCase.java b/catalog/src/main/java/com/ning/billing/catalog/PlanChangeCase.java new file mode 100644 index 0000000000..a548f0e589 --- /dev/null +++ b/catalog/src/main/java/com/ning/billing/catalog/PlanChangeCase.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.catalog; + +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlElement; +import javax.xml.bind.annotation.XmlIDREF; + +import com.ning.billing.catalog.api.ActionPolicy; +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.PlanSpecifier; + +@XmlAccessorType(XmlAccessType.NONE) +public class PlanChangeCase extends ValidatingConfig { + + @XmlElement(required=false) + private PhaseType phaseType; + + @XmlElement(required=false) + @XmlIDREF + private Product fromProduct; + + @XmlElement(required=false) + private BillingPeriod fromBillingPeriod; + + @XmlElement(required=false) + @XmlIDREF + private Product toProduct; + + @XmlElement(required=false) + private BillingPeriod toBillingPeriod; + + @XmlElement(required=false) + @XmlIDREF + private PriceList fromPriceList; + + @XmlElement(required=false) + @XmlIDREF + private PriceList toPriceList; + + @XmlElement(required=true) + private ActionPolicy policy; + + public PlanChangeCase(){} + + protected PlanChangeCase ( + Product from, Product to, + BillingPeriod fromBP, BillingPeriod toBP, + PhaseType fromType, PhaseType toType, + PriceList fromPriceList, PriceList toPriceList, + ActionPolicy policy) { + this.fromProduct = from; + this.toProduct = to; + this.fromBillingPeriod = fromBP; + this.toBillingPeriod = toBP; + this.phaseType = fromType; + this.fromPriceList = fromPriceList; + this.toPriceList = toPriceList; + this.policy = policy; + } + + public Product getFromProduct() { + return fromProduct; + } + + public BillingPeriod getFromBillingPeriod() { + return fromBillingPeriod; + } + + public Product getToProduct() { + return toProduct; + } + + public BillingPeriod getToBillingPeriod() { + return toBillingPeriod; + } + + public ActionPolicy getPolicy() { + return policy; + } + public void setPolicy(ActionPolicy policy) { + this.policy = policy; + } + + public PhaseType getFromPhaseType() { + return phaseType; + } + + public ActionPolicy getPlanChangePolicy(PlanPhaseSpecifier from, + PlanSpecifier to, Catalog catalog) { + if( + (phaseType == null || from.getPhaseType() == phaseType) && + (fromProduct == null || fromProduct.equals(catalog.getProductFromName(from.getProductName()))) && + (fromBillingPeriod == null || fromBillingPeriod.equals(from.getBillingPeriod())) && + (toProduct == null || toProduct.equals(catalog.getProductFromName(to.getProductName()))) && + (toBillingPeriod == null || toBillingPeriod.equals(to.getBillingPeriod())) && + (fromPriceList == null || fromPriceList.equals(catalog.getPriceListFromName(from.getPriceListName()))) && + (toPriceList == null || toPriceList.equals(catalog.getPriceListFromName(to.getPriceListName()))) + ) { + return getPolicy(); + } + return null; + } + + @Override + public ValidationErrors validate(Catalog catalog, ValidationErrors errors) { + // TODO Auto-generated method stub + return errors; + } + + + +} diff --git a/catalog/src/main/java/com/ning/billing/catalog/PlanChangeRule.java b/catalog/src/main/java/com/ning/billing/catalog/PlanChangeRule.java new file mode 100644 index 0000000000..829a363011 --- /dev/null +++ b/catalog/src/main/java/com/ning/billing/catalog/PlanChangeRule.java @@ -0,0 +1,95 @@ +/* + * Copyright 2010-2011 Ning, Inc. + * + * Ning licenses this file to you under the Apache License, version 2.0 + * (the "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF 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 javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlElement; + +import com.ning.billing.catalog.api.ActionPolicy; +import com.ning.billing.catalog.api.PhaseType; + +@XmlAccessorType(XmlAccessType.NONE) +public class PlanChangeRule extends ValidatingConfig { + public enum Qualifier { + DEFAULT, + PRODUCT_FROM_LOW_TO_HIGH, + PRODUCT_FROM_HIGH_TO_LOW, + TERM_FROM_SHORT_TO_LONG, + TERM_FROM_LONG_TO_SHORT + } + + @XmlElement(required=false) + private PhaseType restrictedToPhaseType; + + @XmlElement(required=true) + private Qualifier qualifier; + + @XmlElement(required=true) + private ActionPolicy policy; + + public PlanChangeRule(){} + + protected PlanChangeRule(Qualifier qualifier, ActionPolicy policy, PhaseType type) { + this.qualifier = qualifier; + this.policy = policy; + this.restrictedToPhaseType = type; + } + + public Qualifier getQualifier() { + return qualifier; + } + + public ActionPolicy getPolicy() { + return policy; + } + + public PhaseType getRestrictedToPhaseType() { + return restrictedToPhaseType; + } + + public ActionPolicy getPlanChangePolicy( + int fromProductIndex, int fromBillingPeriodIndex, + int toProductIndex, int toBillingPeriodIndex, + PhaseType fromType) { + if(restrictedToPhaseType != null ) { + if(fromType != restrictedToPhaseType) { + return null; + } + } + if (qualifier == Qualifier.DEFAULT ) { + return policy; + } else if(qualifier == Qualifier.PRODUCT_FROM_HIGH_TO_LOW && fromProductIndex > toProductIndex) { + return policy; + } else if (qualifier == Qualifier.PRODUCT_FROM_LOW_TO_HIGH && fromProductIndex < toProductIndex) { + return policy; + } else if (qualifier == Qualifier.TERM_FROM_LONG_TO_SHORT && fromBillingPeriodIndex > toBillingPeriodIndex) { + return policy; + } else if (qualifier == Qualifier.TERM_FROM_SHORT_TO_LONG && fromBillingPeriodIndex < toBillingPeriodIndex) { + return policy; + } + return null; + } + + @Override + public ValidationErrors validate(Catalog catalog, ValidationErrors errors) { + // TODO Auto-generated method stub + return errors; + } + + +} diff --git a/catalog/src/main/java/com/ning/billing/catalog/PlanPhase.java b/catalog/src/main/java/com/ning/billing/catalog/PlanPhase.java new file mode 100644 index 0000000000..b1367c090b --- /dev/null +++ b/catalog/src/main/java/com/ning/billing/catalog/PlanPhase.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.catalog; + +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 com.ning.billing.catalog.api.BillingPeriod; +import com.ning.billing.catalog.api.IDuration; +import com.ning.billing.catalog.api.IInternationalPrice; +import com.ning.billing.catalog.api.IPlan; +import com.ning.billing.catalog.api.IPlanPhase; +import com.ning.billing.catalog.api.PhaseType; + +@XmlAccessorType(XmlAccessType.NONE) +public class PlanPhase extends ValidatingConfig implements IPlanPhase { + + @XmlAttribute (required=true) + private PhaseType type; + + @XmlElement(required=true) + private Duration duration; + + @XmlElement(required=false) + private BillingPeriod billingPeriod; + + @XmlElement(required=false) + private InternationalPrice recurringPrice; + + @XmlElement(required=false) + private InternationalPrice fixedPrice; + +// @XmlElement(required=false) +// private InternationalPrice unitPrice; + + //Not exposed in XML + private IPlan plan; + + public PlanPhase(){} + + protected PlanPhase(BillingPeriod period, PhaseType type) { + this.billingPeriod = period; + this.type = type; + } + + /* (non-Javadoc) + * @see com.ning.billing.catalog.IPlanPhase#getRecurringPrice() + */ + @Override + public IInternationalPrice getRecurringPrice() { + return recurringPrice; + } + + public void setReccuringPrice(InternationalPrice price) { + this.recurringPrice = price; + } + + /* (non-Javadoc) + * @see com.ning.billing.catalog.IPlanPhase#getInternationalPrice() + */ + @Override + public IInternationalPrice getFixedPrice() { + return fixedPrice; + } + + public void setFixedPrice(InternationalPrice price) { + this.fixedPrice = price; + } + + /* (non-Javadoc) + * @see com.ning.billing.catalog.IPlanPhase#getBillCycleDuration() + */ + @Override + public BillingPeriod getBillingPeriod() { + return billingPeriod; + } + + public void setBillCycleDuration(BillingPeriod billingPeriod) { + this.billingPeriod = billingPeriod; + } + + /* (non-Javadoc) + * @see com.ning.billing.catalog.IPlanPhase#getName() + */ + @Override + public String getName() { + return plan.getName() + "-" + type.toString().toLowerCase(); + } + + /* (non-Javadoc) + * @see com.ning.billing.catalog.IPlanPhase#getPlan() + */ + @Override + public IPlan getPlan() { + return plan; + } + + public void setPlan(IPlan plan) { + this.plan = plan; + } + + /* (non-Javadoc) + * @see com.ning.billing.catalog.IPlanPhase#getDuration() + */ + @Override + public IDuration getDuration() { + return duration; + } + + public void setDuration(Duration duration) { + this.duration = duration; + } + + @Override + public ValidationErrors validate(Catalog catalog, ValidationErrors errors) { + return errors; + + } + + /* (non-Javadoc) + * @see com.ning.billing.catalog.IPlanPhase#getCohort() + */ + @Override + public PhaseType getPhaseType() { + return type; + } + + public void setCohort(PhaseType cohort) { + this.type = cohort; + } + + public void setBillingPeriod(BillingPeriod billingPeriod) { + this.billingPeriod = billingPeriod; + } + + //TODO MDW - validation: if there is a recurring price there must be a billing period + //TODO MDW - validation: if there is no reccuring price there should be no billing period + + +} diff --git a/catalog/src/main/java/com/ning/billing/catalog/PlanRules.java b/catalog/src/main/java/com/ning/billing/catalog/PlanRules.java new file mode 100644 index 0000000000..2d742a2d9e --- /dev/null +++ b/catalog/src/main/java/com/ning/billing/catalog/PlanRules.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.catalog; + +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 com.ning.billing.catalog.api.ActionPolicy; +import com.ning.billing.catalog.api.BillingPeriod; +import com.ning.billing.catalog.api.IProduct; +import com.ning.billing.catalog.api.PlanAlignment; +import com.ning.billing.catalog.api.PlanPhaseSpecifier; +import com.ning.billing.catalog.api.PlanSpecifier; + +@XmlAccessorType(XmlAccessType.NONE) +public class PlanRules extends ValidatingConfig { + + @XmlElementWrapper(name="tiers", required=true) + @XmlElement(name="tier", required=false) // may not have tiers in some catalogs + private ProductTier[] productTiers; + + @XmlElement(name="changeRule", required=true) + private PlanChangeRule[] rules; + + @XmlElement(name="changeCase", required=false) + private PlanChangeCase[] changeCase; + + @XmlElement(name="cancelCase", required=false) + private PlanCancelCase[] cancelCase; + + @XmlElement(name="alignmentCase", required=false) + private PlanAlignmentCase[] alignmentCase; + + public PlanChangeRule[] getRules() { + return rules; + } + + public void setGeneralRules(PlanChangeRule[] generalRules) { + this.rules = generalRules; + } + + public PlanChangeCase[] getSpecialCase() { + return changeCase; + } + + protected void setSpecialCaseRules(PlanChangeCase[] specialchangeCaseCaseRules) { + this.changeCase = specialchangeCaseCaseRules; + } + + protected void setCancelCaseRules(PlanCancelCase[] cancelCase) { + this.cancelCase = cancelCase; + } + + public void setAlignmentCase(PlanAlignmentCase[] alignmentCase) { + this.alignmentCase = alignmentCase; + } + + @Override + public ValidationErrors validate(Catalog catalog, ValidationErrors errors) { + return errors; + + } + + public ActionPolicy getPlanChangePolicy(PlanPhaseSpecifier from, + PlanSpecifier to, Catalog catalog) { + if(changeCase != null) { + for(int i = changeCase.length - 1; i >=0; i --) { + ActionPolicy policy = changeCase[i].getPlanChangePolicy(from, to, catalog); + if (policy != null) { return policy; } + } + } + for(int i = rules.length - 1; i >=0; i --) { + int fromProductIndex = getProductIndex(catalog.getProductFromName(from.getProductName())); + int fromBillingPeriodIndex = getBillingPeriodIndex(from.getBillingPeriod()); + int toProductIndex = getProductIndex(catalog.getProductFromName(to.getProductName())); + int toBillingPeriodIndex = getBillingPeriodIndex(to.getBillingPeriod()); + + ActionPolicy policy = rules[i].getPlanChangePolicy( + fromProductIndex, fromBillingPeriodIndex, + toProductIndex, toBillingPeriodIndex, + from.getPhaseType()); + if (policy != null) { return policy; } + } + return null; + + } + + public PlanAlignment getPlanAlignment(PlanPhaseSpecifier from, + PlanSpecifier to, Catalog catalog) { + if(alignmentCase != null) { + for(int i = alignmentCase.length - 1; i >=0; i --) { + PlanAlignment alignment = alignmentCase[i].getPlanAlignment(from, to, catalog); + if(alignment != null) { + return alignment; + } + } + } + return null; + + } + + + public ActionPolicy getPlanCancelPolicy(PlanPhaseSpecifier planPhase, Catalog catalog) { + if(cancelCase != null) { + for(int i = cancelCase.length - 1; i >=0; i --) { + ActionPolicy policy = cancelCase[i].getPlanCancelPolicy(planPhase, catalog); + if (policy != null) { + return policy; + } + } + } + + return null; + } + + private int getBillingPeriodIndex(BillingPeriod src) { + return src.ordinal(); + } + + private int getProductIndex(IProduct src) { + for(ProductTier tier : productTiers) { + for(int i = 0; i < tier.getProducts().length; i++ ){ + if (src.equals(tier.getProducts()[i])) { + return i; + } + } + } + return 0; + } + + protected void setProductTiers(ProductTier[] productTiers) { + this.productTiers = productTiers; + } + + + + //TODO: MDW - Validation: check that the plan change special case pairs are unique! + //TODO: MDW - Validation: check that the each product appears in at most one tier. + //TODO: MDW - Unit tests for rules + //TODO: MDW - validate that there is a default policy for change AND cancel + +} diff --git a/catalog/src/main/java/com/ning/billing/catalog/Price.java b/catalog/src/main/java/com/ning/billing/catalog/Price.java new file mode 100644 index 0000000000..9dcbcbab9b --- /dev/null +++ b/catalog/src/main/java/com/ning/billing/catalog/Price.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.catalog; + +import java.math.BigDecimal; + +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlElement; + +import com.ning.billing.catalog.api.Currency; +import com.ning.billing.catalog.api.IPrice; + +@XmlAccessorType(XmlAccessType.NONE) +public class Price extends ValidatingConfig implements IPrice { + @XmlElement(required=true) + private Currency currency; + + @XmlElement(required=true) + private BigDecimal value; + + /* (non-Javadoc) + * @see com.ning.billing.catalog.IPrice#getCurrency() + */ + @Override + public Currency getCurrency() { + return currency; + } + public void setCurrency(Currency currency) { + this.currency = currency; + } + /* (non-Javadoc) + * @see com.ning.billing.catalog.IPrice#getValue() + */ + @Override + public BigDecimal getValue() { + return value; + } + public void setValue(BigDecimal value) { + this.value = value; + } + @Override + public ValidationErrors validate(Catalog catalog, ValidationErrors errors) { + return errors; + + } +} diff --git a/catalog/src/main/java/com/ning/billing/catalog/PriceList.java b/catalog/src/main/java/com/ning/billing/catalog/PriceList.java new file mode 100644 index 0000000000..76e55bb5c9 --- /dev/null +++ b/catalog/src/main/java/com/ning/billing/catalog/PriceList.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.catalog; + +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlAttribute; +import javax.xml.bind.annotation.XmlElement; +import javax.xml.bind.annotation.XmlElementWrapper; +import javax.xml.bind.annotation.XmlID; +import javax.xml.bind.annotation.XmlIDREF; + +import com.ning.billing.catalog.api.IPriceList; + +@XmlAccessorType(XmlAccessType.NONE) +public class PriceList extends ValidatingConfig implements IPriceList { + + @XmlAttribute(required=true) + @XmlID + private String name; + + @XmlElement(required=false) + private Boolean isDefault = false; + + @XmlElementWrapper(name="plans", required=true) + @XmlElement(name="plan", required=true) + @XmlIDREF + private Plan[] plans; + + /* (non-Javadoc) + * @see com.ning.billing.catalog.IPlanSet#getName() + */ + @Override + public String getName() { + return name; + } + + /* (non-Javadoc) + * @see com.ning.billing.catalog.IPlanSet#getPlans() + */ + @Override + public Plan[] getPlans() { + return plans; + } + + /* (non-Javadoc) + * @see com.ning.billing.catalog.IPlanSet#findPlanByProductName(java.lang.String) + */ + @Override + public Plan findPlanByProductName(String productName) { + for (Plan cur : plans) { + if (cur.getProduct().getName().equals(productName)) { + return cur; + } + } + return null; + } + + public void setName(String name) { + this.name = name; + } + + public void setPlans(Plan[] plans) { + this.plans = plans; + } + + /* (non-Javadoc) + * @see com.ning.billing.catalog.IPlanSet#getProductType() + */ + @Override + public boolean isDefault() { + return isDefault; + } + + public void setProductType(boolean isDefault) { + this.isDefault = isDefault; + } + + @Override + public ValidationErrors validate(Catalog catalog, ValidationErrors errors) { + return errors; + + } + +} diff --git a/catalog/src/main/java/com/ning/billing/catalog/Product.java b/catalog/src/main/java/com/ning/billing/catalog/Product.java new file mode 100644 index 0000000000..253fe1e5ab --- /dev/null +++ b/catalog/src/main/java/com/ning/billing/catalog/Product.java @@ -0,0 +1,121 @@ +/* + * Copyright 2010-2011 Ning, Inc. + * + * Ning licenses this file to you under the Apache License, version 2.0 + * (the "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF 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 javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlAttribute; +import javax.xml.bind.annotation.XmlElement; +import javax.xml.bind.annotation.XmlElementWrapper; +import javax.xml.bind.annotation.XmlID; +import javax.xml.bind.annotation.XmlIDREF; + +import com.ning.billing.catalog.api.IProduct; +import com.ning.billing.catalog.api.IProductType; +import com.ning.billing.catalog.api.ProductCategory; + +@XmlAccessorType(XmlAccessType.NONE) +public class Product extends ValidatingConfig implements IProduct { + + @XmlAttribute (required=true) + @XmlID + private String name; + + @XmlIDREF @XmlElement(required=true) + private ProductType type; + + @XmlElement(required=true) + private ProductCategory category; + + @XmlElementWrapper(name="included", required=false) + @XmlIDREF @XmlElement(name="addonProduct", required=true) + private Product[] included; + + @XmlElementWrapper(name="available", required=false) + @XmlIDREF @XmlElement(name="addonProduct", required=true) + private Product[] available; + + @Override + public ProductCategory getCategory() { + return category; + } + + @Override + public Product[] getIncluded() { + return included; + } + + @Override + public Product[] getAvailable() { + return available; + } + + public Product() { + } + + protected Product(IProductType type, String name) { + this.name = name; + } + + @Override + public ProductType getType() { + return type; + } + + @Override + public String getName() { + return name; + } + + public void setType(ProductType type) { + this.type = type; + } + + public void setName(String name) { + this.name = name; + } + + public void setCatagory(ProductCategory category) { + this.category = category; + } + + @Override + public ValidationErrors validate(Catalog catalog, ValidationErrors errors) { + return errors; + } + + public boolean isIncluded(Product addon) { + for(Product p : included) { + if (addon == p) { + return true; + } + } + return false; + } + + public boolean isAvailable(Product addon) { + for(Product p : included) { + if (addon == p) { + return true; + } + } + return false; + } + + //TODO: MDW validation: inclusion and exclusion lists can only contain addon products + //TODO: MDW validation: a given product can only be in, at most, one of inclusion and exclusion lists +} diff --git a/catalog/src/main/java/com/ning/billing/catalog/ProductTier.java b/catalog/src/main/java/com/ning/billing/catalog/ProductTier.java new file mode 100644 index 0000000000..a7990177ce --- /dev/null +++ b/catalog/src/main/java/com/ning/billing/catalog/ProductTier.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.catalog; + +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlElement; +import javax.xml.bind.annotation.XmlIDREF; + +import com.ning.billing.catalog.api.IProduct; +import com.ning.billing.catalog.api.IProductTier; + +@XmlAccessorType(XmlAccessType.NONE) +public class ProductTier implements IProductTier { + + @XmlElement(name="product",required=true) + @XmlIDREF + private Product[] products; + + public ProductTier(){} + + protected ProductTier(Product[] products) { + this.products = products; + } + /* (non-Javadoc) + * @see com.ning.billing.catalog.IPlanTierSet#getProducts() + */ + @Override + public IProduct[] getProducts() { + return products; + } + public void setProducts(Product[] products) { + this.products = products; + } +} diff --git a/catalog/src/main/java/com/ning/billing/catalog/ProductType.java b/catalog/src/main/java/com/ning/billing/catalog/ProductType.java new file mode 100644 index 0000000000..a4c11c2f58 --- /dev/null +++ b/catalog/src/main/java/com/ning/billing/catalog/ProductType.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.catalog; + +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlAttribute; +import javax.xml.bind.annotation.XmlID; + +import com.ning.billing.catalog.api.IProductType; + +@XmlAccessorType(XmlAccessType.NONE) +public class ProductType extends ValidatingConfig implements IProductType { + + @XmlID @XmlAttribute(required=true) + private String name; + + public ProductType() { + super(); + } + + public ProductType(String name) { + super(); + this.name = name; + } + + /* (non-Javadoc) + * @see com.ning.billing.catalog.IProductType#getName() + */ + @Override + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + @Override + public ValidationErrors validate(Catalog catalog, ValidationErrors errors) { + return errors; + + } +} diff --git a/catalog/src/main/java/com/ning/billing/catalog/ValidatingConfig.java b/catalog/src/main/java/com/ning/billing/catalog/ValidatingConfig.java new file mode 100644 index 0000000000..532a063141 --- /dev/null +++ b/catalog/src/main/java/com/ning/billing/catalog/ValidatingConfig.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.catalog; + +import java.net.URL; +import java.util.ArrayList; + +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; + +@XmlAccessorType(XmlAccessType.NONE) +public abstract class ValidatingConfig { + public static class ValidationErrors extends ArrayList{ + private static final long serialVersionUID = 1L; + + public void add(String description, URL catalogURL, + Class objectType, String objectName) { + add(new ValidationError(description, catalogURL, objectType, objectName)); + + } + + } + + public abstract ValidationErrors validate(Catalog catalog, ValidationErrors errors); + + public ValidationErrors validate() { + if(!(this instanceof Catalog)) { + ValidationErrors errors = new ValidationErrors(); + errors.add("Root type was not ICatalog", null, this.getClass(), null); + return errors; + } + return validate((Catalog)this, new ValidationErrors()); + } + + protected ValidationErrors validate(Catalog catalog, ValidationErrors errors, ValidatingConfig[] configs) { + for(ValidatingConfig c : configs) { + errors.addAll(c.validate(catalog, errors)); + } + return errors; + } + + public void initialize(Catalog catalog) { + + } + +} diff --git a/catalog/src/main/java/com/ning/billing/catalog/ValidationError.java b/catalog/src/main/java/com/ning/billing/catalog/ValidationError.java new file mode 100644 index 0000000000..6498307fae --- /dev/null +++ b/catalog/src/main/java/com/ning/billing/catalog/ValidationError.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.catalog; + +import java.net.URL; + +public class ValidationError { + private final String description; + private final URL catalogURL; + private final Class objectType; + private final String objectName; + public ValidationError(String description, URL catalogURL, + Class objectType, String objectName) { + super(); + this.description = description; + this.catalogURL = catalogURL; + this.objectType = objectType; + this.objectName = objectName; + } + public String getDescription() { + return description; + } + public URL getCatalogURL() { + return catalogURL; + } + public Class getObjectType() { + return objectType; + } + public String getObjectName() { + return objectName; + } + + +} diff --git a/catalog/src/main/java/com/ning/billing/catalog/VersionedCatalog.java b/catalog/src/main/java/com/ning/billing/catalog/VersionedCatalog.java new file mode 100644 index 0000000000..d4aa4c622f --- /dev/null +++ b/catalog/src/main/java/com/ning/billing/catalog/VersionedCatalog.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.catalog; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.Date; +import java.util.Iterator; +import java.util.List; + +import com.ning.billing.catalog.api.ActionPolicy; +import com.ning.billing.catalog.api.BillingPeriod; +import com.ning.billing.catalog.api.Currency; +import com.ning.billing.catalog.api.ICatalog; +import com.ning.billing.catalog.api.IPlan; +import com.ning.billing.catalog.api.IPlanPhase; +import com.ning.billing.catalog.api.IProduct; +import com.ning.billing.catalog.api.IProductType; +import com.ning.billing.catalog.api.PlanAlignment; +import com.ning.billing.catalog.api.PlanPhaseSpecifier; +import com.ning.billing.catalog.api.PlanSpecifier; + +public class VersionedCatalog extends ValidatingConfig implements ICatalog { + + private Catalog currentCatalog; + + private final List versions = new ArrayList(); + + public VersionedCatalog() { + Catalog baseline = new Catalog(new Date(0)); // init with an empty catalog may need to + // populate some empty pieces here to make validation work + add(baseline); + } + + private Catalog versionForDate(Date date) { + Catalog previous = versions.get(0); + for(Catalog c : versions) { + if(c.getEffectiveDate().getTime() > date.getTime()) { + return previous; + } + previous = c; + } + return versions.get(versions.size() - 1); + } + + public void add(Catalog e) { + if(currentCatalog == null) { + currentCatalog = e; + } + versions.add(e); + Collections.sort(versions,new Comparator() { + @Override + public int compare(Catalog c1, Catalog c2) { + return c1.getEffectiveDate().compareTo(c2.getEffectiveDate()); + } + }); + } + + public Iterator iterator() { + return versions.iterator(); + } + + public void applyEffectiveDate(Date date) { + currentCatalog = versionForDate(date); // + } + + public int size() { + return versions.size(); + } + + public boolean equals(Object arg0) { + return currentCatalog.equals(arg0); + } + + public ProductType[] getProductTypes() { + return currentCatalog.getProductTypes(); + } + + public Product[] getProducts() { + return currentCatalog.getProducts(); + } + + public void setProducts(Product[] products) { + currentCatalog.setProducts(products); + } + + public PriceList[] getPriceLists() { + return currentCatalog.getPriceLists(); + } + + public void setPlanSets(PriceList[] planSets) { + currentCatalog.setPlanSets(planSets); + } + + public PriceList getPriceListFromName(String planSetName) { + return currentCatalog.getPriceListFromName(planSetName); + } + + public List getProductsForType(IProductType productType) { + return currentCatalog.getProductsForType(productType); + } + + public Plan getPlan(String productName, BillingPeriod term, + String planSetName) { + return currentCatalog.getPlan(productName, term, planSetName); + } + + public void setProductTypes(ProductType[] productTypes) { + currentCatalog.setProductTypes(productTypes); + } + + public Currency[] getSupportedCurrencies() { + return currentCatalog.getSupportedCurrencies(); + } + + public Plan[] getPlans() { + return currentCatalog.getPlans(); + } + + public IPlan getPlanFromName(String name) { + return currentCatalog.getPlanFromName(name); + } + + public IPlanPhase getPhaseFromName(String name) { + return currentCatalog.getPhaseFromName(name); + } + + public Date getEffectiveDate() { + return currentCatalog.getEffectiveDate(); + } + + public int hashCode() { + return currentCatalog.hashCode(); + } + + public void initialize(Catalog catalog) { + currentCatalog.initialize(catalog); + } + + public void setSupportedCurrencies(Currency[] supportedCurrencies) { + currentCatalog.setSupportedCurrencies(supportedCurrencies); + } + + public void setPlanChangeRules(PlanRules planChangeRules) { + currentCatalog.setPlanChangeRules(planChangeRules); + } + + public void setPlans(Plan[] plans) { + currentCatalog.setPlans(plans); + } + + public void setEffectiveDate(Date effectiveDate) { + currentCatalog.setEffectiveDate(effectiveDate); + } + + public String toString() { + return currentCatalog.toString(); + } + + @Override + public ValidationErrors validate(Catalog catalog, ValidationErrors errors) { + for(Catalog c : versions) { + errors.addAll(c.validate()); + } + return errors; + } + + @Override + public PlanPhase getPhaseFor(String name, Date date) { + Catalog c = versionForDate(date); + return c.getPhaseFromName(name); + } + + @Override + public ActionPolicy getPlanChangePolicy(PlanPhaseSpecifier from, + PlanSpecifier to) { + return currentCatalog.getPlanChangePolicy(from, to); + } + + @Override + public IProduct getProductFromName(String name) { + return currentCatalog.getProductFromName(name); + } + + @Override + public ActionPolicy getPlanCancelPolicy(PlanPhaseSpecifier planPhase) { + return currentCatalog.getPlanCancelPolicy(planPhase); + } + + @Override + public PlanAlignment getPlanAlignment(PlanPhaseSpecifier from, + PlanSpecifier to) { + return currentCatalog.getPlanAlignment(from, to); + } + +} diff --git a/catalog/src/main/java/com/ning/billing/catalog/io/VersionedCatalogLoader.java b/catalog/src/main/java/com/ning/billing/catalog/io/VersionedCatalogLoader.java new file mode 100644 index 0000000000..9c795cb209 --- /dev/null +++ b/catalog/src/main/java/com/ning/billing/catalog/io/VersionedCatalogLoader.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.catalog.io; + +import java.io.IOException; +import java.io.InputStream; +import java.net.MalformedURLException; +import java.net.URL; +import java.net.URLConnection; +import java.util.ArrayList; +import java.util.List; +import java.util.Scanner; + +import javax.xml.bind.JAXBException; + +import org.xml.sax.SAXException; + +import com.ning.billing.catalog.Catalog; +import com.ning.billing.catalog.VersionedCatalog; +import com.ning.billing.catalog.api.InvalidConfigException; + +public class VersionedCatalogLoader { + private static final String XML_EXTENSION = ".xml"; + private static final String HREF_LOW_START = "href=\""; + private static final String HREF_CAPS_START = "HREF=\""; + private static final String HREF_SEARCH_END = "\""; + + public static VersionedCatalog load(URL url) throws IOException, SAXException, InvalidConfigException, JAXBException { + String directoryContents = pullContentsFrom(url); + List xmlURLs = findXmlReferences(directoryContents, url); + VersionedCatalog result = new VersionedCatalog(); + for(URL u : xmlURLs) { + Catalog catalog = XMLReader.getCatalogFromName(u); + result.add(catalog); + } + return result; + } + + protected static List findXmlReferences(String directoryContents, URL url) throws MalformedURLException { + if(url.getProtocol().equals("file")) { + return findXmlFileReferences(directoryContents, url); + } + return findXmlUrlReferences(directoryContents, url); + } + + protected static List findXmlUrlReferences(String directoryContents, URL url) throws MalformedURLException { + List results = new ArrayList(); + List urlFragments = extractHrefs(directoryContents); + for(String u : urlFragments) { + if(u.endsWith(XML_EXTENSION)) { //points to xml + if(u.startsWith("/")) { //absolute path need to add the protocol + results.add(new URL(url.getProtocol() + ":" + u)); + } else if (u.startsWith("http:")) { // full url + results.add(new URL(u)); + } else { // relative url stick the name on the end + results.add(appendToURL(url,u)); + } + } + } + return results; + } + + protected static List extractHrefs(String directoryContents) { + List results = new ArrayList(); + int start = 0; + int end = 0; + while(start >= 0) { + start = directoryContents.indexOf(HREF_LOW_START, end); + if (start > 0) start = start + HREF_LOW_START.length(); + + end = directoryContents.indexOf(HREF_SEARCH_END, start); + if(start >= 0) { // We found something + results.add(directoryContents.substring(start, end)); + } + } + + start = 0; + end = 0; + while(start >= 0) { + start = directoryContents.indexOf(HREF_CAPS_START, end); + if (start > 0) start =+ HREF_LOW_START.length(); + + end = directoryContents.indexOf(HREF_SEARCH_END, start); + if(start >= 0) { // We found something + results.add(directoryContents.substring(start, end)); + } + } + return results; + } + + protected static List findXmlFileReferences(String directoryContents, URL url) throws MalformedURLException { + List results = new ArrayList(); + String[] filenames = directoryContents.split("\\n"); + for(String filename : filenames) { + if(filename.endsWith(XML_EXTENSION)) { + results.add(appendToURL(url,filename)); + } + } + return results; + } + + protected static URL appendToURL(final URL url, final String filename) throws MalformedURLException { + String f = filename; + if (!url.toString().endsWith("/")) { + f = "/" + filename; + } + return new URL(url.toString() + f); + } + + protected static String pullContentsFrom(final URL url) throws IOException { + URLConnection connection = url.openConnection(); + InputStream content = connection.getInputStream(); + return new Scanner(content).useDelimiter("\\A").next(); + } + + public static void main (String[] args) throws Exception { + //new VersionedCatalog().initialize(new URL("http://gepo.ningops.net/config/trunk/xno/viking/master/viking-core/")); + new VersionedCatalogLoader().load(new URL("file:///Users/mwesthead/work/killbill/catalog/src/test/resources/")); + } +} diff --git a/catalog/src/main/java/com/ning/billing/catalog/io/XMLReader.java b/catalog/src/main/java/com/ning/billing/catalog/io/XMLReader.java new file mode 100644 index 0000000000..1d77249018 --- /dev/null +++ b/catalog/src/main/java/com/ning/billing/catalog/io/XMLReader.java @@ -0,0 +1,84 @@ +/* + * Copyright 2010-2011 Ning, Inc. + * + * Ning licenses this file to you under the Apache License, version 2.0 + * (the "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF 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.io; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; + +import javax.xml.XMLConstants; +import javax.xml.bind.JAXBContext; +import javax.xml.bind.JAXBException; +import javax.xml.bind.Unmarshaller; +import javax.xml.transform.TransformerException; +import javax.xml.transform.stream.StreamSource; +import javax.xml.validation.Schema; +import javax.xml.validation.SchemaFactory; + +import org.xml.sax.SAXException; + +import com.ning.billing.catalog.Catalog; +import com.ning.billing.catalog.Duration; +import com.ning.billing.catalog.Plan; +import com.ning.billing.catalog.PlanPhase; +import com.ning.billing.catalog.PriceList; +import com.ning.billing.catalog.Product; +import com.ning.billing.catalog.ProductType; +import com.ning.billing.catalog.ValidatingConfig.ValidationErrors; +import com.ning.billing.catalog.api.InvalidConfigException; + +public class XMLReader { + + + public static Catalog getCatalogFromName(URL url) throws SAXException, InvalidConfigException, JAXBException { + JAXBContext context =JAXBContext.newInstance(Catalog.class,Plan.class,Duration.class, Product.class, ProductType.class, PlanPhase.class, PriceList.class); + + InputStream resourceStream = XMLReader.class.getResourceAsStream("/CatalogSchema.xsd"); + SchemaFactory factory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI ); + Schema schema = factory.newSchema(new StreamSource(resourceStream)); + + Unmarshaller um = context.createUnmarshaller(); + um.setSchema(schema); + + Object o = um.unmarshal(url); + + if(o instanceof Catalog) { + Catalog c = (Catalog)o; + c.setCatalogURL(url); + c.initialize(c); + ValidationErrors errs = c.validate(); + System.out.println("Errors: " + errs.size() + " for " + url); + return (Catalog) o; + } else { + return null; + } + } + /** + * @param args + * @throws SAXException + * @throws InvalidConfigException + */ + public static void main(String[] args) throws IOException, TransformerException, JAXBException, SAXException, InvalidConfigException { + String curDir = System.getProperty("user.dir"); + getCatalogFromName(new File("src/test/resources/WeaponsHire.xml").toURI().toURL()); + getCatalogFromName(new File("src/test/resources/WeaponsHireSmall.xml").toURI().toURL()); + + } + + +} diff --git a/catalog/src/main/java/com/ning/billing/catalog/io/XMLSchemaGenerator.java b/catalog/src/main/java/com/ning/billing/catalog/io/XMLSchemaGenerator.java new file mode 100644 index 0000000000..c28eccc617 --- /dev/null +++ b/catalog/src/main/java/com/ning/billing/catalog/io/XMLSchemaGenerator.java @@ -0,0 +1,89 @@ +/* + * Copyright 2010-2011 Ning, Inc. + * + * Ning licenses this file to you under the Apache License, version 2.0 + * (the "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF 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.io; + +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.util.ArrayList; +import java.util.List; + +import javax.xml.bind.JAXBContext; +import javax.xml.bind.JAXBException; +import javax.xml.bind.SchemaOutputResolver; +import javax.xml.transform.OutputKeys; +import javax.xml.transform.Result; +import javax.xml.transform.Transformer; +import javax.xml.transform.TransformerException; +import javax.xml.transform.TransformerFactory; +import javax.xml.transform.dom.DOMResult; +import javax.xml.transform.dom.DOMSource; +import javax.xml.transform.stream.StreamResult; + +import org.w3c.dom.Document; + +import com.ning.billing.catalog.Catalog; +import com.ning.billing.catalog.Duration; +import com.ning.billing.catalog.Plan; +import com.ning.billing.catalog.PlanPhase; +import com.ning.billing.catalog.PriceList; +import com.ning.billing.catalog.Product; +import com.ning.billing.catalog.ProductType; + +public class XMLSchemaGenerator { + + public static void main(String[] args) throws IOException, TransformerException, JAXBException { + JAXBContext context =JAXBContext.newInstance(Catalog.class,Plan.class,Duration.class, Product.class, ProductType.class, PlanPhase.class, PriceList.class); + String xsdFileName = "CatalogSchema.xsd"; + if(args.length != 0) { + xsdFileName = args[0] + "/" + xsdFileName; + } + FileOutputStream s = new FileOutputStream(xsdFileName); + pojoToXSD(context, s); + } + + + public static void pojoToXSD(JAXBContext context, OutputStream out) + throws IOException, TransformerException + { + final List results = new ArrayList(); + + context.generateSchema(new SchemaOutputResolver() { + @Override + public Result createOutput(String ns, String file) + throws IOException { + DOMResult result = new DOMResult(); + result.setSystemId(file); + results.add(result); + return result; + } + }); + + DOMResult domResult = results.get(0); + Document doc = (Document) domResult.getNode(); + + // Use a Transformer for output + TransformerFactory tFactory = TransformerFactory.newInstance(); + Transformer transformer = tFactory.newTransformer(); + + DOMSource source = new DOMSource(doc); + StreamResult result = new StreamResult(out); + transformer.setOutputProperty(OutputKeys.INDENT, "yes"); + transformer.transform(source, result); + } + +} diff --git a/catalog/src/main/resources/.dont-let-git-remove-this-directory b/catalog/src/main/resources/.dont-let-git-remove-this-directory new file mode 100644 index 0000000000..e69de29bb2 diff --git a/catalog/src/test/java/com/ning/billing/catalog/TestCancelCase.java b/catalog/src/test/java/com/ning/billing/catalog/TestCancelCase.java new file mode 100644 index 0000000000..11b83bda0b --- /dev/null +++ b/catalog/src/test/java/com/ning/billing/catalog/TestCancelCase.java @@ -0,0 +1,123 @@ +/* + * Copyright 2010-2011 Ning, Inc. + * + * Ning licenses this file to you under the Apache License, version 2.0 + * (the "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF 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 static com.ning.billing.catalog.api.ActionPolicy.END_OF_TERM; +import static com.ning.billing.catalog.api.ActionPolicy.IMMEDIATE; +import static com.ning.billing.catalog.api.BillingPeriod.ANNUAL; +import static com.ning.billing.catalog.api.BillingPeriod.MONTHLY; +import static com.ning.billing.catalog.api.PhaseType.EVERGREEN; +import static com.ning.billing.catalog.api.PhaseType.TRIAL; +import static org.testng.AssertJUnit.assertEquals; +import static org.testng.AssertJUnit.assertNull; + +import org.testng.annotations.Test; + +import com.ning.billing.catalog.api.PlanPhaseSpecifier; + +public class TestCancelCase extends TestPlanRules { + @Test(enabled=true) + public void testCancelCase() { + Product P1 = createProduct("FP"); + Product P2 = createProduct("BP"); + + Catalog c = createCatalog( + new PlanChangeRule[]{ + }, + new PlanChangeCase[] { + }, + new PlanCancelCase[] { + new PlanCancelCase(P1,MONTHLY,EVERGREEN,IMMEDIATE) + }, + P1, + P2 + ); + assertEquals(IMMEDIATE,c.getPlanCancelPolicy( + new PlanPhaseSpecifier("FP", MONTHLY, null, EVERGREEN) + )); + + assertNull(c.getPlanCancelPolicy( + new PlanPhaseSpecifier("BP", MONTHLY, null, EVERGREEN) + )); + assertNull(c.getPlanCancelPolicy( + new PlanPhaseSpecifier("FP", ANNUAL, null, EVERGREEN) + )); + assertNull(c.getPlanCancelPolicy( + new PlanPhaseSpecifier("FP", MONTHLY, null, TRIAL) + )); + } + + @Test(enabled=true) + public void testCancelCaseWildcard() { + Product P1 = createProduct("FP"); + Product P2 = createProduct("BP"); + + Catalog c = createCatalog( + new PlanChangeRule[]{ + }, + new PlanChangeCase[] { + }, + new PlanCancelCase[] { + new PlanCancelCase(P1,null,null,IMMEDIATE) + }, + P1, + P2 + ); + assertEquals(IMMEDIATE,c.getPlanCancelPolicy( + new PlanPhaseSpecifier("FP", MONTHLY, null, EVERGREEN) + )); + assertEquals(IMMEDIATE,c.getPlanCancelPolicy( + new PlanPhaseSpecifier("FP", ANNUAL, null, EVERGREEN) + )); + assertEquals(IMMEDIATE,c.getPlanCancelPolicy( + new PlanPhaseSpecifier("FP", ANNUAL, null, TRIAL) + )); + + assertNull(c.getPlanCancelPolicy( + new PlanPhaseSpecifier("BP", MONTHLY, null, EVERGREEN) + )); + } + + @Test(enabled=true) + public void testCancelCasePrecedence() { + Product P1 = createProduct("FP"); + Product P2 = createProduct("BP"); + + Catalog c = createCatalog( + new PlanChangeRule[]{ + }, + new PlanChangeCase[] { + }, + new PlanCancelCase[] { + new PlanCancelCase(P1,null,null,END_OF_TERM), + new PlanCancelCase(P1,null,TRIAL,IMMEDIATE) + }, + P1, + P2 + ); + assertEquals(END_OF_TERM,c.getPlanCancelPolicy( + new PlanPhaseSpecifier("FP", MONTHLY, null, EVERGREEN) + )); + assertEquals(END_OF_TERM,c.getPlanCancelPolicy( + new PlanPhaseSpecifier("FP", ANNUAL, null, EVERGREEN) + )); + assertEquals(IMMEDIATE,c.getPlanCancelPolicy( + new PlanPhaseSpecifier("FP", ANNUAL, null, TRIAL) + )); + } + +} diff --git a/catalog/src/test/java/com/ning/billing/catalog/TestPlanChangeCase.java b/catalog/src/test/java/com/ning/billing/catalog/TestPlanChangeCase.java new file mode 100644 index 0000000000..e4f2119c04 --- /dev/null +++ b/catalog/src/test/java/com/ning/billing/catalog/TestPlanChangeCase.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.catalog; + +import static com.ning.billing.catalog.api.ActionPolicy.END_OF_TERM; +import static com.ning.billing.catalog.api.ActionPolicy.IMMEDIATE; +import static com.ning.billing.catalog.api.BillingPeriod.ANNUAL; +import static com.ning.billing.catalog.api.BillingPeriod.MONTHLY; +import static com.ning.billing.catalog.api.PhaseType.EVERGREEN; +import static com.ning.billing.catalog.api.PhaseType.TRIAL; +import static org.testng.AssertJUnit.assertEquals; +import static org.testng.AssertJUnit.assertNull; + +import org.testng.annotations.Test; + +import com.ning.billing.catalog.PlanChangeRule.Qualifier; +import com.ning.billing.catalog.api.PhaseType; +import com.ning.billing.catalog.api.PlanPhaseSpecifier; + +public class TestPlanChangeCase extends TestPlanRules { + + + @Test(enabled=true) + public void testSpecialCase() { + Product P1 = createProduct("FP"); + Product P2 = createProduct("BP"); + + Catalog c = createCatalog( + new PlanChangeRule[]{ + }, + new PlanChangeCase[] { + new PlanChangeCase(P1, P2, ANNUAL, MONTHLY, null, null, null, null, IMMEDIATE ), + }, + null, + P1, + P2 + ); + assertEquals(IMMEDIATE,c.getPlanChangePolicy( + new PlanPhaseSpecifier("FP", ANNUAL, null, EVERGREEN), + new PlanPhaseSpecifier("BP", MONTHLY, null, EVERGREEN) + )); + + assertNull(c.getPlanChangePolicy( + new PlanPhaseSpecifier("BP", MONTHLY, null, EVERGREEN), + new PlanPhaseSpecifier("FP", ANNUAL, null, EVERGREEN) + )); + } + + @Test(enabled=true) + public void testSpecialCaseWildCards() { + Product P1 = createProduct("FP"); + Product P2 = createProduct("BP"); + + Catalog c = createCatalog( + new PlanChangeRule[]{ + }, + new PlanChangeCase[] { + new PlanChangeCase(null, null, ANNUAL, MONTHLY, null, null, null, null, IMMEDIATE ), + }, + null, + P1, + P2 + ); + assertEquals(IMMEDIATE,c.getPlanChangePolicy( + new PlanPhaseSpecifier("FP", ANNUAL, null, EVERGREEN), + new PlanPhaseSpecifier("BP", MONTHLY, null, EVERGREEN) + )); + + assertEquals(IMMEDIATE,c.getPlanChangePolicy( + new PlanPhaseSpecifier("BP", ANNUAL, null, EVERGREEN), + new PlanPhaseSpecifier("FP", MONTHLY, null, EVERGREEN) + )); + + assertEquals(IMMEDIATE,c.getPlanChangePolicy( + new PlanPhaseSpecifier("BP", ANNUAL, null, EVERGREEN), + new PlanPhaseSpecifier("BP", MONTHLY, null, EVERGREEN) + )); + + assertNull(c.getPlanChangePolicy( + new PlanPhaseSpecifier("FP", MONTHLY, null, EVERGREEN), + new PlanPhaseSpecifier("FP", MONTHLY, null, EVERGREEN) + )); + + assertNull(c.getPlanChangePolicy( + new PlanPhaseSpecifier("BP", ANNUAL, null, EVERGREEN), + new PlanPhaseSpecifier("FP", ANNUAL, null, EVERGREEN) + )); + + c = createCatalog( + new PlanChangeRule[]{ + }, + new PlanChangeCase[] { + new PlanChangeCase(null, null, null, MONTHLY, null, null, null, null, IMMEDIATE ), + }, + null, + P1, + P2 + ); + + assertEquals(IMMEDIATE,c.getPlanChangePolicy( + new PlanPhaseSpecifier("FP", ANNUAL, null, EVERGREEN), + new PlanPhaseSpecifier("BP", MONTHLY, null, EVERGREEN) + )); + assertEquals(IMMEDIATE,c.getPlanChangePolicy( + new PlanPhaseSpecifier("BP", ANNUAL, null, EVERGREEN), + new PlanPhaseSpecifier("FP", MONTHLY, null, EVERGREEN) + )); + + assertEquals(IMMEDIATE,c.getPlanChangePolicy( + new PlanPhaseSpecifier("FP", MONTHLY, null, EVERGREEN), + new PlanPhaseSpecifier("FP", MONTHLY, null, EVERGREEN) + )); + + assertNull(c.getPlanChangePolicy( + new PlanPhaseSpecifier("FP", ANNUAL, null, EVERGREEN), + new PlanPhaseSpecifier("FP", ANNUAL, null, EVERGREEN) + )); + + assertNull(c.getPlanChangePolicy( + new PlanPhaseSpecifier("FP", MONTHLY, null, EVERGREEN), + new PlanPhaseSpecifier("FP", ANNUAL, null, EVERGREEN) + )); + + c = createCatalog( + new PlanChangeRule[]{ + }, + new PlanChangeCase[] { + new PlanChangeCase(P1, null, null, null, null, null,null, null, IMMEDIATE ), + }, + null, + P1, + P2 + ); + + assertEquals(IMMEDIATE,c.getPlanChangePolicy( + new PlanPhaseSpecifier("FP", ANNUAL, null, EVERGREEN), + new PlanPhaseSpecifier("BP", MONTHLY, null, EVERGREEN) + )); + + assertEquals(IMMEDIATE,c.getPlanChangePolicy( + new PlanPhaseSpecifier("FP", ANNUAL, null, EVERGREEN), + new PlanPhaseSpecifier("FP", MONTHLY, null, EVERGREEN) + )); + + assertEquals(IMMEDIATE,c.getPlanChangePolicy( + new PlanPhaseSpecifier("FP", MONTHLY, null, EVERGREEN), + new PlanPhaseSpecifier("FP", MONTHLY, null, EVERGREEN) + )); + + assertNull(c.getPlanChangePolicy( + new PlanPhaseSpecifier("BP", ANNUAL, null, EVERGREEN), + new PlanPhaseSpecifier("FP", ANNUAL, null, EVERGREEN) + )); + + assertNull(c.getPlanChangePolicy( + new PlanPhaseSpecifier("BP", MONTHLY, null, EVERGREEN), + new PlanPhaseSpecifier("FP", ANNUAL, null, EVERGREEN) + )); + + } + + @Test(enabled=true) + public void testSpecialCasePhaseTypeSpecific() { + Product P1 = createProduct("FP"); + Product P2 = createProduct("BP"); + PriceList PL1 = createPriceList("PL1"); + PriceList PL2 = createPriceList("PL2"); + + Catalog c = createCatalog( + new PlanChangeRule[]{ + }, + new PlanChangeCase[] { + new PlanChangeCase(P1, P2, ANNUAL, MONTHLY, EVERGREEN, EVERGREEN, PL1, PL2, IMMEDIATE), + }, + null, + P1, + P2 + ); + c.setPriceLists(new PriceList[]{PL1, PL2}); + + assertEquals(IMMEDIATE,c.getPlanChangePolicy( + new PlanPhaseSpecifier("FP", ANNUAL, "PL1", EVERGREEN), + new PlanPhaseSpecifier("BP", MONTHLY, "PL2", EVERGREEN) + )); + + assertNull(c.getPlanChangePolicy( + new PlanPhaseSpecifier("FP", ANNUAL, "PL1", EVERGREEN), + new PlanPhaseSpecifier("BP", ANNUAL, "PL2", EVERGREEN) + )); + + + c = createCatalog( + new PlanChangeRule[]{ + new PlanChangeRule(Qualifier.DEFAULT, END_OF_TERM, PhaseType.EVERGREEN) + }, + new PlanChangeCase[] { + new PlanChangeCase(P1, P2, ANNUAL, MONTHLY, EVERGREEN, EVERGREEN, null, null, IMMEDIATE ), + }, + null, + P1, + P2 + ); + + assertEquals(IMMEDIATE,c.getPlanChangePolicy( + new PlanPhaseSpecifier("FP", ANNUAL, null, EVERGREEN), + new PlanPhaseSpecifier("BP", MONTHLY, null, EVERGREEN) + )); + + assertEquals(END_OF_TERM, c.getPlanChangePolicy( + new PlanPhaseSpecifier("BP", MONTHLY, null, EVERGREEN), + new PlanPhaseSpecifier("FP", ANNUAL, null, EVERGREEN) + )); + assertNull(c.getPlanChangePolicy( + new PlanPhaseSpecifier("BP", MONTHLY, null, TRIAL), + new PlanPhaseSpecifier("FP", ANNUAL, null, TRIAL) + )); + + + + } + + @Test(enabled=true) + public void testPrecedenceSpecialCaseTrumpsRule() { + Product P1 = createProduct("FP"); + Product P2 = createProduct("BP"); + + Catalog c = createCatalog( + new PlanChangeRule[]{ + new PlanChangeRule(Qualifier.PRODUCT_FROM_LOW_TO_HIGH, END_OF_TERM, null) + }, + new PlanChangeCase[] { + new PlanChangeCase(P1, P2, ANNUAL, MONTHLY, null, null, null, null, IMMEDIATE ), + }, + null, + P1, + P2 + ); + + assertEquals(IMMEDIATE,c.getPlanChangePolicy( + new PlanPhaseSpecifier("FP", ANNUAL, null, EVERGREEN), + new PlanPhaseSpecifier("BP", MONTHLY, null, EVERGREEN) + )); + + assertNull(c.getPlanChangePolicy( + new PlanPhaseSpecifier("BP", MONTHLY, null, EVERGREEN), + new PlanPhaseSpecifier("FP", ANNUAL, null, EVERGREEN) + )); + + } + +} diff --git a/catalog/src/test/java/com/ning/billing/catalog/TestPlanChangeRules.java b/catalog/src/test/java/com/ning/billing/catalog/TestPlanChangeRules.java new file mode 100644 index 0000000000..ef0fe65761 --- /dev/null +++ b/catalog/src/test/java/com/ning/billing/catalog/TestPlanChangeRules.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.catalog; + +import static com.ning.billing.catalog.api.ActionPolicy.END_OF_TERM; +import static com.ning.billing.catalog.api.ActionPolicy.IMMEDIATE; +import static com.ning.billing.catalog.api.BillingPeriod.ANNUAL; +import static com.ning.billing.catalog.api.BillingPeriod.MONTHLY; +import static com.ning.billing.catalog.api.PhaseType.EVERGREEN; +import static com.ning.billing.catalog.api.PhaseType.TRIAL; +import static org.testng.AssertJUnit.assertEquals; +import static org.testng.AssertJUnit.assertNull; + +import org.testng.annotations.Test; + +import com.ning.billing.catalog.PlanChangeRule.Qualifier; +import com.ning.billing.catalog.api.ActionPolicy; +import com.ning.billing.catalog.api.PhaseType; +import com.ning.billing.catalog.api.PlanPhaseSpecifier; + +public class TestPlanChangeRules extends TestPlanRules { + + @Test(enabled=true) + public void testDefault() { + Product P1 = createProduct("FP"); + Product P2 = createProduct("BP"); + + Catalog c = createCatalog( + new PlanChangeRule[]{ + new PlanChangeRule(Qualifier.DEFAULT, ActionPolicy.END_OF_TERM, null) + }, + null, + null, + P1, + P2 + ); + assertEquals(ActionPolicy.END_OF_TERM,c.getPlanChangePolicy( + new PlanPhaseSpecifier("FP", ANNUAL, null, EVERGREEN), + new PlanPhaseSpecifier("BP", ANNUAL, null, EVERGREEN) + )); + } + + @Test(enabled=true) + public void testBillingPeriodRule() { + Product P1 = createProduct("FP"); + Product P2 = createProduct("BP"); + + Catalog c = createCatalog( + new PlanChangeRule[]{ + new PlanChangeRule(Qualifier.TERM_FROM_LONG_TO_SHORT, END_OF_TERM, null) + }, + null, + null, + P1, + P2 + ); + + assertEquals(END_OF_TERM,c.getPlanChangePolicy( + new PlanPhaseSpecifier("FP", ANNUAL, null, EVERGREEN), + new PlanPhaseSpecifier("BP", MONTHLY, null, EVERGREEN) + )); + + assertNull(c.getPlanChangePolicy( + new PlanPhaseSpecifier("FP", MONTHLY, null, EVERGREEN), + new PlanPhaseSpecifier("BP", ANNUAL, null, EVERGREEN) + )); + + + + } + + @Test(enabled=true) + public void testBillingPeriodRule2() { + Product P1 = createProduct("FP"); + Product P2 = createProduct("BP"); + + Catalog c = createCatalog( + new PlanChangeRule[]{ + new PlanChangeRule(Qualifier.TERM_FROM_SHORT_TO_LONG, END_OF_TERM, null) + }, + null, + null, + P1, + P2 + ); + + assertEquals(END_OF_TERM,c.getPlanChangePolicy( + new PlanPhaseSpecifier("FP", MONTHLY, null, EVERGREEN), + new PlanPhaseSpecifier("BP", ANNUAL, null, EVERGREEN) + )); + + assertNull(c.getPlanChangePolicy( + new PlanPhaseSpecifier("FP", ANNUAL, null, EVERGREEN), + new PlanPhaseSpecifier("BP", MONTHLY, null, EVERGREEN) + )); + +} + + @Test(enabled=true) + public void testBillingPeriodRulePhaseSpecific() { + Product P1 = createProduct("FP"); + Product P2 = createProduct("BP"); + + Catalog c = createCatalog( + new PlanChangeRule[]{ + new PlanChangeRule(Qualifier.TERM_FROM_SHORT_TO_LONG, END_OF_TERM, PhaseType.EVERGREEN) + }, + null, + null, + P1, + P2 + ); + + assertEquals(END_OF_TERM,c.getPlanChangePolicy( + new PlanPhaseSpecifier("FP", MONTHLY, null, EVERGREEN), + new PlanPhaseSpecifier("BP", ANNUAL, null, EVERGREEN) + )); + + assertNull(c.getPlanChangePolicy( + new PlanPhaseSpecifier("FP", MONTHLY, null, TRIAL), + new PlanPhaseSpecifier("BP", ANNUAL, null, TRIAL) + )); + + } + + @Test(enabled=true) + public void testTierChangeRule() { + Product P1 = createProduct("FP"); + Product P2 = createProduct("BP"); + + Catalog c = createCatalog( + new PlanChangeRule[]{ + new PlanChangeRule(Qualifier.PRODUCT_FROM_LOW_TO_HIGH, IMMEDIATE, null) + }, + null, + null, + P1, + P2 + ); + + assertEquals(IMMEDIATE,c.getPlanChangePolicy( + new PlanPhaseSpecifier("FP", MONTHLY, null, EVERGREEN), + new PlanPhaseSpecifier("BP", ANNUAL, null, EVERGREEN) + )); + + assertNull(c.getPlanChangePolicy( + new PlanPhaseSpecifier("BP", MONTHLY, null, EVERGREEN), + new PlanPhaseSpecifier("FP", ANNUAL, null, EVERGREEN) + )); + + + } + + @Test(enabled=true) + public void testTierChangeRule2() { + Product P1 = createProduct("FP"); + Product P2 = createProduct("BP"); + + Catalog c = createCatalog( + new PlanChangeRule[]{ + new PlanChangeRule(Qualifier.PRODUCT_FROM_HIGH_TO_LOW, IMMEDIATE, null) + }, + null, + null, + P1, + P2 + ); + + assertEquals(IMMEDIATE,c.getPlanChangePolicy( + new PlanPhaseSpecifier("BP", ANNUAL, null, EVERGREEN), + new PlanPhaseSpecifier("FP", MONTHLY, null, EVERGREEN) + )); + + assertNull(c.getPlanChangePolicy( + new PlanPhaseSpecifier("FP", MONTHLY, null, TRIAL), + new PlanPhaseSpecifier("BP", ANNUAL, null, TRIAL) + )); + + } + + @Test(enabled=true) + public void testPrecedenceRuleOrderAndSpecialCaseIgnored() { + Product P1 = createProduct("FP"); + Product P2 = createProduct("BP"); + + Catalog c = createCatalog( + new PlanChangeRule[]{ + new PlanChangeRule(Qualifier.TERM_FROM_LONG_TO_SHORT, IMMEDIATE, null), + new PlanChangeRule(Qualifier.PRODUCT_FROM_LOW_TO_HIGH, END_OF_TERM, null) + }, + new PlanChangeCase[] { + new PlanChangeCase(P1, P2, MONTHLY, MONTHLY, null, null, null, null, null ), + }, + null, + P1, + P2 + ); + + assertEquals(END_OF_TERM,c.getPlanChangePolicy( + new PlanPhaseSpecifier("FP", ANNUAL, null, EVERGREEN), + new PlanPhaseSpecifier("BP", MONTHLY, null, EVERGREEN) + )); + + assertNull(c.getPlanChangePolicy( + new PlanPhaseSpecifier("BP", MONTHLY, null, EVERGREEN), + new PlanPhaseSpecifier("FP", ANNUAL, null, EVERGREEN) + )); + + } + + +} diff --git a/catalog/src/test/java/com/ning/billing/catalog/TestPlanRules.java b/catalog/src/test/java/com/ning/billing/catalog/TestPlanRules.java new file mode 100644 index 0000000000..0f2ff5052f --- /dev/null +++ b/catalog/src/test/java/com/ning/billing/catalog/TestPlanRules.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.catalog; + + +public class TestPlanRules { + + public Catalog createCatalog(PlanChangeRule[] generalRules, + PlanChangeCase[] specialCaseRules, + PlanCancelCase[] cancelCaseRules, + Product fromP, + Product toP) { + Catalog c = new Catalog(); + PlanRules pcr = new PlanRules(); + pcr.setGeneralRules(generalRules); + pcr.setSpecialCaseRules(specialCaseRules); + pcr.setCancelCaseRules(cancelCaseRules); + c.setPlanChangeRules(pcr); + pcr.setProductTiers(new ProductTier[] {new ProductTier(new Product[]{ fromP, toP })}); + c.setProducts(new Product[] { fromP, toP }); + return c; + } + + public Catalog createCatalog(PlanChangeRule[] generalRules, + PlanChangeCase[] specialCaseRules, + PlanCancelCase[] cancelCaseRules, + PlanAlignmentCase[] alignmentCases, + Product fromP, + Product toP) { + Catalog c = new Catalog(); + PlanRules pcr = new PlanRules(); + pcr.setGeneralRules(generalRules); + pcr.setSpecialCaseRules(specialCaseRules); + pcr.setCancelCaseRules(cancelCaseRules); + pcr.setAlignmentCase(alignmentCases); + c.setPlanChangeRules(pcr); + pcr.setProductTiers(new ProductTier[] {new ProductTier(new Product[]{ fromP, toP })}); + c.setProducts(new Product[] { fromP, toP }); + return c; + } + + + public Product createProduct(String name) { + ProductType type = new ProductType("TestType"); + return new Product(type, name); + } + + + protected PriceList createPriceList(String name) { + PriceList result = new PriceList(); + result.setName(name); + return result; + } + + +} diff --git a/catalog/src/test/java/com/ning/billing/catalog/TestVersionedCatalog.java b/catalog/src/test/java/com/ning/billing/catalog/TestVersionedCatalog.java new file mode 100644 index 0000000000..a4d211cec3 --- /dev/null +++ b/catalog/src/test/java/com/ning/billing/catalog/TestVersionedCatalog.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.catalog; + +import static org.testng.AssertJUnit.assertEquals; +import java.io.File; +import java.io.IOException; +import java.net.MalformedURLException; +import java.util.Date; + +import javax.xml.bind.JAXBException; + +import org.joda.time.DateTime; +import org.testng.annotations.Test; +import org.xml.sax.SAXException; + +import com.ning.billing.catalog.Catalog; +import com.ning.billing.catalog.VersionedCatalog; +import com.ning.billing.catalog.api.InvalidConfigException; +import com.ning.billing.catalog.io.VersionedCatalogLoader; + +public class TestVersionedCatalog { + @Test(enabled=true) + public void testAddCatalog() throws MalformedURLException, IOException, SAXException, InvalidConfigException, JAXBException { + VersionedCatalog vc = VersionedCatalogLoader.load(new File("src/test/resources/versionedCatalog").toURI().toURL()); + vc.add(new Catalog(new Date())); + assertEquals(5, vc.size()); + } + + @Test(enabled=true) + public void testApplyEffectiveDate() throws MalformedURLException, IOException, SAXException, InvalidConfigException, JAXBException { + VersionedCatalog vc = VersionedCatalogLoader.load(new File("src/test/resources/versionedCatalog").toURI().toURL()); + Date d = new Date(1L); + vc.applyEffectiveDate(d); + assertEquals(new Date(0), vc.getEffectiveDate()); // Start at the begining of time + + DateTime dt = new DateTime("2011-01-01T00:00:00+00:00"); + d = new Date(dt.getMillis() + 1000); + vc.applyEffectiveDate(d); + assertEquals(dt.toDate(),vc.getEffectiveDate()); + + dt = new DateTime("2011-02-02T00:00:00+00:00"); + d = new Date(dt.getMillis() + 1000); + vc.applyEffectiveDate(d); + assertEquals(dt.toDate(),vc.getEffectiveDate()); + + dt = new DateTime("2011-03-03T00:00:00+00:00"); + d = new Date(dt.getMillis() + 1000); + vc.applyEffectiveDate(d); + assertEquals(dt.toDate(),vc.getEffectiveDate()); + + } + +} diff --git a/catalog/src/test/java/com/ning/billing/catalog/io/TestVersionedCatalogLoader.java b/catalog/src/test/java/com/ning/billing/catalog/io/TestVersionedCatalogLoader.java new file mode 100644 index 0000000000..1e978a2d81 --- /dev/null +++ b/catalog/src/test/java/com/ning/billing/catalog/io/TestVersionedCatalogLoader.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.catalog.io; + +import static com.ning.billing.catalog.io.VersionedCatalogLoader.appendToURL; +import static com.ning.billing.catalog.io.VersionedCatalogLoader.extractHrefs; +import static com.ning.billing.catalog.io.VersionedCatalogLoader.findXmlFileReferences; +import static com.ning.billing.catalog.io.VersionedCatalogLoader.findXmlUrlReferences; +import static com.ning.billing.catalog.io.VersionedCatalogLoader.load; +import static com.ning.billing.catalog.io.VersionedCatalogLoader.pullContentsFrom; +import static org.testng.AssertJUnit.assertEquals; +import static org.testng.AssertJUnit.assertTrue; + +import java.io.File; +import java.io.IOException; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.Date; +import java.util.Iterator; +import java.util.List; + +import javax.xml.bind.JAXBException; + +import org.joda.time.DateTime; +import org.testng.annotations.Test; +import org.xml.sax.SAXException; + +import com.ning.billing.catalog.Catalog; +import com.ning.billing.catalog.VersionedCatalog; +import com.ning.billing.catalog.api.InvalidConfigException; + +public class TestVersionedCatalogLoader { + + + @Test(enabled=true) + public void testPullContentsFrom() throws MalformedURLException, IOException { + String contents = pullContentsFrom(new File("src/test/resources/WeaponsHireSmall.xml").toURI().toURL()); + + assertTrue(contents.length() > 0); + + } + + @Test(enabled=true) + public void testAppendToURL() throws MalformedURLException, IOException { + URL u1 = new URL("http://www.ning.com/foo"); + assertEquals("http://www.ning.com/foo/bar",appendToURL(u1, "bar").toString()); + + URL u2 = new URL("http://www.ning.com/foo/"); + assertEquals("http://www.ning.com/foo/bar",appendToURL(u2, "bar").toString()); + + } + + + + + @Test(enabled=true) + public void testFindXmlFileReferences() throws MalformedURLException { + String page = "dg.xml\n" + + "replica.foo\n" + + "snv1/\n" + + "viking.xml\n" ; + List urls = findXmlFileReferences(page, new URL("http://ning.com/")); + assertEquals(2, urls.size()); + assertEquals("http://ning.com/dg.xml", urls.get(0).toString()); + assertEquals("http://ning.com/viking.xml", urls.get(1).toString()); + + } + + @Test(enabled=true) + public void testExtractHrefs() { + String page = "" + + "" + + " " + + " Index of /config/trunk/xno" + + " " + + " " + + "

Index of /config/trunk/xno

" + + "" + + "
Apache/2.2.3 (CentOS) Server at gepo.ningops.net Port 80
" + + "" ; + List hrefs = extractHrefs(page); + assertEquals(8, hrefs.size()); + assertEquals("/config/trunk/", hrefs.get(0)); + assertEquals("dg.xml", hrefs.get(1)); + } + + @Test(enabled=true) + public void testFindXmlUrlReferences() throws MalformedURLException { + String page = "" + + "" + + " " + + " Index of /config/trunk/xno" + + " " + + " " + + "

Index of /config/trunk/xno

" + + "" + + "
Apache/2.2.3 (CentOS) Server at gepo.ningops.net Port 80
" + + "" ; + List urls = findXmlUrlReferences(page, new URL("http://ning.com/")); + assertEquals(2, urls.size()); + assertEquals("http://ning.com/dg.xml", urls.get(0).toString()); + assertEquals("http://ning.com/viking.xml", urls.get(1).toString()); + + } + + @Test(enabled=true) + public void testLoad() throws MalformedURLException, IOException, SAXException, InvalidConfigException, JAXBException { + VersionedCatalog c = load(new File("src/test/resources/versionedCatalog").toURI().toURL()); + assertEquals(4, c.size()); + Iterator it = c.iterator(); + it.next(); //discard the baseline + DateTime dt = new DateTime("2011-01-01T00:00:00+00:00"); + assertEquals(dt.toDate(),it.next().getEffectiveDate()); + dt = new DateTime("2011-02-02T00:00:00+00:00"); + assertEquals(dt.toDate(),it.next().getEffectiveDate()); + dt = new DateTime("2011-03-03T00:00:00+00:00"); + assertEquals(dt.toDate(),it.next().getEffectiveDate()); + } +} diff --git a/catalog/src/test/resources/WeaponsHire.xml b/catalog/src/test/resources/WeaponsHire.xml new file mode 100644 index 0000000000..292879d76f --- /dev/null +++ b/catalog/src/test/resources/WeaponsHire.xml @@ -0,0 +1,608 @@ + + + + + + + 2011-10-08T00:00:00+00:00 + + + USD + EUR + GBP + + + + + + + + + + Firearms + BASE + + Telescopic-Scope + Laser-Scope + + + + Firearms + BASE + + + Firearms + BASE + + Telescopic-Scope + + + Laser-Scope + + + + Firearms + ADD_ON + + + Firearms + ADD_ON + + + Firearms + ADD_ON + + + Firearms + ADD_ON + + + Firearms + ADD_ON + + + Blade + BASE + + + Blade + BASE + + + + + + + Pistol + Shotgun + Assault-Rifle + + + + DEFAULT + END_OF_TERM + + + TERM_FROM_SHORT_TO_LONG + IMMEDIATE + + + PRODUCT_FROM_LOW_TO_HIGH + IMMEDIATE + + + MONTHLY + Assault-Rifle + MONTHLY + END_OF_TERM + + + rescue + END_OF_TERM + + + TRIAL + IMMEDIATE + + + END_OF_TERM + + + TRIAL + IMMEDIATE + + + START_OF_SUBSCRIPTION + + + rescue + CHANGE_OF_PLAN + + + rescue + rescue + CHANGE_OF_PRICELIST + + + + + + Pistol + + + + DAYS + 30 + + + + + + + + UNLIMITED + + MONTHLY + + GBP29.95 + EUR29.95 + USD29.95 + + + ACCOUNT + + + Shotgun + + + + DAYS + 30 + + + + + + + + UNLIMITED + -1 + + MONTHLY + + USD249.95 + EUR149.95 + GBP169.95 + + + ACCOUNT + + + Assault-Rifle + + + + DAYS + 30 + + + + + + + + UNLIMITED + + MONTHLY + + USD599.95 + EUR349.95 + GBP399.95 + + + ACCOUNT + + + Pistol + + + + DAYS + 30 + + + + + + + + UNLIMITED + + ANNUAL + + USD199.95 + EUR199.95 + GBP199.95 + + + SUBSCRIPTION + + + Shotgun + + + + DAYS + 30 + + + + + + + + UNLIMITED + + ANNUAL + + USD2399.95 + EUR1499.95 + GBP1699.95 + + + SUBSCRIPTION + + + Assault-Rifle + + + + DAYS + 30 + + + + + + + + UNLIMITED + + ANNUAL + + USD5999.95 + EUR3499.95 + GBP3999.95 + + + SUBSCRIPTION + + + Pistol + + + + DAYS + 30 + + + + + + + MONTHS + 6 + + MONTHLY + + USD9.95 + EUR9.95 + GBP9.95 + + + + + + UNLIMITED + + ANNUAL + + USD199.95 + EUR199.95 + GBP199.95 + + + SUBSCRIPTION + + + Shotgun + + + + DAYS + 30 + + + + + + + MONTHS + 6 + + MONTHLY + + USD19.95 + EUR49.95 + GBP69.95 + + + + + + UNLIMITED + + ANNUAL + + USD2399.95 + EUR1499.95 + GBP1699.95 + + + SUBSCRIPTION + + + Assault-Rifle + + + + DAYS + 30 + + + + + + + MONTHS + 6 + + MONTHLY + + USD99.95 + EUR99.95 + GBP99.95 + + + + + + UNLIMITED + + ANNUAL + + USD5999.95 + EUR3499.95 + GBP3999.95 + + + SUBSCRIPTION + + + Laser-Scope + + + UNLIMITED + + MONTHLY + + USD1999.95 + EUR1499.95 + GBP1999.95 + + + BUNDLE + + + Telescopic-Scope + + + UNLIMITED + + MONTHLY + + USD999.95 + EUR499.95 + GBP999.95 + + + BUNDLE + + + Extra-Ammo + + + UNLIMITED + + MONTHLY + + USD999.95 + EUR499.95 + GBP999.95 + + + BUNDLE + -1 + + + Holster + + + + DAYS + 30 + + + + + + + + UNLIMITED + + ANNUAL + + USD199.95 + EUR199.95 + GBP199.95 + + + BUNDLE + + + Holster + + + + DAYS + 30 + + + + + + + + UNLIMITED + + ANNUAL + + USD199.95 + EUR199.95 + GBP199.95 + + + BUNDLE + + + Assault-Rifle + + + + YEARS + 1 + + ANNUAL + + USD5999.95 + EUR3499.95 + GBP3999.95 + + + + + + UNLIMITED + + ANNUAL + + USD5999.95 + EUR3499.95 + GBP3999.95 + + + SUBSCRIPTION + + + Refurbish-Maintenance + + + MONTHS + 12 + + MONTHLY + + USD199.95 + EUR199.95 + GBP199.95 + + + USD599.95 + EUR599.95 + GBP599.95 + + + BUNDLE + + + + + true + + pistol-monthly + shotgun-monthly + assault-rifle-monthly + pistol-annual + shotgun-annual + assault-rifle-annual + laser-scope-monthly + telescopic-scope-monthly + extra-ammo-monthly + holster-monthly-regular + refurbish-maintenance + + + + + pistol-monthly + shotgun-monthly + assault-rifle-monthly + pistol-annual-gunclub-discount + shotgun-annual-gunclub-discount + assault-rifle-annual-gunclub-discount + holster-monthly-special + + + + + assault-rifle-annual-rescue + + + + + diff --git a/catalog/src/test/resources/WeaponsHireSmall.xml b/catalog/src/test/resources/WeaponsHireSmall.xml new file mode 100644 index 0000000000..9b8bbbcc22 --- /dev/null +++ b/catalog/src/test/resources/WeaponsHireSmall.xml @@ -0,0 +1,174 @@ + + + + + + 2011-10-08T00:00:00+00:00 + + + USD + EUR + GBP + + + + + + + + + Firearms + BASE + + + Firearms + BASE + + + Firearms + ADD_ON + + + + + + + Pistol + Shotgun + + + + DEFAULT + END_OF_TERM + + + TERM_FROM_SHORT_TO_LONG + IMMEDIATE + + + PRODUCT_FROM_LOW_TO_HIGH + IMMEDIATE + + + PRODUCT_FROM_LOW_TO_HIGH + IMMEDIATE + + + MONTHLY + Shotgun + MONTHLY + END_OF_TERM + + + TRIAL + IMMEDIATE + + + START_OF_SUBSCRIPTION + + + + + + Pistol + + + + DAYS + 30 + + + + + + + + UNLIMITED + + MONTHLY + + GBP29.95 + EUR29.95 + USD29.95 + + + ACCOUNT + + + Shotgun + + + + DAYS + 30 + + + + + + + + UNLIMITED + -1 + + MONTHLY + + USD249.95 + EUR149.95 + GBP169.95 + + + ACCOUNT + + + Shotgun + + + + DAYS + 30 + + + + + + + + UNLIMITED + + ANNUAL + + USD2399.95 + EUR1499.95 + GBP1699.95 + + + SUBSCRIPTION + + + + + true + + pistol-monthly + shotgun-monthly + shotgun-annual + + + + diff --git a/catalog/src/test/resources/versionedCatalog/WeaponsHireSmall-1.xml b/catalog/src/test/resources/versionedCatalog/WeaponsHireSmall-1.xml new file mode 100644 index 0000000000..ead1bb0309 --- /dev/null +++ b/catalog/src/test/resources/versionedCatalog/WeaponsHireSmall-1.xml @@ -0,0 +1,174 @@ + + + + + + 2011-01-01T00:00:00+00:00 + + + USD + EUR + GBP + + + + + + + + + Firearms + BASE + + + Firearms + BASE + + + Firearms + ADD_ON + + + + + + + Pistol + Shotgun + + + + DEFAULT + END_OF_TERM + + + TERM_FROM_SHORT_TO_LONG + IMMEDIATE + + + PRODUCT_FROM_LOW_TO_HIGH + IMMEDIATE + + + PRODUCT_FROM_LOW_TO_HIGH + IMMEDIATE + + + MONTHLY + Shotgun + MONTHLY + END_OF_TERM + + + TRIAL + IMMEDIATE + + + START_OF_SUBSCRIPTION + + + + + + Pistol + + + + DAYS + 30 + + + + + + + + UNLIMITED + + MONTHLY + + GBP29.95 + EUR29.95 + USD29.95 + + + ACCOUNT + + + Shotgun + + + + DAYS + 30 + + + + + + + + UNLIMITED + -1 + + MONTHLY + + USD249.95 + EUR149.95 + GBP169.95 + + + ACCOUNT + + + Shotgun + + + + DAYS + 30 + + + + + + + + UNLIMITED + + ANNUAL + + USD2399.95 + EUR1499.95 + GBP1699.95 + + + SUBSCRIPTION + + + + + true + + pistol-monthly + shotgun-monthly + shotgun-annual + + + + diff --git a/catalog/src/test/resources/versionedCatalog/WeaponsHireSmall-2.xml b/catalog/src/test/resources/versionedCatalog/WeaponsHireSmall-2.xml new file mode 100644 index 0000000000..8d32846ede --- /dev/null +++ b/catalog/src/test/resources/versionedCatalog/WeaponsHireSmall-2.xml @@ -0,0 +1,175 @@ + + + + + + 2011-02-02T00:00:00+00:00 + + + USD + EUR + GBP + + + + + + + + + Firearms + BASE + + + Firearms + BASE + + + Firearms + ADD_ON + + + + + + + Pistol + Shotgun + + + + DEFAULT + END_OF_TERM + + + TERM_FROM_SHORT_TO_LONG + IMMEDIATE + + + PRODUCT_FROM_LOW_TO_HIGH + IMMEDIATE + + + PRODUCT_FROM_LOW_TO_HIGH + IMMEDIATE + + + MONTHLY + Shotgun + MONTHLY + END_OF_TERM + + + TRIAL + IMMEDIATE + + + START_OF_SUBSCRIPTION + + + + + + + Pistol + + + + DAYS + 30 + + + + + + + + UNLIMITED + + MONTHLY + + GBP29.95 + EUR29.95 + USD29.95 + + + ACCOUNT + + + Shotgun + + + + DAYS + 30 + + + + + + + + UNLIMITED + -1 + + MONTHLY + + USD249.95 + EUR149.95 + GBP169.95 + + + ACCOUNT + + + Shotgun + + + + DAYS + 30 + + + + + + + + UNLIMITED + + ANNUAL + + USD2399.95 + EUR1499.95 + GBP1699.95 + + + SUBSCRIPTION + + + + + true + + pistol-monthly + shotgun-monthly + shotgun-annual + + + + diff --git a/catalog/src/test/resources/versionedCatalog/WeaponsHireSmall-3.xml b/catalog/src/test/resources/versionedCatalog/WeaponsHireSmall-3.xml new file mode 100644 index 0000000000..e1c8bff984 --- /dev/null +++ b/catalog/src/test/resources/versionedCatalog/WeaponsHireSmall-3.xml @@ -0,0 +1,174 @@ + + + + + + 2011-03-03T00:00:00+00:00 + + + USD + EUR + GBP + + + + + + + + + Firearms + BASE + + + Firearms + BASE + + + Firearms + ADD_ON + + + + + + + Pistol + Shotgun + + + + DEFAULT + END_OF_TERM + + + TERM_FROM_SHORT_TO_LONG + IMMEDIATE + + + PRODUCT_FROM_LOW_TO_HIGH + IMMEDIATE + + + PRODUCT_FROM_LOW_TO_HIGH + IMMEDIATE + + + MONTHLY + Shotgun + MONTHLY + END_OF_TERM + + + TRIAL + IMMEDIATE + + + START_OF_SUBSCRIPTION + + + + + + Pistol + + + + DAYS + 30 + + + + + + + + UNLIMITED + + MONTHLY + + GBP29.95 + EUR29.95 + USD29.95 + + + ACCOUNT + + + Shotgun + + + + DAYS + 30 + + + + + + + + UNLIMITED + -1 + + MONTHLY + + USD249.95 + EUR149.95 + GBP169.95 + + + ACCOUNT + + + Shotgun + + + + DAYS + 30 + + + + + + + + UNLIMITED + + ANNUAL + + USD2399.95 + EUR1499.95 + GBP1699.95 + + + SUBSCRIPTION + + + + + true + + pistol-monthly + shotgun-monthly + shotgun-annual + + + + diff --git a/entitlement/pom.xml b/entitlement/pom.xml new file mode 100644 index 0000000000..3dc5510d68 --- /dev/null +++ b/entitlement/pom.xml @@ -0,0 +1,110 @@ + + + + + 4.0.0 + + com.ning.billing + killbill + 0.0.3-SNAPSHOT + ../pom.xml + + killbill-entitlement + killbill-entitlement + jar + + + org.jdbi + jdbi + + + mysql + mysql-connector-java + + + com.google.inject + guice + provided + + + com.ning.billing + killbill-api + + + com.ning.billing + killbill-catalog + + + com.ning.billing + killbill-util + + + joda-time + joda-time + + + org.skife.config + config-magic + + + org.slf4j + slf4j-api + + + org.slf4j + slf4j-log4j12 + + + org.testng + testng + test + + + + + + org.apache.maven.plugins + maven-surefire-plugin + + setup,fast + + + + + + + test-sql + + + + maven-surefire-plugin + + setup,sql + + + + + + + test-stress + + + + maven-surefire-plugin + + setup,stress + + + + + + + diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/alignment/EntitlementAlignment.java b/entitlement/src/main/java/com/ning/billing/entitlement/alignment/EntitlementAlignment.java new file mode 100644 index 0000000000..8ad50a4814 --- /dev/null +++ b/entitlement/src/main/java/com/ning/billing/entitlement/alignment/EntitlementAlignment.java @@ -0,0 +1,148 @@ +/* + * Copyright 2010-2011 Ning, Inc. + * + * Ning licenses this file to you under the Apache License, version 2.0 + * (the "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF 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.alignment; + +import java.util.LinkedList; +import java.util.List; +import java.util.UUID; + +import org.joda.time.DateTime; + +import com.ning.billing.catalog.api.IDuration; +import com.ning.billing.catalog.api.IPlan; +import com.ning.billing.catalog.api.IPlanPhase; +import com.ning.billing.catalog.api.PlanAlignment; +import com.ning.billing.entitlement.events.phase.IPhaseEvent; +import com.ning.billing.entitlement.events.phase.PhaseEvent; +import com.ning.billing.entitlement.exceptions.EntitlementError; +import com.ning.billing.util.clock.Clock; + +public class EntitlementAlignment { + + private final UUID subscriptionId; + private final DateTime bundleStart; + private final IPlan plan; + private final DateTime effectiveDate; + private final long activeVersion; + private final DateTime now; + private final List timedPhases; + + public final static class TimedPhase { + + private final IPlanPhase phase; + private final DateTime startPhase; + + public TimedPhase(IPlanPhase phase, DateTime startPhase) { + super(); + this.phase = phase; + this.startPhase = startPhase; + } + + public IPlanPhase getPhase() { + return phase; + } + + public DateTime getStartPhase() { + return startPhase; + } + } + + public EntitlementAlignment(DateTime bundleStart, IPlan plan, DateTime effectiveDate) { + this(null, null, bundleStart, plan, effectiveDate, 0); + } + + public EntitlementAlignment(UUID subscriptionId, DateTime now, DateTime bundleStart, IPlan plan, + DateTime effectiveDate, long activeVersion) { + super(); + this.subscriptionId = subscriptionId; + this.now = now; + this.bundleStart = bundleStart; + this.plan = plan; + this.effectiveDate = effectiveDate; + this.activeVersion = activeVersion; + this.timedPhases = buildPhaseAlignment(); + } + + public List getTimedPhases() { + return timedPhases; + } + + public TimedPhase getCurrentTimedPhase() { + return getCurOrNextTimedPhase(true); + } + + public TimedPhase getNextTimedPhase() { + return getCurOrNextTimedPhase(false); + } + + public IPhaseEvent getNextPhaseEvent() { + + if (subscriptionId == null || now == null || activeVersion == 0) { + throw new EntitlementError("Missing required arguments for creating next phase event"); + } + TimedPhase nextPhase = getNextTimedPhase(); + IPhaseEvent nextPhaseEvent = (nextPhase == null) ? + null : + new PhaseEvent(subscriptionId, nextPhase.getPhase(), now, nextPhase.getStartPhase(), + now, activeVersion); + return nextPhaseEvent; + } + + private TimedPhase getCurOrNextTimedPhase(boolean returnCurrent) { + TimedPhase cur = null; + TimedPhase next = null; + for (TimedPhase phase : timedPhases) { + if (phase.getStartPhase().isAfter(effectiveDate)) { + next = phase; + break; + } + cur = phase; + } + return (returnCurrent) ? cur : next; + } + + + private List buildPhaseAlignment() { + + List result = new LinkedList(); + + DateTime curPhaseStart = (plan.getPlanAlignment() == PlanAlignment.START_OF_SUBSCRIPTION) ? + effectiveDate : bundleStart; + if (plan.getInitialPhases() == null) { + result.add(new TimedPhase(plan.getFinalPhase(), curPhaseStart)); + return result; + } + + DateTime nextPhaseStart = null; + for (IPlanPhase cur : plan.getInitialPhases()) { + + result.add(new TimedPhase(cur, curPhaseStart)); + + IDuration curPhaseDuration = cur.getDuration(); + nextPhaseStart = Clock.addDuration(curPhaseStart, curPhaseDuration); + if (nextPhaseStart == null) { + throw new EntitlementError(String.format("Unexpected non ending UNLIMITED phase for plan %s", + plan.getName())); + } + curPhaseStart = nextPhaseStart; + } + result.add(new TimedPhase(plan.getFinalPhase(), nextPhaseStart)); + return result; + } + + +} diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/api/billing/BillingApi.java b/entitlement/src/main/java/com/ning/billing/entitlement/api/billing/BillingApi.java new file mode 100644 index 0000000000..86e50890b4 --- /dev/null +++ b/entitlement/src/main/java/com/ning/billing/entitlement/api/billing/BillingApi.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.entitlement.api.billing; + +import java.util.List; +import java.util.SortedSet; +import java.util.UUID; + +import org.joda.time.DateTime; + +import com.google.inject.Inject; +import com.ning.billing.account.api.IAccount; +import com.ning.billing.catalog.api.ICatalog; +import com.ning.billing.entitlement.api.user.ISubscription; +import com.ning.billing.entitlement.api.user.Subscription; +import com.ning.billing.entitlement.api.user.Subscription.SubscriptionBuilder; +import com.ning.billing.entitlement.engine.core.Engine; +import com.ning.billing.entitlement.engine.dao.IEntitlementDao; +import com.ning.billing.util.clock.IClock; + +public class BillingApi implements IBillingApi { + + private final Engine engine; + private final IClock clock; + private final IEntitlementDao dao; + + @Inject + public BillingApi(Engine engine, IClock clock, IEntitlementDao dao) { + super(); + this.engine = engine; + this.clock = clock; + this.dao = dao; + } + + @Override + public List getActiveAccounts() { + return null; + } + + @Override + public SortedSet getBillingEventsForSubscription( + UUID subscriptionId) { + return null; + } + + @Override + public void setChargedThroughDate(UUID subscriptionId, DateTime ctd) { + Subscription subscription = (Subscription) dao.getSubscriptionFromId(subscriptionId); + if (subscription == null) { + new EntitlementBillingApiException(String.format("Unknwon subscription %s", subscriptionId)); + } + + Subscription updatedSubscription = new SubscriptionBuilder() + .setId(subscription.getId()) + .setBundleId(subscription.getBundleId()) + .setStartDate(subscription.getStartDate()) + .setBundleStartDate(subscription.getBundleStartDate()) + .setChargedThroughDate(ctd) + .setPaidThroughDate(subscription.getPaidThroughDate()) + .setActiveVersion(subscription.getActiveVersion()) + .setCategory(subscription.getCategory()) + .build(); + + dao.updateSubscription(updatedSubscription); + } + +} diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/api/user/PrivateFields.java b/entitlement/src/main/java/com/ning/billing/entitlement/api/user/PrivateFields.java new file mode 100644 index 0000000000..ecd10ebf4f --- /dev/null +++ b/entitlement/src/main/java/com/ning/billing/entitlement/api/user/PrivateFields.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.user; + +public class PrivateFields implements IPrivateFields { + + public PrivateFields() { + } + + @Override + public void setPrivate(String name, String value) { + // TODO Auto-generated method stub + + } + + @Override + public String getPrivate(String name) { + // TODO Auto-generated method stub + return null; + } + +} diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/api/user/Subscription.java b/entitlement/src/main/java/com/ning/billing/entitlement/api/user/Subscription.java new file mode 100644 index 0000000000..9e7e15493c --- /dev/null +++ b/entitlement/src/main/java/com/ning/billing/entitlement/api/user/Subscription.java @@ -0,0 +1,521 @@ +/* + * Copyright 2010-2011 Ning, Inc. + * + * Ning licenses this file to you under the Apache License, version 2.0 + * (the "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF 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.lang.reflect.Field; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.UUID; + +import com.ning.billing.ErrorCode; +import com.ning.billing.account.api.IAccount; +import org.joda.time.DateTime; + +import com.ning.billing.catalog.api.ActionPolicy; +import com.ning.billing.catalog.api.BillingPeriod; +import com.ning.billing.catalog.api.ICatalog; +import com.ning.billing.catalog.api.IPlan; +import com.ning.billing.catalog.api.IPlanPhase; +import com.ning.billing.catalog.api.PlanPhaseSpecifier; +import com.ning.billing.catalog.api.ProductCategory; +import com.ning.billing.entitlement.alignment.EntitlementAlignment; +import com.ning.billing.entitlement.api.user.ISubscription.SubscriptionState; +import com.ning.billing.entitlement.engine.core.Engine; +import com.ning.billing.entitlement.engine.dao.IEntitlementDao; +import com.ning.billing.entitlement.events.IEvent; +import com.ning.billing.entitlement.events.IEvent.EventType; +import com.ning.billing.entitlement.events.phase.IPhaseEvent; +import com.ning.billing.entitlement.events.user.ApiEventCancel; +import com.ning.billing.entitlement.events.user.ApiEventChange; +import com.ning.billing.entitlement.events.user.ApiEventType; +import com.ning.billing.entitlement.events.user.ApiEventUncancel; +import com.ning.billing.entitlement.events.user.IUserEvent; +import com.ning.billing.entitlement.exceptions.EntitlementError; +import com.ning.billing.util.clock.IClock; + +public class Subscription extends PrivateFields implements ISubscription { + + private final UUID id; + private final UUID bundleId; + private final DateTime startDate; + private final DateTime bundleStartDate; + private final long activeVersion; + private final ProductCategory category; + + private final IClock clock; + private final IEntitlementDao dao; + private final ICatalog catalog; + + // STEPH interaction with billing /payment system + private final DateTime chargedThroughDate; + private final DateTime paidThroughDate; + + // STEPH non final because of change/ cancel API at the object level + private List transitions; + + + public static class SubscriptionBuilder { + private UUID id; + private UUID bundleId; + private DateTime startDate; + private DateTime bundleStartDate; + private Long activeVersion; + private ProductCategory category; + private DateTime chargedThroughDate; + private DateTime paidThroughDate; + + public SubscriptionBuilder setId(UUID id) { + this.id = id; + return this; + } + public SubscriptionBuilder setBundleId(UUID bundleId) { + this.bundleId = bundleId; + return this; + } + public SubscriptionBuilder setStartDate(DateTime startDate) { + this.startDate = startDate; + return this; + } + public SubscriptionBuilder setBundleStartDate(DateTime bundleStartDate) { + this.bundleStartDate = bundleStartDate; + return this; + } + public SubscriptionBuilder setActiveVersion(long activeVersion) { + this.activeVersion = activeVersion; + return this; + } + public SubscriptionBuilder setChargedThroughDate(DateTime chargedThroughDate) { + this.chargedThroughDate = chargedThroughDate; + return this; + } + public SubscriptionBuilder setPaidThroughDate(DateTime paidThroughDate) { + this.paidThroughDate = paidThroughDate; + return this; + } + public SubscriptionBuilder setCategory(ProductCategory category) { + this.category = category; + return this; + } + + private void checkAllFieldsSet() { + for (Field cur : SubscriptionBuilder.class.getDeclaredFields()) { + try { + Object value = cur.get(this); + if (value == null) { + throw new EntitlementError(String.format("Field %s has not been set for Subscription", + cur.getName())); + } + } catch (IllegalAccessException e) { + throw new EntitlementError(String.format("Failed to access value for field %s for Subscription", + cur.getName()), e); + } + } + } + + public Subscription build() { + //checkAllFieldsSet(); + return new Subscription(id, bundleId, category, bundleStartDate, startDate, chargedThroughDate, paidThroughDate, activeVersion); + } + + } + + public Subscription(UUID bundleId, ProductCategory category, DateTime bundleStartDate, DateTime startDate) { + this(UUID.randomUUID(), bundleId, category, bundleStartDate, startDate, null, null, SubscriptionEvents.INITIAL_VERSION); + } + + + public Subscription(UUID id, UUID bundleId, ProductCategory category, DateTime bundleStartDate, DateTime startDate, DateTime ctd, DateTime ptd, long activeVersion) { + + super(); + + Engine engine = Engine.getInstance(); + this.clock = engine.getClock(); + this.dao = engine.getDao(); + this.catalog = engine.getCatalog(); + + this.id = id; + this.bundleId = bundleId; + this.startDate = startDate; + this.bundleStartDate = bundleStartDate; + this.category = category; + + this.activeVersion = activeVersion; + + this.chargedThroughDate = ctd; + this.paidThroughDate = ptd; + + rebuildTransitions(); + } + + + @Override + public UUID getId() { + return id; + } + + @Override + public UUID getBundleId() { + return bundleId; + } + + + @Override + public DateTime getStartDate() { + return startDate; + } + + + @Override + public SubscriptionState getState() { + return (transitions == null) ? null : getLatestTranstion().getNextState(); + } + + @Override + public IPlanPhase getCurrentPhase() { + return (transitions == null) ? null : getLatestTranstion().getNextPhase(); + } + + + @Override + public IPlan getCurrentPlan() { + return (transitions == null) ? null : getLatestTranstion().getNextPlan(); + } + + @Override + public String getCurrentPriceList() { + return (transitions == null) ? null : getLatestTranstion().getNextPriceList(); + } + + + @Override + public IAccount getAccount() { + return null; + } + + @Override + public void cancel() throws EntitlementUserApiException { + + SubscriptionState currentState = getState(); + if (currentState != SubscriptionState.ACTIVE) { + throw new EntitlementUserApiException(ErrorCode.ENT_CANCEL_BAD_STATE, id, currentState); + } + + DateTime now = clock.getUTCNow(); + + PlanPhaseSpecifier planPhase = new PlanPhaseSpecifier(getCurrentPlan().getProduct().getName(), + getCurrentPlan().getBillingPeriod(), getCurrentPriceList(), getCurrentPhase().getPhaseType()); + ActionPolicy policy = catalog.getPlanCancelPolicy(planPhase); + DateTime effectiveDate = getPlanChangeEffectiveDate(policy, now); + IEvent cancelEvent = new ApiEventCancel(id, bundleStartDate, now, now, effectiveDate, activeVersion); + dao.cancelSubscription(id, cancelEvent); + rebuildTransitions(); + } + + @Override + public void uncancel() throws EntitlementUserApiException { + if (!isSubscriptionFutureCancelled()) { + throw new EntitlementUserApiException(ErrorCode.ENT_UNCANCEL_BAD_STATE, id.toString()); + } + DateTime now = clock.getUTCNow(); + IEvent uncancelEvent = new ApiEventUncancel(id, bundleStartDate, now, now, now, activeVersion); + List uncancelEvents = new ArrayList(); + uncancelEvents.add(uncancelEvent); + + // Recalculate Plan alignment for next phase event + EntitlementAlignment planPhaseAlignment = new EntitlementAlignment(id, now, bundleStartDate, getCurrentPlan(), + now, activeVersion); + IPhaseEvent nextPhaseEvent = planPhaseAlignment.getNextPhaseEvent(); + if (nextPhaseEvent != null) { + uncancelEvents.add(nextPhaseEvent); + } + dao.uncancelSubscription(id, uncancelEvents); + rebuildTransitions(); + } + + @Override + public void changePlan(String productName, BillingPeriod term, + String priceList) throws EntitlementUserApiException { + + String currentPriceList = getCurrentPriceList(); + String realPriceList = (priceList == null) ? currentPriceList : priceList; + + SubscriptionState currentState = getState(); + if (currentState != SubscriptionState.ACTIVE) { + throw new EntitlementUserApiException(ErrorCode.ENT_CHANGE_NON_ACTIVE, id, currentState); + } + + if (isSubscriptionFutureCancelled()) { + throw new EntitlementUserApiException(ErrorCode.ENT_CHANGE_FUTURE_CANCELLED, id); + } + + DateTime now = clock.getUTCNow(); + IPlan newPlan = catalog.getPlan(productName, term, realPriceList); + if (newPlan == null) { + throw new EntitlementUserApiException(ErrorCode.ENT_CREATE_BAD_CATALOG, + productName, term.toString(), realPriceList); + } + + PlanPhaseSpecifier fromPlanPhase = new PlanPhaseSpecifier(getCurrentPlan().getProduct().getName(), + getCurrentPlan().getBillingPeriod(), + currentPriceList, getCurrentPhase().getPhaseType()); + PlanPhaseSpecifier toPlanPhase = new PlanPhaseSpecifier(newPlan.getProduct().getName(), + newPlan.getBillingPeriod(), + realPriceList, null); + + ActionPolicy policy = catalog.getPlanChangePolicy(fromPlanPhase, toPlanPhase); + DateTime effectiveDate = getPlanChangeEffectiveDate(policy, now); + IEvent changeEvent = new ApiEventChange(id, bundleStartDate, now, newPlan, realPriceList, now, effectiveDate, activeVersion); + + EntitlementAlignment planPhaseAlignment = new EntitlementAlignment(id, now, bundleStartDate, newPlan, + effectiveDate, activeVersion); + IPhaseEvent nextPhaseEvent = planPhaseAlignment.getNextPhaseEvent(); + List changeEvents = new ArrayList(); + // Add phase event first so we expect to see PHASE event first-- mostly for test expectation + if (nextPhaseEvent != null) { + changeEvents.add(nextPhaseEvent); + } + changeEvents.add(changeEvent); + dao.changePlan(id, changeEvents); + rebuildTransitions(); + } + + @Override + public void pause() throws EntitlementUserApiException { + throw new EntitlementUserApiException(ErrorCode.NOT_IMPLEMENTED); + } + + @Override + public void resume() throws EntitlementUserApiException { + throw new EntitlementUserApiException(ErrorCode.NOT_IMPLEMENTED); + } + + + public ISubscriptionTransition getLatestTranstion() { + + if (transitions == null) { + return null; + } + ISubscriptionTransition latestSubscription = null; + for (ISubscriptionTransition cur : transitions) { + if (cur.getTransitionTime().isAfter(clock.getUTCNow())) { + break; + } + latestSubscription = cur; + } + return latestSubscription; + } + + public long getActiveVersion() { + return activeVersion; + } + + public ProductCategory getCategory() { + return category; + } + + public DateTime getBundleStartDate() { + return bundleStartDate; + } + + public DateTime getChargedThroughDate() { + return chargedThroughDate; + } + + public DateTime getPaidThroughDate() { + return paidThroughDate; + } + + public List getActiveTransitions() { + if (transitions == null) { + return null; + } + + List activeTransitions = new ArrayList(); + for (ISubscriptionTransition cur : transitions) { + if (cur.getTransitionTime().isAfter(clock.getUTCNow())) { + activeTransitions.add(cur); + } + } + return activeTransitions; + } + + private boolean isSubscriptionFutureCancelled() { + if (transitions == null) { + return false; + } + + for (SubscriptionTransition cur : transitions) { + if (cur.getTransitionTime().isBefore(clock.getUTCNow()) || + cur.getEventType() == EventType.PHASE || + cur.getApiEventType() != ApiEventType.CANCEL) { + continue; + } + return true; + } + return false; + } + + // STEPH do we need that? forgot? + private boolean isSubscriptionFutureChanged() { + if (transitions == null) { + return false; + } + + for (SubscriptionTransition cur : transitions) { + if (cur.getTransitionTime().isBefore(clock.getUTCNow()) || + cur.getEventType() == EventType.PHASE || + cur.getApiEventType() != ApiEventType.CHANGE) { + continue; + } + return true; + } + return false; + } + + private DateTime getPlanChangeEffectiveDate(ActionPolicy policy, DateTime now) { + + if (policy == ActionPolicy.IMMEDIATE) { + return now; + } + if (policy != ActionPolicy.END_OF_TERM) { + throw new EntitlementError(String.format("Unexpected policy type %s", policy.toString())); + } + + // + // If CTD is null or CTD in the past, we default to the start date of the current phase + // + DateTime effectiveDate = chargedThroughDate; + if (chargedThroughDate == null || chargedThroughDate.isBefore(clock.getUTCNow())) { + effectiveDate = getCurrentPhaseStart(); + } + return effectiveDate; + } + + private DateTime getCurrentPhaseStart() { + + if (transitions == null) { + throw new EntitlementError(String.format("No transitions for subscription %s", getId())); + } + + Iterator it = ((LinkedList) transitions).descendingIterator(); + while (it.hasNext()) { + SubscriptionTransition cur = it.next(); + if (cur.getTransitionTime().isAfter(clock.getUTCNow())) { + // Skip future events + continue; + } + if (cur.getEventType() == EventType.PHASE) { + return cur.getTransitionTime(); + } + } + // CREATE event + return transitions.get(0).getTransitionTime(); + } + + private void rebuildTransitions() { + + List events = dao.getEventsForSubscription(id); + if (events == null) { + return; + } + + SubscriptionState nextState = null; + String nextPlanName = null; + String nextPhaseName = null; + String nextPriceList = null; + + SubscriptionState previousState = null; + String previousPlanName = null; + String previousPhaseName = null; + String previousPriceList = null; + + this.transitions = new LinkedList(); + + for (final IEvent cur : events) { + + if (!cur.isActive() || cur.getActiveVersion() < activeVersion) { + continue; + } + + ApiEventType apiEventType = null; + + switch (cur.getType()) { + + case PHASE: + IPhaseEvent phaseEV = (IPhaseEvent) cur; + nextPhaseName = phaseEV.getPhase(); + break; + + case API_USER: + IUserEvent userEV = (IUserEvent) cur; + apiEventType = userEV.getEventType(); + switch(apiEventType) { + case CREATE: + nextState = SubscriptionState.ACTIVE; + nextPlanName = userEV.getEventPlan(); + nextPhaseName = userEV.getEventPlanPhase(); + nextPriceList = userEV.getPriceList(); + break; + case CHANGE: + nextPlanName = userEV.getEventPlan(); + nextPhaseName = userEV.getEventPlanPhase(); + nextPriceList = userEV.getPriceList(); + break; + case PAUSE: + nextState = SubscriptionState.PAUSED; + break; + case RESUME: + nextState = SubscriptionState.ACTIVE; + break; + case CANCEL: + nextState = SubscriptionState.CANCELLED; + nextPlanName = null; + nextPhaseName = null; + break; + case UNCANCEL: + break; + default: + 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())); + } + + IPlan previousPlan = catalog.getPlanFromName(previousPlanName); + IPlanPhase previousPhase = catalog.getPhaseFromName(previousPhaseName); + IPlan nextPlan = catalog.getPlanFromName(nextPlanName); + IPlanPhase nextPhase = catalog.getPhaseFromName(nextPhaseName); + + SubscriptionTransition transition = + new SubscriptionTransition(id, cur.getType(), apiEventType, cur.getEffectiveDate(), + previousState, previousPlan, previousPhase, previousPriceList, + nextState, nextPlan, nextPhase, nextPriceList); + transitions.add(transition); + + previousState = nextState; + previousPlanName = nextPlanName; + previousPhaseName = nextPhaseName; + previousPriceList = nextPriceList; + } + } +} diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/api/user/SubscriptionBundle.java b/entitlement/src/main/java/com/ning/billing/entitlement/api/user/SubscriptionBundle.java new file mode 100644 index 0000000000..b62de56a2b --- /dev/null +++ b/entitlement/src/main/java/com/ning/billing/entitlement/api/user/SubscriptionBundle.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.entitlement.api.user; + +import java.util.UUID; + +import org.joda.time.DateTime; + +import com.ning.billing.catalog.api.BillingPeriod; +import com.ning.billing.entitlement.events.IEvent; +import com.ning.billing.entitlement.events.user.ApiEventCreate; + +public class SubscriptionBundle extends PrivateFields implements ISubscriptionBundle { + + private final UUID id; + private final String name; + private final UUID accountId; + private final DateTime startDate; + + public SubscriptionBundle(String name, UUID accountId) { + this(UUID.randomUUID(), name, accountId, null); + } + + public SubscriptionBundle(UUID id, String name, UUID accountId, DateTime startDate) { + super(); + this.id = id; + this.name = name; + this.accountId = accountId; + this.startDate = startDate; + } + + @Override + public String getName() { + return name; + } + + @Override + public UUID getId() { + return id; + } + + @Override + public UUID getAccountId() { + return accountId; + } + + // STEPH do we need it ? and should we return that and when is that populated/updated? + @Override + public DateTime getStartDate() { + return startDate; + } +} diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/api/user/SubscriptionEvents.java b/entitlement/src/main/java/com/ning/billing/entitlement/api/user/SubscriptionEvents.java new file mode 100644 index 0000000000..02b7c74862 --- /dev/null +++ b/entitlement/src/main/java/com/ning/billing/entitlement/api/user/SubscriptionEvents.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.entitlement.api.user; + +import java.util.LinkedList; +import java.util.UUID; + +import com.ning.billing.entitlement.events.IEvent; + +public class SubscriptionEvents { + + public static final long INITIAL_VERSION = 1; + + private final UUID subscriptionId; + private final LinkedList events; + + private long activeVersion; + + public SubscriptionEvents(UUID subscriptionId) { + super(); + this.subscriptionId = subscriptionId; + this.events = new LinkedList(); + this.activeVersion = INITIAL_VERSION; + } + + public void addEvent(IEvent ev) { + events.add(ev); + } + + public LinkedList getCurrentView() { + return getViewForVersion(activeVersion); + } + + public LinkedList getViewForVersion(final long version) { + + LinkedList result = new LinkedList(); + for (IEvent cur : events) { + if (cur.getActiveVersion() == version) { + result.add(cur); + } + } + return result; + } + + + public long getActiveVersion() { + return activeVersion; + } + + public void setActiveVersion(long activeVersion) { + this.activeVersion = activeVersion; + } +} diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/api/user/SubscriptionTransition.java b/entitlement/src/main/java/com/ning/billing/entitlement/api/user/SubscriptionTransition.java new file mode 100644 index 0000000000..c71c65e01f --- /dev/null +++ b/entitlement/src/main/java/com/ning/billing/entitlement/api/user/SubscriptionTransition.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.entitlement.api.user; + +import java.util.UUID; + +import org.joda.time.DateTime; + +import com.ning.billing.catalog.api.IPlan; +import com.ning.billing.catalog.api.IPlanPhase; +import com.ning.billing.entitlement.api.user.ISubscription.SubscriptionState; +import com.ning.billing.entitlement.events.IEvent.EventType; +import com.ning.billing.entitlement.events.user.ApiEventType; + +public class SubscriptionTransition implements ISubscriptionTransition { + + + private final UUID subscriptionId; + private final EventType eventType; + private final ApiEventType apiEventType; + private final DateTime transitionTime; + private final SubscriptionState previousState; + private final String previousPriceList; + private final IPlan previousPlan; + private final IPlanPhase previousPhase; + private final SubscriptionState nextState; + private final String nextPriceList; + private final IPlan nextPlan; + private final IPlanPhase nextPhase; + + public SubscriptionTransition(UUID subscriptionId, EventType eventType, + ApiEventType apiEventType, DateTime transitionTime, + SubscriptionState previousState, IPlan previousPlan, IPlanPhase previousPhase, String previousPriceList, + SubscriptionState nextState, IPlan nextPlan, IPlanPhase nextPhase, String nextPriceList) { + super(); + this.subscriptionId = subscriptionId; + this.eventType = eventType; + this.apiEventType = apiEventType; + this.transitionTime = transitionTime; + this.previousState = previousState; + this.previousPriceList = previousPriceList; + this.previousPlan = previousPlan; + this.previousPhase = previousPhase; + this.nextState = nextState; + this.nextPlan = nextPlan; + this.nextPriceList = nextPriceList; + this.nextPhase = nextPhase; + } + + @Override + public UUID getSubscriptionId() { + return subscriptionId; + } + + @Override + public DateTime getTransitionTime() { + return transitionTime; + } + + @Override + public SubscriptionState getPreviousState() { + return previousState; + } + + @Override + public IPlan getPreviousPlan() { + return previousPlan; + } + + @Override + public IPlanPhase getPreviousPhase() { + return previousPhase; + } + + @Override + public IPlan getNextPlan() { + return nextPlan; + } + + @Override + public IPlanPhase getNextPhase() { + return nextPhase; + } + + @Override + public SubscriptionState getNextState() { + return nextState; + } + + + @Override + public String getPreviousPriceList() { + return previousPriceList; + } + + @Override + public String getNextPriceList() { + return nextPriceList; + } + + public ApiEventType getApiEventType() { + return apiEventType; + } + + public EventType getEventType() { + return eventType; + } + + @Override + public String toString() { + return "SubscriptionTransition [subscriptionId=" + subscriptionId + + ", eventType=" + eventType + ", apiEventType=" + + apiEventType + ", transitionTime=" + transitionTime + + ", previousState=" + previousState + ", previousPlan=" + + ((previousPlan != null) ? previousPlan.getName() : null) + + ", previousPhase=" + ((previousPhase != null) ? previousPhase.getName() : null) + + ", previousPriceList " + previousPriceList + + ", nextState=" + nextState + + ", nextPlan=" + ((nextPlan != null) ? nextPlan.getName() : null) + + ", nextPriceList " + nextPriceList + + ", nextPhase=" + ((nextPhase != null) ? nextPhase.getName() : null) + "]"; + } + +} diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/api/user/UserApi.java b/entitlement/src/main/java/com/ning/billing/entitlement/api/user/UserApi.java new file mode 100644 index 0000000000..a922486355 --- /dev/null +++ b/entitlement/src/main/java/com/ning/billing/entitlement/api/user/UserApi.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.entitlement.api.user; + +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.account.api.IAccount; +import com.ning.billing.catalog.api.BillingPeriod; +import com.ning.billing.catalog.api.ICatalog; +import com.ning.billing.catalog.api.IPlan; +import com.ning.billing.catalog.api.IPlanPhase; +import com.ning.billing.entitlement.alignment.EntitlementAlignment; +import com.ning.billing.entitlement.alignment.EntitlementAlignment.TimedPhase; +import com.ning.billing.entitlement.api.user.IApiListener; +import com.ning.billing.entitlement.api.user.ISubscription; +import com.ning.billing.entitlement.api.user.ISubscriptionBundle; +import com.ning.billing.entitlement.api.user.IUserApi; +import com.ning.billing.entitlement.engine.core.Engine; +import com.ning.billing.entitlement.engine.dao.IEntitlementDao; +import com.ning.billing.entitlement.events.IEvent; +import com.ning.billing.entitlement.events.phase.IPhaseEvent; +import com.ning.billing.entitlement.events.phase.PhaseEvent; +import com.ning.billing.entitlement.events.user.ApiEventCreate; +import com.ning.billing.entitlement.exceptions.EntitlementError; +import com.ning.billing.util.clock.IClock; + +public class UserApi implements IUserApi { + + private final Engine engine; + private final IClock clock; + private final IEntitlementDao dao; + private final ICatalog catalog; + + @Inject + public UserApi(Engine engine, IClock clock, IEntitlementDao dao) { + super(); + this.engine = engine; + this.clock = clock; + this.dao = dao; + this.catalog = engine.getCatalog(); + } + + @Override + public void initialize(List listeners) { + engine.registerApiObservers(listeners); + } + + @Override + public ISubscriptionBundle getBundleFromId(UUID id) { + return dao.getSubscriptionBundleFromId(id); + } + + @Override + public ISubscription getSubscriptionFromId(UUID id) { + return dao.getSubscriptionFromId(id); + } + + @Override + public List getBundlesForAccount(UUID accountId) { + return dao.getSubscriptionBundleForAccount(accountId); + } + + @Override + public List getSubscriptionsForBundle(UUID bundleId) { + return dao.getSubscriptions(bundleId); + } + + @Override + public ISubscriptionBundle createBundleForAccount(IAccount account, String bundleName) + throws EntitlementUserApiException { + SubscriptionBundle bundle = new SubscriptionBundle(bundleName, account.getId()); + return dao.createSubscriptionBundle(bundle); + } + + @Override + public ISubscription createSubscription(UUID bundleId, String productName, + BillingPeriod term, String priceList) throws EntitlementUserApiException { + + // STEPH Should really get 'standard' from catalog + String realPriceList = (priceList == null) ? "standard" : priceList; + + DateTime now = clock.getUTCNow(); + + IPlan plan = catalog.getPlan(productName, term, realPriceList); + if (plan == null) { + throw new EntitlementUserApiException(ErrorCode.ENT_CREATE_BAD_CATALOG, productName, term, realPriceList); + } + + IPlanPhase planPhase = (plan.getInitialPhases() != null) ? + plan.getInitialPhases()[0] : plan.getFinalPhase(); + if (planPhase == null) { + throw new EntitlementError(String.format("No initial PlanPhase for Product %s, term %s and set %s does not exist in the catalog", + productName, term.toString(), realPriceList)); + } + + ISubscriptionBundle bundle = dao.getSubscriptionBundleFromId(bundleId); + if (bundle == null) { + throw new EntitlementUserApiException(ErrorCode.ENT_CREATE_NO_BUNDLE, bundleId); + } + + DateTime bundleStartDate = null; + ISubscription baseSubscription = dao.getBaseSubscription(bundleId); + + switch(plan.getProduct().getCategory()) { + case BASE: + if (baseSubscription != null) { + throw new EntitlementUserApiException(ErrorCode.ENT_CREATE_BP_EXISTS, bundleId); + } + bundleStartDate = now; + break; + case ADD_ON: + if (baseSubscription == null) { + throw new EntitlementUserApiException(ErrorCode.ENT_CREATE_NO_BP, bundleId); + } + bundleStartDate = baseSubscription.getStartDate(); + break; + default: + throw new EntitlementError(String.format("Can't create subscription of type %s", + plan.getProduct().getCategory().toString())); + } + + Subscription subscription = new Subscription(bundleId, plan.getProduct().getCategory(), bundleStartDate, now); + ApiEventCreate creationEvent = + new ApiEventCreate(subscription.getId(), bundleStartDate, now, plan, realPriceList, + now, now, subscription.getActiveVersion()); + + EntitlementAlignment planPhaseAlignment = new EntitlementAlignment(subscription.getId(), now, + bundleStartDate, plan, now, subscription.getActiveVersion()); + IPhaseEvent nextPhaseEvent = planPhaseAlignment.getNextPhaseEvent(); + List events = new ArrayList(); + events.add(creationEvent); + if (nextPhaseEvent != null) { + events.add(nextPhaseEvent); + } + + // STEPH Also update startDate for bundle ? + return dao.createSubscription(subscription, events); + } + + +} diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/engine/core/ApiEventProcessor.java b/entitlement/src/main/java/com/ning/billing/entitlement/engine/core/ApiEventProcessor.java new file mode 100644 index 0000000000..fdbaefb908 --- /dev/null +++ b/entitlement/src/main/java/com/ning/billing/entitlement/engine/core/ApiEventProcessor.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.engine.core; + +import java.util.List; + +import com.google.inject.Inject; +import com.ning.billing.entitlement.engine.dao.IEntitlementDao; +import com.ning.billing.entitlement.events.IEvent; +import com.ning.billing.entitlement.glue.IEngineConfig; +import com.ning.billing.util.clock.IClock; + +public class ApiEventProcessor extends ApiEventProcessorBase { + + @Inject + public ApiEventProcessor(IClock clock, IEntitlementDao dao, IEngineConfig config) { + super(clock, dao, config); + } + + + @Override + protected void doProcessEvents(int sequenceId) { + long prev = nbProcessedEvents; + List claimedEvents = dao.getEventsReady(apiProcessorId, sequenceId); + if (claimedEvents.size() == 0) { + return; + } + log.debug(String.format("ApiEventProcessor got %d events", claimedEvents.size())); + + for (IEvent cur : claimedEvents) { + log.debug(String.format("ApiEventProcessor seq = %d got event %s", sequenceId, cur.getId())); + listener.processEventReady(cur); + nbProcessedEvents++; + } + log.debug(String.format("ApiEventProcessor processed %d events", nbProcessedEvents - prev)); + //log.debug(String.format("ApiEventProcessor seq = %d processed events %s", sequenceId, claimedEvents.get(0).getId())); + dao.clearEventsReady(apiProcessorId, claimedEvents); + log.debug(String.format("ApiEventProcessor cleared events %d", nbProcessedEvents - prev)); + //log.debug(String.format("ApiEventProcessor seq = %d cleared events %s", sequenceId, claimedEvents.get(0).getId())); + } + +} diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/engine/core/ApiEventProcessorBase.java b/entitlement/src/main/java/com/ning/billing/entitlement/engine/core/ApiEventProcessorBase.java new file mode 100644 index 0000000000..c198226051 --- /dev/null +++ b/entitlement/src/main/java/com/ning/billing/entitlement/engine/core/ApiEventProcessorBase.java @@ -0,0 +1,170 @@ +/* + * Copyright 2010-2011 Ning, Inc. + * + * Ning licenses this file to you under the Apache License, version 2.0 + * (the "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF 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.lang.Thread.UncaughtExceptionHandler; +import java.util.UUID; +import java.util.concurrent.Callable; +import java.util.concurrent.Executor; +import java.util.concurrent.Executors; +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.atomic.AtomicInteger; + +import org.skife.config.Config; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.inject.Inject; +import com.ning.billing.entitlement.engine.dao.IEntitlementDao; +import com.ning.billing.entitlement.events.IEvent; +import com.ning.billing.entitlement.glue.IEngineConfig; +import com.ning.billing.util.clock.IClock; + +public abstract class ApiEventProcessorBase implements IApiEventProcessor { + + // Wait for max 60 sec when shutting down the EventProcessor + private final long STOP_WAIT_TIMEOUT_MS = 60000; + + private static final AtomicInteger sequenceId = new AtomicInteger(); + + // STEPH will change at each restart. can we do better? + protected final UUID apiProcessorId; + + private static final String API_EVENT_THREAD_NAME = "ApiEventNotification"; + protected final static Logger log = LoggerFactory.getLogger(ApiEventProcessor.class); + + protected final IEntitlementDao dao; + protected final IClock clock; + + private Executor executor; + private final IEngineConfig config; + protected IEventListener listener; + + protected long nbProcessedEvents; + protected volatile boolean isProcessingEvents; + + @Inject + public ApiEventProcessorBase(IClock clock, IEntitlementDao dao, IEngineConfig config) { + this.clock = clock; + this.dao = dao; + this.config = config; + this.listener = null; + this.isProcessingEvents = false; + this.apiProcessorId = UUID.randomUUID(); + this.nbProcessedEvents = 0; + } + + + + @Override + public void startNotifications(IEventListener listener) { + + this.listener = listener; + this.isProcessingEvents = true; + this.nbProcessedEvents = 0; + + final ApiEventProcessorBase apiEventProcessor = this; + + synchronized (this) { + + if (executor != null) { + log.warn("There is already an executor thread running, return"); + return; + } + + this.executor = Executors.newSingleThreadExecutor(new ThreadFactory() { + + @Override + public Thread newThread(Runnable r) { + Thread th = new Thread(r); + th.setName(API_EVENT_THREAD_NAME); + //th.setDaemon(true); + th.setUncaughtExceptionHandler(new UncaughtExceptionHandler() { + @Override + public void uncaughtException(Thread t, Throwable e) { + log.error("Uncaught exception for thread " + t.getName(), e); + } + }); + return th; + } + }); + } + + executor.execute(new Runnable() { + @Override + public void run() { + + log.info(String.format("ApiEventProcessor thread %s [%d] started", API_EVENT_THREAD_NAME, + Thread.currentThread().getId())); + try { + while (true) { + synchronized (apiEventProcessor) { + if (!isProcessingEvents) { + log.info(String.format("ApiEventProcessor thread %s [%d] exiting...", API_EVENT_THREAD_NAME, + Thread.currentThread().getId())); + apiEventProcessor.notify(); + break; + } + } + + // Callback may trigger exceptions in user code so catch anything here and live with it. + try { + doProcessEvents(sequenceId.getAndIncrement()); + } catch (OutOfMemoryError e) { + log.warn("",e); + throw e; + } catch (Throwable e) { + log.error(API_EVENT_THREAD_NAME + " got an exception", e); + } + sleepALittle(); + } + log.info(String.format("ApiEventProcessor thread %s [%d] exited...", API_EVENT_THREAD_NAME, + Thread.currentThread().getId())); + } catch (Throwable e) { + log.error(API_EVENT_THREAD_NAME + " got an exception exiting...", e); + // STEPH let's review that later... + System.exit(1); + } + } + + private void sleepALittle() { + try { + Thread.sleep(config.getNotificationSleepTimeMs()); + } catch (Exception e) { + log.warn("Got interrupted exception when sleeeping between event notifications", e); + } + } + }); + } + + @Override + public void stopNotifications() { + synchronized(this) { + isProcessingEvents = false; + try { + log.info("ApiEventProcessor requested to stop"); + wait(STOP_WAIT_TIMEOUT_MS); + executor = null; + log.info("ApiEventProcessor requested should have exited"); + } catch (InterruptedException e) { + log.warn("Got interrupted exception when stopping notifications", e); + } + } + } + + protected abstract void doProcessEvents(int sequenceId); +} 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 new file mode 100644 index 0000000000..62bbae21f9 --- /dev/null +++ b/entitlement/src/main/java/com/ning/billing/entitlement/engine/core/Engine.java @@ -0,0 +1,166 @@ +/* + * Copyright 2010-2011 Ning, Inc. + * + * Ning licenses this file to you under the Apache License, version 2.0 + * (the "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF 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.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.catalog.api.ICatalog; +import com.ning.billing.catalog.api.ICatalogUserApi; +import com.ning.billing.catalog.api.IPlan; +import com.ning.billing.entitlement.alignment.EntitlementAlignment; +import com.ning.billing.entitlement.alignment.EntitlementAlignment.TimedPhase; +import com.ning.billing.entitlement.api.user.IApiListener; +import com.ning.billing.entitlement.api.user.ISubscription; +import com.ning.billing.entitlement.api.user.Subscription; +import com.ning.billing.entitlement.engine.dao.IEntitlementDao; +import com.ning.billing.entitlement.events.IEvent; +import com.ning.billing.entitlement.events.IEvent.EventType; +import com.ning.billing.entitlement.events.phase.IPhaseEvent; +import com.ning.billing.entitlement.events.phase.PhaseEvent; +import com.ning.billing.entitlement.events.user.ApiEventType; +import com.ning.billing.entitlement.events.user.IUserEvent; +import com.ning.billing.entitlement.glue.IEngineConfig; +import com.ning.billing.util.clock.IClock; + +public class Engine implements IEventListener { + + private final static Logger log = LoggerFactory.getLogger(Engine.class); + private static Engine instance = null; + + private final IClock clock; + private final ICatalog catalog; + private final IEntitlementDao dao; + private final IApiEventProcessor apiEventProcessor; + private final ICatalogUserApi catalogApi; + + private List observers; + + @Inject + public Engine(IClock clock, IEntitlementDao dao, IApiEventProcessor apiEventProcessor, ICatalogUserApi catalogApi, IEngineConfig config) { + super(); + this.clock = clock; + this.catalogApi = catalogApi; + this.dao = dao; + this.apiEventProcessor = apiEventProcessor; + this.catalog = readCatalogFromConfig(config.getCatalogConfigFileName()); + this.observers = null; + instance = this; + } + + public void start() { + apiEventProcessor.startNotifications(this); + } + + public void stop() { + apiEventProcessor.stopNotifications(); + } + + public void registerApiObservers(List observers) { + this.observers = observers; + } + + + @Override + public void processEventReady(IEvent event) { + if (observers == null) { + return; + } + Subscription subscription = (Subscription) dao.getSubscriptionFromId(event.getSubscriptionId()); + if (subscription == null) { + log.warn("Failed to retrieve subscription for id %s", event.getSubscriptionId()); + return; + } + if (event.getType() == EventType.API_USER) { + dispatchApiEvent((IUserEvent) event, subscription); + } else { + dispatchPhaseEvent((IPhaseEvent) event, subscription); + insertNextPhaseEvent(subscription); + } + } + + private void dispatchApiEvent(IUserEvent event, Subscription subscription) { + for (IApiListener listener : observers) { + switch(event.getEventType()) { + case CREATE: + listener.subscriptionCreated(subscription.getLatestTranstion()); + break; + case CHANGE: + listener.subscriptionChanged(subscription.getLatestTranstion()); + break; + case CANCEL: + listener.subscriptionCancelled(subscription.getLatestTranstion()); + break; + default: + break; + } + } + } + + private void dispatchPhaseEvent(IPhaseEvent event, Subscription subscription) { + for (IApiListener listener : observers) { + listener.subscriptionPhaseChanged(subscription.getLatestTranstion()); + } + } + + private void insertNextPhaseEvent(Subscription subscription) { + + DateTime now = clock.getUTCNow(); + + + EntitlementAlignment planPhaseAlignment = new EntitlementAlignment(subscription.getId(), + now, subscription.getBundleStartDate(), subscription.getCurrentPlan(), + subscription.getLatestTranstion().getTransitionTime(), subscription.getActiveVersion()); + IPhaseEvent nextPhaseEvent = planPhaseAlignment.getNextPhaseEvent(); + if (nextPhaseEvent != null) { + // STEPH Harden since event could be processed twice + dao.createNextPhaseEvent(subscription.getId(), nextPhaseEvent); + } + } + + + private ICatalog readCatalogFromConfig(String configFile) { + return catalogApi.getCatalog(configFile); + } + + // + // STEPH would be nice to have those go away.. + // + + // For non Guice classes + public synchronized static Engine getInstance() { + return instance; + } + + public IClock getClock() { + return clock; + } + + public ICatalog getCatalog() { + return catalog; + } + + public IEntitlementDao getDao() { + return dao; + } + +} diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/engine/core/IApiEventProcessor.java b/entitlement/src/main/java/com/ning/billing/entitlement/engine/core/IApiEventProcessor.java new file mode 100644 index 0000000000..2f433205f9 --- /dev/null +++ b/entitlement/src/main/java/com/ning/billing/entitlement/engine/core/IApiEventProcessor.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.entitlement.engine.core; + + +public interface IApiEventProcessor extends IEventNotifier { + +} diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/engine/core/IEventListener.java b/entitlement/src/main/java/com/ning/billing/entitlement/engine/core/IEventListener.java new file mode 100644 index 0000000000..f973279b41 --- /dev/null +++ b/entitlement/src/main/java/com/ning/billing/entitlement/engine/core/IEventListener.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.entitlement.engine.core; + +import com.ning.billing.entitlement.events.IEvent; + + + +public interface IEventListener { + public void processEventReady(IEvent event); +} diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/engine/core/IEventNotifier.java b/entitlement/src/main/java/com/ning/billing/entitlement/engine/core/IEventNotifier.java new file mode 100644 index 0000000000..c275cf9019 --- /dev/null +++ b/entitlement/src/main/java/com/ning/billing/entitlement/engine/core/IEventNotifier.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.entitlement.engine.core; + + +public interface IEventNotifier { + + public void startNotifications(IEventListener listener); + + public void stopNotifications(); +} 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 new file mode 100644 index 0000000000..65a7cdb747 --- /dev/null +++ b/entitlement/src/main/java/com/ning/billing/entitlement/engine/dao/EntitlementDao.java @@ -0,0 +1,316 @@ +/* + * Copyright 2010-2011 Ning, Inc. + * + * Ning licenses this file to you under the Apache License, version 2.0 + * (the "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF 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.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Date; +import java.util.LinkedList; +import java.util.List; +import java.util.UUID; + +import org.skife.jdbi.v2.DBI; +import org.skife.jdbi.v2.Transaction; +import org.skife.jdbi.v2.TransactionStatus; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.common.collect.Lists; +import com.google.inject.Inject; +import com.ning.billing.catalog.api.ProductCategory; +import com.ning.billing.entitlement.api.user.ISubscription; +import com.ning.billing.entitlement.api.user.ISubscriptionBundle; +import com.ning.billing.entitlement.api.user.Subscription; +import com.ning.billing.entitlement.api.user.SubscriptionBundle; +import com.ning.billing.entitlement.events.IEvent; +import com.ning.billing.entitlement.events.IEvent.EventType; +import com.ning.billing.entitlement.events.user.ApiEventType; +import com.ning.billing.entitlement.events.user.IUserEvent; +import com.ning.billing.entitlement.exceptions.EntitlementError; +import com.ning.billing.entitlement.glue.IEngineConfig; +import com.ning.billing.util.Hostname; +import com.ning.billing.util.clock.IClock; + +public class EntitlementDao implements IEntitlementDao { + + private final static Logger log = LoggerFactory.getLogger(EntitlementDao.class); + + private final IClock clock; + private final ISubscriptionSqlDao subscriptionsDao; + private final IBundleSqlDao bundlesDao; + private final IEventSqlDao eventsDao; + private final IEngineConfig config; + private final String hostname; + + @Inject + public EntitlementDao(DBI dbi, IClock clock, IEngineConfig config) { + this.clock = clock; + this.config = config; + this.subscriptionsDao = dbi.onDemand(ISubscriptionSqlDao.class); + this.eventsDao = dbi.onDemand(IEventSqlDao.class); + this.bundlesDao = dbi.onDemand(IBundleSqlDao.class); + this.hostname = Hostname.get(); + } + + @Override + public List getSubscriptionBundleForAccount( + UUID accountId) { + return bundlesDao.getBundleFromAccount(accountId.toString()); + } + + @Override + public ISubscriptionBundle getSubscriptionBundleFromId(UUID bundleId) { + return bundlesDao.getBundleFromId(bundleId.toString()); + } + + @Override + public ISubscriptionBundle createSubscriptionBundle(SubscriptionBundle bundle) { + bundlesDao.insertBundle(bundle); + return bundle; + } + + @Override + public ISubscription getSubscriptionFromId(UUID subscriptionId) { + return subscriptionsDao.getSubscriptionFromId(subscriptionId.toString()); + } + + @Override + public ISubscription getBaseSubscription(final UUID bundleId) { + + List subscriptions = subscriptionsDao.getSubscriptionsFromBundleId(bundleId.toString()); + for (ISubscription cur : subscriptions) { + if (((Subscription)cur).getCategory() == ProductCategory.BASE) { + return cur; + } + } + return null; + } + + @Override + public List getSubscriptions(UUID bundleId) { + return subscriptionsDao.getSubscriptionsFromBundleId(bundleId.toString()); + } + + @Override + public void updateSubscription(Subscription subscription) { + Date ctd = (subscription.getChargedThroughDate() != null) ? subscription.getChargedThroughDate().toDate() : null; + Date ptd = (subscription.getPaidThroughDate() != null) ? subscription.getPaidThroughDate().toDate() : null; + subscriptionsDao.updateSubscription(subscription.getActiveVersion(), ctd, ptd); + } + + @Override + public void createNextPhaseEvent(final UUID subscriptionId, final IEvent nextPhase) { + eventsDao.inTransaction(new Transaction() { + + @Override + public Void inTransaction(IEventSqlDao dao, + TransactionStatus status) throws Exception { + cancelNextPhaseEventFromTransaction(subscriptionId, dao); + dao.insertEvent(nextPhase); + return null; + } + }); + } + + + @Override + public List getEventsForSubscription(UUID subscriptionId) { + List events = eventsDao.getEventsForSubscription(subscriptionId.toString()); + return events; + } + + @Override + public List getPendingEventsForSubscription(UUID subscriptionId) { + Date now = clock.getUTCNow().toDate(); + List results = eventsDao.getFutureActiveEventForSubscription(subscriptionId.toString(), now); + return results; + } + + @Override + public List getEventsReady(final UUID ownerId, final int sequenceId) { + + final Date now = clock.getUTCNow().toDate(); + final Date nextAvailable = clock.getUTCNow().plus(config.getDaoClaimTimeMs()).toDate(); + + log.debug(String.format("EntitlementDao getEventsReady START effectiveNow = %s", now)); + + List events = eventsDao.inTransaction(new Transaction, IEventSqlDao>() { + + @Override + public List inTransaction(IEventSqlDao dao, + TransactionStatus status) throws Exception { + + List claimedEvents = new ArrayList(); + List input = dao.getReadyEvents(now, config.getDaoMaxReadyEvents()); + for (IEvent cur : input) { + final boolean claimed = (dao.claimEvent(ownerId.toString(), nextAvailable, cur.getId().toString(), now) == 1); + if (claimed) { + claimedEvents.add(cur); + dao.insertClaimedHistory(sequenceId, ownerId.toString(), hostname, now, cur.getId().toString()); + } + } + return claimedEvents; + } + }); + + for (IEvent cur : events) { + log.debug(String.format("EntitlementDao %s [host %s] claimed events %s", ownerId, hostname, cur.getId())); + if (cur.getOwner() != null && !cur.getOwner().equals(ownerId)) { + log.warn(String.format("EventProcessor %s stealing event %s from %s", ownerId, cur, cur.getOwner())); + } + } + return events; + } + + @Override + public void clearEventsReady(final UUID ownerId, final List cleared) { + + log.debug(String.format("EntitlementDao clearEventsReady START cleared size = %d", cleared.size())); + + eventsDao.inTransaction(new Transaction() { + + @Override + public Void inTransaction(IEventSqlDao dao, + TransactionStatus status) throws Exception { + // STEPH Same here batch would nice + for (IEvent cur : cleared) { + dao.clearEvent(cur.getId().toString(), ownerId.toString()); + log.debug(String.format("EntitlementDao %s [host %s] cleared events %s", ownerId, hostname, cur.getId())); + } + return null; + } + }); + } + + @Override + public ISubscription createSubscription(final Subscription subscription, + final List initialEvents) { + + subscriptionsDao.inTransaction(new Transaction() { + + @Override + public Void inTransaction(ISubscriptionSqlDao dao, + TransactionStatus status) throws Exception { + + dao.insertSubscription(subscription); + // STEPH batch as well + IEventSqlDao eventsDaoFromSameTranscation = dao.become(IEventSqlDao.class); + for (IEvent cur : initialEvents) { + eventsDaoFromSameTranscation.insertEvent(cur); + } + return null; + } + }); + return new Subscription(subscription.getId(), subscription.getBundleId(),subscription.getCategory(), subscription.getBundleStartDate(), + subscription.getStartDate(), subscription.getChargedThroughDate(), subscription.getPaidThroughDate(), subscription.getActiveVersion()); + } + + @Override + public void cancelSubscription(final UUID subscriptionId, final IEvent cancelEvent) { + + eventsDao.inTransaction(new Transaction() { + @Override + public Void inTransaction(IEventSqlDao dao, + TransactionStatus status) throws Exception { + cancelNextChangeEventFromTransaction(subscriptionId, dao); + cancelNextPhaseEventFromTransaction(subscriptionId, dao); + dao.insertEvent(cancelEvent); + return null; + } + }); + } + + @Override + public void uncancelSubscription(final UUID subscriptionId, final List uncancelEvents) { + + eventsDao.inTransaction(new Transaction() { + + @Override + public Void inTransaction(IEventSqlDao dao, + TransactionStatus status) throws Exception { + + UUID existingCancelId = null; + Date now = clock.getUTCNow().toDate(); + List events = dao.getFutureActiveEventForSubscription(subscriptionId.toString(), now); + + for (IEvent cur : events) { + if (cur.getType() == EventType.API_USER && ((IUserEvent) cur).getEventType() == ApiEventType.CANCEL) { + if (existingCancelId != null) { + throw new EntitlementError(String.format("Found multiple cancel active events for subscriptions %s", subscriptionId.toString())); + } + existingCancelId = cur.getId(); + } + } + + if (existingCancelId != null) { + dao.unactiveEvent(existingCancelId.toString(), now); + for (IEvent cur : uncancelEvents) { + dao.insertEvent(cur); + } + } + return null; + } + }); + } + + @Override + public void changePlan(final UUID subscriptionId, final List changeEvents) { + eventsDao.inTransaction(new Transaction() { + @Override + public Void inTransaction(IEventSqlDao dao, + TransactionStatus status) throws Exception { + cancelNextChangeEventFromTransaction(subscriptionId, dao); + cancelNextPhaseEventFromTransaction(subscriptionId, dao); + for (IEvent cur : changeEvents) { + dao.insertEvent(cur); + } + return null; + } + }); + } + + private void cancelNextPhaseEventFromTransaction(final UUID subscriptionId, final IEventSqlDao dao) { + cancelFutureEventFromTransaction(subscriptionId, dao, EventType.PHASE, null); + } + + private void cancelNextChangeEventFromTransaction(final UUID subscriptionId, final IEventSqlDao dao) { + cancelFutureEventFromTransaction(subscriptionId, dao, EventType.API_USER, ApiEventType.CHANGE); + } + + private void cancelFutureEventFromTransaction(final UUID subscriptionId, final IEventSqlDao dao, EventType type, ApiEventType apiType) { + + UUID futureEventId = null; + Date now = clock.getUTCNow().toDate(); + List events = dao.getFutureActiveEventForSubscription(subscriptionId.toString(), now); + for (IEvent cur : events) { + if (cur.getType() == type && + (apiType == null || apiType == ((IUserEvent) cur).getEventType() )) { + if (futureEventId != null) { + throw new EntitlementError( + String.format("Found multiple future events for type %s for subscriptions %s", + type, subscriptionId.toString())); + } + futureEventId = cur.getId(); + } + } + + if (futureEventId != null) { + dao.unactiveEvent(futureEventId.toString(), now); + } + } +} diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/engine/dao/IBundleSqlDao.java b/entitlement/src/main/java/com/ning/billing/entitlement/engine/dao/IBundleSqlDao.java new file mode 100644 index 0000000000..cd7a4b2aa5 --- /dev/null +++ b/entitlement/src/main/java/com/ning/billing/entitlement/engine/dao/IBundleSqlDao.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.entitlement.engine.dao; + +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Timestamp; +import java.util.Date; +import java.util.List; +import java.util.UUID; + +import org.joda.time.DateTime; +import org.joda.time.DateTimeZone; +import org.skife.jdbi.v2.SQLStatement; +import org.skife.jdbi.v2.StatementContext; +import org.skife.jdbi.v2.sqlobject.Bind; +import org.skife.jdbi.v2.sqlobject.Binder; +import org.skife.jdbi.v2.sqlobject.SqlQuery; +import org.skife.jdbi.v2.sqlobject.SqlUpdate; +import org.skife.jdbi.v2.sqlobject.customizers.Mapper; +import org.skife.jdbi.v2.sqlobject.mixins.CloseMe; +import org.skife.jdbi.v2.sqlobject.mixins.Transactional; +import org.skife.jdbi.v2.sqlobject.mixins.Transmogrifier; +import org.skife.jdbi.v2.tweak.ResultSetMapper; + +import com.ning.billing.entitlement.api.user.ISubscriptionBundle; +import com.ning.billing.entitlement.api.user.SubscriptionBundle; + + +public interface IBundleSqlDao extends Transactional, CloseMe, Transmogrifier { + + @SqlUpdate("insert into bundles (id, start_dt, name, account_id) values (:id, :start_dt, :name, :account_id)") + public void insertBundle(@Bind(binder = SubscriptionBundleBinder.class) SubscriptionBundle bundle); + + @SqlQuery("select id, start_dt, name, account_id from bundles where id = :id") + @Mapper(ISubscriptionBundleSqlMapper.class) + public ISubscriptionBundle getBundleFromId(@Bind("id") String id); + + @SqlQuery("select id, start_dt, name, account_id from bundles where account_id = :account_id") + @Mapper(ISubscriptionBundleSqlMapper.class) + public List getBundleFromAccount(@Bind("account_id") String accountId); + + public static class SubscriptionBundleBinder implements Binder { + + private Date getDate(DateTime dateTime) { + return dateTime == null ? null : dateTime.toDate(); + } + + @Override + public void bind(@SuppressWarnings("rawtypes") SQLStatement stmt, Bind bind, SubscriptionBundle bundle) { + stmt.bind("id", bundle.getId().toString()); + stmt.bind("start_dt", getDate(bundle.getStartDate())); + stmt.bind("name", bundle.getName()); + stmt.bind("account_id", bundle.getAccountId().toString()); + } + } + + public static class ISubscriptionBundleSqlMapper implements ResultSetMapper { + + private DateTime getDate(ResultSet r, String fieldName) throws SQLException { + final Timestamp resultStamp = r.getTimestamp(fieldName); + return r.wasNull() ? null : new DateTime(resultStamp).toDateTime(DateTimeZone.UTC); + } + + @Override + public ISubscriptionBundle map(int arg, ResultSet r, + StatementContext ctx) throws SQLException { + + UUID id = UUID.fromString(r.getString("id")); + String name = r.getString("name"); + UUID accountId = UUID.fromString(r.getString("account_id")); + DateTime startDate = getDate(r, "start_dt"); + SubscriptionBundle bundle = new SubscriptionBundle(id, name, accountId, startDate); + return bundle; + } + + } +} diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/engine/dao/IEntitlementDao.java b/entitlement/src/main/java/com/ning/billing/entitlement/engine/dao/IEntitlementDao.java new file mode 100644 index 0000000000..1f35fa07cb --- /dev/null +++ b/entitlement/src/main/java/com/ning/billing/entitlement/engine/dao/IEntitlementDao.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.entitlement.engine.dao; + +import java.util.List; +import java.util.UUID; + +import com.ning.billing.entitlement.api.user.ISubscription; +import com.ning.billing.entitlement.api.user.ISubscriptionBundle; +import com.ning.billing.entitlement.api.user.Subscription; +import com.ning.billing.entitlement.api.user.SubscriptionBundle; +import com.ning.billing.entitlement.events.IEvent; + +public interface IEntitlementDao { + + + // Bundle apis + public List getSubscriptionBundleForAccount(UUID accountId); + + public ISubscriptionBundle getSubscriptionBundleFromId(UUID bundleId); + + public ISubscriptionBundle createSubscriptionBundle(SubscriptionBundle bundle); + + public ISubscription getSubscriptionFromId(UUID subscriptionId); + + + // Subscription retrieval + public ISubscription getBaseSubscription(UUID bundleId); + + public List getSubscriptions(UUID bundleId); + + // Update + public void updateSubscription(Subscription subscription); + + // Event apis + public void createNextPhaseEvent(UUID subscriptionId, IEvent nextPhase); + + public List getEventsForSubscription(UUID subscriptionId); + + public List getPendingEventsForSubscription(UUID subscriptionId); + + public List getEventsReady(UUID ownerId, int sequenceId); + + public void clearEventsReady(UUID ownerId, List cleared); + + // Subscription creation, cancellation, changePlan apis + public ISubscription createSubscription(Subscription subscription, List initialEvents); + + public void cancelSubscription(UUID subscriptionId, IEvent cancelEvent); + + public void uncancelSubscription(UUID subscriptionId, List uncancelEvents); + + public void changePlan(UUID subscriptionId, List changeEvents); +} diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/engine/dao/IEventSqlDao.java b/entitlement/src/main/java/com/ning/billing/entitlement/engine/dao/IEventSqlDao.java new file mode 100644 index 0000000000..5a2ea5484b --- /dev/null +++ b/entitlement/src/main/java/com/ning/billing/entitlement/engine/dao/IEventSqlDao.java @@ -0,0 +1,180 @@ +/* + * Copyright 2010-2011 Ning, Inc. + * + * Ning licenses this file to you under the Apache License, version 2.0 + * (the "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF 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.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Timestamp; +import java.util.Date; +import java.util.List; +import java.util.UUID; + +import org.joda.time.DateTime; +import org.joda.time.DateTimeZone; +import org.skife.jdbi.v2.SQLStatement; +import org.skife.jdbi.v2.StatementContext; +import org.skife.jdbi.v2.sqlobject.Bind; +import org.skife.jdbi.v2.sqlobject.Binder; +import org.skife.jdbi.v2.sqlobject.SqlQuery; +import org.skife.jdbi.v2.sqlobject.SqlUpdate; +import org.skife.jdbi.v2.sqlobject.customizers.Mapper; +import org.skife.jdbi.v2.sqlobject.mixins.CloseMe; +import org.skife.jdbi.v2.sqlobject.mixins.Transactional; +import org.skife.jdbi.v2.sqlobject.mixins.Transmogrifier; +import org.skife.jdbi.v2.tweak.ResultSetMapper; + +import com.ning.billing.entitlement.events.IEvent; +import com.ning.billing.entitlement.events.IEvent.EventType; +import com.ning.billing.entitlement.events.IEventLyfecycle.IEventLyfecycleState; +import com.ning.billing.entitlement.events.phase.IPhaseEvent; +import com.ning.billing.entitlement.events.phase.PhaseEvent; +import com.ning.billing.entitlement.events.user.ApiEventCancel; +import com.ning.billing.entitlement.events.user.ApiEventChange; +import com.ning.billing.entitlement.events.user.ApiEventCreate; +import com.ning.billing.entitlement.events.user.ApiEventPause; +import com.ning.billing.entitlement.events.user.ApiEventResume; +import com.ning.billing.entitlement.events.user.ApiEventType; +import com.ning.billing.entitlement.events.user.ApiEventUncancel; +import com.ning.billing.entitlement.events.user.IUserEvent; +import com.ning.billing.entitlement.exceptions.EntitlementError; + +public interface IEventSqlDao extends Transactional, CloseMe, Transmogrifier { + + static final String EVENT_FIELDS = "event_id, event_type, user_type, created_dt, updated_dt, requested_dt, effective_dt, subscription_id, plan_name, phase_name, plist_name, current_version, is_active, processing_owner, processing_available_dt, processing_state"; + static final String EVENT_VALUES = ":event_id, :event_type, :user_type, :created_dt, :updated_dt, :requested_dt, :effective_dt, :subscription_id, :plan_name, :phase_name, :plist_name, :current_version, :is_active, :processing_owner, :processing_available_dt, :processing_state"; + static final String GET_READY_WHERE = "effective_dt <= :now and is_active = 1 and processing_state != 'PROCESSED' and (processing_owner IS NULL OR processing_available_dt <= :now)"; + static final String CLAIM_WHERE = "event_id = :event_id and is_active = 1 and processing_state != 'PROCESSED' and (processing_owner IS NULL OR processing_available_dt <= :now)"; + static final String EVENT_ORDER = " order by effective_dt asc, created_dt asc, requested_dt asc, id asc"; + + // + // APIs for event notifications + // + @SqlQuery("select " + EVENT_FIELDS + " from events where " + GET_READY_WHERE + EVENT_ORDER + " limit :max") + @Mapper(IEventSqlMapper.class) + public List getReadyEvents(@Bind("now") Date now, @Bind("max") int max); + + @SqlUpdate("update events set processing_owner = :owner, processing_available_dt = :next_available, processing_state = 'IN_PROCESSING' where " + CLAIM_WHERE) + public int claimEvent(@Bind("owner") String owner, @Bind("next_available") Date nextAvailable, @Bind("event_id") String eventId, @Bind("now") Date now); + + @SqlUpdate("update events set processing_owner = NULL, processing_state = 'PROCESSED' where event_id = :event_id and processing_owner = :owner") + public void clearEvent(@Bind("event_id") String eventId, @Bind("owner") String owner); + + + @SqlUpdate("insert into events (" + EVENT_FIELDS + ") values (" + EVENT_VALUES + ")") + public void insertEvent(@Bind(binder = IEventSqlDaoBinder.class) IEvent evt); + + @SqlUpdate("insert into claimed_events (sequence_id, owner_id, hostname, claimed_dt, event_id) values (:sequence_id, :owner_id, :hostname, :claimed_dt, :event_id)") + public void insertClaimedHistory(@Bind("sequence_id") int sequenceId, @Bind("owner_id") String ownerId, @Bind("hostname") String hostname, @Bind("claimed_dt") Date clainedDate, @Bind("event_id") String eventId); + + @SqlUpdate("update events set is_active = 0, updated_dt = :now where event_id = :event_id") + public void unactiveEvent(@Bind("event_id")String eventId, @Bind("now") Date now); + + @SqlUpdate("update events set is_active = 1, updated_dt = :now where event_id = :event_id") + public void reactiveEvent(@Bind("event_id")String eventId, @Bind("now") Date now); + + + @SqlQuery("select " + EVENT_FIELDS + " from events where subscription_id = :subscription_id and is_active = 1 and effective_dt > :now" + EVENT_ORDER) + @Mapper(IEventSqlMapper.class) + public List getFutureActiveEventForSubscription(@Bind("subscription_id") String subscriptionId, @Bind("now") Date now); + + @SqlQuery("select " + EVENT_FIELDS + " from events where subscription_id = :subscription_id" + EVENT_ORDER) + @Mapper(IEventSqlMapper.class) + public List getEventsForSubscription(@Bind("subscription_id") String subscriptionId); + + public static class IEventSqlDaoBinder implements Binder { + + private Date getDate(DateTime dateTime) { + return dateTime == null ? null : dateTime.toDate(); + } + + @Override + public void bind(@SuppressWarnings("rawtypes") SQLStatement stmt, Bind bind, IEvent evt) { + stmt.bind("event_id", evt.getId().toString()); + stmt.bind("event_type", evt.getType().toString()); + stmt.bind("user_type", (evt.getType() == EventType.API_USER) ? ((IUserEvent) evt).getEventType().toString() : null); + stmt.bind("created_dt", getDate(evt.getProcessedDate())); + stmt.bind("updated_dt", getDate(evt.getProcessedDate())); + stmt.bind("requested_dt", getDate(evt.getRequestedDate())); + stmt.bind("effective_dt", getDate(evt.getEffectiveDate())); + stmt.bind("subscription_id", evt.getSubscriptionId().toString()); + stmt.bind("plan_name", (evt.getType() == EventType.API_USER) ? ((IUserEvent) evt).getEventPlan() : null); + stmt.bind("phase_name", (evt.getType() == EventType.API_USER) ? ((IUserEvent) evt).getEventPlanPhase() : ((IPhaseEvent) evt).getPhase()); + stmt.bind("plist_name", (evt.getType() == EventType.API_USER) ? ((IUserEvent) evt).getPriceList() : null); + stmt.bind("current_version", evt.getActiveVersion()); + stmt.bind("is_active", evt.isActive()); + stmt.bind("processing_available_dt", getDate(evt.getNextAvailableDate())); + stmt.bind("processing_owner", (String) null); + stmt.bind("processing_state", IEventLyfecycleState.AVAILABLE.toString()); + } + } + + public static class IEventSqlMapper implements ResultSetMapper { + + private DateTime getDate(ResultSet r, String fieldName) throws SQLException { + final Timestamp resultStamp = r.getTimestamp(fieldName); + return r.wasNull() ? null : new DateTime(resultStamp).toDateTime(DateTimeZone.UTC); + } + + @Override + public IEvent map(int index, ResultSet r, StatementContext ctx) + throws SQLException { + + UUID id = UUID.fromString(r.getString("event_id")); + EventType eventType = EventType.valueOf(r.getString("event_type")); + ApiEventType userType = (eventType == EventType.API_USER) ? ApiEventType.valueOf(r.getString("user_type")) : null; + DateTime createdDate = getDate(r, "created_dt"); + DateTime requestedDate = getDate(r, "requested_dt"); + DateTime effectiveDate = getDate(r, "effective_dt"); + UUID subscriptionId = UUID.fromString(r.getString("subscription_id")); + String planName = r.getString("plan_name"); + String phaseName = r.getString("phase_name"); + String priceListName = r.getString("plist_name"); + long currentVersion = r.getLong("current_version"); + boolean isActive = r.getBoolean("is_active"); + DateTime nextAvailableDate = getDate(r, "processing_available_dt"); + UUID processingOwner = (r.getString("processing_owner") != null) ? UUID.fromString(r.getString("processing_owner")) : null; + IEventLyfecycleState processingState = IEventLyfecycleState.valueOf(r.getString("processing_state")); + + IEvent result = null; + if (eventType == EventType.PHASE) { + result = new PhaseEvent(id, subscriptionId, phaseName, requestedDate, effectiveDate, createdDate, + currentVersion, isActive, processingOwner, nextAvailableDate, processingState); + } else if (userType == ApiEventType.CREATE) { + result = new ApiEventCreate(id, subscriptionId, createdDate, planName, phaseName, priceListName, requestedDate, effectiveDate, + currentVersion, isActive, processingOwner, nextAvailableDate, processingState); + } else if (userType == ApiEventType.CHANGE) { + result = new ApiEventChange(id, subscriptionId, createdDate, planName, phaseName, priceListName, requestedDate, effectiveDate, + currentVersion, isActive, processingOwner, nextAvailableDate, processingState); + } else if (userType == ApiEventType.CANCEL) { + result = new ApiEventCancel(id, subscriptionId, createdDate, planName, phaseName, priceListName, requestedDate, effectiveDate, + currentVersion, isActive, processingOwner, nextAvailableDate, processingState); + } else if (userType == ApiEventType.PAUSE) { + result = new ApiEventPause(id, subscriptionId, createdDate, planName, phaseName, priceListName, requestedDate, effectiveDate, + currentVersion, isActive, processingOwner, nextAvailableDate, processingState); + } else if (userType == ApiEventType.RESUME) { + result = new ApiEventResume(id, subscriptionId, createdDate, planName, phaseName, priceListName, requestedDate, effectiveDate, + currentVersion, isActive, processingOwner, nextAvailableDate, processingState); + } else if (userType == ApiEventType.UNCANCEL) { + result = new ApiEventUncancel(id, subscriptionId, createdDate, planName, phaseName, priceListName, requestedDate, effectiveDate, + currentVersion, isActive, processingOwner, nextAvailableDate, processingState); + } else { + throw new EntitlementError(String.format("Can't deserialize event %s", eventType)); + } + return result; + } + } +} diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/engine/dao/ISubscriptionSqlDao.java b/entitlement/src/main/java/com/ning/billing/entitlement/engine/dao/ISubscriptionSqlDao.java new file mode 100644 index 0000000000..31820826d6 --- /dev/null +++ b/entitlement/src/main/java/com/ning/billing/entitlement/engine/dao/ISubscriptionSqlDao.java @@ -0,0 +1,109 @@ +/* + * Copyright 2010-2011 Ning, Inc. + * + * Ning licenses this file to you under the Apache License, version 2.0 + * (the "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF 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.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Timestamp; +import java.util.Date; +import java.util.List; +import java.util.UUID; + +import org.joda.time.DateTime; +import org.joda.time.DateTimeZone; +import org.skife.jdbi.v2.SQLStatement; +import org.skife.jdbi.v2.StatementContext; +import org.skife.jdbi.v2.sqlobject.Bind; +import org.skife.jdbi.v2.sqlobject.Binder; +import org.skife.jdbi.v2.sqlobject.SqlQuery; +import org.skife.jdbi.v2.sqlobject.SqlUpdate; +import org.skife.jdbi.v2.sqlobject.customizers.Mapper; +import org.skife.jdbi.v2.sqlobject.mixins.CloseMe; +import org.skife.jdbi.v2.sqlobject.mixins.Transactional; +import org.skife.jdbi.v2.sqlobject.mixins.Transmogrifier; +import org.skife.jdbi.v2.tweak.ResultSetMapper; + +import com.ning.billing.catalog.api.ProductCategory; +import com.ning.billing.entitlement.api.user.ISubscription; +import com.ning.billing.entitlement.api.user.Subscription; + +public interface ISubscriptionSqlDao extends Transactional, CloseMe, Transmogrifier { + + final static String SUBSCRIPTION_FIELDS = " id, bundle_id, category, start_dt, bundle_start_dt, active_version, ctd_dt, ptd_dt "; + final static String SUBSCRIPTION_VALUES = " :id, :bundle_id, :category, :start_dt, :bundle_start_dt, :active_version, :ctd_dt, :ptd_dt "; + + + @SqlUpdate("insert into subscriptions (" + SUBSCRIPTION_FIELDS +") value (" + SUBSCRIPTION_VALUES + ")") + public void insertSubscription(@Bind(binder = ISubscriptionDaoBinder.class) Subscription sub); + + @SqlQuery("select " + SUBSCRIPTION_FIELDS + " from subscriptions where id = :id") + @Mapper(ISubscriptionDaoSqlMapper.class) + public ISubscription getSubscriptionFromId(@Bind("id") String id); + + @SqlQuery("select " + SUBSCRIPTION_FIELDS + "from subscriptions where bundle_id = :bundle_id") + @Mapper(ISubscriptionDaoSqlMapper.class) + public List getSubscriptionsFromBundleId(@Bind("bundle_id") String bundleId); + + @SqlUpdate("update subscriptions set active_version = :active_version, ctd_dt = :ctd_dt, ptd_dt = :ptd_dt") + public void updateSubscription(@Bind("active_version") long activeVersion, @Bind("ctd_dt") Date ctd, @Bind("ptd_dt") Date ptd); + + public static class ISubscriptionDaoBinder implements Binder { + + private Date getDate(DateTime dateTime) { + return dateTime == null ? null : dateTime.toDate(); + } + + @Override + public void bind(@SuppressWarnings("rawtypes") SQLStatement stmt, Bind bind, Subscription sub) { + stmt.bind("id", sub.getId().toString()); + stmt.bind("bundle_id", 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.getPaidThroughDate())); + stmt.bind("ptd_dt", getDate(sub.getPaidThroughDate())); + } + } + + public static class ISubscriptionDaoSqlMapper implements ResultSetMapper { + + private DateTime getDate(ResultSet r, String fieldName) throws SQLException { + final Timestamp resultStamp = r.getTimestamp(fieldName); + return r.wasNull() ? null : new DateTime(resultStamp).toDateTime(DateTimeZone.UTC); + } + + @Override + public Subscription map(int arg0, ResultSet r, StatementContext ctx) + throws SQLException { + + 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"); + long activeVersion = r.getLong("active_version"); + + Subscription subscription = new Subscription(id, bundleId, category, bundleStartDate, startDate, ctd, ptd, activeVersion); + return subscription; + } + } + + +} 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 new file mode 100644 index 0000000000..a0c526a8d3 --- /dev/null +++ b/entitlement/src/main/java/com/ning/billing/entitlement/events/EventBase.java @@ -0,0 +1,235 @@ +/* + * Copyright 2010-2011 Ning, Inc. + * + * Ning licenses this file to you under the Apache License, version 2.0 + * (the "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF 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.events; + +import java.util.UUID; + +import org.joda.time.DateTime; + +import com.ning.billing.catalog.api.IPlan; +import com.ning.billing.entitlement.events.IEventLyfecycle.IEventLyfecycleState; +import com.ning.billing.entitlement.events.user.IUserEvent; +import com.ning.billing.entitlement.exceptions.EntitlementError; + +public abstract class EventBase implements IEvent { + + private final UUID uuid; + private final UUID subscriptionId; + private final DateTime requestedDate; + private final DateTime effectiveDate; + private final DateTime processedDate; + + // Lifecyle of the event + private long activeVersion; + private boolean isActive; + private UUID processingOwner; + private DateTime nextAvailableProcessingTime; + private IEventLyfecycleState processingState; + + public EventBase(UUID subscriptionId, DateTime requestedDate, + DateTime effectiveDate, DateTime processedDate, + long activeVersion, boolean isActive) { + this(subscriptionId, requestedDate, effectiveDate, processedDate, activeVersion, isActive, null, null, IEventLyfecycleState.AVAILABLE); + } + + private EventBase(UUID subscriptionId, DateTime requestedDate, + DateTime effectiveDate, DateTime processedDate, + long activeVersion, boolean isActive, + UUID processingOwner, DateTime nextAvailableProcessingTime, + IEventLyfecycleState processingState) { + this(UUID.randomUUID(), subscriptionId, requestedDate, effectiveDate, processedDate, activeVersion, isActive, + processingOwner, nextAvailableProcessingTime, processingState); + } + + public EventBase(UUID id, UUID subscriptionId, DateTime requestedDate, + DateTime effectiveDate, DateTime processedDate, + long activeVersion, boolean isActive, + UUID processingOwner, DateTime nextAvailableProcessingTime, + IEventLyfecycleState processingState) { + this.uuid = id; + this.subscriptionId = subscriptionId; + this.requestedDate = requestedDate; + this.effectiveDate = effectiveDate; + this.processedDate = processedDate; + + this.activeVersion = activeVersion; + this.isActive = isActive; + this.processingOwner = processingOwner; + this.nextAvailableProcessingTime = nextAvailableProcessingTime; + this.processingState = processingState; + + } + + + @Override + public DateTime getRequestedDate() { + return requestedDate; + } + + @Override + public DateTime getEffectiveDate() { + return effectiveDate; + } + + @Override + public DateTime getProcessedDate() { + return processedDate; + } + + @Override + public UUID getSubscriptionId() { + return subscriptionId; + } + + @Override + public UUID getId() { + return uuid; + } + + + @Override + public long getActiveVersion() { + return activeVersion; + } + + @Override + public void setActiveVersion(long activeVersion) { + this.activeVersion = activeVersion; + } + + @Override + public boolean isActive() { + return isActive; + } + + @Override + public void deactivate() { + this.isActive = false; + } + + @Override + public void reactivate() { + this.isActive = true; + } + + + @Override + public UUID getOwner() { + return processingOwner; + } + + @Override + public void setOwner(UUID owner) { + this.processingOwner = owner; + } + + @Override + public DateTime getNextAvailableDate() { + return nextAvailableProcessingTime; + } + + @Override + public void setNextAvailableDate(DateTime dateTime) { + this.nextAvailableProcessingTime = dateTime; + } + + + @Override + public IEventLyfecycleState getProcessingState() { + return processingState; + } + + @Override + public void setProcessingState(IEventLyfecycleState processingState) { + this.processingState = processingState; + } + + @Override + public boolean isAvailableForProcessing(DateTime now) { + + // Event got deactivated, will never be processed + if (!isActive) { + return false; + } + + switch(processingState) { + case AVAILABLE: + break; + case IN_PROCESSING: + // Somebody already got the event, not available yet + if (nextAvailableProcessingTime.isAfter(now)) { + return false; + } + break; + case PROCESSED: + return false; + default: + throw new EntitlementError(String.format("Unkwnon IEvent processing state %s", processingState)); + } + return effectiveDate.isBefore(now); + } + + // + // Really used for unit tesrs only as the sql implementation relies on date first and then event insertion + // + // Order first by: + // - effectiveDate, followed by processedDate, requestedDate + // - if all dates are equal-- unlikely, we first return PHASE EVENTS + // - If both events are User events, return the first CREATE, CHANGE,... as specified by ApiEventType + // - If all that is not enough return consistent by random ordering based on UUID + // + @Override + public int compareTo(IEvent other) { + if (other == null) { + throw new NullPointerException("IEvent is compared to a null instance"); + } + + if (effectiveDate.isBefore(other.getEffectiveDate())) { + return -1; + } else if (effectiveDate.isAfter(other.getEffectiveDate())) { + return 1; + } else if (processedDate.isBefore(other.getProcessedDate())) { + return -1; + } else if (processedDate.isAfter(other.getProcessedDate())) { + return 1; + } else if (requestedDate.isBefore(other.getRequestedDate())) { + return -1; + } else if (requestedDate.isAfter(other.getRequestedDate())) { + return 1; + } else if (getType() != other.getType()) { + return (getType() == EventType.PHASE) ? -1 : 1; + } else if (getType() == EventType.API_USER) { + return ((IUserEvent) this).getEventType().compareTo(((IUserEvent) other).getEventType()); + } else { + return uuid.compareTo(other.getId()); + } + } + + + @Override + public boolean equals(Object other) { + if (! (other instanceof IEvent)) { + return false; + } + return (this.compareTo((IEvent) other) == 0); + } + + @Override + public abstract EventType getType(); + + +} diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/events/IEvent.java b/entitlement/src/main/java/com/ning/billing/entitlement/events/IEvent.java new file mode 100644 index 0000000000..22a47c0e54 --- /dev/null +++ b/entitlement/src/main/java/com/ning/billing/entitlement/events/IEvent.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.events; + +import java.util.UUID; + +import org.joda.time.DateTime; + +import com.ning.billing.catalog.api.IPlan; + + +public interface IEvent extends IEventLyfecycle, Comparable { + + public enum EventType { + API_USER, + PHASE + } + + public EventType getType(); + + public UUID getId(); + + public DateTime getProcessedDate(); + + public DateTime getRequestedDate(); + + public DateTime getEffectiveDate(); + + public UUID getSubscriptionId(); + +} diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/events/IEventLyfecycle.java b/entitlement/src/main/java/com/ning/billing/entitlement/events/IEventLyfecycle.java new file mode 100644 index 0000000000..1ec248ee36 --- /dev/null +++ b/entitlement/src/main/java/com/ning/billing/entitlement/events/IEventLyfecycle.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.events; + +import java.util.UUID; + +import org.joda.time.DateTime; + +public interface IEventLyfecycle { + + public enum IEventLyfecycleState { + AVAILABLE, + IN_PROCESSING, + PROCESSED + } + + public long getActiveVersion(); + + public void setActiveVersion(long activeVersion); + + public boolean isActive(); + + public void deactivate(); + + public void reactivate(); + + public UUID getOwner(); + + public void setOwner(UUID owner); + + public DateTime getNextAvailableDate(); + + public void setNextAvailableDate(DateTime dateTime); + + public IEventLyfecycleState getProcessingState(); + + public void setProcessingState(IEventLyfecycleState procesingState); + + public boolean isAvailableForProcessing(DateTime now); +} diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/events/phase/IPhaseEvent.java b/entitlement/src/main/java/com/ning/billing/entitlement/events/phase/IPhaseEvent.java new file mode 100644 index 0000000000..456171c1ca --- /dev/null +++ b/entitlement/src/main/java/com/ning/billing/entitlement/events/phase/IPhaseEvent.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.entitlement.events.phase; + +import com.ning.billing.entitlement.events.IEvent; + +public interface IPhaseEvent extends IEvent { + + public String getPhase(); +} diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/events/phase/PhaseEvent.java b/entitlement/src/main/java/com/ning/billing/entitlement/events/phase/PhaseEvent.java new file mode 100644 index 0000000000..f57ac4a81e --- /dev/null +++ b/entitlement/src/main/java/com/ning/billing/entitlement/events/phase/PhaseEvent.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.entitlement.events.phase; + +import java.util.UUID; + +import org.joda.time.DateTime; + +import com.ning.billing.catalog.api.IPlan; +import com.ning.billing.catalog.api.IPlanPhase; +import com.ning.billing.entitlement.events.EventBase; +import com.ning.billing.entitlement.events.IEventLyfecycle.IEventLyfecycleState; + + +public class PhaseEvent extends EventBase implements IPhaseEvent { + + private final String phaseName; + + public PhaseEvent(UUID subscriptionId, IPlanPhase phase, DateTime requestedDate, + DateTime effectiveDate, DateTime processedDate, long activeVersion) { + super(subscriptionId, requestedDate, effectiveDate, processedDate, activeVersion, true); + this.phaseName = phase.getName(); + } + + + public PhaseEvent(UUID id, UUID subscriptionId, String phaseName, DateTime requestedDate, + DateTime effectiveDate, DateTime processedDate, + long activeVersion, boolean isActiveVersion, + UUID processingOwner, DateTime nextAvailableProcessingTime, + IEventLyfecycleState processingState) { + super(id, subscriptionId, requestedDate, effectiveDate, processedDate, activeVersion, isActiveVersion, processingOwner, nextAvailableProcessingTime, processingState); + this.phaseName = phaseName; + } + + + @Override + public EventType getType() { + return EventType.PHASE; + } + + @Override + public String getPhase() { + return phaseName; + } + + @Override + public String toString() { + return "PhaseEvent [getId()= " + getId() + + ", phaseName=" + phaseName + + ", getType()=" + getType() + + ", getPhase()=" + getPhase() + + ", getRequestedDate()=" + getRequestedDate() + + ", getEffectiveDate()=" + getEffectiveDate() + + ", getActiveVersion()=" + getActiveVersion() + + ", getProcessedDate()=" + getProcessedDate() + + ", getSubscriptionId()=" + getSubscriptionId() + + ", isActive()=" + isActive() + "]\n"; + } + +} 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 new file mode 100644 index 0000000000..6a06aa8ee8 --- /dev/null +++ b/entitlement/src/main/java/com/ning/billing/entitlement/events/user/ApiEventBase.java @@ -0,0 +1,111 @@ +/* + * Copyright 2010-2011 Ning, Inc. + * + * Ning licenses this file to you under the Apache License, version 2.0 + * (the "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF 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.events.user; + +import java.util.UUID; + +import org.joda.time.DateTime; + +import com.ning.billing.catalog.api.IPlan; +import com.ning.billing.entitlement.alignment.EntitlementAlignment; +import com.ning.billing.entitlement.events.EventBase; +import com.ning.billing.entitlement.events.IEventLyfecycle.IEventLyfecycleState; + +public class ApiEventBase extends EventBase implements IUserEvent { + + private final ApiEventType eventType; + // Only valid for CREATE/CHANGE + private final String eventPlan; + private final String eventPlanPhase; + private final String eventPriceList; + + + public ApiEventBase(UUID subscriptionId, DateTime bundleStartDate, DateTime processed, IPlan eventPlan, + 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 = (eventPlan != null) ? eventPlan.getName() : null; + this.eventPlanPhase = (eventPlan != null) ? getPhaseName(eventPlan, bundleStartDate, effectiveDate) : null; + } + + public ApiEventBase(UUID id, UUID subscriptionId, DateTime processed, String eventPlan, String eventPhase, + String priceList, DateTime requestedDate, ApiEventType eventType, DateTime effectiveDate, long activeVersion, + boolean isActive, UUID processingOwner, DateTime nextAvailableProcessingTime,IEventLyfecycleState processingState) { + super(id, subscriptionId, requestedDate, effectiveDate, processed, activeVersion, isActive, processingOwner, nextAvailableProcessingTime, processingState); + this.eventType = eventType; + this.eventPlan = eventPlan; + this.eventPlanPhase = eventPhase; + this.eventPriceList = priceList; + } + + + @Override + public ApiEventType getEventType() { + return eventType; + } + + @Override + public String getEventPlan() { + return eventPlan; + } + + @Override + public String getEventPlanPhase() { + return eventPlanPhase; + } + + @Override + public EventType getType() { + return EventType.API_USER; + } + + @Override + public String getPriceList() { + return eventPriceList; + } + + + @Override + public String toString() { + return "ApiEventBase [ getId()= " + getId() + + " eventType=" + eventType + + ", eventPlan=" + eventPlan + + ", eventPlanPhase=" + eventPlanPhase + + ", getEventType()=" + getEventType() + + ", getEventPlan()=" + getEventPlan() + + ", getEventPlanPhase()=" + getEventPlanPhase() + + ", getType()=" + getType() + + ", getRequestedDate()=" + getRequestedDate() + + ", getEffectiveDate()=" + getEffectiveDate() + + ", getActiveVersion()=" + getActiveVersion() + + ", getProcessedDate()=" + getProcessedDate() + + ", getSubscriptionId()=" + getSubscriptionId() + + ", isActive()=" + isActive() + "]"; + } + + private String getPhaseName(IPlan eventPlan, DateTime bundleStartDate, DateTime effectiveDate) { + + if (eventType != ApiEventType.CREATE && eventType != ApiEventType.CHANGE) { + return null; + } + EntitlementAlignment align = new EntitlementAlignment(bundleStartDate, eventPlan, effectiveDate); + return align.getCurrentTimedPhase().getPhase().getName(); + } + + +} diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/events/user/ApiEventCancel.java b/entitlement/src/main/java/com/ning/billing/entitlement/events/user/ApiEventCancel.java new file mode 100644 index 0000000000..1ea1b57acf --- /dev/null +++ b/entitlement/src/main/java/com/ning/billing/entitlement/events/user/ApiEventCancel.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.entitlement.events.user; + +import java.util.UUID; + +import org.joda.time.DateTime; + +import com.ning.billing.catalog.api.IPlan; +import com.ning.billing.entitlement.events.IEventLyfecycle.IEventLyfecycleState; + +public class ApiEventCancel extends ApiEventBase { + + public ApiEventCancel(UUID subscriptionId, DateTime bundleStartDate, DateTime now, DateTime requestedDate, DateTime effectiveDate, long version) { + super(subscriptionId, bundleStartDate, now, null, null, requestedDate, ApiEventType.CANCEL, effectiveDate, version); + } + + public ApiEventCancel(UUID id, UUID subscriptionId, DateTime processed, String eventPlan, String eventPhase, + String priceList, DateTime requestedDate, DateTime effectiveDate, long activeVersion, + boolean isActive, UUID processingOwner, DateTime nextAvailableProcessingTime,IEventLyfecycleState processingState) { + super(id, subscriptionId, processed, eventPlan, eventPhase, priceList, requestedDate, ApiEventType.CANCEL, effectiveDate, + activeVersion, isActive, processingOwner, nextAvailableProcessingTime, processingState); + } +} diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/events/user/ApiEventChange.java b/entitlement/src/main/java/com/ning/billing/entitlement/events/user/ApiEventChange.java new file mode 100644 index 0000000000..bdb0a3887f --- /dev/null +++ b/entitlement/src/main/java/com/ning/billing/entitlement/events/user/ApiEventChange.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.entitlement.events.user; + + +import java.util.UUID; + +import org.joda.time.DateTime; + +import com.ning.billing.catalog.api.IPlan; +import com.ning.billing.entitlement.events.IEventLyfecycle.IEventLyfecycleState; + + +public class ApiEventChange extends ApiEventBase { + + public ApiEventChange(UUID subscriptionId, DateTime bundleStartDate, DateTime now, IPlan eventPlan, String priceList, DateTime requestedDate, DateTime effectiveDate, long version) { + super(subscriptionId, bundleStartDate, now, eventPlan, priceList, requestedDate, ApiEventType.CHANGE, effectiveDate, version); + } + + public ApiEventChange(UUID id, UUID subscriptionId, DateTime processed, String eventPlan, String eventPhase, String priceList, + DateTime requestedDate, DateTime effectiveDate, long activeVersion, + boolean isActive, UUID processingOwner, DateTime nextAvailableProcessingTime,IEventLyfecycleState processingState) { + super(id, subscriptionId, processed, eventPlan, eventPhase, priceList, requestedDate, ApiEventType.CHANGE, effectiveDate, + activeVersion, isActive, processingOwner, nextAvailableProcessingTime, processingState); + } +} diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/events/user/ApiEventCreate.java b/entitlement/src/main/java/com/ning/billing/entitlement/events/user/ApiEventCreate.java new file mode 100644 index 0000000000..3b2588b220 --- /dev/null +++ b/entitlement/src/main/java/com/ning/billing/entitlement/events/user/ApiEventCreate.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.events.user; + + +import java.util.UUID; + +import org.joda.time.DateTime; +import com.ning.billing.catalog.api.IPlan; +import com.ning.billing.entitlement.events.IEventLyfecycle.IEventLyfecycleState; + +public class ApiEventCreate extends ApiEventBase { + + + public ApiEventCreate(UUID subscriptionId, DateTime bundleStartDate, DateTime now, IPlan eventPlan, String priceList, + DateTime requestedDate, DateTime effectiveDate, long version) { + super(subscriptionId, bundleStartDate, now, eventPlan, priceList, requestedDate, ApiEventType.CREATE, effectiveDate, version); + } + + + public ApiEventCreate(UUID id, UUID subscriptionId, DateTime processed, String eventPlan, String eventPhase, String priceList, + DateTime requestedDate, DateTime effectiveDate, long activeVersion, + boolean isActive, UUID processingOwner, DateTime nextAvailableProcessingTime,IEventLyfecycleState processingState) { + super(id, subscriptionId, processed, eventPlan, eventPhase, priceList, requestedDate, ApiEventType.CREATE, effectiveDate, + activeVersion, isActive, processingOwner, nextAvailableProcessingTime, processingState); + } + +} diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/events/user/ApiEventPause.java b/entitlement/src/main/java/com/ning/billing/entitlement/events/user/ApiEventPause.java new file mode 100644 index 0000000000..cd708042a4 --- /dev/null +++ b/entitlement/src/main/java/com/ning/billing/entitlement/events/user/ApiEventPause.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.entitlement.events.user; + + +import java.util.UUID; + +import org.joda.time.DateTime; + +import com.ning.billing.catalog.api.IPlan; +import com.ning.billing.entitlement.events.IEventLyfecycle.IEventLyfecycleState; + +public class ApiEventPause extends ApiEventBase { + + public ApiEventPause(UUID subscriptionId, DateTime now, DateTime requestedDate, DateTime effectiveDate, long version) { + super(subscriptionId, null, now, null, null, requestedDate, ApiEventType.PAUSE, effectiveDate, version); + } + + public ApiEventPause(UUID id, UUID subscriptionId, DateTime processed, String eventPlan, String eventPhase, + String priceList, DateTime requestedDate, DateTime effectiveDate, long activeVersion, + boolean isActive, UUID processingOwner, DateTime nextAvailableProcessingTime,IEventLyfecycleState processingState) { + super(id, subscriptionId, processed, eventPlan, eventPhase, priceList, requestedDate, ApiEventType.PAUSE, effectiveDate, + activeVersion, isActive, processingOwner, nextAvailableProcessingTime, processingState); + } +} diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/events/user/ApiEventResume.java b/entitlement/src/main/java/com/ning/billing/entitlement/events/user/ApiEventResume.java new file mode 100644 index 0000000000..05a2461528 --- /dev/null +++ b/entitlement/src/main/java/com/ning/billing/entitlement/events/user/ApiEventResume.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.events.user; + + +import java.util.UUID; + +import org.joda.time.DateTime; + +import com.ning.billing.catalog.api.IPlan; +import com.ning.billing.entitlement.events.IEventLyfecycle.IEventLyfecycleState; + + +public class ApiEventResume extends ApiEventBase { + + public ApiEventResume(UUID subscriptionId, DateTime now, DateTime requestedDate, DateTime effectiveDate, long version) { + super(subscriptionId, null, now, null, null, requestedDate, ApiEventType.RESUME, effectiveDate, version); + } + + public ApiEventResume(UUID id, UUID subscriptionId, DateTime processed, String eventPlan, String eventPhase, + String priceList, DateTime requestedDate, DateTime effectiveDate, long activeVersion, + boolean isActive, UUID processingOwner, DateTime nextAvailableProcessingTime,IEventLyfecycleState processingState) { + super(id, subscriptionId, processed, eventPlan, eventPhase, priceList, requestedDate, ApiEventType.RESUME, effectiveDate, + activeVersion, isActive, processingOwner, nextAvailableProcessingTime, processingState); + } + +} 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 new file mode 100644 index 0000000000..e07e39f8c0 --- /dev/null +++ b/entitlement/src/main/java/com/ning/billing/entitlement/events/user/ApiEventType.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.events.user; + + +public enum ApiEventType { + /* + * STEPH should be changed + * Ordering is important for unit tests today. + */ + CREATE, + CHANGE, + PAUSE, + RESUME, + CANCEL, + UNCANCEL +} diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/events/user/ApiEventUncancel.java b/entitlement/src/main/java/com/ning/billing/entitlement/events/user/ApiEventUncancel.java new file mode 100644 index 0000000000..a5c75a8288 --- /dev/null +++ b/entitlement/src/main/java/com/ning/billing/entitlement/events/user/ApiEventUncancel.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.events.user; + +import java.util.UUID; + +import org.joda.time.DateTime; + + +public class ApiEventUncancel extends ApiEventBase { + + public ApiEventUncancel(UUID subscriptionId, DateTime bundleStartDate, DateTime now, DateTime requestedDate, DateTime effectiveDate, long version) { + super(subscriptionId, bundleStartDate, now, null, null, requestedDate, ApiEventType.UNCANCEL, effectiveDate, version); + } + + public ApiEventUncancel(UUID id, UUID subscriptionId, DateTime processed, String eventPlan, String eventPhase, + String priceList, DateTime requestedDate, DateTime effectiveDate, long activeVersion, + boolean isActive, UUID processingOwner, DateTime nextAvailableProcessingTime,IEventLyfecycleState processingState) { + super(id, subscriptionId, processed, eventPlan, eventPhase, priceList, requestedDate, ApiEventType.UNCANCEL, effectiveDate, + activeVersion, isActive, processingOwner, nextAvailableProcessingTime, processingState); + } +} diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/events/user/IUserEvent.java b/entitlement/src/main/java/com/ning/billing/entitlement/events/user/IUserEvent.java new file mode 100644 index 0000000000..7c5fb56735 --- /dev/null +++ b/entitlement/src/main/java/com/ning/billing/entitlement/events/user/IUserEvent.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.entitlement.events.user; + +import com.ning.billing.entitlement.events.IEvent; + + +public interface IUserEvent extends IEvent { + + public String getEventPlan(); + + public String getEventPlanPhase(); + + public ApiEventType getEventType(); + + public String getPriceList(); + +} diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/exceptions/EntitlementError.java b/entitlement/src/main/java/com/ning/billing/entitlement/exceptions/EntitlementError.java new file mode 100644 index 0000000000..2cdba51234 --- /dev/null +++ b/entitlement/src/main/java/com/ning/billing/entitlement/exceptions/EntitlementError.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.entitlement.exceptions; + +public class EntitlementError extends Error { + + private static final long serialVersionUID = 131398536; + + public EntitlementError() { + super(); + } + + public EntitlementError(String msg, Throwable arg1) { + super(msg, arg1); + } + + public EntitlementError(String msg) { + super(msg); + } + + public EntitlementError(Throwable msg) { + super(msg); + } +} diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/glue/EngineModule.java b/entitlement/src/main/java/com/ning/billing/entitlement/glue/EngineModule.java new file mode 100644 index 0000000000..b98813d9e4 --- /dev/null +++ b/entitlement/src/main/java/com/ning/billing/entitlement/glue/EngineModule.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.entitlement.glue; + +import org.skife.config.ConfigurationObjectFactory; +import org.skife.jdbi.v2.DBI; + +import com.google.inject.AbstractModule; +import com.ning.billing.catalog.CatalogUserApi; +import com.ning.billing.catalog.api.ICatalogUserApi; +import com.ning.billing.dbi.DBIProvider; +import com.ning.billing.dbi.DbiConfig; +import com.ning.billing.entitlement.api.billing.BillingApi; +import com.ning.billing.entitlement.api.billing.IBillingApi; +import com.ning.billing.entitlement.api.user.IUserApi; +import com.ning.billing.entitlement.api.user.UserApi; +import com.ning.billing.entitlement.engine.core.ApiEventProcessor; +import com.ning.billing.entitlement.engine.core.Engine; +import com.ning.billing.entitlement.engine.core.IApiEventProcessor; +import com.ning.billing.entitlement.engine.dao.EntitlementDao; +import com.ning.billing.entitlement.engine.dao.IEntitlementDao; +import com.ning.billing.util.clock.Clock; +import com.ning.billing.util.clock.IClock; + + + +public class EngineModule extends AbstractModule { + + protected void installCatalog() { + bind(ICatalogUserApi.class).to(CatalogUserApi.class).asEagerSingleton(); + } + + protected void installAccount() { +// bind(IAccount.class).to(CatalogUserApi.class); + } + + protected void installClock() { + bind(IClock.class).to(Clock.class).asEagerSingleton(); + } + + protected void installConfig() { + final IEngineConfig config = new ConfigurationObjectFactory(System.getProperties()).build(IEngineConfig.class); + bind(IEngineConfig.class).toInstance(config); + } + + protected void installApiEventProcessor() { + bind(IApiEventProcessor.class).to(ApiEventProcessor.class).asEagerSingleton(); + } + + protected void installEntitlementDao() { + bind(IEntitlementDao.class).to(EntitlementDao.class).asEagerSingleton(); + } + + protected void installEngine() { + bind(Engine.class).asEagerSingleton(); + } + + protected void installUserApi() { + bind(IUserApi.class).to(UserApi.class).asEagerSingleton(); + } + + protected void installBillingApi() { + bind(IBillingApi.class).to(BillingApi.class).asEagerSingleton(); + } + + protected void installDBI() { + bind(DBI.class).toProvider(DBIProvider.class).asEagerSingleton(); + final DbiConfig config = new ConfigurationObjectFactory(System.getProperties()).build(DbiConfig.class); + bind(DbiConfig.class).toInstance(config); + } + + @Override + protected void configure() { + installConfig(); + installClock(); + installCatalog(); + installApiEventProcessor(); + installEntitlementDao(); + installEngine(); + installUserApi(); + installBillingApi(); + installDBI(); + } +} diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/glue/IEngineConfig.java b/entitlement/src/main/java/com/ning/billing/entitlement/glue/IEngineConfig.java new file mode 100644 index 0000000000..4cc0ffcb12 --- /dev/null +++ b/entitlement/src/main/java/com/ning/billing/entitlement/glue/IEngineConfig.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.entitlement.glue; + +import org.skife.config.Config; + +public interface IEngineConfig { + + @Config("killbill.entitlement.dao.claim.time") + public long getDaoClaimTimeMs(); + + @Config("killbill.entitlement.dao.ready.max") + public int getDaoMaxReadyEvents(); + + @Config("killbill.entitlement.catalog.config.file") + public String getCatalogConfigFileName(); + + @Config("killbill.entitlement.engine.notifications.sleep") + public long getNotificationSleepTimeMs(); +} diff --git a/entitlement/src/main/resources/ddl.sql b/entitlement/src/main/resources/ddl.sql new file mode 100644 index 0000000000..b26f4a2212 --- /dev/null +++ b/entitlement/src/main/resources/ddl.sql @@ -0,0 +1,59 @@ +CREATE DATABASE IF NOT EXISTS killbill; + +USE killbill; + +DROP TABLE IF EXISTS events; +CREATE TABLE events ( + id int(11) unsigned NOT NULL AUTO_INCREMENT, + event_id char(36) NOT NULL, + event_type varchar(9) NOT NULL, + user_type varchar(10) DEFAULT NULL, + created_dt datetime NOT NULL, + updated_dt datetime NOT NULL, + requested_dt datetime NOT NULL, + effective_dt 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, + current_version int(11) DEFAULT 1, + is_active bool DEFAULT 1, + processing_owner char(36) DEFAULT NULL, + processing_available_dt datetime DEFAULT NULL, + processing_state varchar(14) DEFAULT 'AVAILABLE', + PRIMARY KEY(id) +) ENGINE=innodb; + +DROP TABLE IF EXISTS claimed_events; +CREATE TABLE claimed_events ( + sequence_id int(11) unsigned NOT NULL, + owner_id char(36) NOT NULL, + hostname varchar(64) NOT NULL, + claimed_dt datetime NOT NULL, + event_id char(36) NOT NULL, + PRIMARY KEY(sequence_id) +) ENGINE=innodb; + + +DROP TABLE IF EXISTS subscriptions; +CREATE TABLE subscriptions ( + 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, + active_version int(11) DEFAULT 1, + ctd_dt datetime DEFAULT NULL, + ptd_dt datetime DEFAULT NULL, + PRIMARY KEY(id) +) ENGINE=innodb; + +DROP TABLE IF EXISTS bundles; +CREATE TABLE bundles ( + id char(36) NOT NULL, + start_dt datetime, /*NOT NULL*/ + name varchar(64) NOT NULL, + account_id char(36) NOT NULL, + PRIMARY KEY(id) +) ENGINE=innodb; + diff --git a/entitlement/src/test/java/com/ning/billing/entitlement/api/ApiTestListener.java b/entitlement/src/test/java/com/ning/billing/entitlement/api/ApiTestListener.java new file mode 100644 index 0000000000..6433911df9 --- /dev/null +++ b/entitlement/src/test/java/com/ning/billing/entitlement/api/ApiTestListener.java @@ -0,0 +1,143 @@ +/* + * Copyright 2010-2011 Ning, Inc. + * + * Ning licenses this file to you under the Apache License, version 2.0 + * (the "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF 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.EmptyStackException; +import java.util.Stack; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.ning.billing.entitlement.api.user.IApiListener; +import com.ning.billing.entitlement.api.user.ISubscriptionTransition; + +public class ApiTestListener implements IApiListener { + + private static final Logger log = LoggerFactory.getLogger(ApiTestListener.class); + + private final Stack nextExpectedEvent; + + private volatile boolean completed; + + public enum NextEvent { + CREATE, + CHANGE, + CANCEL, + PAUSE, + RESUME, + PHASE + } + + public ApiTestListener() { + this.nextExpectedEvent = new Stack(); + this.completed = false; + } + + public void pushExpectedEvent(NextEvent next) { + synchronized (this) { + nextExpectedEvent.push(next); + completed = false; + } + } + + public boolean isCompleted(long timeout) { + synchronized (this) { + try { + wait(timeout); + } catch (Exception ignore) { + } + } + if (!completed) { + log.warn("ApiTestListener did not complete in " + timeout + " ms"); + } + return completed; + } + + public void reset() { + nextExpectedEvent.clear(); + } + + private void notifyIfStackEmpty() { + log.info("notifyIfStackEmpty ENTER"); + synchronized (this) { + if (nextExpectedEvent.isEmpty()) { + log.info("notifyIfStackEmpty EMPTY"); + completed = true; + notify(); + } + } + log.info("notifyIfStackEmpty EXIT"); + } + + private void assertEqualsNicely(NextEvent expected, NextEvent real) { + if (expected != real) { + System.err.println("Expected event " + expected + " got " + real); + try { + NextEvent next = nextExpectedEvent.pop(); + while (next != null) { + System.err.println("Also got event " + next); + next = nextExpectedEvent.pop(); + } + } catch (EmptyStackException ignore) { + } + System.exit(1); + } + } + + @Override + public void subscriptionCreated(ISubscriptionTransition created) { + log.info("-> Got event CREATED"); + assertEqualsNicely(nextExpectedEvent.pop(), NextEvent.CREATE); + notifyIfStackEmpty(); + } + + @Override + public void subscriptionCancelled(ISubscriptionTransition cancelled) { + log.info("-> Got event CANCEL"); + assertEqualsNicely(nextExpectedEvent.pop(), NextEvent.CANCEL); + notifyIfStackEmpty(); + } + + @Override + public void subscriptionChanged(ISubscriptionTransition changed) { + log.info("-> Got event CHANGE"); + assertEqualsNicely(nextExpectedEvent.pop(), NextEvent.CHANGE); + notifyIfStackEmpty(); + } + + @Override + public void subscriptionPaused(ISubscriptionTransition paused) { + log.info("-> Got event PAUSE"); + assertEqualsNicely(nextExpectedEvent.pop(), NextEvent.PAUSE); + notifyIfStackEmpty(); + } + + @Override + public void subscriptionResumed(ISubscriptionTransition resumed) { + log.info("-> Got event RESUME"); + assertEqualsNicely(nextExpectedEvent.pop(), NextEvent.RESUME); + notifyIfStackEmpty(); + } + + @Override + public void subscriptionPhaseChanged( + ISubscriptionTransition phaseChanged) { + log.info("-> Got event PHASE"); + assertEqualsNicely(nextExpectedEvent.pop(), NextEvent.PHASE); + notifyIfStackEmpty(); + } +} diff --git a/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiBase.java b/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiBase.java new file mode 100644 index 0000000000..95a5b51849 --- /dev/null +++ b/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiBase.java @@ -0,0 +1,321 @@ +/* + * Copyright 2010-2011 Ning, Inc. + * + * Ning licenses this file to you under the Apache License, version 2.0 + * (the "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package com.ning.billing.entitlement.api.user; + +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertNotNull; +import static org.testng.Assert.assertTrue; +import static org.testng.Assert.assertFalse; + +import java.io.IOException; +import java.lang.reflect.Method; +import java.net.URL; +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 org.testng.Assert; +import org.testng.annotations.AfterMethod; +import org.testng.annotations.BeforeClass; +import org.testng.annotations.BeforeMethod; + +import com.google.inject.Injector; +import com.ning.billing.account.api.IAccount; +import com.ning.billing.catalog.api.BillingPeriod; +import com.ning.billing.catalog.api.Currency; +import com.ning.billing.catalog.api.ICatalog; +import com.ning.billing.catalog.api.ICatalogUserApi; +import com.ning.billing.catalog.api.IDuration; +import com.ning.billing.catalog.api.TimeUnit; +import com.ning.billing.entitlement.api.ApiTestListener; +import com.ning.billing.entitlement.api.ApiTestListener.NextEvent; +import com.ning.billing.entitlement.api.billing.IBillingApi; +import com.ning.billing.entitlement.engine.core.Engine; +import com.ning.billing.entitlement.engine.dao.IEntitlementDao; +import com.ning.billing.entitlement.engine.dao.IEntitlementDaoMock; +import com.ning.billing.entitlement.events.IEvent; +import com.ning.billing.entitlement.events.phase.IPhaseEvent; +import com.ning.billing.entitlement.events.user.ApiEventType; +import com.ning.billing.entitlement.events.user.IUserEvent; +import com.ning.billing.entitlement.glue.IEngineConfig; +import com.ning.billing.util.clock.ClockMock; +import com.ning.billing.util.clock.IClock; + +public abstract class TestUserApiBase { + + protected static final Logger log = LoggerFactory.getLogger(TestUserApiBase.class); + + protected static final long DAY_IN_MS = (24 * 3600 * 1000); + + protected Engine engine; + protected IUserApi entitlementApi; + protected IBillingApi billingApi; + protected ICatalogUserApi catalogApi; + protected IEngineConfig config; + protected IEntitlementDao dao; + protected ClockMock clock; + + protected IAccount account; + protected ICatalog catalog; + protected ApiTestListener testListener; + protected ISubscriptionBundle bundle; + + + public static void loadSystemPropertiesFromClasspath( final String resource ) + { + final URL url = TestUserApiBase.class.getResource(resource); + assertNotNull(url); + + try { + System.getProperties().load( url.openStream() ); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + + @BeforeClass(groups={"setup"}) + public void setup() { + + // Does not see to work ... + /* + TimeZone tz = TimeZone.getDefault(); + TimeZone.setDefault(TimeZone.getTimeZone("Etc/UTC")); + tz = TimeZone.getDefault(); + */ + + loadSystemPropertiesFromClasspath("/entitlement.properties"); + final Injector g = getInjector(); + + engine = g.getInstance(Engine.class); + entitlementApi = g.getInstance(IUserApi.class); + catalogApi = g.getInstance(ICatalogUserApi.class); + billingApi = g.getInstance(IBillingApi.class); + config = g.getInstance(IEngineConfig.class); + dao = g.getInstance(IEntitlementDao.class); + clock = (ClockMock) g.getInstance(IClock.class); + try { + init(); + } catch (EntitlementUserApiException e) { + Assert.fail(e.getMessage()); + } + } + + protected abstract Injector getInjector(); + + private void init() throws EntitlementUserApiException { + account = getAccount(); + assertNotNull(account); + + catalog = catalogApi.getCatalog(config.getCatalogConfigFileName()); + assertNotNull(catalog); + + testListener = new ApiTestListener(); + List listeners = new ArrayList(); + listeners.add(testListener); + entitlementApi.initialize(listeners); + + } + + @BeforeMethod(groups={"setup"}) + public void setupTest() { + log.warn("\n"); + log.warn("RESET TEST FRAMEWORK\n\n"); + + testListener.reset(); + clock.resetDeltaFromReality(); + ((IEntitlementDaoMock) dao).reset(); + try { + bundle = entitlementApi.createBundleForAccount(account, "myDefaultBundle"); + } catch (EntitlementUserApiException e) { + Assert.fail(e.getMessage()); + } + assertNotNull(bundle); + engine.start(); + } + + @AfterMethod(groups={"setup"}) + public void cleanupTest() { + engine.stop(); + log.warn("DONE WITH TEST\n"); + } + + // Glue magic to invoke the real test + protected void invokeRealMethod(Object invoker) { + + try { + String methodName = Thread.currentThread().getStackTrace()[2].getMethodName(); + String realMethodName= methodName + "Real"; + + Class thisClass = invoker.getClass(); + Class superClass = thisClass.getSuperclass(); + Method [] methods = superClass.getDeclaredMethods(); + for (Method cur : methods) { + if (cur.getName().equals(realMethodName)) { + cur.invoke(invoker); + return; + } + } + } catch (Exception e) { + e.printStackTrace(); + Assert.fail(e.getMessage()); + } + } + + protected Subscription createSubscription(String productName, BillingPeriod term, String planSet) throws EntitlementUserApiException { + testListener.pushExpectedEvent(NextEvent.CREATE); + Subscription subscription = (Subscription) entitlementApi.createSubscription(bundle.getId(), productName, term, planSet); + assertNotNull(subscription); + assertTrue(testListener.isCompleted(3000)); + return subscription; + } + + protected void checkNextPhaseChange(Subscription subscription, int expPendingEvents, DateTime expPhaseChange) { + + List events = dao.getPendingEventsForSubscription(subscription.getId()); + assertNotNull(events); + printEvents(events); + assertEquals(events.size(), expPendingEvents); + if (events.size() > 0 && expPhaseChange != null) { + boolean foundPhase = false; + boolean foundChange = false; + + for (IEvent cur : events) { + if (cur instanceof IPhaseEvent) { + assertEquals(foundPhase, false); + foundPhase = true; + assertEquals(cur.getEffectiveDate(), expPhaseChange); + } else if (cur instanceof IUserEvent) { + IUserEvent uEvent = (IUserEvent) cur; + assertEquals(ApiEventType.CHANGE, uEvent.getEventType()); + assertEquals(foundChange, false); + foundChange = true; + } else { + assertFalse(true); + } + } + } + } + + + protected void assertDateWithin(DateTime in, DateTime lower, DateTime upper) { + assertTrue(in.isEqual(lower) || in.isAfter(lower)); + assertTrue(in.isEqual(upper) || in.isBefore(upper)); + } + + protected IDuration getDurationDay(final int days) { + IDuration result = new IDuration() { + @Override + public TimeUnit getUnit() { + return TimeUnit.DAYS; + } + @Override + public int getLength() { + return days; + } + }; + return result; + } + + protected IDuration getDurationMonth(final int months) { + IDuration result = new IDuration() { + @Override + public TimeUnit getUnit() { + return TimeUnit.MONTHS; + } + @Override + public int getLength() { + return months; + } + }; + return result; + } + + + protected IDuration getDurationYear(final int years) { + IDuration result = new IDuration() { + @Override + public TimeUnit getUnit() { + return TimeUnit.YEARS; + } + @Override + public int getLength() { + return years; + } + }; + return result; + } + + protected IAccount getAccount() { + IAccount account = new IAccount() { + @Override + public String getName() { + return "accountName"; + } + @Override + public String getEmail() { + return "accountName@yahoo.com"; + } + @Override + public String getPhone() { + return "4152876341"; + } + @Override + public String getKey() { + return "k123456"; + } + @Override + public int getBillCycleDay() { + return 1; + } + @Override + public Currency getCurrency() { + return Currency.USD; + } + @Override + public void setPrivate(String name, String value) { + } + @Override + public String getPrivate(String name) { + return null; + } + @Override + public UUID getId() { + return UUID.randomUUID(); + } + }; + return account; + } + + + protected void printEvents(List events) { + for (IEvent cur : events) { + log.debug("Inspect event " + cur); + } + } + + protected void printSubscriptionTransitions(List transitions) { + for (ISubscriptionTransition cur : transitions) { + log.debug("Transition " + cur); + } + } + +} 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 new file mode 100644 index 0000000000..38c55f2344 --- /dev/null +++ b/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiCancel.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.entitlement.api.user; + +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertNotNull; +import static org.testng.Assert.assertNull; +import static org.testng.Assert.assertTrue; +import static org.testng.Assert.assertFalse; + +import java.util.List; + +import org.joda.time.DateTime; +import org.testng.Assert; +import org.testng.annotations.AfterMethod; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +import com.ning.billing.catalog.api.BillingPeriod; +import com.ning.billing.catalog.api.IDuration; +import com.ning.billing.catalog.api.IPlan; +import com.ning.billing.catalog.api.IPlanPhase; +import com.ning.billing.catalog.api.PhaseType; +import com.ning.billing.catalog.api.TimeUnit; +import com.ning.billing.entitlement.api.ApiTestListener.NextEvent; +import com.ning.billing.util.clock.Clock; + +public abstract class TestUserApiCancel extends TestUserApiBase { + + protected void testCancelSubscriptionIMMReal() { + + log.info("Starting testChangeSubscriptionEOTWithNoChargeThroughDate"); + + try { + + DateTime init = clock.getUTCNow(); + + String prod = "Shotgun"; + BillingPeriod term = BillingPeriod.MONTHLY; + String planSet = "standard"; + + // CREATE + Subscription subscription = createSubscription(prod, term, planSet); + IPlanPhase currentPhase = subscription.getCurrentPhase(); + assertEquals(currentPhase.getPhaseType(), PhaseType.TRIAL); + + // ADVANCE TIME still in trial + IDuration moveALittleInTime = getDurationDay(3); + clock.setDeltaFromReality(moveALittleInTime, 0); + + DateTime future = clock.getUTCNow(); + testListener.pushExpectedEvent(NextEvent.CANCEL); + + // CANCEL in trial period to get IMM policy + subscription.cancel(); + currentPhase = subscription.getCurrentPhase(); + + testListener.isCompleted(1000); + + List allTransitions = subscription.getActiveTransitions(); + printSubscriptionTransitions(allTransitions); + + assertNull(currentPhase); + checkNextPhaseChange(subscription, 0, null); + + } catch (EntitlementUserApiException e) { + Assert.fail(e.getMessage()); + } + } + + + protected void testCancelSubscriptionEOTWithChargeThroughDateReal() { + log.info("Starting testCancelSubscriptionEOTWithChargeThroughDate"); + + try { + + String prod = "Shotgun"; + BillingPeriod term = BillingPeriod.MONTHLY; + String planSet = "standard"; + + // CREATE + Subscription subscription = createSubscription(prod, term, planSet); + IPlanPhase trialPhase = subscription.getCurrentPhase(); + assertEquals(trialPhase.getPhaseType(), PhaseType.TRIAL); + + // NEXT PHASE + DateTime expectedPhaseTrialChange = Clock.addDuration(subscription.getStartDate(), trialPhase.getDuration()); + checkNextPhaseChange(subscription, 1, expectedPhaseTrialChange); + + // MOVE TO NEXT PHASE + testListener.pushExpectedEvent(NextEvent.PHASE); + clock.setDeltaFromReality(trialPhase.getDuration(), DAY_IN_MS); + assertTrue(testListener.isCompleted(2000)); + trialPhase = subscription.getCurrentPhase(); + assertEquals(trialPhase.getPhaseType(), PhaseType.EVERGREEN); + + // SET CTD + RE READ SUBSCRIPTION + CHANGE PLAN + IDuration ctd = getDurationMonth(1); + DateTime newChargedThroughDate = Clock.addDuration(expectedPhaseTrialChange, ctd); + billingApi.setChargedThroughDate(subscription.getId(), newChargedThroughDate); + subscription = (Subscription) entitlementApi.getSubscriptionFromId(subscription.getId()); + + testListener.pushExpectedEvent(NextEvent.CANCEL); + + // CANCEL + subscription.cancel(); + assertFalse(testListener.isCompleted(2000)); + + // MOVE TO EOT + RECHECK + clock.addDeltaFromReality(ctd); + DateTime future = clock.getUTCNow(); + assertTrue(testListener.isCompleted(2000)); + + IPlanPhase currentPhase = subscription.getCurrentPhase(); + assertNull(currentPhase); + checkNextPhaseChange(subscription, 0, null); + + } catch (EntitlementUserApiException e) { + Assert.fail(e.getMessage()); + } + } + + + protected void testCancelSubscriptionEOTWithNoChargeThroughDateReal() { + + log.info("Starting testCancelSubscriptionEOTWithNoChargeThroughDate"); + + try { + + String prod = "Shotgun"; + BillingPeriod term = BillingPeriod.MONTHLY; + String planSet = "standard"; + + // CREATE + Subscription subscription = createSubscription(prod, term, planSet); + IPlanPhase trialPhase = subscription.getCurrentPhase(); + assertEquals(trialPhase.getPhaseType(), PhaseType.TRIAL); + + // NEXT PHASE + DateTime expectedPhaseTrialChange = Clock.addDuration(subscription.getStartDate(), trialPhase.getDuration()); + checkNextPhaseChange(subscription, 1, expectedPhaseTrialChange); + + // MOVE TO NEXT PHASE + testListener.pushExpectedEvent(NextEvent.PHASE); + clock.setDeltaFromReality(trialPhase.getDuration(), DAY_IN_MS); + assertTrue(testListener.isCompleted(2000)); + trialPhase = subscription.getCurrentPhase(); + assertEquals(trialPhase.getPhaseType(), PhaseType.EVERGREEN); + + testListener.pushExpectedEvent(NextEvent.CANCEL); + + // CANCEL + subscription.cancel(); + assertTrue(testListener.isCompleted(2000)); + + IPlanPhase currentPhase = subscription.getCurrentPhase(); + assertNull(currentPhase); + checkNextPhaseChange(subscription, 0, null); + + } catch (EntitlementUserApiException e) { + Assert.fail(e.getMessage()); + } + } + + // Similar test to testCancelSubscriptionEOTWithChargeThroughDate except we uncancel and check things + // are as they used to be and we can move forward without hitting cancellation + // + protected void testUncancelReal() { + + log.info("Starting testUncancel"); + + try { + + String prod = "Shotgun"; + BillingPeriod term = BillingPeriod.MONTHLY; + String planSet = "standard"; + + // CREATE + Subscription subscription = createSubscription(prod, term, planSet); + IPlanPhase trialPhase = subscription.getCurrentPhase(); + assertEquals(trialPhase.getPhaseType(), PhaseType.TRIAL); + + // NEXT PHASE + DateTime expectedPhaseTrialChange = Clock.addDuration(subscription.getStartDate(), trialPhase.getDuration()); + checkNextPhaseChange(subscription, 1, expectedPhaseTrialChange); + + // MOVE TO NEXT PHASE + testListener.pushExpectedEvent(NextEvent.PHASE); + clock.setDeltaFromReality(trialPhase.getDuration(), DAY_IN_MS); + assertTrue(testListener.isCompleted(2000)); + IPlanPhase currentPhase = subscription.getCurrentPhase(); + assertEquals(currentPhase.getPhaseType(), PhaseType.EVERGREEN); + + // SET CTD + RE READ SUBSCRIPTION + CHANGE PLAN + IDuration ctd = getDurationMonth(1); + DateTime newChargedThroughDate = Clock.addDuration(expectedPhaseTrialChange, ctd); + billingApi.setChargedThroughDate(subscription.getId(), newChargedThroughDate); + subscription = (Subscription) entitlementApi.getSubscriptionFromId(subscription.getId()); + + testListener.pushExpectedEvent(NextEvent.CANCEL); + + // CANCEL + subscription.cancel(); + assertFalse(testListener.isCompleted(2000)); + + subscription.uncancel(); + + // MOVE TO EOT + RECHECK + clock.addDeltaFromReality(ctd); + DateTime future = clock.getUTCNow(); + assertFalse(testListener.isCompleted(2000)); + + IPlan currentPlan = subscription.getCurrentPlan(); + assertEquals(currentPlan.getProduct().getName(), prod); + currentPhase = subscription.getCurrentPhase(); + assertEquals(currentPhase.getPhaseType(), PhaseType.EVERGREEN); + + } 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 new file mode 100644 index 0000000000..05fe153b77 --- /dev/null +++ b/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiCancelMemory.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.user; + +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.entitlement.glue.EngineModuleMemoryMock; + +public class TestUserApiCancelMemory extends TestUserApiCancel { + + + @Override + protected Injector getInjector() { + return Guice.createInjector(Stage.DEVELOPMENT, new EngineModuleMemoryMock()); + } + + @Test(enabled=true, groups={"fast"}) + public void testCancelSubscriptionIMM() { + invokeRealMethod(this); + } + + @Test(enabled=true, groups={"fast"}) + public void testCancelSubscriptionEOTWithChargeThroughDate() { + invokeRealMethod(this); + } + + @Test(enabled=true, groups={"fast"}) + public void testCancelSubscriptionEOTWithNoChargeThroughDate() { + invokeRealMethod(this); + } + + @Test(enabled=true, groups={"fast"}) + public void testUncancel() { + invokeRealMethod(this); + } +} 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 new file mode 100644 index 0000000000..84f11e2833 --- /dev/null +++ b/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiCancelSql.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.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.EngineModuleSqlMock; + +public class TestUserApiCancelSql extends TestUserApiCancel { + + + private final int MAX_STRESS_ITERATIONS = 10; + + @Override + public Injector getInjector() { + return Guice.createInjector(Stage.DEVELOPMENT, new EngineModuleSqlMock()); + } + + @Test(enabled= true, groups={"stress"}) + public void stressTest() { + for (int i = 0; i < MAX_STRESS_ITERATIONS; i++) { + cleanupTest(); + setupTest(); + testCancelSubscriptionIMM(); + cleanupTest(); + setupTest(); + testCancelSubscriptionEOTWithChargeThroughDate(); + cleanupTest(); + setupTest(); + testCancelSubscriptionEOTWithNoChargeThroughDate(); + } + } + + @Test(enabled=true, groups={"sql"}) + public void testCancelSubscriptionIMM() { + invokeRealMethod(this); + } + + @Test(enabled=true, groups={"sql"}) + public void testCancelSubscriptionEOTWithChargeThroughDate() { + invokeRealMethod(this); + } + + @Test(enabled=true, groups={"sql"}) + public void testCancelSubscriptionEOTWithNoChargeThroughDate() { + invokeRealMethod(this); + } + + @Test(enabled=true, groups={"sql"}) + public void testUncancel() { + invokeRealMethod(this); + } + +} 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 new file mode 100644 index 0000000000..6a07d2503b --- /dev/null +++ b/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiChangePlan.java @@ -0,0 +1,413 @@ +/* + * Copyright 2010-2011 Ning, Inc. + * + * Ning licenses this file to you under the Apache License, version 2.0 + * (the "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package com.ning.billing.entitlement.api.user; + +import static org.testng.Assert.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 org.joda.time.DateTime; +import org.testng.Assert; +import org.testng.annotations.AfterMethod; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +import com.ning.billing.account.api.IAccount; +import com.ning.billing.catalog.api.BillingPeriod; +import com.ning.billing.catalog.api.ICatalog; +import com.ning.billing.catalog.api.IDuration; +import com.ning.billing.catalog.api.IPlan; +import com.ning.billing.catalog.api.IPlanPhase; +import com.ning.billing.catalog.api.PhaseType; +import com.ning.billing.catalog.api.PlanAlignment; +import com.ning.billing.catalog.api.ProductCategory; +import com.ning.billing.entitlement.api.ApiTestListener.NextEvent; +import com.ning.billing.entitlement.events.IEvent; +import com.ning.billing.entitlement.events.phase.IPhaseEvent; +import com.ning.billing.entitlement.events.user.IUserEvent; +import com.ning.billing.util.clock.Clock; + +public abstract class TestUserApiChangePlan extends TestUserApiBase { + + + + private void checkChangePlan(Subscription subscription, String expProduct, ProductCategory expCategory, + BillingPeriod expBillingPeriod, PhaseType expPhase) { + + IPlan currentPlan = subscription.getCurrentPlan(); + assertNotNull(currentPlan); + assertEquals(currentPlan.getProduct().getName(),expProduct); + assertEquals(currentPlan.getProduct().getCategory(), expCategory); + assertEquals(currentPlan.getBillingPeriod(), expBillingPeriod); + + IPlanPhase currentPhase = subscription.getCurrentPhase(); + assertNotNull(currentPhase); + assertEquals(currentPhase.getPhaseType(), expPhase); + } + + + + protected void testChangePlanBundleAlignEOTWithNoChargeThroughDateReal() { + tChangePlanBundleAlignEOTWithNoChargeThroughDate("Shotgun", BillingPeriod.MONTHLY, "standard", "Pistol", BillingPeriod.MONTHLY, "standard"); + } + + + private void tChangePlanBundleAlignEOTWithNoChargeThroughDate(String fromProd, BillingPeriod fromTerm, String fromPlanSet, + String toProd, BillingPeriod toTerm, String toPlanSet) { + + log.info("Starting testChangeSubscriptionEOTWithNoChargeThroughDate"); + + try { + + // CREATE + Subscription subscription = createSubscription(fromProd, fromTerm, fromPlanSet); + + // MOVE TO NEXT PHASE + IPlanPhase currentPhase = subscription.getCurrentPhase(); + testListener.pushExpectedEvent(NextEvent.PHASE); + clock.setDeltaFromReality(currentPhase.getDuration(), DAY_IN_MS); + DateTime futureNow = clock.getUTCNow(); + DateTime nextExpectedPhaseChange = Clock.addDuration(subscription.getStartDate(), currentPhase.getDuration()); + assertTrue(futureNow.isAfter(nextExpectedPhaseChange)); + assertTrue(testListener.isCompleted(3000)); + + // CHANGE PLAN + testListener.pushExpectedEvent(NextEvent.CHANGE); + subscription.changePlan(toProd, toTerm, toPlanSet); + assertTrue(testListener.isCompleted(2000)); + + // CHECK CHANGE PLAN + currentPhase = subscription.getCurrentPhase(); + checkChangePlan(subscription, toProd, ProductCategory.BASE, toTerm, PhaseType.EVERGREEN); + + } catch (EntitlementUserApiException e) { + Assert.fail(e.getMessage()); + } + } + + + protected void testChangePlanBundleAlignEOTWithChargeThroughDateReal() { + testChangePlanBundleAlignEOTWithChargeThroughDate("Shotgun", BillingPeriod.ANNUAL, "gunclubDiscount", "Pistol", BillingPeriod.ANNUAL, "gunclubDiscount"); + } + + private void testChangePlanBundleAlignEOTWithChargeThroughDate(String fromProd, BillingPeriod fromTerm, String fromPlanSet, + String toProd, BillingPeriod toTerm, String toPlanSet) { + + log.info("Starting testChangeSubscriptionEOTWithChargeThroughDate"); + try { + + // CREATE + Subscription subscription = createSubscription(fromProd, fromTerm, fromPlanSet); + IPlanPhase trialPhase = subscription.getCurrentPhase(); + DateTime expectedPhaseTrialChange = Clock.addDuration(subscription.getStartDate(), trialPhase.getDuration()); + assertEquals(trialPhase.getPhaseType(), PhaseType.TRIAL); + + + // MOVE TO NEXT PHASE + testListener.pushExpectedEvent(NextEvent.PHASE); + clock.setDeltaFromReality(trialPhase.getDuration(), DAY_IN_MS); + assertTrue(testListener.isCompleted(2000)); + IPlanPhase currentPhase = subscription.getCurrentPhase(); + assertEquals(currentPhase.getPhaseType(), PhaseType.DISCOUNT); + + + // SET CTD + IDuration ctd = getDurationMonth(1); + DateTime newChargedThroughDate = Clock.addDuration(expectedPhaseTrialChange, ctd); + billingApi.setChargedThroughDate(subscription.getId(), newChargedThroughDate); + + // RE READ SUBSCRIPTION + CHANGE PLAN + testListener.pushExpectedEvent(NextEvent.CHANGE); + subscription = (Subscription) entitlementApi.getSubscriptionFromId(subscription.getId()); + subscription.changePlan(toProd, toTerm, toPlanSet); + assertFalse(testListener.isCompleted(2000)); + testListener.reset(); + + // CHECK CHANGE PLAN + currentPhase = subscription.getCurrentPhase(); + checkChangePlan(subscription, fromProd, ProductCategory.BASE, fromTerm, PhaseType.DISCOUNT); + + // NEXT PHASE + DateTime nextExpectedPhaseChange = Clock.addDuration(expectedPhaseTrialChange, currentPhase.getDuration()); + checkNextPhaseChange(subscription, 2, nextExpectedPhaseChange); + + // ALSO VERIFY PENDING CHANGE EVENT + List events = dao.getPendingEventsForSubscription(subscription.getId()); + assertTrue(events.get(0) instanceof IUserEvent); + + + // MOVE TO EOT + testListener.pushExpectedEvent(NextEvent.CHANGE); + clock.addDeltaFromReality(ctd); + assertTrue(testListener.isCompleted(5000)); + + subscription = (Subscription) entitlementApi.getSubscriptionFromId(subscription.getId()); + currentPhase = subscription.getCurrentPhase(); + checkChangePlan(subscription, toProd, ProductCategory.BASE, toTerm, PhaseType.DISCOUNT); + + } catch (EntitlementUserApiException e) { + Assert.fail(e.getMessage()); + } + } + + + protected void testChangePlanBundleAlignIMMReal() { + tChangePlanBundleAlignIMM("Shotgun", BillingPeriod.MONTHLY, "standard", "Assault-Rifle", BillingPeriod.MONTHLY, "standard"); + } + + + private void tChangePlanBundleAlignIMM(String fromProd, BillingPeriod fromTerm, String fromPlanSet, + String toProd, BillingPeriod toTerm, String toPlanSet) { + + log.info("Starting testChangePlanBundleAlignIMM"); + + try { + + Subscription subscription = createSubscription(fromProd, fromTerm, fromPlanSet); + + testListener.pushExpectedEvent(NextEvent.CHANGE); + + IDuration moveALittleInTime = getDurationDay(3); + clock.setDeltaFromReality(moveALittleInTime, 0); + + // CHANGE PLAN IMM + subscription.changePlan(toProd, toTerm, toPlanSet); + checkChangePlan(subscription, toProd, ProductCategory.BASE, toTerm, PhaseType.TRIAL); + + assertTrue(testListener.isCompleted(2000)); + + IPlanPhase currentPhase = subscription.getCurrentPhase(); + DateTime nextExpectedPhaseChange = Clock.addDuration(subscription.getStartDate(), currentPhase.getDuration()); + checkNextPhaseChange(subscription, 1, nextExpectedPhaseChange); + + // NEXT PHASE + testListener.pushExpectedEvent(NextEvent.PHASE); + clock.addDeltaFromReality(currentPhase.getDuration()); + DateTime futureNow = clock.getUTCNow(); + assertTrue(futureNow.isAfter(nextExpectedPhaseChange)); + assertTrue(testListener.isCompleted(3000)); + + } catch (EntitlementUserApiException e) { + Assert.fail(e.getMessage()); + } + } + + + protected void testChangePlanSubscriptionAlignEOTWithChargeThroughDateReal() { + tChangePlanSubscriptionAlignEOTWithChargeThroughDate("Shotgun", BillingPeriod.ANNUAL, "standard", "Assault-Rifle", BillingPeriod.ANNUAL, "rescue"); + } + + private void tChangePlanSubscriptionAlignEOTWithChargeThroughDate(String fromProd, BillingPeriod fromTerm, String fromPlanSet, + String toProd, BillingPeriod toTerm, String toPlanSet) { + + log.info("Starting testChangePlanBundleAlignEOTWithChargeThroughDate"); + + try { + + DateTime currentTime = clock.getUTCNow(); + + Subscription subscription = createSubscription(fromProd, fromTerm, fromPlanSet); + IPlanPhase trialPhase = subscription.getCurrentPhase(); + DateTime expectedPhaseTrialChange = Clock.addDuration(subscription.getStartDate(), trialPhase.getDuration()); + assertEquals(trialPhase.getPhaseType(), PhaseType.TRIAL); + + // MOVE TO NEXT PHASE + testListener.pushExpectedEvent(NextEvent.PHASE); + currentTime = clock.getUTCNow(); + clock.setDeltaFromReality(trialPhase.getDuration(), DAY_IN_MS); + currentTime = clock.getUTCNow(); + assertTrue(testListener.isCompleted(2000)); + + // SET CTD + IDuration ctd = getDurationMonth(1); + DateTime newChargedThroughDate = Clock.addDuration(expectedPhaseTrialChange, ctd); + billingApi.setChargedThroughDate(subscription.getId(), newChargedThroughDate); + + // RE READ SUBSCRIPTION + CHECK CURRENT PHASE + subscription = (Subscription) entitlementApi.getSubscriptionFromId(subscription.getId()); + IPlanPhase currentPhase = subscription.getCurrentPhase(); + assertNotNull(currentPhase); + assertEquals(currentPhase.getPhaseType(), PhaseType.EVERGREEN); + + // CHANGE PLAN + currentTime = clock.getUTCNow(); + + testListener.pushExpectedEvent(NextEvent.CHANGE); + subscription.changePlan(toProd, toTerm, toPlanSet); + + checkChangePlan(subscription, fromProd, ProductCategory.BASE, fromTerm, PhaseType.EVERGREEN); + + // CHECK CHANGE DID NOT KICK IN YET + assertFalse(testListener.isCompleted(2000)); + + // MOVE TO AFTER CTD + clock.addDeltaFromReality(ctd); + currentTime = clock.getUTCNow(); + assertTrue(testListener.isCompleted(2000)); + + // CHECK CORRECT PRODUCT, PHASE, PLAN SET + String currentProduct = subscription.getCurrentPlan().getProduct().getName(); + assertNotNull(currentProduct); + assertEquals(currentProduct, toProd); + currentPhase = subscription.getCurrentPhase(); + 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)); + + currentTime = clock.getUTCNow(); + assertFalse(testListener.isCompleted(2000)); + + DateTime nextExpectedPhaseChange = Clock.addDuration(newChargedThroughDate, currentPhase.getDuration()); + checkNextPhaseChange(subscription, 1, nextExpectedPhaseChange); + + // MOVE TIME RIGHT AFTER NEXT EXPECTED PHASE CHANGE + clock.addDeltaFromReality(getDurationMonth(1)); + currentTime = clock.getUTCNow(); + assertTrue(testListener.isCompleted(2000)); + + } catch (EntitlementUserApiException e) { + Assert.fail(e.getMessage()); + } + } + + protected void testMultipleChangeLastIMMReal() { + + try { + Subscription subscription = createSubscription("Assault-Rifle", BillingPeriod.MONTHLY, "gunclubDiscount"); + IPlanPhase trialPhase = subscription.getCurrentPhase(); + assertEquals(trialPhase.getPhaseType(), PhaseType.TRIAL); + + // MOVE TO NEXT PHASE + testListener.pushExpectedEvent(NextEvent.PHASE); + clock.setDeltaFromReality(trialPhase.getDuration(), DAY_IN_MS); + assertTrue(testListener.isCompleted(2000)); + + // SET CTD + List durationList = new ArrayList(); + durationList.add(trialPhase.getDuration()); + //durationList.add(subscription.getCurrentPhase().getDuration()); + DateTime startDiscountPhase = Clock.addDuration(subscription.getStartDate(), durationList); + IDuration ctd = getDurationMonth(1); + DateTime newChargedThroughDate = Clock.addDuration(startDiscountPhase, ctd); + billingApi.setChargedThroughDate(subscription.getId(), newChargedThroughDate); + subscription = (Subscription) entitlementApi.getSubscriptionFromId(subscription.getId()); + + // CHANGE EOT + testListener.pushExpectedEvent(NextEvent.CHANGE); + subscription.changePlan("Pistol", BillingPeriod.MONTHLY, "gunclubDiscount"); + assertFalse(testListener.isCompleted(2000)); + + // CHANGE + testListener.pushExpectedEvent(NextEvent.CHANGE); + subscription.changePlan("Assault-Rifle", BillingPeriod.ANNUAL, "gunclubDiscount"); + assertFalse(testListener.isCompleted(2000)); + + IPlan currentPlan = subscription.getCurrentPlan(); + assertNotNull(currentPlan); + assertEquals(currentPlan.getProduct().getName(), "Assault-Rifle"); + assertEquals(currentPlan.getProduct().getCategory(), ProductCategory.BASE); + assertEquals(currentPlan.getPlanAlignment(), PlanAlignment.START_OF_BUNDLE); + assertEquals(currentPlan.getBillingPeriod(), BillingPeriod.ANNUAL); + + IPlanPhase currentPhase = subscription.getCurrentPhase(); + assertNotNull(currentPhase); + assertEquals(currentPhase.getPhaseType(), PhaseType.DISCOUNT); + + } catch (EntitlementUserApiException e) { + Assert.fail(e.getMessage()); + } + } + + protected void testMultipleChangeLastEOTReal() { + + try { + + Subscription subscription = createSubscription("Assault-Rifle", BillingPeriod.ANNUAL, "gunclubDiscount"); + IPlanPhase trialPhase = subscription.getCurrentPhase(); + assertEquals(trialPhase.getPhaseType(), PhaseType.TRIAL); + + // MOVE TO NEXT PHASE + testListener.pushExpectedEvent(NextEvent.PHASE); + clock.setDeltaFromReality(trialPhase.getDuration(), DAY_IN_MS); + assertTrue(testListener.isCompleted(2000)); + + // SET CTD + List durationList = new ArrayList(); + durationList.add(trialPhase.getDuration()); + DateTime startDiscountPhase = Clock.addDuration(subscription.getStartDate(), durationList); + IDuration ctd = getDurationMonth(1); + DateTime newChargedThroughDate = Clock.addDuration(startDiscountPhase, ctd); + billingApi.setChargedThroughDate(subscription.getId(), newChargedThroughDate); + subscription = (Subscription) entitlementApi.getSubscriptionFromId(subscription.getId()); + + // CHANGE EOT + testListener.pushExpectedEvent(NextEvent.CHANGE); + subscription.changePlan("Shotgun", BillingPeriod.MONTHLY, "gunclubDiscount"); + assertFalse(testListener.isCompleted(2000)); + testListener.reset(); + + // CHANGE EOT + testListener.pushExpectedEvent(NextEvent.CHANGE); + subscription.changePlan("Pistol", BillingPeriod.ANNUAL, "gunclubDiscount"); + assertFalse(testListener.isCompleted(2000)); + testListener.reset(); + + // CHECK NO CHANGE OCCURED YET + IPlan currentPlan = subscription.getCurrentPlan(); + assertNotNull(currentPlan); + assertEquals(currentPlan.getProduct().getName(), "Assault-Rifle"); + assertEquals(currentPlan.getProduct().getCategory(), ProductCategory.BASE); + assertEquals(currentPlan.getPlanAlignment(), PlanAlignment.START_OF_BUNDLE); + assertEquals(currentPlan.getBillingPeriod(), BillingPeriod.ANNUAL); + + IPlanPhase currentPhase = subscription.getCurrentPhase(); + assertNotNull(currentPhase); + assertEquals(currentPhase.getPhaseType(), PhaseType.DISCOUNT); + + // MOVE TO NEXT PHASE + testListener.pushExpectedEvent(NextEvent.PHASE); + testListener.pushExpectedEvent(NextEvent.CHANGE); + clock.addDeltaFromReality(currentPhase.getDuration()); + assertTrue(testListener.isCompleted(3000)); + subscription = (Subscription) entitlementApi.getSubscriptionFromId(subscription.getId()); + + currentPlan = subscription.getCurrentPlan(); + assertNotNull(currentPlan); + assertEquals(currentPlan.getProduct().getName(), "Pistol"); + assertEquals(currentPlan.getProduct().getCategory(), ProductCategory.BASE); + assertEquals(currentPlan.getPlanAlignment(), PlanAlignment.START_OF_BUNDLE); + assertEquals(currentPlan.getBillingPeriod(), BillingPeriod.ANNUAL); + + currentPhase = subscription.getCurrentPhase(); + assertNotNull(currentPhase); + assertEquals(currentPhase.getPhaseType(), PhaseType.EVERGREEN); + + + } 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 new file mode 100644 index 0000000000..157b8eeb40 --- /dev/null +++ b/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiChangePlanMemory.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.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.EngineModuleMemoryMock; + +public class TestUserApiChangePlanMemory extends TestUserApiChangePlan { + + @Override + protected Injector getInjector() { + return Guice.createInjector(Stage.DEVELOPMENT, new EngineModuleMemoryMock()); + } + + + @Test(enabled=true, groups={"fast"}) + public void testChangePlanBundleAlignEOTWithNoChargeThroughDate() { + System.err.println("STEPH THE TEST"); + invokeRealMethod(this); + } + + @Test(enabled=true, groups={"fast"}) + public void testChangePlanBundleAlignEOTWithChargeThroughDate() { + invokeRealMethod(this); + } + + @Test(enabled=true, groups={"fast"}) + public void testChangePlanBundleAlignIMM() { + invokeRealMethod(this); + } + + @Test(enabled=true, groups={"fast"}) + public void testChangePlanSubscriptionAlignEOTWithChargeThroughDate() { + invokeRealMethod(this); + } + + @Test(enabled=true, groups={"fast"}) + public void testMultipleChangeLastIMM() { + invokeRealMethod(this); + } + + @Test(enabled=true, groups={"fast"}) + public void testMultipleChangeLastEOT() { + invokeRealMethod(this); + } + + @Test(enabled=false, groups={"stress"}) + public void stressTest() { + for (int i = 0; i < 20; i++) { + cleanupTest(); + setupTest(); + testMultipleChangeLastEOT(); + cleanupTest(); + setupTest(); + testMultipleChangeLastIMM(); + } + } + +} 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 new file mode 100644 index 0000000000..689dafae1c --- /dev/null +++ b/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiChangePlanSql.java @@ -0,0 +1,85 @@ +/* + * Copyright 2010-2011 Ning, Inc. + * + * Ning licenses this file to you under the Apache License, version 2.0 + * (the "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF 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 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.EngineModuleSqlMock; + +public class TestUserApiChangePlanSql extends TestUserApiChangePlan { + + private final int MAX_STRESS_ITERATIONS = 30; + + @Override + public Injector getInjector() { + return Guice.createInjector(Stage.DEVELOPMENT, new EngineModuleSqlMock()); + } + + @Test(enabled= true, groups={"stress"}) + public void stressTest() { + for (int i = 0; i < MAX_STRESS_ITERATIONS; i++) { + cleanupTest(); + setupTest(); + testChangePlanBundleAlignEOTWithNoChargeThroughDate(); + cleanupTest(); + setupTest(); + testChangePlanBundleAlignEOTWithChargeThroughDate(); + cleanupTest(); + setupTest(); + testChangePlanBundleAlignIMM(); + cleanupTest(); + setupTest(); + testMultipleChangeLastIMM(); + cleanupTest(); + setupTest(); + testMultipleChangeLastEOT(); + } + } + + @Test(enabled=true, groups={"sql"}) + public void testChangePlanBundleAlignEOTWithNoChargeThroughDate() { + invokeRealMethod(this); + } + + @Test(enabled=true, groups={"sql"}) + public void testChangePlanBundleAlignEOTWithChargeThroughDate() { + invokeRealMethod(this); + } + + @Test(enabled=true, groups={"sql"}) + public void testChangePlanBundleAlignIMM() { + invokeRealMethod(this); + } + + @Test(enabled=true, groups={"sql"}) + public void testChangePlanSubscriptionAlignEOTWithChargeThroughDate() { + invokeRealMethod(this); + } + + @Test(enabled=true, groups={"sql"}) + public void testMultipleChangeLastIMM() { + invokeRealMethod(this); + } + + @Test(enabled=true, groups={"sql"}) + public void testMultipleChangeLastEOT() { + invokeRealMethod(this); + } +} 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 new file mode 100644 index 0000000000..26fdcb09ab --- /dev/null +++ b/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiCreate.java @@ -0,0 +1,180 @@ +/* + * Copyright 2010-2011 Ning, Inc. + * + * Ning licenses this file to you under the Apache License, version 2.0 + * (the "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package com.ning.billing.entitlement.api.user; + +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertNotNull; +import static org.testng.Assert.assertTrue; + +import java.util.List; + +import org.joda.time.DateTime; +import org.testng.Assert; +import org.testng.annotations.AfterMethod; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +import com.ning.billing.catalog.api.BillingPeriod; +import com.ning.billing.catalog.api.IPlan; +import com.ning.billing.catalog.api.IPlanPhase; +import com.ning.billing.catalog.api.PhaseType; +import com.ning.billing.catalog.api.PlanAlignment; +import com.ning.billing.catalog.api.ProductCategory; +import com.ning.billing.entitlement.api.ApiTestListener.NextEvent; +import com.ning.billing.entitlement.events.IEvent; +import com.ning.billing.entitlement.events.phase.IPhaseEvent; +import com.ning.billing.util.clock.Clock; + +public abstract class TestUserApiCreate extends TestUserApiBase { + + + protected void testSimpleCreateSubscriptionReal() { + + log.info("Starting testSimpleCreateSubscription"); + try { + + DateTime init = clock.getUTCNow(); + + String productName = "Shotgun"; + BillingPeriod term = BillingPeriod.MONTHLY; + String planSetName = "standard"; + + testListener.pushExpectedEvent(NextEvent.CREATE); + + Subscription subscription = (Subscription) entitlementApi.createSubscription(bundle.getId(), productName, term, planSetName); + assertNotNull(subscription); + + assertEquals(subscription.getActiveVersion(), SubscriptionEvents.INITIAL_VERSION); + //assertEquals(subscription.getAccount(), account.getId()); + assertEquals(subscription.getBundleId(), bundle.getId()); + assertDateWithin(subscription.getStartDate(), init, clock.getUTCNow()); + assertDateWithin(subscription.getBundleStartDate(), init, clock.getUTCNow()); + + printSubscriptionTransitions(subscription.getActiveTransitions()); + + IPlan currentPlan = subscription.getCurrentPlan(); + assertNotNull(currentPlan); + assertEquals(currentPlan.getProduct().getName(), productName); + assertEquals(currentPlan.getProduct().getCategory(), ProductCategory.BASE); + assertEquals(currentPlan.getPlanAlignment(), PlanAlignment.START_OF_BUNDLE); + assertEquals(currentPlan.getBillingPeriod(), BillingPeriod.MONTHLY); + + IPlanPhase currentPhase = subscription.getCurrentPhase(); + assertNotNull(currentPhase); + assertEquals(currentPhase.getPhaseType(), PhaseType.TRIAL); + + List transitions = subscription.getActiveTransitions(); + assertNotNull(transitions); + assertEquals(transitions.size(), 1); + + assertTrue(testListener.isCompleted(5000)); + + List events = dao.getPendingEventsForSubscription(subscription.getId()); + assertNotNull(events); + printEvents(events); + assertTrue(events.size() == 1); + assertTrue(events.get(0) instanceof IPhaseEvent); + DateTime nextPhaseChange = ((IPhaseEvent ) events.get(0)).getEffectiveDate(); + DateTime nextExpectedPhaseChange = Clock.addDuration(subscription.getStartDate(), currentPhase.getDuration()); + assertEquals(nextPhaseChange, nextExpectedPhaseChange); + + testListener.pushExpectedEvent(NextEvent.PHASE); + + clock.setDeltaFromReality(currentPhase.getDuration(), DAY_IN_MS); + + DateTime futureNow = clock.getUTCNow(); + assertTrue(futureNow.isAfter(nextPhaseChange)); + + assertTrue(testListener.isCompleted(5000)); + + } catch (EntitlementUserApiException e) { + Assert.fail(e.getMessage()); + } + } + + + protected void testSimpleSubscriptionThroughPhasesReal() { + + log.info("Starting testSimpleSubscriptionThroughPhases"); + try { + + DateTime curTime = clock.getUTCNow(); + + String productName = "Pistol"; + BillingPeriod term = BillingPeriod.ANNUAL; + String planSetName = "gunclubDiscount"; + + testListener.pushExpectedEvent(NextEvent.CREATE); + + // CREATE SUBSCRIPTION + Subscription subscription = (Subscription) entitlementApi.createSubscription(bundle.getId(), productName, term, planSetName); + assertNotNull(subscription); + + IPlanPhase currentPhase = subscription.getCurrentPhase(); + assertNotNull(currentPhase); + assertEquals(currentPhase.getPhaseType(), PhaseType.TRIAL); + assertTrue(testListener.isCompleted(5000)); + + + + // MOVE TO DISCOUNT PHASE + testListener.pushExpectedEvent(NextEvent.PHASE); + clock.setDeltaFromReality(currentPhase.getDuration(), DAY_IN_MS); + curTime = clock.getUTCNow(); + 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)); + + subscription = (Subscription) entitlementApi.getSubscriptionFromId(subscription.getId()); + curTime = clock.getUTCNow(); + currentPhase = subscription.getCurrentPhase(); + assertNotNull(currentPhase); + assertEquals(currentPhase.getPhaseType(), PhaseType.EVERGREEN); + + + } catch (EntitlementUserApiException e) { + Assert.fail(e.getMessage()); + } + } + + protected void testSubscriptionWithAddOnReal() { + + log.info("Starting testSubscriptionWithAddOn"); + try { + + String productName = "Shotgun"; + BillingPeriod term = BillingPeriod.ANNUAL; + String planSetName = "standard"; + + testListener.pushExpectedEvent(NextEvent.CREATE); + + Subscription subscription = (Subscription) entitlementApi.createSubscription(bundle.getId(), productName, term, planSetName); + assertNotNull(subscription); + + } 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 new file mode 100644 index 0000000000..8ec02d7463 --- /dev/null +++ b/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiCreateMemory.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.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.EngineModuleMemoryMock; + +public class TestUserApiCreateMemory extends TestUserApiCreate { + + + @Override + protected Injector getInjector() { + return Guice.createInjector(Stage.DEVELOPMENT, new EngineModuleMemoryMock()); + } + + @Test(enabled=true, groups={"fast"}) + public void testSimpleCreateSubscription() { + invokeRealMethod(this); + } + + @Test(enabled=true, groups={"fast"}) + protected void testSimpleSubscriptionThroughPhases() { + invokeRealMethod(this); + } + + @Test(enabled=false, groups={"fast"}) + protected void testSubscriptionWithAddOn() { + invokeRealMethod(this); + } + +} 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 new file mode 100644 index 0000000000..58fa52b29c --- /dev/null +++ b/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiCreateSql.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.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.EngineModuleSqlMock; + +public class TestUserApiCreateSql extends TestUserApiCreate { + + @Override + protected Injector getInjector() { + return Guice.createInjector(Stage.DEVELOPMENT, new EngineModuleSqlMock()); + } + + @Test(enabled=true, groups={"sql"}) + public void testSimpleCreateSubscription() { + invokeRealMethod(this); + } + + @Test(enabled=true, groups={"sql"}) + protected void testSimpleSubscriptionThroughPhases() { + invokeRealMethod(this); + } + + @Test(enabled=false, groups={"sql"}) + protected void testSubscriptionWithAddOn() { + invokeRealMethod(this); + } + +} 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 new file mode 100644 index 0000000000..8f22f8357d --- /dev/null +++ b/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiError.java @@ -0,0 +1,193 @@ +/* + * Copyright 2010-2011 Ning, Inc. + * + * Ning licenses this file to you under the Apache License, version 2.0 + * (the "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package com.ning.billing.entitlement.api.user; + +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertNotNull; +import static org.testng.Assert.assertNull; +import static org.testng.Assert.assertTrue; +import static org.testng.Assert.assertFalse; + + +import java.util.UUID; + +import org.joda.time.DateTime; +import org.testng.Assert; +import org.testng.annotations.Test; + +import com.google.inject.Guice; +import com.google.inject.Injector; +import com.google.inject.Stage; +import com.ning.billing.ErrorCode; +import com.ning.billing.catalog.api.BillingPeriod; +import com.ning.billing.catalog.api.IDuration; +import com.ning.billing.catalog.api.IPlanPhase; +import com.ning.billing.entitlement.api.ApiTestListener.NextEvent; +import com.ning.billing.entitlement.glue.EngineModuleMemoryMock; +import com.ning.billing.util.clock.Clock; + +public class TestUserApiError extends TestUserApiBase { + + + /* + * ENT_CREATE_BAD_CATALOG(1011, "Plan for product %s, term %s and set %s does not exist in the catalog"), + ENT_CREATE_NO_BUNDLE(1012, "Bundle %s does not exists"), + ENT_CREATE_NO_BP(1013, "Missing Base Subscription for bundle %s"), + ENT_CREATE_BP_EXISTS(1015, "Subscription bundle %s already has a base subscription"), + ENT_CHANGE_BAD_STATE(1021, "Subscription %s is in state %s"), + ENT_CANCEL_BAD_STATE(1031, "Subscription %s is in state %s"), + ENT_UNCANCEL_BAD_STATE(1070, "Subscription %s was not in a cancelled state") + */ + + @Override + protected Injector getInjector() { + return Guice.createInjector(Stage.DEVELOPMENT, new EngineModuleMemoryMock()); + } + + @Test(enabled=true) + public void testCreateSubscriptionBadCatalog() { + // WRONG PRODUTCS + tCreateSubscriptionInternal(bundle.getId(), null, BillingPeriod.ANNUAL, "standard", ErrorCode.ENT_CREATE_BAD_CATALOG); + tCreateSubscriptionInternal(bundle.getId(), "Whatever", BillingPeriod.ANNUAL, "standard", ErrorCode.ENT_CREATE_BAD_CATALOG); + // WRONG BILLING PERIOD + tCreateSubscriptionInternal(bundle.getId(), "Shotgun", null, "standard", ErrorCode.ENT_CREATE_BAD_CATALOG); + // WRONG PLAN SET + tCreateSubscriptionInternal(bundle.getId(), "Shotgun", BillingPeriod.ANNUAL, null, ErrorCode.ENT_CREATE_BAD_CATALOG); + tCreateSubscriptionInternal(bundle.getId(), "Shotgun", BillingPeriod.ANNUAL, "Whatever", ErrorCode.ENT_CREATE_BAD_CATALOG); + + } + + @Test(enabled=true) + public void testCreateSubscriptionNoBundle() { + tCreateSubscriptionInternal(null, "Shotgun", BillingPeriod.ANNUAL, "standard", ErrorCode.ENT_CREATE_NO_BUNDLE); + } + + @Test(enabled=false) + public void testCreateSubscriptionNoBP() { + //tCreateSubscriptionInternal(bundle.getId(), "Shotgun", BillingPeriod.ANNUAL, "standard", ErrorCode.ENT_CREATE_NO_BP); + } + + @Test(enabled=true) + public void testCreateSubscriptionBPExists() { + try { + createSubscription("Shotgun", BillingPeriod.ANNUAL, "standard"); + tCreateSubscriptionInternal(bundle.getId(), "Shotgun", BillingPeriod.ANNUAL, "standard", ErrorCode.ENT_CREATE_BP_EXISTS); + } catch (Exception e) { + e.printStackTrace(); + Assert.assertFalse(true); + } + } + + private void tCreateSubscriptionInternal(UUID bundleId, String productName, + BillingPeriod term, String planSet, ErrorCode expected) { + try { + entitlementApi.createSubscription(bundleId, productName, term, planSet); + assertFalse(true); + } catch (EntitlementUserApiException e) { + assertEquals(e.getCode(), expected.getCode()); + try { + log.info(e.getMessage()); + } catch (Throwable el) { + assertFalse(true); + } + } + } + + + @Test(enabled=true) + public void testChangeSubscriptionNonActive() { + try { + ISubscription subscription = createSubscription("Shotgun", BillingPeriod.ANNUAL, "standard"); + + testListener.pushExpectedEvent(NextEvent.CANCEL); + subscription.cancel(); + try { + subscription.changePlan("Pistol", BillingPeriod.MONTHLY, "standard"); + } catch (EntitlementUserApiException e) { + assertEquals(e.getCode(), ErrorCode.ENT_CHANGE_NON_ACTIVE.getCode()); + try { + log.info(e.getMessage()); + } catch (Throwable el) { + assertFalse(true); + } + } + } catch (Exception e) { + e.printStackTrace(); + Assert.assertFalse(true); + } + } + + + @Test(enabled=true) + public void testChangeSubscriptionFutureCancelled() { + try { + ISubscription subscription = createSubscription("Shotgun", BillingPeriod.MONTHLY, "standard"); + + // SET CTD TO CANCEL IN FUTURE + IPlanPhase trialPhase = subscription.getCurrentPhase(); + DateTime expectedPhaseTrialChange = Clock.addDuration(subscription.getStartDate(), trialPhase.getDuration()); + IDuration ctd = getDurationMonth(1); + DateTime newChargedThroughDate = Clock.addDuration(expectedPhaseTrialChange, ctd); + billingApi.setChargedThroughDate(subscription.getId(), newChargedThroughDate); + subscription = entitlementApi.getSubscriptionFromId(subscription.getId()); + + subscription.cancel(); + try { + subscription.changePlan("Pistol", BillingPeriod.MONTHLY, "standard"); + } catch (EntitlementUserApiException e) { + assertEquals(e.getCode(), ErrorCode.ENT_CHANGE_FUTURE_CANCELLED.getCode()); + try { + log.info(e.getMessage()); + } catch (Throwable el) { + assertFalse(true); + } + } + } catch (Exception e) { + e.printStackTrace(); + Assert.assertFalse(true); + } + } + + + @Test(enabled=false) + public void testCancelBadState() { + } + + @Test(enabled=true) + public void testUncancelBadState() { + try { + ISubscription subscription = createSubscription("Shotgun", BillingPeriod.MONTHLY, "standard"); + + try { + subscription.uncancel(); + } catch (EntitlementUserApiException e) { + assertEquals(e.getCode(), ErrorCode.ENT_UNCANCEL_BAD_STATE.getCode()); + try { + log.info(e.getMessage()); + } catch (Throwable el) { + assertFalse(true); + } + } + + } catch (Exception e) { + e.printStackTrace(); + Assert.assertFalse(true); + } + + } + +} diff --git a/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiPriceList.java b/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiPriceList.java new file mode 100644 index 0000000000..d6a2b126e4 --- /dev/null +++ b/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiPriceList.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.user; + +import com.google.inject.Injector; + +public class TestUserApiPriceList extends TestUserApiBase { + + @Override + protected Injector getInjector() { + return null; + } + + protected void testChangeDefaultToDiscountToDefault() { + + } + + protected void testChangeDiscountToDefaultoDiscount() { + + } + +} 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 new file mode 100644 index 0000000000..7f3dbba0cc --- /dev/null +++ b/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiScenarios.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.entitlement.api.user; + +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertTrue; +import static org.testng.Assert.assertFalse; + +import org.joda.time.DateTime; +import org.testng.Assert; +import org.testng.annotations.Test; + +import com.google.inject.Guice; +import com.google.inject.Injector; +import com.google.inject.Stage; +import com.ning.billing.catalog.api.BillingPeriod; +import com.ning.billing.catalog.api.IDuration; +import com.ning.billing.catalog.api.IPlanPhase; +import com.ning.billing.catalog.api.PhaseType; +import com.ning.billing.entitlement.api.ApiTestListener.NextEvent; +import com.ning.billing.entitlement.glue.EngineModuleSqlMock; +import com.ning.billing.util.clock.Clock; + +public class TestUserApiScenarios extends TestUserApiBase { + + @Override + protected Injector getInjector() { + return Guice.createInjector(Stage.DEVELOPMENT, new EngineModuleSqlMock()); + } + + @Test(enabled=true) + public void testChangeIMMCancelUncancelChangeEOT() { + + log.info("Starting testChangeIMMCancelUncancelChangeEOT"); + + try { + Subscription subscription = createSubscription("Assault-Rifle", BillingPeriod.MONTHLY, "gunclubDiscount"); + IPlanPhase trialPhase = subscription.getCurrentPhase(); + assertEquals(trialPhase.getPhaseType(), PhaseType.TRIAL); + + testListener.pushExpectedEvent(NextEvent.CHANGE); + subscription.changePlan("Pistol", BillingPeriod.ANNUAL, "gunclubDiscount"); + testListener.isCompleted(3000); + + // MOVE TO NEXT PHASE + testListener.pushExpectedEvent(NextEvent.PHASE); + clock.setDeltaFromReality(trialPhase.getDuration(), DAY_IN_MS); + assertTrue(testListener.isCompleted(2000)); + + // SET CTD + IDuration ctd = getDurationMonth(1); + DateTime expectedPhaseTrialChange = Clock.addDuration(subscription.getStartDate(), trialPhase.getDuration()); + DateTime newChargedThroughDate = Clock.addDuration(expectedPhaseTrialChange, ctd); + billingApi.setChargedThroughDate(subscription.getId(), newChargedThroughDate); + subscription = (Subscription) entitlementApi.getSubscriptionFromId(subscription.getId()); + + // CANCEL EOT + testListener.pushExpectedEvent(NextEvent.CANCEL); + subscription.cancel(); + assertFalse(testListener.isCompleted(2000)); + testListener.reset(); + + // UNCANCEL + subscription.uncancel(); + + // CHANGE EOT + testListener.pushExpectedEvent(NextEvent.CHANGE); + subscription.changePlan("Pistol", BillingPeriod.MONTHLY, "gunclubDiscount"); + assertFalse(testListener.isCompleted(2000)); + + clock.addDeltaFromReality(ctd); + assertTrue(testListener.isCompleted(3000)); + + + } catch (EntitlementUserApiException e) { + Assert.fail(e.getMessage()); + } + } + + @Test(enabled=false) + private void testChangeEOTCancelUncancelChangeIMM() { + + } + +} diff --git a/entitlement/src/test/java/com/ning/billing/entitlement/engine/core/ApiEventProcessorMemoryMock.java b/entitlement/src/test/java/com/ning/billing/entitlement/engine/core/ApiEventProcessorMemoryMock.java new file mode 100644 index 0000000000..2c35a8e73b --- /dev/null +++ b/entitlement/src/test/java/com/ning/billing/entitlement/engine/core/ApiEventProcessorMemoryMock.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.engine.core; + +import java.util.List; + +import com.google.inject.Inject; +import com.ning.billing.entitlement.engine.dao.IEntitlementDao; +import com.ning.billing.entitlement.events.IEvent; +import com.ning.billing.entitlement.glue.IEngineConfig; +import com.ning.billing.util.clock.IClock; + +public class ApiEventProcessorMemoryMock extends ApiEventProcessorBase { + + + @Inject + public ApiEventProcessorMemoryMock(IClock clock, IEntitlementDao dao, IEngineConfig config) { + super(clock, dao, config); + } + + + @Override + protected void doProcessEvents(int sequenceId) { + + List events = dao.getEventsReady(apiProcessorId, sequenceId); + log.info(String.format("doProcessEvents : Got %d event(s)", events.size() )); + for (IEvent cur : events) { + log.info(String.format("doProcessEvents : (clock = %s) CALLING Engine with event %s", clock.getUTCNow(), cur)); + listener.processEventReady(cur); + log.info(String.format("doProcessEvents : PROCESSED event %s", cur)); + nbProcessedEvents++; + } + dao.clearEventsReady(apiProcessorId, events); + log.info(String.format("doProcessEvents : clearEvents")); + } +} diff --git a/entitlement/src/test/java/com/ning/billing/entitlement/engine/dao/EntitlementDaoMemoryMock.java b/entitlement/src/test/java/com/ning/billing/entitlement/engine/dao/EntitlementDaoMemoryMock.java new file mode 100644 index 0000000000..45754d0413 --- /dev/null +++ b/entitlement/src/test/java/com/ning/billing/entitlement/engine/dao/EntitlementDaoMemoryMock.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.entitlement.engine.dao; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.TreeSet; +import java.util.UUID; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.inject.Inject; +import com.ning.billing.catalog.api.ProductCategory; +import com.ning.billing.catalog.api.TimeUnit; +import com.ning.billing.entitlement.api.user.ISubscription; +import com.ning.billing.entitlement.api.user.ISubscriptionBundle; +import com.ning.billing.entitlement.api.user.Subscription; +import com.ning.billing.entitlement.api.user.SubscriptionBundle; +import com.ning.billing.entitlement.events.IEvent; +import com.ning.billing.entitlement.events.IEvent.EventType; +import com.ning.billing.entitlement.events.IEventLyfecycle.IEventLyfecycleState; +import com.ning.billing.entitlement.events.phase.IPhaseEvent; +import com.ning.billing.entitlement.events.phase.PhaseEvent; +import com.ning.billing.entitlement.events.user.ApiEventType; +import com.ning.billing.entitlement.events.user.IUserEvent; +import com.ning.billing.entitlement.glue.IEngineConfig; +import com.ning.billing.util.clock.IClock; + +public class EntitlementDaoMemoryMock implements IEntitlementDao, IEntitlementDaoMock { + + protected final static Logger log = LoggerFactory.getLogger(IEntitlementDao.class); + + private final List bundles; + private final List subscriptions; + private final TreeSet events; + private final IClock clock; + private final IEngineConfig config; + + @Inject + public EntitlementDaoMemoryMock(IClock clock, IEngineConfig config) { + super(); + this.clock = clock; + this.config = config; + this.bundles = new ArrayList(); + this.subscriptions = new ArrayList(); + this.events = new TreeSet(); + } + + @Override + public void reset() { + bundles.clear(); + subscriptions.clear(); + events.clear(); + } + + @Override + public List getSubscriptionBundleForAccount(UUID accountId) { + List results = new ArrayList(); + for (ISubscriptionBundle cur : bundles) { + if (cur.getAccountId().equals(accountId)) { + results.add(cur); + } + } + return results; + } + + @Override + public ISubscriptionBundle getSubscriptionBundleFromId(UUID bundleId) { + for (ISubscriptionBundle cur : bundles) { + if (cur.getId().equals(bundleId)) { + return cur; + } + } + return null; + } + + @Override + public ISubscriptionBundle createSubscriptionBundle(SubscriptionBundle bundle) { + bundles.add(bundle); + return getSubscriptionBundleFromId(bundle.getId()); + } + + @Override + public ISubscription getSubscriptionFromId(UUID subscriptionId) { + for (ISubscription cur : subscriptions) { + if (cur.getId().equals(subscriptionId)) { + return buildSubscription((Subscription) cur); + } + } + return null; + } + + @Override + public ISubscription createSubscription(Subscription subscription, List initalEvents) { + + synchronized(events) { + events.addAll(initalEvents); + } + ISubscription updatedSubscription = buildSubscription(subscription); + subscriptions.add(updatedSubscription); + return updatedSubscription; + } + + @Override + public List getSubscriptions(UUID bundleId) { + + List results = new ArrayList(); + for (ISubscription cur : subscriptions) { + if (cur.getBundleId().equals(bundleId)) { + results.add(buildSubscription((Subscription) cur)); + } + } + return results; + } + + @Override + public List getEventsForSubscription(UUID subscriptionId) { + synchronized(events) { + List results = new LinkedList(); + for (IEvent cur : events) { + if (cur.getSubscriptionId().equals(subscriptionId)) { + results.add(cur); + } + } + return results; + } + } + + @Override + public List getPendingEventsForSubscription(UUID subscriptionId) { + synchronized(events) { + List results = new LinkedList(); + for (IEvent cur : events) { + if (cur.isActive() && + cur.getProcessingState() == IEventLyfecycleState.AVAILABLE && + cur.getSubscriptionId().equals(subscriptionId)) { + results.add(cur); + } + } + return results; + } + } + + + @Override + public ISubscription getBaseSubscription(UUID bundleId) { + for (ISubscription cur : subscriptions) { + if (cur.getBundleId().equals(bundleId) && + cur.getCurrentPlan().getProduct().getCategory() == ProductCategory.BASE) { + return buildSubscription((Subscription) cur); + } + } + return null; + } + + @Override + public void createNextPhaseEvent(UUID subscriptionId, IEvent nextPhase) { + cancelNextPhaseEvent(subscriptionId); + insertEvent(nextPhase); + } + + + @Override + public List getEventsReady(UUID ownerId, int sequenceId) { + synchronized(events) { + List readyList = new LinkedList(); + for (IEvent cur : events) { + if (cur.isAvailableForProcessing(clock.getUTCNow())) { + + if (cur.getOwner() != null) { + log.warn(String.format("EventProcessor %s stealing event %s from %s", ownerId, cur, cur.getOwner())); + } + cur.setOwner(ownerId); + cur.setNextAvailableDate(clock.getUTCNow().plus(config.getDaoClaimTimeMs())); + cur.setProcessingState(IEventLyfecycleState.IN_PROCESSING); + readyList.add(cur); + } + } + Collections.sort(readyList); + return readyList; + } + } + + @Override + public void clearEventsReady(UUID ownerId, List cleared) { + synchronized(events) { + for (IEvent cur : cleared) { + if (cur.getOwner().equals(ownerId)) { + cur.setProcessingState(IEventLyfecycleState.PROCESSED); + } else { + log.warn(String.format("EventProcessor %s trying to clear event %s that it does not own", ownerId, cur)); + } + } + } + } + + private ISubscription buildSubscription(Subscription in) { + return new Subscription(in.getId(), in.getBundleId(), in.getCategory(), in.getBundleStartDate(), + in.getStartDate(), in.getChargedThroughDate(), in.getPaidThroughDate(), in.getActiveVersion()); + } + + @Override + public void updateSubscription(Subscription subscription) { + + boolean found = false; + Iterator it = subscriptions.iterator(); + while (it.hasNext()) { + ISubscription cur = it.next(); + if (cur.getId().equals(subscription.getId())) { + found = true; + it.remove(); + break; + } + } + if (found) { + subscriptions.add(subscription); + } + } + + @Override + public void cancelSubscription(UUID subscriptionId, IEvent cancelEvent) { + synchronized (cancelEvent) { + cancelNextPhaseEvent(subscriptionId); + insertEvent(cancelEvent); + } + } + + @Override + public void changePlan(UUID subscriptionId, List changeEvents) { + synchronized(events) { + cancelNextChangeEvent(subscriptionId); + cancelNextPhaseEvent(subscriptionId); + events.addAll(changeEvents); + } + } + + private void insertEvent(IEvent event) { + synchronized(events) { + events.add(event); + } + } + + private void cancelNextPhaseEvent(UUID subscriptionId) { + + ISubscription curSubscription = getSubscriptionFromId(subscriptionId); + if (curSubscription.getCurrentPhase() == null || + curSubscription.getCurrentPhase().getDuration().getUnit() == TimeUnit.UNLIMITED) { + return; + } + + synchronized(events) { + + Iterator it = events.descendingIterator(); + while (it.hasNext()) { + IEvent cur = it.next(); + if (cur.getSubscriptionId() != subscriptionId) { + continue; + } + if (cur.getType() == EventType.PHASE && + cur.getProcessingState() == IEventLyfecycleState.AVAILABLE) { + cur.deactivate(); + break; + } + } + } + } + + + private void cancelNextChangeEvent(UUID subscriptionId) { + + synchronized(events) { + + Iterator it = events.descendingIterator(); + while (it.hasNext()) { + IEvent cur = it.next(); + if (cur.getSubscriptionId() != subscriptionId) { + continue; + } + if (cur.getType() == EventType.API_USER && + ApiEventType.CHANGE == ((IUserEvent) cur).getEventType() && + cur.getProcessingState() == IEventLyfecycleState.AVAILABLE) { + cur.deactivate(); + break; + } + } + } + } + + @Override + public void uncancelSubscription(UUID subscriptionId, List uncancelEvents) { + + synchronized (events) { + boolean foundCancel = false; + Iterator it = events.descendingIterator(); + while (it.hasNext()) { + IEvent cur = it.next(); + if (cur.getSubscriptionId() != subscriptionId) { + continue; + } + if (cur.getType() == EventType.API_USER && + ((IUserEvent) cur).getEventType() == ApiEventType.CANCEL) { + cur.deactivate(); + foundCancel = true; + break; + } + } + if (foundCancel) { + for (IEvent cur : uncancelEvents) { + insertEvent(cur); + } + } + } + } +} diff --git a/entitlement/src/test/java/com/ning/billing/entitlement/engine/dao/EntitlementDaoSqlMock.java b/entitlement/src/test/java/com/ning/billing/entitlement/engine/dao/EntitlementDaoSqlMock.java new file mode 100644 index 0000000000..081405916b --- /dev/null +++ b/entitlement/src/test/java/com/ning/billing/entitlement/engine/dao/EntitlementDaoSqlMock.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.entitlement.engine.dao; + +import org.skife.jdbi.v2.DBI; +import org.skife.jdbi.v2.Transaction; +import org.skife.jdbi.v2.TransactionStatus; +import org.skife.jdbi.v2.sqlobject.SqlUpdate; +import org.skife.jdbi.v2.sqlobject.mixins.CloseMe; +import org.skife.jdbi.v2.sqlobject.mixins.Transactional; + +import com.google.inject.Inject; +import com.ning.billing.entitlement.glue.IEngineConfig; +import com.ning.billing.util.clock.IClock; + +public class EntitlementDaoSqlMock extends EntitlementDao implements IEntitlementDaoMock { + + private final ResetSqlDao resetDao; + + @Inject + public EntitlementDaoSqlMock(DBI dbi, IClock clock, IEngineConfig config) { + super(dbi, clock, config); + this.resetDao = dbi.onDemand(ResetSqlDao.class); + } + + @Override + public void reset() { + resetDao.inTransaction(new Transaction() { + + @Override + public Void inTransaction(ResetSqlDao dao, TransactionStatus status) + throws Exception { + resetDao.resetEvents(); + resetDao.resetClaimedEvents(); + resetDao.resetSubscriptions(); + resetDao.resetBundles(); + return null; + } + }); + } + + public static interface ResetSqlDao extends Transactional, CloseMe { + + @SqlUpdate("truncate table events") + public void resetEvents(); + + @SqlUpdate("truncate table claimed_events") + public void resetClaimedEvents(); + + @SqlUpdate("truncate table subscriptions") + public void resetSubscriptions(); + + @SqlUpdate("truncate table bundles") + public void resetBundles(); + } + +} diff --git a/entitlement/src/test/java/com/ning/billing/entitlement/engine/dao/IEntitlementDaoMock.java b/entitlement/src/test/java/com/ning/billing/entitlement/engine/dao/IEntitlementDaoMock.java new file mode 100644 index 0000000000..6dc68fe268 --- /dev/null +++ b/entitlement/src/test/java/com/ning/billing/entitlement/engine/dao/IEntitlementDaoMock.java @@ -0,0 +1,21 @@ +/* + * Copyright 2010-2011 Ning, Inc. + * + * Ning licenses this file to you under the Apache License, version 2.0 + * (the "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package com.ning.billing.entitlement.engine.dao; + +public interface IEntitlementDaoMock { + public void reset(); +} diff --git a/entitlement/src/test/java/com/ning/billing/entitlement/engine/dao/TestEntitlementDao.java b/entitlement/src/test/java/com/ning/billing/entitlement/engine/dao/TestEntitlementDao.java new file mode 100644 index 0000000000..0694faca80 --- /dev/null +++ b/entitlement/src/test/java/com/ning/billing/entitlement/engine/dao/TestEntitlementDao.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.entitlement.engine.dao; + +import java.util.List; +import java.util.UUID; + +import org.joda.time.DateTime; +import org.skife.jdbi.v2.DBI; +import org.skife.jdbi.v2.Transaction; +import org.skife.jdbi.v2.TransactionStatus; +import org.testng.annotations.Test; + +import com.google.inject.Guice; +import com.google.inject.Injector; +import com.google.inject.Stage; +import com.ning.billing.catalog.api.BillingPeriod; +import com.ning.billing.catalog.api.IPlan; +import com.ning.billing.catalog.api.ProductCategory; +import com.ning.billing.entitlement.api.user.Subscription; +import com.ning.billing.entitlement.api.user.TestUserApiBase; +import com.ning.billing.entitlement.events.IEvent; +import com.ning.billing.entitlement.events.user.ApiEventCreate; +import com.ning.billing.entitlement.glue.EngineModuleMemoryMock; + +public class TestEntitlementDao extends TestUserApiBase { + + + private void sleep() { + try { + Thread.sleep(100); + } catch (InterruptedException ignore) { + } + } + @Test(enabled=false) + public void testEventDao() { + + UUID me = UUID.randomUUID(); + + String dbiString = "jdbc:mysql://127.0.0.1:3306/killbill?createDatabaseIfNotExist=true"; + DBI dbi = new DBI(dbiString, "root", "root"); + IEventSqlDao dao = dbi.onDemand(IEventSqlDao.class); + + String productName = "Shotgun"; + BillingPeriod term = BillingPeriod.MONTHLY; + String planSetName = "standard"; + + DateTime now = new DateTime(); + IPlan plan = catalog.getPlan(productName, term, planSetName); + IEvent event = new ApiEventCreate(UUID.randomUUID(), now, now, plan,planSetName, now, now, 1); + dao.insertEvent(event); + + sleep(); + + List events = dao.getReadyEvents(new DateTime().toDate(), 1); + //for (IEvent cur : events) { + + //} +// @Bind("owner") String owner, @Bind("next_available") Date nextAvailable, @Bind("id") String eventId, @Bind("now") Date now); + dao.claimEvent(me.toString(), now.plusDays(1).toDate(), events.get(0).getId().toString(), now.toDate()); + + dao.clearEvent(events.get(0).getId().toString(), me.toString()); + + System.out.println("youpi"); + } + + @Test(enabled=false) + public void testSubscriptionDao() { + String dbiString = "jdbc:mysql://127.0.0.1:3306/killbill?createDatabaseIfNotExist=true"; + DBI dbi = new DBI(dbiString, "root", "root"); + ISubscriptionSqlDao dao = dbi.onDemand(ISubscriptionSqlDao.class); + + DateTime now = new DateTime(); + Subscription sub = new Subscription(UUID.randomUUID(), UUID.randomUUID(), ProductCategory.BASE, now, now, now, now, 1); + dao.insertSubscription(sub); + + } + + @Test(enabled=true) + public void testMixin() { + + String dbiString = "jdbc:mysql://127.0.0.1:3306/killbill?createDatabaseIfNotExist=true"; + DBI dbi = new DBI(dbiString, "root", "root"); + ISubscriptionSqlDao dao = dbi.onDemand(ISubscriptionSqlDao.class); + + DateTime now = new DateTime(); + final Subscription sub = new Subscription(UUID.randomUUID(), UUID.randomUUID(), ProductCategory.BASE, now, now, now, now, 1); + + String productName = "Shotgun"; + BillingPeriod term = BillingPeriod.MONTHLY; + String planSetName = "standard"; + + IPlan plan = catalog.getPlan(productName, term, planSetName); + final IEvent event = new ApiEventCreate(UUID.randomUUID(), now, now, plan, planSetName, now, now, 1); + + dao.inTransaction(new Transaction() { + + @Override + public Void inTransaction(ISubscriptionSqlDao subscriptionDao, + TransactionStatus status) throws Exception { + + subscriptionDao.insertSubscription(sub); + IEventSqlDao eventDao = subscriptionDao.become(IEventSqlDao.class); + eventDao.insertEvent(event); + return null; + } + }); + + + } + @Override + protected Injector getInjector() { + return Guice.createInjector(Stage.DEVELOPMENT, new EngineModuleMemoryMock()); + } + +} diff --git a/entitlement/src/test/java/com/ning/billing/entitlement/glue/EngineModuleMemoryMock.java b/entitlement/src/test/java/com/ning/billing/entitlement/glue/EngineModuleMemoryMock.java new file mode 100644 index 0000000000..0f422f1460 --- /dev/null +++ b/entitlement/src/test/java/com/ning/billing/entitlement/glue/EngineModuleMemoryMock.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.entitlement.glue; + + +import com.ning.billing.entitlement.engine.core.ApiEventProcessorMemoryMock; +import com.ning.billing.entitlement.engine.core.IApiEventProcessor; +import com.ning.billing.entitlement.engine.dao.EntitlementDaoMemoryMock; +import com.ning.billing.entitlement.engine.dao.IEntitlementDao; +import com.ning.billing.entitlement.glue.EngineModule; +import com.ning.billing.util.clock.Clock; +import com.ning.billing.util.clock.ClockMock; +import com.ning.billing.util.clock.IClock; + + +public class EngineModuleMemoryMock extends EngineModule { + @Override + protected void installApiEventProcessor() { + bind(IApiEventProcessor.class).to(ApiEventProcessorMemoryMock.class).asEagerSingleton(); + } + + @Override + protected void installEntitlementDao() { + bind(IEntitlementDao.class).to(EntitlementDaoMemoryMock.class).asEagerSingleton(); + } + + @Override + protected void installClock() { + bind(IClock.class).to(ClockMock.class).asEagerSingleton(); + } + + @Override + protected void installDBI() { + } +} diff --git a/entitlement/src/test/java/com/ning/billing/entitlement/glue/EngineModuleSqlMock.java b/entitlement/src/test/java/com/ning/billing/entitlement/glue/EngineModuleSqlMock.java new file mode 100644 index 0000000000..3e8a0b4144 --- /dev/null +++ b/entitlement/src/test/java/com/ning/billing/entitlement/glue/EngineModuleSqlMock.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.entitlement.glue; + +import com.ning.billing.entitlement.engine.dao.EntitlementDaoSqlMock; +import com.ning.billing.entitlement.engine.dao.IEntitlementDao; +import com.ning.billing.util.clock.ClockMock; +import com.ning.billing.util.clock.IClock; + +public class EngineModuleSqlMock extends EngineModule { + + @Override + protected void installEntitlementDao() { + bind(IEntitlementDao.class).to(EntitlementDaoSqlMock.class).asEagerSingleton(); + } + + @Override + protected void installClock() { + bind(IClock.class).to(ClockMock.class).asEagerSingleton(); + } +} diff --git a/entitlement/src/test/resources/entitlement.properties b/entitlement/src/test/resources/entitlement.properties new file mode 100644 index 0000000000..372ce23b20 --- /dev/null +++ b/entitlement/src/test/resources/entitlement.properties @@ -0,0 +1,6 @@ +killbill.entitlement.catalog.config.file=src/test/resources/testInput.xml +killbill.entitlement.dao.claim.time=60000 +killbill.entitlement.dao.ready.max=1 +killbill.entitlement.engine.notifications.sleep=500 + + diff --git a/entitlement/src/test/resources/log4j.xml b/entitlement/src/test/resources/log4j.xml new file mode 100644 index 0000000000..4f9885bcf3 --- /dev/null +++ b/entitlement/src/test/resources/log4j.xml @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/entitlement/src/test/resources/testInput.xml b/entitlement/src/test/resources/testInput.xml new file mode 100644 index 0000000000..7ba09ed1c6 --- /dev/null +++ b/entitlement/src/test/resources/testInput.xml @@ -0,0 +1,612 @@ + + + + + + + 2011-10-08T00:00:00+00:00 + + + USD + EUR + GBP + + + + + + + + + + Firearms + BASE + + Telescopic-Scope + Laser-Scope + + + + Firearms + BASE + + + Firearms + BASE + + Telescopic-Scope + + + Laser-Scope + + + + Firearms + ADD_ON + + + Firearms + ADD_ON + + + Firearms + ADD_ON + + + Firearms + ADD_ON + + + Firearms + ADD_ON + + + Blade + BASE + + + Blade + BASE + + + + + + + Pistol + Shotgun + Assault-Rifle + + + + DEFAULT + END_OF_TERM + + + TERM_FROM_SHORT_TO_LONG + IMMEDIATE + + + PRODUCT_FROM_LOW_TO_HIGH + IMMEDIATE + + + MONTHLY + Assault-Rifle + MONTHLY + END_OF_TERM + + + rescue + END_OF_TERM + + + TRIAL + IMMEDIATE + + + END_OF_TERM + + + TRIAL + IMMEDIATE + + + + + + Pistol + + + + DAYS + 30 + + + + + + + + UNLIMITED + + MONTHLY + + GBP29.95 + EUR29.95 + USD29.95 + + + START_OF_BUNDLE + ACCOUNT + + + Shotgun + + + + DAYS + 30 + + + + + + + + UNLIMITED + -1 + + MONTHLY + + USD249.95 + EUR149.95 + GBP169.95 + + + START_OF_BUNDLE + ACCOUNT + + + Assault-Rifle + + + + DAYS + 30 + + + + + + + + UNLIMITED + + MONTHLY + + USD599.95 + EUR349.95 + GBP399.95 + + + START_OF_BUNDLE + ACCOUNT + + + Pistol + + + + DAYS + 30 + + + + + + + + UNLIMITED + + ANNUAL + + USD199.95 + EUR199.95 + GBP199.95 + + + START_OF_BUNDLE + SUBSCRIPTION + + + Shotgun + + + + DAYS + 30 + + + + + + + + UNLIMITED + + ANNUAL + + USD2399.95 + EUR1499.95 + GBP1699.95 + + + START_OF_BUNDLE + SUBSCRIPTION + + + Assault-Rifle + + + + DAYS + 30 + + + + + + + + UNLIMITED + + ANNUAL + + USD5999.95 + EUR3499.95 + GBP3999.95 + + + START_OF_BUNDLE + SUBSCRIPTION + + + Pistol + + + + DAYS + 30 + + + + + + + MONTHS + 6 + + MONTHLY + + USD9.95 + EUR9.95 + GBP9.95 + + + + + + UNLIMITED + + ANNUAL + + USD199.95 + EUR199.95 + GBP199.95 + + + START_OF_BUNDLE + SUBSCRIPTION + + + Shotgun + + + + DAYS + 30 + + + + + + + MONTHS + 6 + + MONTHLY + + USD19.95 + EUR49.95 + GBP69.95 + + + + + + UNLIMITED + + ANNUAL + + USD2399.95 + EUR1499.95 + GBP1699.95 + + + START_OF_BUNDLE + SUBSCRIPTION + + + Assault-Rifle + + + + DAYS + 30 + + + + + + + MONTHS + 6 + + MONTHLY + + USD99.95 + EUR99.95 + GBP99.95 + + + + + + UNLIMITED + + ANNUAL + + USD5999.95 + EUR3499.95 + GBP3999.95 + + + START_OF_BUNDLE + SUBSCRIPTION + + + Laser-Scope + + + UNLIMITED + + MONTHLY + + USD1999.95 + EUR1499.95 + GBP1999.95 + + + START_OF_BUNDLE + BUNDLE + + + Telescopic-Scope + + + UNLIMITED + + MONTHLY + + USD999.95 + EUR499.95 + GBP999.95 + + + START_OF_BUNDLE + BUNDLE + + + Extra-Ammo + + + UNLIMITED + + MONTHLY + + USD999.95 + EUR499.95 + GBP999.95 + + + START_OF_BUNDLE + BUNDLE + -1 + + + Holster + + + + DAYS + 30 + + + + + + + + UNLIMITED + + ANNUAL + + USD199.95 + EUR199.95 + GBP199.95 + + + START_OF_BUNDLE + BUNDLE + + + Holster + + + + DAYS + 30 + + + + + + + + UNLIMITED + + ANNUAL + + USD199.95 + EUR199.95 + GBP199.95 + + + START_OF_SUBSCRIPTION + BUNDLE + + + Assault-Rifle + + + + YEARS + 1 + + ANNUAL + + USD5999.95 + EUR3499.95 + GBP3999.95 + + + + + + UNLIMITED + + ANNUAL + + USD5999.95 + EUR3499.95 + GBP3999.95 + + + START_OF_SUBSCRIPTION + SUBSCRIPTION + + + Refurbish-Maintenance + + + MONTHS + 12 + + MONTHLY + + USD199.95 + EUR199.95 + GBP199.95 + + + USD599.95 + EUR599.95 + GBP599.95 + + + START_OF_SUBSCRIPTION + BUNDLE + + + + + true + + pistol-monthly + shotgun-monthly + assault-rifle-monthly + pistol-annual + shotgun-annual + assault-rifle-annual + laser-scope-monthly + telescopic-scope-monthly + extra-ammo-monthly + holster-monthly-regular + refurbish-maintenance + + + + + pistol-monthly + shotgun-monthly + assault-rifle-monthly + pistol-annual-gunclub-discount + shotgun-annual-gunclub-discount + assault-rifle-annual-gunclub-discount + holster-monthly-special + + + + + assault-rifle-annual-rescue + + + + + diff --git a/invoice/pom.xml b/invoice/pom.xml new file mode 100644 index 0000000000..d1080e6d05 --- /dev/null +++ b/invoice/pom.xml @@ -0,0 +1,43 @@ + + + + + 4.0.0 + + com.ning.billing + killbill + 0.0.3-SNAPSHOT + ../pom.xml + + killbill-invoice + killbill-invoice + jar + + + com.ning.billing + killbill-api + 0.0.3-SNAPSHOT + + + org.testng + testng + test + + + + + diff --git a/invoice/src/main/java/com/ning/billing/invoice/model/BillingModeBase.java b/invoice/src/main/java/com/ning/billing/invoice/model/BillingModeBase.java new file mode 100644 index 0000000000..094ab2d716 --- /dev/null +++ b/invoice/src/main/java/com/ning/billing/invoice/model/BillingModeBase.java @@ -0,0 +1,84 @@ +/* + * Copyright 2010-2011 Ning, Inc. + * + * Ning licenses this file to you under the Apache License, version 2.0 + * (the "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF 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.BillingPeriod; +import org.joda.time.DateTime; + +import java.math.BigDecimal; + +public abstract class BillingModeBase implements IBillingMode { + @Override + public BigDecimal calculateNumberOfBillingCycles(final DateTime startDate, final DateTime endDate, final DateTime targetDate, final int billingCycleDay, final BillingPeriod billingPeriod) throws InvalidDateSequenceException { + if (endDate.isBefore(startDate)) {throw new InvalidDateSequenceException();} + if (targetDate.isBefore(startDate)) {throw new InvalidDateSequenceException();} + + BigDecimal precedingProRation = calculateProRationBeforeFirstBillingPeriod(startDate, billingCycleDay, billingPeriod); + + DateTime firstBillCycleDate = calculateBillingCycleDateOnOrAfter(startDate, billingCycleDay); + DateTime endBillCycleDate; + BigDecimal trailingProRation; + BigDecimal numberOfBillingPeriods; + + DateTime effectiveEndDate = calculateEffectiveEndDate(firstBillCycleDate, targetDate, endDate, billingPeriod); + endBillCycleDate = calculateLastBillingCycleDateBefore(effectiveEndDate, firstBillCycleDate, billingCycleDay, billingPeriod); + numberOfBillingPeriods = calculateNumberOfWholeBillingPeriods(firstBillCycleDate, endBillCycleDate, billingPeriod); + + trailingProRation = calculateProRationAfterLastBillingCycleDate(effectiveEndDate, endBillCycleDate, billingPeriod); + + return precedingProRation.add(numberOfBillingPeriods).add(trailingProRation); + } + + @Override + public BigDecimal calculateNumberOfBillingCycles(final DateTime startDate, final DateTime targetDate, final int billingCycleDay, final BillingPeriod billingPeriod) throws InvalidDateSequenceException { + if (targetDate.isBefore(startDate)) {throw new InvalidDateSequenceException();} + + BigDecimal precedingProRation = calculateProRationBeforeFirstBillingPeriod(startDate, billingCycleDay, billingPeriod); + + DateTime firstBillCycleDate = calculateBillingCycleDateOnOrAfter(startDate, billingCycleDay); + DateTime endBillCycleDate = calculateBillingCycleDateAfter(targetDate, firstBillCycleDate, billingCycleDay, billingPeriod); + BigDecimal numberOfBillingPeriods = calculateNumberOfWholeBillingPeriods(firstBillCycleDate, endBillCycleDate, billingPeriod); + + if (targetDate.equals(endBillCycleDate)) { + numberOfBillingPeriods = numberOfBillingPeriods.add(BigDecimal.ONE); + } + + return precedingProRation.add(numberOfBillingPeriods); +} + + protected DateTime buildDate(final int year, final int month, final int day) { + return new DateTime(year, month, day, 0, 0, 0, 0); + } + + protected boolean isBetween(DateTime targetDate, DateTime startDate, DateTime endDate) { + return !(targetDate.isBefore(startDate) || !targetDate.isBefore(endDate)); + } + + protected abstract DateTime calculateEffectiveEndDate(DateTime billCycleDate, DateTime targetDate, DateTime endDate, BillingPeriod billingPeriod); + + protected abstract BigDecimal calculateNumberOfWholeBillingPeriods(final DateTime startDate, final DateTime endDate, final BillingPeriod billingPeriod); + + protected abstract DateTime calculateBillingCycleDateOnOrAfter(final DateTime date, final int billingCycleDay); + + protected abstract DateTime calculateBillingCycleDateAfter(final DateTime date, final DateTime billingCycleDate, final int billingCycleDay, final BillingPeriod billingPeriod); + + protected abstract DateTime calculateLastBillingCycleDateBefore(final DateTime date, final DateTime previousBillCycleDate, final int billingCycleDay, final BillingPeriod billingPeriod); + + protected abstract BigDecimal calculateProRationBeforeFirstBillingPeriod(final DateTime startDate, final int billingCycleDay, final BillingPeriod billingPeriod); + + protected abstract BigDecimal calculateProRationAfterLastBillingCycleDate(final DateTime endDate, final DateTime previousBillThroughDate, final BillingPeriod billingPeriod); +} 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 new file mode 100644 index 0000000000..c99ff5a21f --- /dev/null +++ b/invoice/src/main/java/com/ning/billing/invoice/model/DefaultInvoiceGenerator.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.invoice.model; + +import com.ning.billing.catalog.api.BillingPeriod; +import com.ning.billing.catalog.api.Currency; +import com.ning.billing.entitlement.api.billing.BillingMode; +import com.ning.billing.entitlement.api.billing.IBillingEvent; +import com.ning.billing.invoice.api.BillingEventSet; +import org.joda.time.DateTime; + +import java.math.BigDecimal; +import java.util.Collections; + +public class DefaultInvoiceGenerator implements IInvoiceGenerator { + @Override + public Invoice generateInvoice(BillingEventSet events) { + if (events == null) {return new Invoice();} + if (events.size() == 0) {return new Invoice();} + + Currency targetCurrency = events.getTargetCurrency(); + Invoice invoice = new Invoice(targetCurrency); + DateTime targetDate = events.getTargetDate(); + + // sort events; this relies on the sort order being by subscription id then start date + Collections.sort(events); + + // for each event, process it either as a terminated event (if there's a subsequent event) + // ...or as a non-terminated event (if no subsequent event exists) + for (int i = 0; i < (events.size() - 1); i++) { + IBillingEvent thisEvent = events.get(i); + IBillingEvent nextEvent = events.get(i + 1); + + if (thisEvent.getSubscriptionId() == nextEvent.getSubscriptionId()) { + processEvents(thisEvent, nextEvent, invoice, targetDate, targetCurrency); + } else { + processEvent(thisEvent, invoice, targetDate, targetCurrency); + } + } + + // process the last item in the event set + processEvent(events.getLast(), invoice, targetDate, targetCurrency); + + return invoice; + } + + private void processEvent(IBillingEvent event, Invoice invoice, DateTime targetDate, Currency targetCurrency) { + BigDecimal rate = event.getPrice(targetCurrency); + BigDecimal invoiceItemAmount = calculateInvoiceItemAmount(event, targetDate, rate); + + addInvoiceItem(invoice, invoiceItemAmount); + } + + private void processEvents(IBillingEvent firstEvent, IBillingEvent secondEvent, Invoice invoice, DateTime targetDate, Currency targetCurrency) { + BigDecimal rate = firstEvent.getPrice(targetCurrency); + BigDecimal invoiceItemAmount = calculateInvoiceItemAmount(firstEvent, secondEvent, targetDate, rate); + + addInvoiceItem(invoice, invoiceItemAmount); + } + + private void addInvoiceItem(Invoice invoice, BigDecimal amount) { + if (!amount.equals(BigDecimal.ZERO)) { + InvoiceItem item = new InvoiceItem(amount); + invoice.add(item); + } + } + + private BigDecimal calculateInvoiceItemAmount(IBillingEvent event, DateTime targetDate, BigDecimal rate){ + IBillingMode billingMode = getBillingMode(event.getBillingMode()); + DateTime startDate = event.getEffectiveDate(); + int billingCycleDay = event.getBillCycleDay(); + BillingPeriod billingPeriod = event.getBillingPeriod(); + + try { + BigDecimal numberOfBillingCycles; + numberOfBillingCycles = billingMode.calculateNumberOfBillingCycles(startDate, targetDate, billingCycleDay, billingPeriod); + return numberOfBillingCycles.multiply(rate); + } catch (InvalidDateSequenceException e) { + // TODO: Jeff -- log issue + return BigDecimal.ZERO; + } + } + + private BigDecimal calculateInvoiceItemAmount(IBillingEvent firstEvent, IBillingEvent secondEvent, DateTime targetDate, BigDecimal rate) { + IBillingMode billingMode = getBillingMode(firstEvent.getBillingMode()); + DateTime startDate = firstEvent.getEffectiveDate(); + int billingCycleDay = firstEvent.getBillCycleDay(); + BillingPeriod billingPeriod = firstEvent.getBillingPeriod(); + + DateTime endDate = secondEvent.getEffectiveDate(); + + try { + BigDecimal numberOfBillingCycles; + numberOfBillingCycles = billingMode.calculateNumberOfBillingCycles(startDate, endDate, targetDate, billingCycleDay, billingPeriod); + return numberOfBillingCycles.multiply(rate); + } catch (InvalidDateSequenceException e) { + // TODO: Jeff -- log issue + return BigDecimal.ZERO; + } + } + + private IBillingMode getBillingMode(BillingMode billingMode) { + return new InAdvanceBillingMode(); + } +} \ No newline at end of file diff --git a/invoice/src/main/java/com/ning/billing/invoice/model/IBillingMode.java b/invoice/src/main/java/com/ning/billing/invoice/model/IBillingMode.java new file mode 100644 index 0000000000..e716e47b72 --- /dev/null +++ b/invoice/src/main/java/com/ning/billing/invoice/model/IBillingMode.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.model; + +import com.ning.billing.catalog.api.BillingPeriod; +import org.joda.time.DateTime; + +import java.math.BigDecimal; + +public interface IBillingMode { + BigDecimal calculateNumberOfBillingCycles(DateTime startDate, DateTime endDate, DateTime targetDate, int billingCycleDay, BillingPeriod billingPeriod) throws InvalidDateSequenceException; + + BigDecimal calculateNumberOfBillingCycles(DateTime startDate, DateTime targetDate, int billingCycleDay, BillingPeriod billingPeriod) throws InvalidDateSequenceException; +} \ No newline at end of file diff --git a/invoice/src/main/java/com/ning/billing/invoice/model/IInvoiceGenerator.java b/invoice/src/main/java/com/ning/billing/invoice/model/IInvoiceGenerator.java new file mode 100644 index 0000000000..6d03a9b2ff --- /dev/null +++ b/invoice/src/main/java/com/ning/billing/invoice/model/IInvoiceGenerator.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.model; + +import com.ning.billing.invoice.api.BillingEventSet; + +// TODO: Jeff -- Determine what the consequence of account-level currency changes are on repair scenarios +public interface IInvoiceGenerator { + public Invoice generateInvoice(BillingEventSet events); +} diff --git a/invoice/src/main/java/com/ning/billing/invoice/model/InAdvanceBillingMode.java b/invoice/src/main/java/com/ning/billing/invoice/model/InAdvanceBillingMode.java new file mode 100644 index 0000000000..caf7e3da67 --- /dev/null +++ b/invoice/src/main/java/com/ning/billing/invoice/model/InAdvanceBillingMode.java @@ -0,0 +1,148 @@ +/* + * Copyright 2010-2011 Ning, Inc. + * + * Ning licenses this file to you under the Apache License, version 2.0 + * (the "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF 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.BillingPeriod; +import org.joda.time.DateTime; +import org.joda.time.Days; +import org.joda.time.Months; + +import java.math.BigDecimal; + +public class InAdvanceBillingMode extends BillingModeBase { + private static final int ROUNDING_METHOD = InvoicingConfiguration.getRoundingMethod(); + private static final int NUMBER_OF_DECIMALS = InvoicingConfiguration.getNumberOfDecimals(); + + @Override + protected BigDecimal calculateNumberOfWholeBillingPeriods(final DateTime startDate, final DateTime endDate, final BillingPeriod billingPeriod) { + int numberOfMonths = Months.monthsBetween(startDate, endDate).getMonths(); + BigDecimal numberOfMonthsInPeriod = new BigDecimal(billingPeriod.getNumberOfMonths()); + return new BigDecimal(numberOfMonths).divide(numberOfMonthsInPeriod, 0, ROUNDING_METHOD); + } + + @Override + protected DateTime calculateBillingCycleDateOnOrAfter(final DateTime date, final int billingCycleDay) { + int lastDayOfMonth = date.dayOfMonth().getMaximumValue(); + + DateTime proposedDate; + if (billingCycleDay > lastDayOfMonth) { + proposedDate = buildDate(date.getYear(), date.getMonthOfYear(), lastDayOfMonth); + } else { + proposedDate = buildDate(date.getYear(), date.getMonthOfYear(), billingCycleDay); + } + + while (proposedDate.isBefore(date)) { + proposedDate = proposedDate.plusMonths(1); + } + + return proposedDate; + } + + protected DateTime calculateBillingCycleDateAfter(final DateTime date, final DateTime billingCycleDate, final int billingCycleDay, final BillingPeriod billingPeriod) { + DateTime proposedDate = billingCycleDate; + + while (proposedDate.isBefore(date)) { + proposedDate = proposedDate.plusMonths(billingPeriod.getNumberOfMonths()); + + if (proposedDate.dayOfMonth().get() != billingCycleDay) { + int lastDayOfMonth = proposedDate.dayOfMonth().getMaximumValue(); + + if (lastDayOfMonth < billingCycleDay) { + proposedDate = buildDate(proposedDate.getYear(), proposedDate.getMonthOfYear(), lastDayOfMonth); + } else { + proposedDate = buildDate(proposedDate.getYear(), proposedDate.getMonthOfYear(), billingCycleDay); + } + } + } + + return proposedDate; + } + + @Override + protected DateTime calculateLastBillingCycleDateBefore(final DateTime date, final DateTime previousBillCycleDate, final int billingCycleDay, final BillingPeriod billingPeriod) { + DateTime proposedDate = previousBillCycleDate; + proposedDate = proposedDate.plusMonths(billingPeriod.getNumberOfMonths()); + + if (!proposedDate.isBefore(date)) {return previousBillCycleDate;} + + while (proposedDate.isBefore(date)) { + proposedDate = proposedDate.plusMonths(billingPeriod.getNumberOfMonths()); + } + + proposedDate = proposedDate.plusMonths(-billingPeriod.getNumberOfMonths()); + + if (proposedDate.dayOfMonth().get() < billingCycleDay) { + int lastDayOfTheMonth = proposedDate.dayOfMonth().getMaximumValue(); + if (lastDayOfTheMonth < billingCycleDay) { + return buildDate(proposedDate.getYear(), proposedDate.getMonthOfYear(), lastDayOfTheMonth); + } else { + return buildDate(proposedDate.getYear(), proposedDate.getMonthOfYear(), billingCycleDay); + } + } else { + return proposedDate; + } + } + + @Override + protected BigDecimal calculateProRationBeforeFirstBillingPeriod(final DateTime startDate, final int billingCycleDay, final BillingPeriod billingPeriod) { + DateTime nextBillingCycleDate = calculateBillingCycleDateOnOrAfter(startDate, billingCycleDay); + DateTime previousBillingCycleDate = nextBillingCycleDate.plusMonths(-billingPeriod.getNumberOfMonths()); + + BigDecimal daysInPeriod = new BigDecimal(Days.daysBetween(previousBillingCycleDate, nextBillingCycleDate).getDays()); + BigDecimal days = new BigDecimal(Days.daysBetween(startDate, nextBillingCycleDate).getDays()); + + return days.divide(daysInPeriod, NUMBER_OF_DECIMALS, ROUNDING_METHOD); + } + + @Override + protected BigDecimal calculateProRationAfterLastBillingCycleDate(final DateTime endDate, final DateTime previousBillThroughDate, final BillingPeriod billingPeriod) { + // note: assumption is that previousBillThroughDate is correctly aligned with the billing cycle day + DateTime nextBillThroughDate = previousBillThroughDate.plusMonths(billingPeriod.getNumberOfMonths()); + BigDecimal daysInPeriod = new BigDecimal(Days.daysBetween(previousBillThroughDate, nextBillThroughDate).getDays()); + + BigDecimal days = new BigDecimal(Days.daysBetween(previousBillThroughDate, endDate).getDays()); + + return days.divide(daysInPeriod, NUMBER_OF_DECIMALS, ROUNDING_METHOD); + } + + protected DateTime calculateEffectiveEndDate(DateTime billCycleDate, DateTime targetDate, DateTime endDate, BillingPeriod billingPeriod) { + if (targetDate.isBefore(endDate)) { + if (targetDate.isBefore(billCycleDate)) { + return billCycleDate; + } + + int numberOfMonthsInPeriod = billingPeriod.getNumberOfMonths(); + DateTime startOfPeriod = billCycleDate; + DateTime startOfNextPeriod = billCycleDate.plusMonths(numberOfMonthsInPeriod); + + while (!isBetween(targetDate, startOfPeriod, startOfNextPeriod)) { + startOfPeriod = startOfNextPeriod; + startOfNextPeriod = startOfPeriod.plusMonths(numberOfMonthsInPeriod); + } + + // the current period includes the target date + // check to see whether the end date truncates the period + if (endDate.isBefore(startOfNextPeriod)) { + return endDate; + } else { + return startOfNextPeriod; + } + } else { + return endDate; + } + } +} \ No newline at end of file diff --git a/invoice/src/main/java/com/ning/billing/invoice/model/InvalidDateSequenceException.java b/invoice/src/main/java/com/ning/billing/invoice/model/InvalidDateSequenceException.java new file mode 100644 index 0000000000..850499e7ae --- /dev/null +++ b/invoice/src/main/java/com/ning/billing/invoice/model/InvalidDateSequenceException.java @@ -0,0 +1,21 @@ +/* + * Copyright 2010-2011 Ning, Inc. + * + * Ning licenses this file to you under the Apache License, version 2.0 + * (the "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package com.ning.billing.invoice.model; + +public class InvalidDateSequenceException extends Exception { + +} diff --git a/invoice/src/main/java/com/ning/billing/invoice/model/Invoice.java b/invoice/src/main/java/com/ning/billing/invoice/model/Invoice.java new file mode 100644 index 0000000000..5ba92cf580 --- /dev/null +++ b/invoice/src/main/java/com/ning/billing/invoice/model/Invoice.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.invoice.model; + +import com.ning.billing.catalog.api.Currency; + +import java.math.BigDecimal; +import java.util.ArrayList; +import java.util.List; + +public class Invoice { + private static final int NUMBER_OF_DECIMALS = InvoicingConfiguration.getNumberOfDecimals(); + + private final List items = new ArrayList(); + private Currency currency; + + public Invoice() {} + + public Invoice(Currency currency) { + this.currency = currency; + } + + public boolean add(InvoiceItem item) { + return items.add(item); + } + + public Currency getCurrency() { + return currency; + } + + public BigDecimal getTotalAmount() { + // TODO: Jeff -- naive implementation, assumes all invoice items share the same currency + BigDecimal total = new BigDecimal("0"); + + for (InvoiceItem item : items) { + total = total.add(item.getAmount()); + } + + return total.setScale(NUMBER_OF_DECIMALS); + } + + public int getNumberOfItems() { + return items.size(); + } +} + diff --git a/invoice/src/main/java/com/ning/billing/invoice/model/InvoiceItem.java b/invoice/src/main/java/com/ning/billing/invoice/model/InvoiceItem.java new file mode 100644 index 0000000000..45880bbebc --- /dev/null +++ b/invoice/src/main/java/com/ning/billing/invoice/model/InvoiceItem.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.invoice.model; + +import java.math.BigDecimal; + +public class InvoiceItem { +// private final String description; +// private final DateTime startDate; +// private final DateTime endDate; + private final BigDecimal amount; +// private final Currency currency; + + // TODO: Jeff -- determine if a default constructor is required for InvoiceItem + //public InvoiceItem(DateTime startDate, DateTime endDate, String description, BigDecimal amount, Currency currency) { + public InvoiceItem(BigDecimal amount) { + //this.description = description; + this.amount = amount; +// this.currency = currency; +// this.startDate = startDate; +// this.endDate = endDate; + } + + public BigDecimal getAmount() { + return amount; + } +} diff --git a/invoice/src/main/java/com/ning/billing/invoice/model/InvoicingConfiguration.java b/invoice/src/main/java/com/ning/billing/invoice/model/InvoicingConfiguration.java new file mode 100644 index 0000000000..7482465d73 --- /dev/null +++ b/invoice/src/main/java/com/ning/billing/invoice/model/InvoicingConfiguration.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.invoice.model; + +import java.math.BigDecimal; + +public class InvoicingConfiguration { + private final static int roundingMethod = BigDecimal.ROUND_HALF_UP; + private final static int numberOfDecimals = 4; + + public static int getRoundingMethod() { + return roundingMethod; + } + + public static int getNumberOfDecimals() { + return numberOfDecimals; + } +} diff --git a/invoice/src/main/java/com/ning/billing/invoice/model/TestConsole.java b/invoice/src/main/java/com/ning/billing/invoice/model/TestConsole.java new file mode 100644 index 0000000000..b637323873 --- /dev/null +++ b/invoice/src/main/java/com/ning/billing/invoice/model/TestConsole.java @@ -0,0 +1,145 @@ +/* + * Copyright 2010-2011 Ning, Inc. + * + * Ning licenses this file to you under the Apache License, version 2.0 + * (the "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF 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.BillingPeriod; +import org.joda.time.DateTime; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.math.BigDecimal; + +public class TestConsole { + public static void main(String[] args) { + System.out.println("Starting monthly in-advance pro-ration demo"); + System.out.println(); + + executeBasicTestCase(); + executeSimpleProRationTest(); + executeCancelledPlanProRationTest(); + executeCrossingYearBoundary(); + System.out.println(); + } + + private static void executeBasicTestCase() { + System.out.println("** Starting basic test case"); + + DateTime startDate = buildDateTime(2011, 1, 15); + DateTime targetDate = buildDateTime(2011, 1, 15); + testScenario(startDate, targetDate, 15, BillingPeriod.MONTHLY, new BigDecimal("1.0")); + + targetDate = buildDateTime(2011, 1, 23); + testScenario(startDate, targetDate, 15, BillingPeriod.MONTHLY, new BigDecimal("1.0")); + + targetDate = buildDateTime(2011, 2, 15); + testScenario(startDate, targetDate, 15, BillingPeriod.MONTHLY, new BigDecimal("2.0")); + + targetDate = buildDateTime(2011, 2, 16); + testScenario(startDate, targetDate, 15, BillingPeriod.MONTHLY, new BigDecimal("2.0")); + } + + private static void executeSimpleProRationTest() { + System.out.println("** Starting pro-ration test case 1"); + System.out.println("Immediate plan change, including February"); + + DateTime startDate = buildDateTime(2011, 1, 15); + DateTime targetDate = buildDateTime(2011, 3, 3); + testScenario(startDate, targetDate, 31, BillingPeriod.MONTHLY, new BigDecimal(2.0 + 16.0 / 31)); + } + + private static void executeCancelledPlanProRationTest() { + System.out.println("** Starting pro-ration test case 2"); + System.out.println("** Cancelled plan, including February, pro-ration on both sides"); + + DateTime startDate = buildDateTime(2011, 1, 23); + DateTime endDate = buildDateTime(2011, 3, 15); + DateTime targetDate = buildDateTime(2011, 3, 16); + testScenario(startDate, endDate, targetDate, 8, BillingPeriod.MONTHLY, new BigDecimal(16.0/31 + 1.0 + 7.0/31)); + } + + private static void executeCrossingYearBoundary() { + System.out.println("** Starting pro-ration test case 3"); + System.out.println("** Crossing year boundary, including leap year February"); + + DateTime startDate = buildDateTime(2011, 12, 23); + DateTime targetDate = buildDateTime(2012, 4, 1); + testScenario(startDate, targetDate, 23, BillingPeriod.MONTHLY, new BigDecimal(4.0)); + } + + private static void testScenario(DateTime startDate, DateTime targetDate, int billingCycleDay, BillingPeriod billingPeriod, BigDecimal expectedValue) { + IBillingMode billingMode = new InAdvanceBillingMode(); + + System.out.println("Start date: " + startDate.toLocalDate()); + System.out.println("Target date: " + targetDate.toLocalDate()); + System.out.println("Billing cycle day: " + billingCycleDay); + System.out.println("Expected value: " + expectedValue.toString()); + System.out.println(); + + try { + BigDecimal numberOfBillingCycles = billingMode.calculateNumberOfBillingCycles(startDate, targetDate, billingCycleDay, billingPeriod); + System.out.println("Result: " + numberOfBillingCycles.toString() + " billing cycles"); + } catch (InvalidDateSequenceException e) { + e.printStackTrace(); //To change body of catch statement use File | Settings | File Templates. + System.out.println("Invalid date sequence"); + } + + System.out.println(); + + try { + BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); + System.out.println("Hit to continue"); + br.readLine(); + } catch (IOException ioex) { + System.out.println("IO error getting input"); + System.exit(1); + } + } + + private static void testScenario(DateTime startDate, DateTime endDate, DateTime targetDate, int billingCycleDay, BillingPeriod billingPeriod, BigDecimal expectedValue) { + IBillingMode billingMode = new InAdvanceBillingMode(); + + System.out.println("Start date: " + startDate.toLocalDate()); + System.out.println("End date: " + endDate.toLocalDate()); + System.out.println("Target date: " + targetDate.toLocalDate()); + System.out.println("Billing cycle day: " + billingCycleDay); + System.out.println("Expected value: " + expectedValue.toString()); + System.out.println(); + + try { + BigDecimal numberOfBillingCycles = billingMode.calculateNumberOfBillingCycles(startDate, endDate, targetDate, billingCycleDay, billingPeriod); + System.out.println("Result: " + numberOfBillingCycles.toString() + " billing cycles"); + } catch (InvalidDateSequenceException idse) { + System.out.println("Invalid date sequence"); + } + + System.out.println(); + + try { + BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); + System.out.println("Hit to continue"); + br.readLine(); + } catch (IOException ioex) { + System.out.println("IO error getting input"); + System.exit(1); + } + } + + private static DateTime buildDateTime(int year, int month, int day) { + return new DateTime(year, month, day, 0, 0, 0, 0); + } +} \ No newline at end of file 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 new file mode 100644 index 0000000000..cfa0ce14cb --- /dev/null +++ b/invoice/src/test/java/com/ning/billing/invoice/tests/DefaultInvoiceGeneratorTests.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.tests; + +import com.ning.billing.catalog.api.BillingPeriod; +import com.ning.billing.catalog.api.Currency; +import com.ning.billing.entitlement.api.billing.BillingMode; +import com.ning.billing.entitlement.api.billing.IBillingEvent; +import com.ning.billing.invoice.api.BillingEvent; +import com.ning.billing.invoice.api.BillingEventSet; +import com.ning.billing.invoice.model.DefaultInvoiceGenerator; +import com.ning.billing.invoice.model.IInvoiceGenerator; +import com.ning.billing.invoice.model.Invoice; +import org.joda.time.DateTime; +import org.testng.annotations.Test; + +import java.util.UUID; + +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertNotNull; + +@Test(groups = {"invoicing", "invoiceGenerator"}) +public class DefaultInvoiceGeneratorTests extends InvoicingTestBase { + private final IInvoiceGenerator generator = new DefaultInvoiceGenerator(); + + @Test + public void testWithNullEventSet() { + Invoice invoice = generator.generateInvoice(null); + + assertNotNull(invoice); + assertEquals(invoice.getNumberOfItems(), 0); + assertEquals(invoice.getTotalAmount(), ZERO); + } + + @Test + public void testWithEmptyEventSet() { + BillingEventSet events = new BillingEventSet(Currency.USD); + + Invoice invoice = generator.generateInvoice(events); + + assertNotNull(invoice); + assertEquals(invoice.getNumberOfItems(), 0); + assertEquals(invoice.getTotalAmount(), ZERO); + } + + @Test + public void testWithSingleSimpleEvent() { + DateTime targetDate = buildDateTime(2011, 10, 3); + BillingEventSet events = new BillingEventSet(Currency.USD, targetDate); + + UUID subscriptionId = UUID.randomUUID(); + DateTime startDate = buildDateTime(2011, 9, 1); + String planName = "World Domination"; + String phaseName = "Build Space Laser"; + IBillingEvent event = new BillingEvent(subscriptionId, startDate, planName, phaseName, + new InternationalPriceMock(TEN), BillingPeriod.MONTHLY, + 1, BillingMode.IN_ADVANCE); + + events.add(event); + + Invoice invoice = generator.generateInvoice(events); + + assertNotNull(invoice); + assertEquals(invoice.getNumberOfItems(), 1); + assertEquals(invoice.getTotalAmount(), TWENTY); + } + + @Test + public void testTwoSubscriptionsWithAlignedBillingDates() { + DateTime targetDate = buildDateTime(2011, 10, 3); + BillingEventSet events = new BillingEventSet(Currency.USD, targetDate); + + IBillingEvent event1 = new BillingEvent(UUID.randomUUID(), buildDateTime(2011, 9, 1), + "World Domination", "Build Space Laser", + new InternationalPriceMock(FIVE), BillingPeriod.MONTHLY, + 1, BillingMode.IN_ADVANCE); + events.add(event1); + + IBillingEvent event2 = new BillingEvent(UUID.randomUUID(), buildDateTime(2011, 10, 1), + "Groceries", "Pick Up Milk", + new InternationalPriceMock(TEN), BillingPeriod.MONTHLY, + 1, BillingMode.IN_ADVANCE); + events.add(event2); + + Invoice invoice = generator.generateInvoice(events); + + assertNotNull(invoice); + assertEquals(invoice.getNumberOfItems(), 2); + assertEquals(invoice.getTotalAmount(), FIVE.multiply(TWO).add(TEN).setScale(NUMBER_OF_DECIMALS)); + } + + @Test + public void testOnePlan_ThreePhases_ChangeEOT() { + DateTime targetDate = buildDateTime(2011, 12, 3); + BillingEventSet events = new BillingEventSet(Currency.USD, targetDate); + + UUID subscriptionId = UUID.randomUUID(); + IBillingEvent event1 = new BillingEvent(subscriptionId, buildDateTime(2011, 9, 1), + "World Domination", "Build Space Laser", + new InternationalPriceMock(FIVE), BillingPeriod.MONTHLY, + 1, BillingMode.IN_ADVANCE); + events.add(event1); + + IBillingEvent event2 = new BillingEvent(subscriptionId, buildDateTime(2011, 10, 1), + "World Domination", "Incinerate James Bond", + new InternationalPriceMock(TEN), BillingPeriod.MONTHLY, + 1, BillingMode.IN_ADVANCE); + events.add(event2); + + IBillingEvent event3 = new BillingEvent(subscriptionId, buildDateTime(2011, 11, 1), + "World Domination", "Cackle Gleefully", + new InternationalPriceMock(THIRTY), BillingPeriod.MONTHLY, + 1, BillingMode.IN_ADVANCE); + events.add(event3); + + Invoice invoice = generator.generateInvoice(events); + + assertNotNull(invoice); + assertEquals(invoice.getNumberOfItems(), 3); + assertEquals(invoice.getTotalAmount(), FIVE.add(TEN).add(TWO.multiply(THIRTY)).setScale(NUMBER_OF_DECIMALS)); + } +} diff --git a/invoice/src/test/java/com/ning/billing/invoice/tests/InternationalPriceMock.java b/invoice/src/test/java/com/ning/billing/invoice/tests/InternationalPriceMock.java new file mode 100644 index 0000000000..c2df6ef190 --- /dev/null +++ b/invoice/src/test/java/com/ning/billing/invoice/tests/InternationalPriceMock.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.invoice.tests; + +import com.ning.billing.catalog.api.Currency; +import com.ning.billing.catalog.api.IInternationalPrice; +import com.ning.billing.catalog.api.IPrice; + +import java.math.BigDecimal; + +import static org.testng.Assert.fail; + +public class InternationalPriceMock implements IInternationalPrice { + private final BigDecimal rate; + + public InternationalPriceMock(BigDecimal rate) { + this.rate = rate; + } + + @Override + public IPrice[] getPrices() { + fail(); + + return null; + } + + @Override + public BigDecimal getPrice(Currency currency) { + return rate; + } +} 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 new file mode 100644 index 0000000000..9413b4cf59 --- /dev/null +++ b/invoice/src/test/java/com/ning/billing/invoice/tests/InvoicingTestBase.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.invoice.tests; + + +import com.ning.billing.invoice.model.InvoicingConfiguration; +import org.joda.time.DateTime; + +import java.math.BigDecimal; + +public abstract class InvoicingTestBase { + protected static final int NUMBER_OF_DECIMALS = InvoicingConfiguration.getNumberOfDecimals(); + protected static final int ROUNDING_METHOD = InvoicingConfiguration.getRoundingMethod(); + + protected static final BigDecimal ZERO = new BigDecimal("0.0").setScale(NUMBER_OF_DECIMALS); + protected static final BigDecimal ONE_HALF = new BigDecimal("0.5").setScale(NUMBER_OF_DECIMALS); + protected static final BigDecimal ONE = new BigDecimal("1.0").setScale(NUMBER_OF_DECIMALS); + 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 FIVE = new BigDecimal("5.0").setScale(NUMBER_OF_DECIMALS); + + protected static final BigDecimal SEVEN = new BigDecimal("7.0").setScale(NUMBER_OF_DECIMALS); + protected static final BigDecimal EIGHT = new BigDecimal("8.0").setScale(NUMBER_OF_DECIMALS); + + protected static final BigDecimal TEN = new BigDecimal("10.0").setScale(NUMBER_OF_DECIMALS); + + protected static final BigDecimal TWELVE = new BigDecimal("12.0").setScale(NUMBER_OF_DECIMALS); + protected static final BigDecimal THIRTEEN = new BigDecimal("13.0").setScale(NUMBER_OF_DECIMALS); + protected static final BigDecimal FOURTEEN = new BigDecimal("14.0").setScale(NUMBER_OF_DECIMALS); + protected static final BigDecimal FIFTEEN = new BigDecimal("15.0").setScale(NUMBER_OF_DECIMALS); + + protected static final BigDecimal TWENTY = new BigDecimal("20.0").setScale(NUMBER_OF_DECIMALS); + protected static final BigDecimal TWENTY_ONE = new BigDecimal("21.0").setScale(NUMBER_OF_DECIMALS); + + protected static final BigDecimal TWENTY_FOUR = new BigDecimal("24.0").setScale(NUMBER_OF_DECIMALS); + + protected static final BigDecimal TWENTY_SIX = new BigDecimal("26.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); + protected static final BigDecimal THIRTY = new BigDecimal("30.0").setScale(NUMBER_OF_DECIMALS); + protected static final BigDecimal THIRTY_ONE = new BigDecimal("31.0").setScale(NUMBER_OF_DECIMALS); + + protected static final BigDecimal EIGHTY_NINE = new BigDecimal("89.0").setScale(NUMBER_OF_DECIMALS); + protected static final BigDecimal NINETY = new BigDecimal("90.0").setScale(NUMBER_OF_DECIMALS); + protected static final BigDecimal NINETY_ONE = new BigDecimal("91.0").setScale(NUMBER_OF_DECIMALS); + protected static final BigDecimal NINETY_TWO = new BigDecimal("92.0").setScale(NUMBER_OF_DECIMALS); + + protected static final BigDecimal THREE_HUNDRED_AND_SIXTY_FIVE = new BigDecimal("365.0").setScale(NUMBER_OF_DECIMALS); + protected static final BigDecimal THREE_HUNDRED_AND_SIXTY_SIX = new BigDecimal("366.0").setScale(NUMBER_OF_DECIMALS); + + protected DateTime buildDateTime(int year, int month, int day) { + return new DateTime(year, month, day, 0, 0, 0, 0); + } +} 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 new file mode 100644 index 0000000000..835836e76c --- /dev/null +++ b/invoice/src/test/java/com/ning/billing/invoice/tests/ProRationTestBase.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.invoice.tests; + +import com.ning.billing.catalog.api.BillingPeriod; +import com.ning.billing.invoice.model.IBillingMode; +import com.ning.billing.invoice.model.InvalidDateSequenceException; +import org.joda.time.DateTime; + +import java.math.BigDecimal; + +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.fail; + +public abstract class ProRationTestBase extends InvoicingTestBase{ + protected abstract IBillingMode getBillingMode(); + protected abstract BillingPeriod getBillingPeriod(); + + protected void testCalculateNumberOfBillingCycles(DateTime startDate, DateTime targetDate, int billingCycleDay, BigDecimal expectedValue) throws InvalidDateSequenceException { + try { + BigDecimal numberOfBillingCycles; + numberOfBillingCycles = getBillingMode().calculateNumberOfBillingCycles(startDate, targetDate, billingCycleDay, getBillingPeriod()); + + assertEquals(numberOfBillingCycles, expectedValue); + } catch (InvalidDateSequenceException idse) { + throw idse; + } catch (Exception e) { + fail("Unexpected exception: " + e.getMessage()); + } + } + + protected void testCalculateNumberOfBillingCycles(DateTime startDate, DateTime endDate, DateTime targetDate, int billingCycleDay, BigDecimal expectedValue) throws InvalidDateSequenceException { + try { + BigDecimal numberOfBillingCycles; + numberOfBillingCycles = getBillingMode().calculateNumberOfBillingCycles(startDate, endDate, targetDate, billingCycleDay, getBillingPeriod()); + + assertEquals(numberOfBillingCycles, expectedValue); + } catch (InvalidDateSequenceException idse) { + throw idse; + } catch (Exception e) { + fail("Unexpected exception: " + e.getMessage()); + } + } +} \ No newline at end of file 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 new file mode 100644 index 0000000000..d4e4c241fd --- /dev/null +++ b/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/GenericProRationTestBase.java @@ -0,0 +1,183 @@ +/* + * Copyright 2010-2011 Ning, Inc. + * + * Ning licenses this file to you under the Apache License, version 2.0 + * (the "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF 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.tests.inAdvance; + +import com.ning.billing.invoice.model.InvalidDateSequenceException; +import org.joda.time.DateTime; +import org.testng.annotations.Test; + +import java.math.BigDecimal; + +@Test(groups = {"invoicing", "proRation"}) +public abstract class GenericProRationTestBase extends ProRationInAdvanceTestBase { + /** + * used for testing cancellation in less than a single billing period + * @return BigDecimal the number of days in the billing period beginning 2011/1/1 + */ + protected abstract BigDecimal getDaysInTestPeriod(); + + @Test + public void testSinglePlan_OnStartDate() throws InvalidDateSequenceException { + DateTime startDate = buildDateTime(2011, 2, 15); + + testCalculateNumberOfBillingCycles(startDate, startDate, 15, ONE); + } + + @Test + public void testSinglePlan_LessThanOnePeriod() throws InvalidDateSequenceException { + DateTime startDate = buildDateTime(2011, 2, 15); + DateTime targetDate = buildDateTime(2011, 3, 1); + + testCalculateNumberOfBillingCycles(startDate, targetDate, 15, ONE); + } + + @Test + public void testSinglePlan_OnePeriodLessADayAfterStart() throws InvalidDateSequenceException { + DateTime startDate = buildDateTime(2011, 2, 15); + DateTime targetDate = startDate.plusMonths(getBillingPeriod().getNumberOfMonths()).plusDays(-1); + + testCalculateNumberOfBillingCycles(startDate, targetDate, 15, ONE); + } + + @Test + public void testSinglePlan_ExactlyOnePeriodAfterStart() throws InvalidDateSequenceException { + DateTime startDate = buildDateTime(2011, 2, 15); + DateTime targetDate = startDate.plusMonths(getBillingPeriod().getNumberOfMonths()); + + testCalculateNumberOfBillingCycles(startDate, targetDate, 15, TWO); + } + + @Test + public void testSinglePlan_SlightlyMoreThanOnePeriodAfterStart() throws InvalidDateSequenceException { + DateTime startDate = buildDateTime(2011, 2, 15); + DateTime targetDate = startDate.plusMonths(getBillingPeriod().getNumberOfMonths()).plusDays(1); + + testCalculateNumberOfBillingCycles(startDate, targetDate, 15, TWO); + } + + @Test + public void testSinglePlan_CrossingYearBoundary() throws InvalidDateSequenceException { + DateTime startDate = buildDateTime(2011, 12, 15); + DateTime oneCycleLater = startDate.plusMonths(getBillingPeriod().getNumberOfMonths()); + + // test just before the billing cycle day + testCalculateNumberOfBillingCycles(startDate, oneCycleLater.plusDays(-1), 15, ONE); + + // test on the billing cycle day + testCalculateNumberOfBillingCycles(startDate, oneCycleLater, 15, TWO); + + // test just after the billing cycle day + testCalculateNumberOfBillingCycles(startDate, oneCycleLater.plusDays(1), 15, TWO); + } + + @Test + public void testSinglePlan_StartingMidFebruary() throws InvalidDateSequenceException { + DateTime startDate = buildDateTime(2011, 2, 15); + DateTime targetDate = startDate.plusMonths(getBillingPeriod().getNumberOfMonths()); + + testCalculateNumberOfBillingCycles(startDate, targetDate, 15, TWO); + } + + @Test + public void testSinglePlan_StartingMidFebruaryOfLeapYear() throws InvalidDateSequenceException { + DateTime startDate = buildDateTime(2012, 2, 15); + DateTime targetDate = startDate.plusMonths(getBillingPeriod().getNumberOfMonths()); + + testCalculateNumberOfBillingCycles(startDate, targetDate, 15, TWO); + } + + @Test + public void testSinglePlan_MovingForwardThroughTime() throws InvalidDateSequenceException { + DateTime startDate = buildDateTime(2011, 1, 31); + BigDecimal expectedValue = ONE; + + for (int i = 1; i <= 12; i++) { + DateTime oneCycleLater = startDate.plusMonths(i * getBillingPeriod().getNumberOfMonths()); + // test just before the billing cycle day + testCalculateNumberOfBillingCycles(startDate, oneCycleLater.plusDays(-1), 31, expectedValue); + + expectedValue = expectedValue.add(ONE); + + // test on the billing cycle day + testCalculateNumberOfBillingCycles(startDate, oneCycleLater, 31, expectedValue); + + // test just after the billing cycle day + testCalculateNumberOfBillingCycles(startDate, oneCycleLater.plusDays(1), 31, expectedValue); + } + } + + // tests for cancellation in less than one period, beginning Jan 1 + @Test + public void testCancelledBeforeOnePeriod_TargetDateInStartDate() throws InvalidDateSequenceException { + DateTime startDate = buildDateTime(2011, 1, 1); + DateTime targetDate = buildDateTime(2011, 1, 1); + DateTime endDate = buildDateTime(2011, 1, 15); + + BigDecimal expectedValue = FOURTEEN.divide(getDaysInTestPeriod(), NUMBER_OF_DECIMALS, ROUNDING_METHOD); + testCalculateNumberOfBillingCycles(startDate, endDate, targetDate, 1, expectedValue); + } + + @Test + public void testCancelledBeforeOnePeriod_TargetDateInSubscriptionPeriod() throws InvalidDateSequenceException { + DateTime startDate = buildDateTime(2011, 1, 1); + DateTime targetDate = buildDateTime(2011, 1, 7); + DateTime endDate = buildDateTime(2011, 1, 15); + + BigDecimal expectedValue = FOURTEEN.divide(getDaysInTestPeriod(), NUMBER_OF_DECIMALS, ROUNDING_METHOD); + testCalculateNumberOfBillingCycles(startDate, endDate, targetDate, 1, expectedValue); + } + + @Test + public void testCancelledBeforeOnePeriod_TargetDateOnEndDate() throws InvalidDateSequenceException { + DateTime startDate = buildDateTime(2011, 1, 1); + DateTime targetDate = buildDateTime(2011, 1, 15); + DateTime endDate = buildDateTime(2011, 1, 15); + + BigDecimal expectedValue = FOURTEEN.divide(getDaysInTestPeriod(), NUMBER_OF_DECIMALS, ROUNDING_METHOD); + testCalculateNumberOfBillingCycles(startDate, endDate, targetDate, 1, expectedValue); + } + + @Test + public void testCancelledBeforeOnePeriod_TargetDateAfterEndDateButInFirstBillingPeriod() throws InvalidDateSequenceException { + DateTime startDate = buildDateTime(2011, 1, 1); + DateTime targetDate = buildDateTime(2011, 1, 17); + DateTime endDate = buildDateTime(2011, 1, 15); + + BigDecimal expectedValue = FOURTEEN.divide(getDaysInTestPeriod(), NUMBER_OF_DECIMALS, ROUNDING_METHOD); + testCalculateNumberOfBillingCycles(startDate, endDate, targetDate, 1, expectedValue); + } + + @Test + public void testCancelledBeforeOnePeriod_TargetDateAtEndOfFirstBillingPeriod() throws InvalidDateSequenceException { + DateTime startDate = buildDateTime(2011, 1, 1); + DateTime targetDate = buildDateTime(2011, 2, 1); + DateTime endDate = buildDateTime(2011, 1, 15); + + BigDecimal expectedValue = FOURTEEN.divide(getDaysInTestPeriod(), NUMBER_OF_DECIMALS, ROUNDING_METHOD); + testCalculateNumberOfBillingCycles(startDate, endDate, targetDate, 1, expectedValue); + } + + @Test + public void testCancelledBeforeOnePeriod_TargetDateAfterFirstBillingPeriod() throws InvalidDateSequenceException { + DateTime startDate = buildDateTime(2011, 1, 1); + DateTime targetDate = buildDateTime(2011, 4, 5); + DateTime endDate = buildDateTime(2011, 1, 15); + + BigDecimal expectedValue = FOURTEEN.divide(getDaysInTestPeriod(), NUMBER_OF_DECIMALS, ROUNDING_METHOD); + testCalculateNumberOfBillingCycles(startDate, endDate, targetDate, 1, expectedValue); + } +} 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 new file mode 100644 index 0000000000..2948557636 --- /dev/null +++ b/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/ProRationInAdvanceTestBase.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.invoice.tests.inAdvance; + +import com.ning.billing.invoice.model.IBillingMode; +import com.ning.billing.invoice.model.InAdvanceBillingMode; +import com.ning.billing.invoice.tests.ProRationTestBase; +import org.testng.annotations.Test; + +@Test(groups = {"invoicing", "proRation"}) +public abstract class ProRationInAdvanceTestBase extends ProRationTestBase { + @Override + protected IBillingMode getBillingMode() { + return new InAdvanceBillingMode(); + } +} 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 new file mode 100644 index 0000000000..c57fe327bb --- /dev/null +++ b/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/ValidationProRationTests.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.invoice.tests.inAdvance; + +import com.ning.billing.catalog.api.BillingPeriod; +import com.ning.billing.invoice.model.IBillingMode; +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 = {"invoicing", "proRation"}) +public class ValidationProRationTests extends ProRationTestBase { + protected BillingPeriod getBillingPeriod() { + return BillingPeriod.MONTHLY; + } + + @Override + protected IBillingMode getBillingMode() { + return new InAdvanceBillingMode(); + } + + protected BigDecimal calculateNumberOfBillingCycles(DateTime startDate, DateTime targetDate, int billingCycleDay) throws InvalidDateSequenceException { + return getBillingMode().calculateNumberOfBillingCycles(startDate, targetDate, billingCycleDay, getBillingPeriod()); + } + + protected BigDecimal calculateNumberOfBillingCycles(DateTime startDate, DateTime endDate, DateTime targetDate, int billingCycleDay) throws InvalidDateSequenceException { + return getBillingMode().calculateNumberOfBillingCycles(startDate, endDate, targetDate, billingCycleDay, getBillingPeriod()); + } + + @Test(expectedExceptions = InvalidDateSequenceException.class) + public void testTargetStartEnd() throws InvalidDateSequenceException { + DateTime startDate = buildDateTime(2011, 1, 30); + DateTime endDate = buildDateTime(2011, 3, 15); + DateTime targetDate = buildDateTime(2011, 1, 15); + + calculateNumberOfBillingCycles(startDate, endDate, targetDate, 15); + } + + @Test(expectedExceptions = InvalidDateSequenceException.class) + public void testTargetEndStart() throws InvalidDateSequenceException { + DateTime startDate = buildDateTime(2011, 4, 30); + DateTime endDate = buildDateTime(2011, 3, 15); + DateTime targetDate = buildDateTime(2011, 2, 15); + + calculateNumberOfBillingCycles(startDate, endDate, targetDate, 15); + } + + @Test(expectedExceptions = InvalidDateSequenceException.class) + public void testEndTargetStart() throws InvalidDateSequenceException { + DateTime startDate = buildDateTime(2011, 3, 30); + DateTime endDate = buildDateTime(2011, 1, 15); + DateTime targetDate = buildDateTime(2011, 2, 15); + + calculateNumberOfBillingCycles(startDate, endDate, targetDate, 15); + } + + @Test(expectedExceptions = InvalidDateSequenceException.class) + public void testEndStartTarget() throws InvalidDateSequenceException { + DateTime startDate = buildDateTime(2011, 1, 30); + DateTime endDate = buildDateTime(2011, 1, 15); + DateTime targetDate = buildDateTime(2011, 2, 15); + + calculateNumberOfBillingCycles(startDate, endDate, targetDate, 15); + } + + @Test(expectedExceptions = InvalidDateSequenceException.class) + public void testTargetStart() throws InvalidDateSequenceException { + DateTime startDate = buildDateTime(2011, 4, 30); + DateTime targetDate = buildDateTime(2011, 2, 15); + + calculateNumberOfBillingCycles(startDate, targetDate, 15); + } + + @Test + public void testBigDecimalTruncation() { + BigDecimal value = new BigDecimal("1.3349573498567"); + BigDecimal truncated = value.setScale(0, BigDecimal.ROUND_DOWN).setScale(NUMBER_OF_DECIMALS); + + assertEquals(truncated, ONE); + } +} + 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 new file mode 100644 index 0000000000..84bf79560c --- /dev/null +++ b/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/annual/DoubleProRationTests.java @@ -0,0 +1,145 @@ +/* + * Copyright 2010-2011 Ning, Inc. + * + * Ning licenses this file to you under the Apache License, version 2.0 + * (the "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF 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.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 org.joda.time.DateTime; +import org.testng.annotations.Test; + +import java.math.BigDecimal; + +@Test(groups = {"invoicing", "proRation"}) +public class DoubleProRationTests extends ProRationInAdvanceTestBase { + @Override + protected BillingPeriod getBillingPeriod() { + return BillingPeriod.ANNUAL; + } + + @Test + public void testDoubleProRation_TargetDateOnStartDate() throws InvalidDateSequenceException { + DateTime startDate = buildDateTime(2011, 1, 1); + DateTime targetDate = buildDateTime(2011, 1, 1); + DateTime endDate = buildDateTime(2012, 1, 27); + + BigDecimal expectedValue = FOURTEEN.divide(THREE_HUNDRED_AND_SIXTY_FIVE, NUMBER_OF_DECIMALS, ROUNDING_METHOD); + testCalculateNumberOfBillingCycles(startDate, endDate, targetDate, 15, expectedValue); + } + + @Test + public void testDoubleProRation_TargetDateInFirstProRationPeriod() throws InvalidDateSequenceException { + DateTime startDate = buildDateTime(2011, 1, 1); + DateTime targetDate = buildDateTime(2011, 1, 7); + DateTime endDate = buildDateTime(2012, 1, 27); + + BigDecimal expectedValue = FOURTEEN.divide(THREE_HUNDRED_AND_SIXTY_FIVE, NUMBER_OF_DECIMALS, ROUNDING_METHOD); + testCalculateNumberOfBillingCycles(startDate, endDate, targetDate, 15, expectedValue); + } + + @Test + public void testDoubleProRation_TargetDateOnFirstBillingCycleDate() throws InvalidDateSequenceException { + DateTime startDate = buildDateTime(2011, 1, 1); + DateTime targetDate = buildDateTime(2011, 1, 15); + DateTime endDate = buildDateTime(2012, 1, 27); + + BigDecimal expectedValue = ONE.add(FOURTEEN.divide(THREE_HUNDRED_AND_SIXTY_FIVE, NUMBER_OF_DECIMALS, ROUNDING_METHOD)); + testCalculateNumberOfBillingCycles(startDate, endDate, targetDate, 15, expectedValue); + } + + @Test + public void testDoubleProRation_TargetDateInFullBillingPeriod() throws InvalidDateSequenceException { + DateTime startDate = buildDateTime(2011, 1, 1); + DateTime targetDate = buildDateTime(2011, 1, 22); + DateTime endDate = buildDateTime(2012, 1, 27); + + BigDecimal expectedValue; + expectedValue = FOURTEEN.divide(THREE_HUNDRED_AND_SIXTY_FIVE, NUMBER_OF_DECIMALS, ROUNDING_METHOD); + expectedValue = expectedValue.add(ONE); + + testCalculateNumberOfBillingCycles(startDate, endDate, targetDate, 15, expectedValue); + } + + @Test + public void testDoubleProRation_TargetDateOnSecondBillingCycleDate() throws InvalidDateSequenceException { + DateTime startDate = buildDateTime(2011, 1, 1); + DateTime targetDate = buildDateTime(2012, 1, 15); + DateTime endDate = buildDateTime(2012, 1, 27); + + BigDecimal expectedValue; + expectedValue = FOURTEEN.divide(THREE_HUNDRED_AND_SIXTY_FIVE, NUMBER_OF_DECIMALS, ROUNDING_METHOD); + expectedValue = expectedValue.add(ONE); + expectedValue = expectedValue.add(TWELVE.divide(THREE_HUNDRED_AND_SIXTY_SIX, NUMBER_OF_DECIMALS, ROUNDING_METHOD)); + + testCalculateNumberOfBillingCycles(startDate, endDate, targetDate, 15, expectedValue); + } + + @Test + public void testDoubleProRation_TargetDateInSecondProRationPeriod() throws InvalidDateSequenceException { + DateTime startDate = buildDateTime(2011, 1, 1); + DateTime targetDate = buildDateTime(2012, 1, 17); + DateTime endDate = buildDateTime(2012, 1, 27); + + BigDecimal expectedValue; + expectedValue = FOURTEEN.divide(THREE_HUNDRED_AND_SIXTY_FIVE, NUMBER_OF_DECIMALS, ROUNDING_METHOD); + expectedValue = expectedValue.add(ONE); + expectedValue = expectedValue.add(TWELVE.divide(THREE_HUNDRED_AND_SIXTY_SIX, NUMBER_OF_DECIMALS, ROUNDING_METHOD)); + + testCalculateNumberOfBillingCycles(startDate, endDate, targetDate, 15, expectedValue); + } + + @Test + public void testDoubleProRation_TargetDateOnEndDate() throws InvalidDateSequenceException { + DateTime startDate = buildDateTime(2011, 1, 1); + DateTime targetDate = buildDateTime(2012, 1, 27); + DateTime endDate = buildDateTime(2012, 1, 27); + + BigDecimal expectedValue; + expectedValue = FOURTEEN.divide(THREE_HUNDRED_AND_SIXTY_FIVE, NUMBER_OF_DECIMALS, ROUNDING_METHOD); + expectedValue = expectedValue.add(ONE); + expectedValue = expectedValue.add(TWELVE.divide(THREE_HUNDRED_AND_SIXTY_SIX, NUMBER_OF_DECIMALS, ROUNDING_METHOD)); + + testCalculateNumberOfBillingCycles(startDate, endDate, targetDate, 15, expectedValue); + } + + @Test + public void testDoubleProRation_TargetDateAfterEndDate() throws InvalidDateSequenceException { + DateTime startDate = buildDateTime(2011, 1, 1); + DateTime targetDate = buildDateTime(2012, 3, 7); + DateTime endDate = buildDateTime(2012, 1, 27); + + BigDecimal expectedValue; + expectedValue = FOURTEEN.divide(THREE_HUNDRED_AND_SIXTY_FIVE, NUMBER_OF_DECIMALS, ROUNDING_METHOD); + expectedValue = expectedValue.add(ONE); + expectedValue = expectedValue.add(TWELVE.divide(THREE_HUNDRED_AND_SIXTY_SIX, NUMBER_OF_DECIMALS, ROUNDING_METHOD)); + + testCalculateNumberOfBillingCycles(startDate, endDate, targetDate, 15, expectedValue); + } + + @Test + public void testDoubleProRationWithMultiplePeriods_TargetDateInSecondFullBillingPeriod() throws InvalidDateSequenceException { + DateTime startDate = buildDateTime(2011, 1, 1); + DateTime targetDate = buildDateTime(2012, 2, 26); + DateTime endDate = buildDateTime(2013, 4, 27); + + BigDecimal expectedValue; + expectedValue = FOURTEEN.divide(THREE_HUNDRED_AND_SIXTY_FIVE, NUMBER_OF_DECIMALS, ROUNDING_METHOD); + expectedValue = expectedValue.add(TWO); + + testCalculateNumberOfBillingCycles(startDate, endDate, targetDate, 15, expectedValue); + } +} 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 new file mode 100644 index 0000000000..f612f107d9 --- /dev/null +++ b/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/annual/GenericProRationTests.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.invoice.tests.inAdvance.annual; + +import com.ning.billing.catalog.api.BillingPeriod; +import com.ning.billing.invoice.tests.inAdvance.GenericProRationTestBase; +import org.testng.annotations.Test; + +import java.math.BigDecimal; + +@Test(groups = {"invoicing", "proRation"}) +public class GenericProRationTests extends GenericProRationTestBase { + @Override + protected BillingPeriod getBillingPeriod() { + return BillingPeriod.ANNUAL; + } + + @Override + protected BigDecimal getDaysInTestPeriod() { + return THREE_HUNDRED_AND_SIXTY_FIVE; + } +} 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 new file mode 100644 index 0000000000..f44876eb44 --- /dev/null +++ b/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/annual/LeadingProRationTests.java @@ -0,0 +1,149 @@ +/* + * Copyright 2010-2011 Ning, Inc. + * + * Ning licenses this file to you under the Apache License, version 2.0 + * (the "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF 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.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 org.joda.time.DateTime; +import org.testng.annotations.Test; + +import java.math.BigDecimal; + +@Test(groups = {"invoicing", "proRation"}) +public class LeadingProRationTests extends ProRationInAdvanceTestBase { + @Override + protected BillingPeriod getBillingPeriod() { + return BillingPeriod.ANNUAL; + } + + @Test + public void testLeadingProRation_Evergreen_TargetDateOnStartDate() throws InvalidDateSequenceException { + DateTime startDate = buildDateTime(2011, 2, 1); + DateTime targetDate = buildDateTime(2011, 2, 1); + + BigDecimal expectedValue; + expectedValue = TWELVE.divide(THREE_HUNDRED_AND_SIXTY_FIVE, NUMBER_OF_DECIMALS, ROUNDING_METHOD); + + testCalculateNumberOfBillingCycles(startDate, targetDate, 13, expectedValue); + } + + @Test + public void testLeadingProRation_Evergreen_TargetDateInProRationPeriod() throws InvalidDateSequenceException { + DateTime startDate = buildDateTime(2011, 2, 1); + DateTime targetDate = buildDateTime(2011, 2, 4); + + BigDecimal expectedValue; + expectedValue = TWELVE.divide(THREE_HUNDRED_AND_SIXTY_FIVE, NUMBER_OF_DECIMALS, ROUNDING_METHOD); + + testCalculateNumberOfBillingCycles(startDate, targetDate, 13, expectedValue); + } + + @Test + public void testLeadingProRation_Evergreen_TargetDateOnFirstBillingDate() throws InvalidDateSequenceException { + DateTime startDate = buildDateTime(2011, 2, 1); + DateTime targetDate = buildDateTime(2011, 2, 13); + + BigDecimal expectedValue; + expectedValue = TWELVE.divide(THREE_HUNDRED_AND_SIXTY_FIVE, NUMBER_OF_DECIMALS, ROUNDING_METHOD).add(ONE); + + testCalculateNumberOfBillingCycles(startDate, targetDate, 13, expectedValue); + } + + @Test + public void testLeadingProRation_Evergreen_TargetDateAfterFirstBillingPeriod() throws InvalidDateSequenceException { + DateTime startDate = buildDateTime(2011, 2, 1); + DateTime targetDate = buildDateTime(2011, 4, 13); + + BigDecimal expectedValue; + expectedValue = TWELVE.divide(THREE_HUNDRED_AND_SIXTY_FIVE, NUMBER_OF_DECIMALS, ROUNDING_METHOD).add(ONE); + + testCalculateNumberOfBillingCycles(startDate, targetDate, 13, expectedValue); + } + + @Test + public void testLeadingProRation_WithEndDate_TargetDateOnStartDate() throws InvalidDateSequenceException { + DateTime startDate = buildDateTime(2011, 2, 1); + DateTime targetDate = buildDateTime(2011, 2, 1); + DateTime endDate = buildDateTime(2012, 2, 13); + + BigDecimal expectedValue; + expectedValue = TWELVE.divide(THREE_HUNDRED_AND_SIXTY_FIVE, NUMBER_OF_DECIMALS, ROUNDING_METHOD); + + testCalculateNumberOfBillingCycles(startDate, endDate, targetDate, 13, expectedValue); + } + + @Test + public void testLeadingProRation_WithEndDate_TargetDateInProRationPeriod() throws InvalidDateSequenceException { + DateTime startDate = buildDateTime(2011, 2, 1); + DateTime targetDate = buildDateTime(2011, 2, 4); + DateTime endDate = buildDateTime(2012, 2, 13); + + BigDecimal expectedValue; + expectedValue = TWELVE.divide(THREE_HUNDRED_AND_SIXTY_FIVE, NUMBER_OF_DECIMALS, ROUNDING_METHOD); + + testCalculateNumberOfBillingCycles(startDate, endDate, targetDate, 13, expectedValue); + } + + @Test + public void testLeadingProRation_WithEndDate_TargetDateOnFirstBillingDate() throws InvalidDateSequenceException { + DateTime startDate = buildDateTime(2011, 2, 1); + DateTime targetDate = buildDateTime(2011, 2, 13); + DateTime endDate = buildDateTime(2012, 2, 13); + + BigDecimal expectedValue; + expectedValue = TWELVE.divide(THREE_HUNDRED_AND_SIXTY_FIVE, NUMBER_OF_DECIMALS, ROUNDING_METHOD).add(ONE); + + testCalculateNumberOfBillingCycles(startDate, endDate, targetDate, 13, expectedValue); + } + + @Test + public void testLeadingProRation_WithEndDate_TargetDateInFinalBillingPeriod() throws InvalidDateSequenceException { + DateTime startDate = buildDateTime(2011, 2, 1); + DateTime targetDate = buildDateTime(2011, 4, 10); + DateTime endDate = buildDateTime(2012, 2, 13); + + BigDecimal expectedValue; + expectedValue = TWELVE.divide(THREE_HUNDRED_AND_SIXTY_FIVE, NUMBER_OF_DECIMALS, ROUNDING_METHOD).add(ONE); + + testCalculateNumberOfBillingCycles(startDate, endDate, targetDate, 13, expectedValue); + } + + @Test + public void testLeadingProRation_WithEndDate_TargetDateOnEndDate() throws InvalidDateSequenceException { + DateTime startDate = buildDateTime(2011, 2, 1); + DateTime targetDate = buildDateTime(2012, 2, 13); + DateTime endDate = buildDateTime(2012, 2, 13); + + BigDecimal expectedValue; + expectedValue = TWELVE.divide(THREE_HUNDRED_AND_SIXTY_FIVE, NUMBER_OF_DECIMALS, ROUNDING_METHOD).add(ONE); + + testCalculateNumberOfBillingCycles(startDate, endDate, targetDate, 13, expectedValue); + } + + @Test + public void testLeadingProRation_WithEndDate_TargetDateAfterEndDate() throws InvalidDateSequenceException { + DateTime startDate = buildDateTime(2011, 2, 1); + DateTime targetDate = buildDateTime(2012, 4, 10); + DateTime endDate = buildDateTime(2012, 2, 13); + + BigDecimal expectedValue; + expectedValue = TWELVE.divide(THREE_HUNDRED_AND_SIXTY_FIVE, NUMBER_OF_DECIMALS, ROUNDING_METHOD).add(ONE); + + testCalculateNumberOfBillingCycles(startDate, endDate, targetDate, 13, expectedValue); + } +} 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 new file mode 100644 index 0000000000..274a8e7b13 --- /dev/null +++ b/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/annual/ProRationTests.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.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 org.joda.time.DateTime; +import org.testng.annotations.Test; + +import java.math.BigDecimal; + +@Test(groups = {"invoicing", "proRation"}) +public class ProRationTests extends ProRationInAdvanceTestBase { + @Override + protected BillingPeriod getBillingPeriod() { + return BillingPeriod.ANNUAL; + } + + @Test + public void testSinglePlan_PrecedingProRation() throws InvalidDateSequenceException { + DateTime startDate = buildDateTime(2011, 1, 31); + DateTime targetDate = buildDateTime(2011, 2, 24); + + BigDecimal expectedValue = ONE.add(FIFTEEN.divide(THREE_HUNDRED_AND_SIXTY_FIVE, NUMBER_OF_DECIMALS, ROUNDING_METHOD)); + testCalculateNumberOfBillingCycles(startDate, targetDate, 15, expectedValue); + } + + @Test + public void testSinglePlan_PrecedingProRation_CrossingYearBoundary() throws InvalidDateSequenceException { + DateTime startDate = buildDateTime(2010, 12, 15); + DateTime targetDate = buildDateTime(2011, 1, 13); + + BigDecimal expectedValue = ONE.add(TWENTY.divide(THREE_HUNDRED_AND_SIXTY_FIVE, NUMBER_OF_DECIMALS, ROUNDING_METHOD)); + testCalculateNumberOfBillingCycles(startDate, targetDate, 4, expectedValue); + } + + @Test(enabled = false) + public void testSinglePlanDoubleProRation() throws InvalidDateSequenceException { + DateTime startDate = buildDateTime(2011, 1, 10); + DateTime endDate = buildDateTime(2012, 3, 4); + DateTime targetDate = buildDateTime(2012, 4, 5); + + BigDecimal expectedValue = BigDecimal.ZERO; + testCalculateNumberOfBillingCycles(startDate, endDate, targetDate, 17, expectedValue); + } +} 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 new file mode 100644 index 0000000000..0689c690df --- /dev/null +++ b/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/annual/TrailingProRationTests.java @@ -0,0 +1,90 @@ +/* + * Copyright 2010-2011 Ning, Inc. + * + * Ning licenses this file to you under the Apache License, version 2.0 + * (the "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package com.ning.billing.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 org.joda.time.DateTime; +import org.testng.annotations.Test; + +import java.math.BigDecimal; + +@Test(groups = {"invoicing", "proRation"}) +public class TrailingProRationTests extends ProRationInAdvanceTestBase { + @Override + protected BillingPeriod getBillingPeriod() { + return BillingPeriod.ANNUAL; + } + + @Test + public void testTargetDateOnStartDate() throws InvalidDateSequenceException { + DateTime startDate = buildDateTime(2010, 6, 17); + DateTime endDate = buildDateTime(2012, 6, 25); + DateTime targetDate = buildDateTime(2010, 6, 17); + + testCalculateNumberOfBillingCycles(startDate, endDate, targetDate, 17, ONE); + } + + @Test + public void testTargetDateInFirstBillingPeriod() throws InvalidDateSequenceException { + DateTime startDate = buildDateTime(2010, 6, 17); + DateTime endDate = buildDateTime(2011, 6, 25); + DateTime targetDate = buildDateTime(2010, 6, 20); + + testCalculateNumberOfBillingCycles(startDate, endDate, targetDate, 17, ONE); + } + + @Test + public void testTargetDateAtEndOfFirstBillingCycle() throws InvalidDateSequenceException { + DateTime startDate = buildDateTime(2010, 6, 17); + DateTime endDate = buildDateTime(2011, 6, 25); + DateTime targetDate = buildDateTime(2011, 6, 17); + + BigDecimal expectedValue = ONE.add(EIGHT.divide(THREE_HUNDRED_AND_SIXTY_FIVE, NUMBER_OF_DECIMALS, ROUNDING_METHOD)); + testCalculateNumberOfBillingCycles(startDate, endDate, targetDate, 17, expectedValue); + } + + @Test + public void testTargetDateInProRationPeriod() throws InvalidDateSequenceException { + DateTime startDate = buildDateTime(2010, 6, 17); + DateTime endDate = buildDateTime(2011, 6, 25); + DateTime targetDate = buildDateTime(2011, 6, 18); + + BigDecimal expectedValue = ONE.add(EIGHT.divide(THREE_HUNDRED_AND_SIXTY_SIX, NUMBER_OF_DECIMALS, ROUNDING_METHOD)); + testCalculateNumberOfBillingCycles(startDate, endDate, targetDate, 17, expectedValue); + } + + @Test + public void testTargetDateOnEndDate() throws InvalidDateSequenceException { + DateTime startDate = buildDateTime(2010, 6, 17); + DateTime endDate = buildDateTime(2011, 6, 25); + + BigDecimal expectedValue = ONE.add(EIGHT.divide(THREE_HUNDRED_AND_SIXTY_SIX, NUMBER_OF_DECIMALS, ROUNDING_METHOD)); + testCalculateNumberOfBillingCycles(startDate, endDate, endDate, 17, expectedValue); + } + + @Test + public void testTargetDateAfterEndDate() throws InvalidDateSequenceException { + DateTime startDate = buildDateTime(2010, 6, 17); + DateTime endDate = buildDateTime(2011, 6, 25); + DateTime targetDate = buildDateTime(2011, 7, 30); + + BigDecimal expectedValue = ONE.add(EIGHT.divide(THREE_HUNDRED_AND_SIXTY_SIX, NUMBER_OF_DECIMALS, ROUNDING_METHOD)); + testCalculateNumberOfBillingCycles(startDate, endDate, targetDate, 17, expectedValue); + } +} \ No newline at end of file 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 new file mode 100644 index 0000000000..4799e4089d --- /dev/null +++ b/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/monthly/DoubleProRationTests.java @@ -0,0 +1,145 @@ +/* + * Copyright 2010-2011 Ning, Inc. + * + * Ning licenses this file to you under the Apache License, version 2.0 + * (the "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF 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.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 org.joda.time.DateTime; +import org.testng.annotations.Test; + +import java.math.BigDecimal; + +@Test(groups = {"invoicing", "proRation"}) +public class DoubleProRationTests extends ProRationInAdvanceTestBase { + @Override + protected BillingPeriod getBillingPeriod() { + return BillingPeriod.MONTHLY; + } + + @Test + public void testDoubleProRation_TargetDateOnStartDate() throws InvalidDateSequenceException { + DateTime startDate = buildDateTime(2011, 1, 1); + DateTime targetDate = buildDateTime(2011, 1, 1); + DateTime endDate = buildDateTime(2011, 2, 27); + + BigDecimal expectedValue = FOURTEEN.divide(THIRTY_ONE, NUMBER_OF_DECIMALS, ROUNDING_METHOD); + testCalculateNumberOfBillingCycles(startDate, endDate, targetDate, 15, expectedValue); + } + + @Test + public void testDoubleProRation_TargetDateInFirstProRationPeriod() throws InvalidDateSequenceException { + DateTime startDate = buildDateTime(2011, 1, 1); + DateTime targetDate = buildDateTime(2011, 1, 7); + DateTime endDate = buildDateTime(2011, 2, 27); + + BigDecimal expectedValue = FOURTEEN.divide(THIRTY_ONE, NUMBER_OF_DECIMALS, ROUNDING_METHOD); + testCalculateNumberOfBillingCycles(startDate, endDate, targetDate, 15, expectedValue); + } + + @Test + public void testDoubleProRation_TargetDateOnFirstBillingCycleDate() throws InvalidDateSequenceException { + DateTime startDate = buildDateTime(2011, 1, 1); + DateTime targetDate = buildDateTime(2011, 1, 15); + DateTime endDate = buildDateTime(2011, 2, 27); + + BigDecimal expectedValue = ONE.add(FOURTEEN.divide(THIRTY_ONE, NUMBER_OF_DECIMALS, ROUNDING_METHOD)); + testCalculateNumberOfBillingCycles(startDate, endDate, targetDate, 15, expectedValue); + } + + @Test + public void testDoubleProRation_TargetDateInFullBillingPeriod() throws InvalidDateSequenceException { + DateTime startDate = buildDateTime(2011, 1, 1); + DateTime targetDate = buildDateTime(2011, 1, 22); + DateTime endDate = buildDateTime(2011, 2, 27); + + BigDecimal expectedValue; + expectedValue = FOURTEEN.divide(THIRTY_ONE, NUMBER_OF_DECIMALS, ROUNDING_METHOD); + expectedValue = expectedValue.add(ONE); + + testCalculateNumberOfBillingCycles(startDate, endDate, targetDate, 15, expectedValue); + } + + @Test + public void testDoubleProRation_TargetDateOnSecondBillingCycleDate() throws InvalidDateSequenceException { + DateTime startDate = buildDateTime(2011, 1, 1); + DateTime targetDate = buildDateTime(2011, 2, 27); + DateTime endDate = buildDateTime(2011, 2, 27); + + BigDecimal expectedValue; + expectedValue = FOURTEEN.divide(THIRTY_ONE, NUMBER_OF_DECIMALS, ROUNDING_METHOD); + expectedValue = expectedValue.add(ONE); + expectedValue = expectedValue.add(TWELVE.divide(TWENTY_EIGHT, NUMBER_OF_DECIMALS, ROUNDING_METHOD)); + + testCalculateNumberOfBillingCycles(startDate, endDate, targetDate, 15, expectedValue); + } + + @Test + public void testDoubleProRation_TargetDateInSecondProRationPeriod() throws InvalidDateSequenceException { + DateTime startDate = buildDateTime(2011, 1, 1); + DateTime targetDate = buildDateTime(2011, 2, 26); + DateTime endDate = buildDateTime(2011, 2, 27); + + BigDecimal expectedValue; + expectedValue = FOURTEEN.divide(THIRTY_ONE, NUMBER_OF_DECIMALS, ROUNDING_METHOD); + expectedValue = expectedValue.add(ONE); + expectedValue = expectedValue.add(TWELVE.divide(TWENTY_EIGHT, NUMBER_OF_DECIMALS, ROUNDING_METHOD)); + + testCalculateNumberOfBillingCycles(startDate, endDate, targetDate, 15, expectedValue); + } + + @Test + public void testDoubleProRation_TargetDateOnEndDate() throws InvalidDateSequenceException { + DateTime startDate = buildDateTime(2011, 1, 1); + DateTime targetDate = buildDateTime(2011, 2, 27); + DateTime endDate = buildDateTime(2011, 2, 27); + + BigDecimal expectedValue; + expectedValue = FOURTEEN.divide(THIRTY_ONE, NUMBER_OF_DECIMALS, ROUNDING_METHOD); + expectedValue = expectedValue.add(ONE); + expectedValue = expectedValue.add(TWELVE.divide(TWENTY_EIGHT, NUMBER_OF_DECIMALS, ROUNDING_METHOD)); + + testCalculateNumberOfBillingCycles(startDate, endDate, targetDate, 15, expectedValue); + } + + @Test + public void testDoubleProRation_TargetDateAfterEndDate() throws InvalidDateSequenceException { + DateTime startDate = buildDateTime(2011, 1, 1); + DateTime targetDate = buildDateTime(2011, 3, 7); + DateTime endDate = buildDateTime(2011, 2, 27); + + BigDecimal expectedValue; + expectedValue = FOURTEEN.divide(THIRTY_ONE, NUMBER_OF_DECIMALS, ROUNDING_METHOD); + expectedValue = expectedValue.add(ONE); + expectedValue = expectedValue.add(TWELVE.divide(TWENTY_EIGHT, NUMBER_OF_DECIMALS, ROUNDING_METHOD)); + + testCalculateNumberOfBillingCycles(startDate, endDate, targetDate, 15, expectedValue); + } + + @Test + public void testDoubleProRationWithMultiplePeriods_TargetDateInSecondFullBillingPeriod() throws InvalidDateSequenceException { + DateTime startDate = buildDateTime(2011, 1, 1); + DateTime targetDate = buildDateTime(2011, 2, 26); + DateTime endDate = buildDateTime(2011, 4, 27); + + BigDecimal expectedValue; + expectedValue = FOURTEEN.divide(THIRTY_ONE, NUMBER_OF_DECIMALS, ROUNDING_METHOD); + expectedValue = expectedValue.add(TWO); + + testCalculateNumberOfBillingCycles(startDate, endDate, targetDate, 15, expectedValue); + } +} 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 new file mode 100644 index 0000000000..eb3b1c94ef --- /dev/null +++ b/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/monthly/GenericProRationTests.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.invoice.tests.inAdvance.monthly; + +import com.ning.billing.catalog.api.BillingPeriod; +import com.ning.billing.invoice.tests.inAdvance.GenericProRationTestBase; +import org.testng.annotations.Test; + +import java.math.BigDecimal; + +@Test(groups = {"invoicing", "proRation"}) +public class GenericProRationTests extends GenericProRationTestBase { + @Override + protected BillingPeriod getBillingPeriod() { + return BillingPeriod.MONTHLY; + } + + @Override + protected BigDecimal getDaysInTestPeriod() { + return THIRTY_ONE; + } +} 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 new file mode 100644 index 0000000000..5db69117f1 --- /dev/null +++ b/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/monthly/LeadingProRationTests.java @@ -0,0 +1,149 @@ +/* + * Copyright 2010-2011 Ning, Inc. + * + * Ning licenses this file to you under the Apache License, version 2.0 + * (the "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF 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.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 org.joda.time.DateTime; +import org.testng.annotations.Test; + +import java.math.BigDecimal; + +@Test(groups = {"invoicing", "proRation"}) +public class LeadingProRationTests extends ProRationInAdvanceTestBase { + @Override + protected BillingPeriod getBillingPeriod() { + return BillingPeriod.MONTHLY; + } + + @Test + public void testLeadingProRation_Evergreen_TargetDateOnStartDate() throws InvalidDateSequenceException { + DateTime startDate = buildDateTime(2011, 2, 1); + DateTime targetDate = buildDateTime(2011, 2, 1); + + BigDecimal expectedValue; + expectedValue = TWELVE.divide(THIRTY_ONE, NUMBER_OF_DECIMALS, ROUNDING_METHOD); + + testCalculateNumberOfBillingCycles(startDate, targetDate, 13, expectedValue); + } + + @Test + public void testLeadingProRation_Evergreen_TargetDateInProRationPeriod() throws InvalidDateSequenceException { + DateTime startDate = buildDateTime(2011, 2, 1); + DateTime targetDate = buildDateTime(2011, 2, 4); + + BigDecimal expectedValue; + expectedValue = TWELVE.divide(THIRTY_ONE, NUMBER_OF_DECIMALS, ROUNDING_METHOD); + + testCalculateNumberOfBillingCycles(startDate, targetDate, 13, expectedValue); + } + + @Test + public void testLeadingProRation_Evergreen_TargetDateOnFirstBillingDate() throws InvalidDateSequenceException { + DateTime startDate = buildDateTime(2011, 2, 1); + DateTime targetDate = buildDateTime(2011, 2, 13); + + BigDecimal expectedValue; + expectedValue = TWELVE.divide(THIRTY_ONE, NUMBER_OF_DECIMALS, ROUNDING_METHOD).add(ONE); + + testCalculateNumberOfBillingCycles(startDate, targetDate, 13, expectedValue); + } + + @Test + public void testLeadingProRation_Evergreen_TargetDateAfterFirstBillingPeriod() throws InvalidDateSequenceException { + DateTime startDate = buildDateTime(2011, 2, 1); + DateTime targetDate = buildDateTime(2011, 4, 13); + + BigDecimal expectedValue; + expectedValue = TWELVE.divide(THIRTY_ONE, NUMBER_OF_DECIMALS, ROUNDING_METHOD).add(THREE); + + testCalculateNumberOfBillingCycles(startDate, targetDate, 13, expectedValue); + } + + @Test + public void testLeadingProRation_WithEndDate_TargetDateOnStartDate() throws InvalidDateSequenceException { + DateTime startDate = buildDateTime(2011, 2, 1); + DateTime targetDate = buildDateTime(2011, 2, 1); + DateTime endDate = buildDateTime(2011, 4, 13); + + BigDecimal expectedValue; + expectedValue = TWELVE.divide(THIRTY_ONE, NUMBER_OF_DECIMALS, ROUNDING_METHOD); + + testCalculateNumberOfBillingCycles(startDate, endDate, targetDate, 13, expectedValue); + } + + @Test + public void testLeadingProRation_WithEndDate_TargetDateInProRationPeriod() throws InvalidDateSequenceException { + DateTime startDate = buildDateTime(2011, 2, 1); + DateTime targetDate = buildDateTime(2011, 2, 4); + DateTime endDate = buildDateTime(2011, 4, 13); + + BigDecimal expectedValue; + expectedValue = TWELVE.divide(THIRTY_ONE, NUMBER_OF_DECIMALS, ROUNDING_METHOD); + + testCalculateNumberOfBillingCycles(startDate, endDate, targetDate, 13, expectedValue); + } + + @Test + public void testLeadingProRation_WithEndDate_TargetDateOnFirstBillingDate() throws InvalidDateSequenceException { + DateTime startDate = buildDateTime(2011, 2, 1); + DateTime targetDate = buildDateTime(2011, 2, 13); + DateTime endDate = buildDateTime(2011, 4, 13); + + BigDecimal expectedValue; + expectedValue = TWELVE.divide(THIRTY_ONE, NUMBER_OF_DECIMALS, ROUNDING_METHOD).add(ONE); + + testCalculateNumberOfBillingCycles(startDate, endDate, targetDate, 13, expectedValue); + } + + @Test + public void testLeadingProRation_WithEndDate_TargetDateInFinalBillingPeriod() throws InvalidDateSequenceException { + DateTime startDate = buildDateTime(2011, 2, 1); + DateTime targetDate = buildDateTime(2011, 4, 10); + DateTime endDate = buildDateTime(2011, 4, 13); + + BigDecimal expectedValue; + expectedValue = TWELVE.divide(THIRTY_ONE, NUMBER_OF_DECIMALS, ROUNDING_METHOD).add(TWO); + + testCalculateNumberOfBillingCycles(startDate, endDate, targetDate, 13, expectedValue); + } + + @Test + public void testLeadingProRation_WithEndDate_TargetDateOnEndDate() throws InvalidDateSequenceException { + DateTime startDate = buildDateTime(2011, 2, 1); + DateTime targetDate = buildDateTime(2011, 4, 13); + DateTime endDate = buildDateTime(2011, 4, 13); + + BigDecimal expectedValue; + expectedValue = TWELVE.divide(THIRTY_ONE, NUMBER_OF_DECIMALS, ROUNDING_METHOD).add(TWO); + + testCalculateNumberOfBillingCycles(startDate, endDate, targetDate, 13, expectedValue); + } + + @Test + public void testLeadingProRation_WithEndDate_TargetDateAfterEndDate() throws InvalidDateSequenceException { + DateTime startDate = buildDateTime(2011, 2, 1); + DateTime targetDate = buildDateTime(2011, 4, 10); + DateTime endDate = buildDateTime(2011, 4, 13); + + BigDecimal expectedValue; + expectedValue = TWELVE.divide(THIRTY_ONE, NUMBER_OF_DECIMALS, ROUNDING_METHOD).add(TWO); + + testCalculateNumberOfBillingCycles(startDate, endDate, targetDate, 13, expectedValue); + } +} 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 new file mode 100644 index 0000000000..13c75dfc18 --- /dev/null +++ b/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/monthly/ProRationTests.java @@ -0,0 +1,235 @@ +/* + * Copyright 2010-2011 Ning, Inc. + * + * Ning licenses this file to you under the Apache License, version 2.0 + * (the "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF 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.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 org.joda.time.DateTime; +import org.testng.annotations.Test; + +import java.math.BigDecimal; + +@Test(groups = {"invoicing", "proRation"}) +public class ProRationTests extends ProRationInAdvanceTestBase { + @Override + protected BillingPeriod getBillingPeriod() { + return BillingPeriod.MONTHLY; + } + + @Test + public void testSinglePlan_WithPhaseChange() throws InvalidDateSequenceException { + DateTime startDate = buildDateTime(2011, 2, 10); + DateTime phaseChangeDate = buildDateTime(2011, 2, 24); + DateTime targetDate = buildDateTime(2011, 3, 6); + + testCalculateNumberOfBillingCycles(startDate, phaseChangeDate, targetDate, 10, ONE_HALF); + testCalculateNumberOfBillingCycles(phaseChangeDate, targetDate, 10, ONE_HALF); + } + + @Test + public void testSinglePlan_WithPhaseChange_BeforeBillCycleDay() throws InvalidDateSequenceException { + DateTime startDate = buildDateTime(2011, 2, 3); + DateTime phaseChangeDate = buildDateTime(2011, 2, 17); + DateTime targetDate = buildDateTime(2011, 3, 1); + + testCalculateNumberOfBillingCycles(startDate, phaseChangeDate, targetDate, 3, ONE_HALF); + testCalculateNumberOfBillingCycles(phaseChangeDate, targetDate, 3, ONE_HALF); + } + + @Test + public void testSinglePlan_WithPhaseChange_OnBillCycleDay() throws InvalidDateSequenceException { + DateTime startDate = buildDateTime(2011, 2, 3); + DateTime phaseChangeDate = buildDateTime(2011, 2, 17); + DateTime targetDate = buildDateTime(2011, 3, 3); + + testCalculateNumberOfBillingCycles(startDate, phaseChangeDate, targetDate, 3, ONE_HALF); + testCalculateNumberOfBillingCycles(phaseChangeDate, targetDate, 3, ONE_AND_A_HALF); + } + + @Test + public void testSinglePlan_WithPhaseChange_AfterBillCycleDay() throws InvalidDateSequenceException { + DateTime startDate = buildDateTime(2011, 2, 3); + DateTime phaseChangeDate = buildDateTime(2011, 2, 17); + DateTime targetDate = buildDateTime(2011, 3, 4); + + testCalculateNumberOfBillingCycles(startDate, phaseChangeDate, targetDate, 3, ONE_HALF); + testCalculateNumberOfBillingCycles(phaseChangeDate, targetDate, 3, ONE_AND_A_HALF); + } + + @Test + public void testPlanChange_WithChangeOfBillCycleDayToLaterDay() throws InvalidDateSequenceException { + DateTime startDate = buildDateTime(2011, 2, 1); + DateTime planChangeDate = buildDateTime(2011, 2, 15); + DateTime targetDate = buildDateTime(2011, 3, 1); + + testCalculateNumberOfBillingCycles(startDate, planChangeDate, targetDate, 1, ONE_HALF); + testCalculateNumberOfBillingCycles(planChangeDate, targetDate, 15, ONE); + } + + @Test + public void testPlanChange_WithChangeOfBillCycleDayToEarlierDay() throws InvalidDateSequenceException { + DateTime startDate = buildDateTime(2011, 2, 20); + DateTime planChangeDate = buildDateTime(2011, 3, 6); + DateTime targetDate = buildDateTime(2011, 3, 9); + + testCalculateNumberOfBillingCycles(startDate, planChangeDate, targetDate, 20, ONE_HALF); + testCalculateNumberOfBillingCycles(planChangeDate, targetDate, 6, ONE); + } + + @Test + public void testSinglePlan_CrossingYearBoundary() throws InvalidDateSequenceException { + DateTime startDate = buildDateTime(2010, 12, 15); + DateTime targetDate = buildDateTime(2011, 1, 16); + + testCalculateNumberOfBillingCycles(startDate, targetDate, 15, TWO); + } + + @Test + public void testSinglePlan_LeapYear_StartingMidFebruary() throws InvalidDateSequenceException { + DateTime startDate = buildDateTime(2012, 2, 15); + DateTime targetDate = buildDateTime(2012, 3, 15); + + testCalculateNumberOfBillingCycles(startDate, targetDate, 15, TWO); + } + + @Test + public void testSinglePlan_LeapYear_StartingBeforeFebruary() throws InvalidDateSequenceException { + DateTime startDate = buildDateTime(2012, 1, 15); + DateTime targetDate = buildDateTime(2012, 2, 3); + + testCalculateNumberOfBillingCycles(startDate, targetDate, 15, ONE); + } + + @Test + public void testSinglePlan_LeapYear_IncludingAllOfFebruary() throws InvalidDateSequenceException { + DateTime startDate = buildDateTime(2012, 1, 30); + DateTime targetDate = buildDateTime(2012, 3, 1); + + testCalculateNumberOfBillingCycles(startDate, targetDate, 30, TWO); + } + + @Test + public void testSinglePlan_ChangeBCDTo31() throws InvalidDateSequenceException { + DateTime startDate = buildDateTime(2011, 2, 1); + DateTime planChangeDate = buildDateTime(2011, 2, 14); + DateTime targetDate = buildDateTime(2011, 3, 1); + + BigDecimal expectedValue; + + expectedValue = THIRTEEN.divide(TWENTY_EIGHT, NUMBER_OF_DECIMALS, ROUNDING_METHOD); + testCalculateNumberOfBillingCycles(startDate, planChangeDate, targetDate, 1, expectedValue); + + expectedValue = ONE.add(FOURTEEN.divide(THIRTY_ONE, NUMBER_OF_DECIMALS, ROUNDING_METHOD)); + testCalculateNumberOfBillingCycles(planChangeDate, targetDate, 31, expectedValue); + } + + @Test + public void testSinglePlan_ChangeBCD() throws InvalidDateSequenceException { + DateTime startDate = buildDateTime(2011, 2, 1); + DateTime planChangeDate = buildDateTime(2011, 2, 14); + DateTime targetDate = buildDateTime(2011, 3, 1); + + BigDecimal expectedValue; + + expectedValue = THIRTEEN.divide(TWENTY_EIGHT, NUMBER_OF_DECIMALS, ROUNDING_METHOD); + testCalculateNumberOfBillingCycles(startDate, planChangeDate, targetDate, 1, expectedValue); + + expectedValue = ONE.add(THIRTEEN.divide(THIRTY_ONE, NUMBER_OF_DECIMALS, ROUNDING_METHOD)); + testCalculateNumberOfBillingCycles(planChangeDate, targetDate, 27, expectedValue); + } + + @Test + public void testSinglePlan_LeapYearFebruaryProRation() throws InvalidDateSequenceException { + DateTime startDate = buildDateTime(2012, 2, 1); + DateTime endDate = buildDateTime(2012, 2, 15); + DateTime targetDate = buildDateTime(2012, 2, 19); + + BigDecimal expectedValue; + expectedValue = FOURTEEN.divide(TWENTY_NINE, NUMBER_OF_DECIMALS, ROUNDING_METHOD); + + testCalculateNumberOfBillingCycles(startDate, endDate, targetDate, 1, expectedValue); + } + + @Test + public void testPlanChange_BeforeBillingDay() throws InvalidDateSequenceException { + DateTime startDate = buildDateTime(2011, 2, 7); + DateTime changeDate = buildDateTime(2011, 2, 15); + DateTime targetDate = buildDateTime(2011, 4, 21); + + BigDecimal expectedValue; + + expectedValue = EIGHT.divide(TWENTY_EIGHT, NUMBER_OF_DECIMALS, ROUNDING_METHOD); + testCalculateNumberOfBillingCycles(startDate, changeDate, targetDate, 7, expectedValue); + + testCalculateNumberOfBillingCycles(changeDate, targetDate, 15, THREE); + } + + @Test + public void testPlanChange_OnBillingDay() throws InvalidDateSequenceException { + DateTime startDate = buildDateTime(2011, 2, 7); + DateTime changeDate = buildDateTime(2011, 3, 7); + DateTime targetDate = buildDateTime(2011, 4, 21); + + testCalculateNumberOfBillingCycles(startDate, changeDate, targetDate, 7, ONE); + + BigDecimal expectedValue; + expectedValue = EIGHT.divide(TWENTY_EIGHT, NUMBER_OF_DECIMALS, ROUNDING_METHOD).add(TWO); + testCalculateNumberOfBillingCycles(changeDate, targetDate, 15, expectedValue); + } + + @Test + public void testPlanChange_AfterBillingDay() throws InvalidDateSequenceException { + DateTime startDate = buildDateTime(2011, 2, 7); + DateTime changeDate = buildDateTime(2011, 3, 10); + DateTime targetDate = buildDateTime(2011, 4, 21); + + BigDecimal expectedValue; + + expectedValue = BigDecimal.ONE.add(THREE.divide(THIRTY_ONE, NUMBER_OF_DECIMALS, ROUNDING_METHOD)); + testCalculateNumberOfBillingCycles(startDate, changeDate, targetDate, 7, expectedValue); + + expectedValue = FIVE.divide(TWENTY_EIGHT, NUMBER_OF_DECIMALS, ROUNDING_METHOD).add(TWO); + testCalculateNumberOfBillingCycles(changeDate, targetDate, 15, expectedValue); + } + + @Test + public void testPlanChange_DoubleProRation() throws InvalidDateSequenceException { + DateTime startDate = buildDateTime(2011, 1, 31); + DateTime planChangeDate = buildDateTime(2011, 3, 10); + DateTime targetDate = buildDateTime(2011, 4, 21); + + BigDecimal expectedValue; + expectedValue = SEVEN.divide(THIRTY_ONE, NUMBER_OF_DECIMALS, ROUNDING_METHOD); + expectedValue = expectedValue.add(ONE); + expectedValue = expectedValue.add(THREE.divide(THIRTY_ONE, NUMBER_OF_DECIMALS, ROUNDING_METHOD)); + testCalculateNumberOfBillingCycles(startDate, planChangeDate, targetDate, 7, expectedValue); + + expectedValue = FIVE.divide(TWENTY_EIGHT, NUMBER_OF_DECIMALS, ROUNDING_METHOD).add(TWO); + testCalculateNumberOfBillingCycles(planChangeDate, targetDate, 15, expectedValue); + } + + @Test + public void testStartTargetEnd() throws InvalidDateSequenceException { + DateTime startDate = buildDateTime(2010, 12, 15); + DateTime targetDate = buildDateTime(2011, 3, 15); + DateTime endDate = buildDateTime(2011, 3, 17); + + BigDecimal expectedValue = THREE.add(TWO.divide(THIRTY_ONE, NUMBER_OF_DECIMALS, ROUNDING_METHOD)); + testCalculateNumberOfBillingCycles(startDate, endDate, targetDate, 15, expectedValue); + } +} \ No newline at end of file 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 new file mode 100644 index 0000000000..9a8e635fc6 --- /dev/null +++ b/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/monthly/TrailingProRationTests.java @@ -0,0 +1,90 @@ +/* + * Copyright 2010-2011 Ning, Inc. + * + * Ning licenses this file to you under the Apache License, version 2.0 + * (the "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package com.ning.billing.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 org.joda.time.DateTime; +import org.testng.annotations.Test; + +import java.math.BigDecimal; + +@Test(groups = {"invoicing", "proRation"}) +public class TrailingProRationTests extends ProRationInAdvanceTestBase { + @Override + protected BillingPeriod getBillingPeriod() { + return BillingPeriod.MONTHLY; + } + + @Test + public void testTargetDateOnStartDate() throws InvalidDateSequenceException { + DateTime startDate = buildDateTime(2010, 6, 17); + DateTime endDate = buildDateTime(2010, 7, 25); + DateTime targetDate = buildDateTime(2010, 6, 17); + + testCalculateNumberOfBillingCycles(startDate, endDate, targetDate, 17, ONE); + } + + @Test + public void testTargetDateInFirstBillingPeriod() throws InvalidDateSequenceException { + DateTime startDate = buildDateTime(2010, 6, 17); + DateTime endDate = buildDateTime(2010, 7, 25); + DateTime targetDate = buildDateTime(2010, 6, 20); + + testCalculateNumberOfBillingCycles(startDate, endDate, targetDate, 17, ONE); + } + + @Test + public void testTargetDateAtEndOfFirstBillingCycle() throws InvalidDateSequenceException { + DateTime startDate = buildDateTime(2010, 6, 17); + DateTime endDate = buildDateTime(2010, 7, 25); + DateTime targetDate = buildDateTime(2010, 7, 17); + + BigDecimal expectedValue = ONE.add(EIGHT.divide(THIRTY_ONE, NUMBER_OF_DECIMALS, ROUNDING_METHOD)); + testCalculateNumberOfBillingCycles(startDate, endDate, targetDate, 17, expectedValue); + } + + @Test + public void testTargetDateInProRationPeriod() throws InvalidDateSequenceException { + DateTime startDate = buildDateTime(2010, 6, 17); + DateTime endDate = buildDateTime(2010, 7, 25); + DateTime targetDate = buildDateTime(2010, 7, 18); + + BigDecimal expectedValue = ONE.add(EIGHT.divide(THIRTY_ONE, NUMBER_OF_DECIMALS, ROUNDING_METHOD)); + testCalculateNumberOfBillingCycles(startDate, endDate, targetDate, 17, expectedValue); + } + + @Test + public void testTargetDateOnEndDate() throws InvalidDateSequenceException { + DateTime startDate = buildDateTime(2010, 6, 17); + DateTime endDate = buildDateTime(2010, 7, 25); + + BigDecimal expectedValue = ONE.add(EIGHT.divide(THIRTY_ONE, NUMBER_OF_DECIMALS, ROUNDING_METHOD)); + testCalculateNumberOfBillingCycles(startDate, endDate, endDate, 17, expectedValue); + } + + @Test + public void testTargetDateAfterEndDate() throws InvalidDateSequenceException { + DateTime startDate = buildDateTime(2010, 6, 17); + DateTime endDate = buildDateTime(2010, 7, 25); + DateTime targetDate = buildDateTime(2010, 7, 30); + + BigDecimal expectedValue = ONE.add(EIGHT.divide(THIRTY_ONE, NUMBER_OF_DECIMALS, ROUNDING_METHOD)); + testCalculateNumberOfBillingCycles(startDate, endDate, targetDate, 17, expectedValue); + } +} \ No newline at end of file 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 new file mode 100644 index 0000000000..68e29fdd00 --- /dev/null +++ b/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/quarterly/DoubleProRationTests.java @@ -0,0 +1,145 @@ +/* + * Copyright 2010-2011 Ning, Inc. + * + * Ning licenses this file to you under the Apache License, version 2.0 + * (the "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF 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.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 org.joda.time.DateTime; +import org.testng.annotations.Test; + +import java.math.BigDecimal; + +@Test(groups = {"invoicing", "proRation"}) +public class DoubleProRationTests extends ProRationInAdvanceTestBase { + @Override + protected BillingPeriod getBillingPeriod() { + return BillingPeriod.QUARTERLY; + } + + @Test + public void testDoubleProRation_TargetDateOnStartDate() throws InvalidDateSequenceException { + DateTime startDate = buildDateTime(2011, 1, 1); + DateTime targetDate = buildDateTime(2011, 1, 1); + DateTime endDate = buildDateTime(2011, 4, 27); + + BigDecimal expectedValue = FOURTEEN.divide(NINETY_TWO, NUMBER_OF_DECIMALS, ROUNDING_METHOD); + testCalculateNumberOfBillingCycles(startDate, endDate, targetDate, 15, expectedValue); + } + + @Test + public void testDoubleProRation_TargetDateInFirstProRationPeriod() throws InvalidDateSequenceException { + DateTime startDate = buildDateTime(2011, 1, 1); + DateTime targetDate = buildDateTime(2011, 1, 7); + DateTime endDate = buildDateTime(2011, 4, 27); + + BigDecimal expectedValue = FOURTEEN.divide(NINETY_TWO, NUMBER_OF_DECIMALS, ROUNDING_METHOD); + testCalculateNumberOfBillingCycles(startDate, endDate, targetDate, 15, expectedValue); + } + + @Test + public void testDoubleProRation_TargetDateOnFirstBillingCycleDate() throws InvalidDateSequenceException { + DateTime startDate = buildDateTime(2011, 1, 1); + DateTime targetDate = buildDateTime(2011, 1, 15); + DateTime endDate = buildDateTime(2011, 4, 27); + + BigDecimal expectedValue = ONE.add(FOURTEEN.divide(NINETY_TWO, NUMBER_OF_DECIMALS, ROUNDING_METHOD)); + testCalculateNumberOfBillingCycles(startDate, endDate, targetDate, 15, expectedValue); + } + + @Test + public void testDoubleProRation_TargetDateInFullBillingPeriod() throws InvalidDateSequenceException { + DateTime startDate = buildDateTime(2011, 1, 1); + DateTime targetDate = buildDateTime(2011, 1, 22); + DateTime endDate = buildDateTime(2011, 4, 27); + + BigDecimal expectedValue; + expectedValue = FOURTEEN.divide(NINETY_TWO, NUMBER_OF_DECIMALS, ROUNDING_METHOD); + expectedValue = expectedValue.add(ONE); + + testCalculateNumberOfBillingCycles(startDate, endDate, targetDate, 15, expectedValue); + } + + @Test + public void testDoubleProRation_TargetDateOnSecondBillingCycleDate() throws InvalidDateSequenceException { + DateTime startDate = buildDateTime(2011, 1, 1); + DateTime targetDate = buildDateTime(2011, 4, 15); + DateTime endDate = buildDateTime(2011, 4, 27); + + BigDecimal expectedValue; + expectedValue = FOURTEEN.divide(NINETY_TWO, NUMBER_OF_DECIMALS, ROUNDING_METHOD); + expectedValue = expectedValue.add(ONE); + expectedValue = expectedValue.add(TWELVE.divide(NINETY_ONE, NUMBER_OF_DECIMALS, ROUNDING_METHOD)); + + testCalculateNumberOfBillingCycles(startDate, endDate, targetDate, 15, expectedValue); + } + + @Test + public void testDoubleProRation_TargetDateInSecondProRationPeriod() throws InvalidDateSequenceException { + DateTime startDate = buildDateTime(2011, 1, 1); + DateTime targetDate = buildDateTime(2011, 4, 26); + DateTime endDate = buildDateTime(2011, 4, 27); + + BigDecimal expectedValue; + expectedValue = FOURTEEN.divide(NINETY_TWO, NUMBER_OF_DECIMALS, ROUNDING_METHOD); + expectedValue = expectedValue.add(ONE); + expectedValue = expectedValue.add(TWELVE.divide(NINETY_ONE, NUMBER_OF_DECIMALS, ROUNDING_METHOD)); + + testCalculateNumberOfBillingCycles(startDate, endDate, targetDate, 15, expectedValue); + } + + @Test + public void testDoubleProRation_TargetDateOnEndDate() throws InvalidDateSequenceException { + DateTime startDate = buildDateTime(2011, 1, 1); + DateTime targetDate = buildDateTime(2011, 4, 27); + DateTime endDate = buildDateTime(2011, 4, 27); + + BigDecimal expectedValue; + expectedValue = FOURTEEN.divide(NINETY_TWO, NUMBER_OF_DECIMALS, ROUNDING_METHOD); + expectedValue = expectedValue.add(ONE); + expectedValue = expectedValue.add(TWELVE.divide(NINETY_ONE, NUMBER_OF_DECIMALS, ROUNDING_METHOD)); + + testCalculateNumberOfBillingCycles(startDate, endDate, targetDate, 15, expectedValue); + } + + @Test + public void testDoubleProRation_TargetDateAfterEndDate() throws InvalidDateSequenceException { + DateTime startDate = buildDateTime(2011, 1, 1); + DateTime targetDate = buildDateTime(2011, 5, 7); + DateTime endDate = buildDateTime(2011, 4, 27); + + BigDecimal expectedValue; + expectedValue = FOURTEEN.divide(NINETY_TWO, NUMBER_OF_DECIMALS, ROUNDING_METHOD); + expectedValue = expectedValue.add(ONE); + expectedValue = expectedValue.add(TWELVE.divide(NINETY_ONE, NUMBER_OF_DECIMALS, ROUNDING_METHOD)); + + testCalculateNumberOfBillingCycles(startDate, endDate, targetDate, 15, expectedValue); + } + + @Test + public void testDoubleProRationWithMultiplePeriods_TargetDateInSecondFullBillingPeriod() throws InvalidDateSequenceException { + DateTime startDate = buildDateTime(2011, 1, 1); + DateTime targetDate = buildDateTime(2011, 6, 26); + DateTime endDate = buildDateTime(2011, 8, 27); + + BigDecimal expectedValue; + expectedValue = FOURTEEN.divide(NINETY_TWO, NUMBER_OF_DECIMALS, ROUNDING_METHOD); + expectedValue = expectedValue.add(TWO); + + testCalculateNumberOfBillingCycles(startDate, endDate, targetDate, 15, expectedValue); + } +} 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 new file mode 100644 index 0000000000..83067165d8 --- /dev/null +++ b/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/quarterly/GenericProRationTests.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.invoice.tests.inAdvance.quarterly; + +import com.ning.billing.catalog.api.BillingPeriod; +import com.ning.billing.invoice.tests.inAdvance.GenericProRationTestBase; +import org.testng.annotations.Test; + +import java.math.BigDecimal; + +@Test(groups = {"invoicing", "proRation"}) +public class GenericProRationTests extends GenericProRationTestBase { + @Override + protected BillingPeriod getBillingPeriod() { + return BillingPeriod.QUARTERLY; + } + + @Override + protected BigDecimal getDaysInTestPeriod() { + return NINETY; + } +} 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 new file mode 100644 index 0000000000..fe614a94ae --- /dev/null +++ b/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/quarterly/LeadingProRationTests.java @@ -0,0 +1,149 @@ +/* + * Copyright 2010-2011 Ning, Inc. + * + * Ning licenses this file to you under the Apache License, version 2.0 + * (the "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF 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.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 org.joda.time.DateTime; +import org.testng.annotations.Test; + +import java.math.BigDecimal; + +@Test(groups = {"invoicing", "proRation"}) +public class LeadingProRationTests extends ProRationInAdvanceTestBase { + @Override + protected BillingPeriod getBillingPeriod() { + return BillingPeriod.QUARTERLY; + } + + @Test + public void testLeadingProRation_Evergreen_TargetDateOnStartDate() throws InvalidDateSequenceException { + DateTime startDate = buildDateTime(2011, 2, 1); + DateTime targetDate = buildDateTime(2011, 2, 1); + + BigDecimal expectedValue; + expectedValue = TWELVE.divide(NINETY_TWO, NUMBER_OF_DECIMALS, ROUNDING_METHOD); + + testCalculateNumberOfBillingCycles(startDate, targetDate, 13, expectedValue); + } + + @Test + public void testLeadingProRation_Evergreen_TargetDateInProRationPeriod() throws InvalidDateSequenceException { + DateTime startDate = buildDateTime(2011, 2, 1); + DateTime targetDate = buildDateTime(2011, 2, 4); + + BigDecimal expectedValue; + expectedValue = TWELVE.divide(NINETY_TWO, NUMBER_OF_DECIMALS, ROUNDING_METHOD); + + testCalculateNumberOfBillingCycles(startDate, targetDate, 13, expectedValue); + } + + @Test + public void testLeadingProRation_Evergreen_TargetDateOnFirstBillingDate() throws InvalidDateSequenceException { + DateTime startDate = buildDateTime(2011, 2, 1); + DateTime targetDate = buildDateTime(2011, 2, 13); + + BigDecimal expectedValue; + expectedValue = TWELVE.divide(NINETY_TWO, NUMBER_OF_DECIMALS, ROUNDING_METHOD).add(ONE); + + testCalculateNumberOfBillingCycles(startDate, targetDate, 13, expectedValue); + } + + @Test + public void testLeadingProRation_Evergreen_TargetDateAfterFirstBillingPeriod() throws InvalidDateSequenceException { + DateTime startDate = buildDateTime(2011, 2, 1); + DateTime targetDate = buildDateTime(2011, 6, 13); + + BigDecimal expectedValue; + expectedValue = TWELVE.divide(NINETY_TWO, NUMBER_OF_DECIMALS, ROUNDING_METHOD).add(TWO); + + testCalculateNumberOfBillingCycles(startDate, targetDate, 13, expectedValue); + } + + @Test + public void testLeadingProRation_WithEndDate_TargetDateOnStartDate() throws InvalidDateSequenceException { + DateTime startDate = buildDateTime(2011, 2, 1); + DateTime targetDate = buildDateTime(2011, 2, 1); + DateTime endDate = buildDateTime(2011, 8, 13); + + BigDecimal expectedValue; + expectedValue = TWELVE.divide(NINETY_TWO, NUMBER_OF_DECIMALS, ROUNDING_METHOD); + + testCalculateNumberOfBillingCycles(startDate, endDate, targetDate, 13, expectedValue); + } + + @Test + public void testLeadingProRation_WithEndDate_TargetDateInProRationPeriod() throws InvalidDateSequenceException { + DateTime startDate = buildDateTime(2011, 2, 1); + DateTime targetDate = buildDateTime(2011, 2, 4); + DateTime endDate = buildDateTime(2011, 8, 13); + + BigDecimal expectedValue; + expectedValue = TWELVE.divide(NINETY_TWO, NUMBER_OF_DECIMALS, ROUNDING_METHOD); + + testCalculateNumberOfBillingCycles(startDate, endDate, targetDate, 13, expectedValue); + } + + @Test + public void testLeadingProRation_WithEndDate_TargetDateOnFirstBillingDate() throws InvalidDateSequenceException { + DateTime startDate = buildDateTime(2011, 2, 1); + DateTime targetDate = buildDateTime(2011, 2, 13); + DateTime endDate = buildDateTime(2011, 8, 13); + + BigDecimal expectedValue; + expectedValue = TWELVE.divide(NINETY_TWO, NUMBER_OF_DECIMALS, ROUNDING_METHOD).add(ONE); + + testCalculateNumberOfBillingCycles(startDate, endDate, targetDate, 13, expectedValue); + } + + @Test + public void testLeadingProRation_WithEndDate_TargetDateInFinalBillingPeriod() throws InvalidDateSequenceException { + DateTime startDate = buildDateTime(2011, 2, 1); + DateTime targetDate = buildDateTime(2011, 8, 10); + DateTime endDate = buildDateTime(2011, 8, 13); + + BigDecimal expectedValue; + expectedValue = TWELVE.divide(NINETY_TWO, NUMBER_OF_DECIMALS, ROUNDING_METHOD).add(TWO); + + testCalculateNumberOfBillingCycles(startDate, endDate, targetDate, 13, expectedValue); + } + + @Test + public void testLeadingProRation_WithEndDate_TargetDateOnEndDate() throws InvalidDateSequenceException { + DateTime startDate = buildDateTime(2011, 2, 1); + DateTime targetDate = buildDateTime(2011, 8, 13); + DateTime endDate = buildDateTime(2011, 8, 13); + + BigDecimal expectedValue; + expectedValue = TWELVE.divide(NINETY_TWO, NUMBER_OF_DECIMALS, ROUNDING_METHOD).add(TWO); + + testCalculateNumberOfBillingCycles(startDate, endDate, targetDate, 13, expectedValue); + } + + @Test + public void testLeadingProRation_WithEndDate_TargetDateAfterEndDate() throws InvalidDateSequenceException { + DateTime startDate = buildDateTime(2011, 2, 1); + DateTime targetDate = buildDateTime(2011, 9, 10); + DateTime endDate = buildDateTime(2011, 8, 13); + + BigDecimal expectedValue; + expectedValue = TWELVE.divide(NINETY_TWO, NUMBER_OF_DECIMALS, ROUNDING_METHOD).add(TWO); + + testCalculateNumberOfBillingCycles(startDate, endDate, targetDate, 13, expectedValue); + } +} 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 new file mode 100644 index 0000000000..c99aa5c41f --- /dev/null +++ b/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/quarterly/ProRationTests.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.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 org.joda.time.DateTime; +import org.testng.annotations.Test; + +import java.math.BigDecimal; + +@Test(groups = {"invoicing", "proRation"}) +public class ProRationTests extends ProRationInAdvanceTestBase { + @Override + protected BillingPeriod getBillingPeriod() { + return BillingPeriod.QUARTERLY; + } + + @Test + public void testSinglePlan_WithPhaseChange() throws InvalidDateSequenceException { + DateTime startDate = buildDateTime(2011, 2, 10); + DateTime phaseChangeDate = buildDateTime(2011, 2, 24); + DateTime targetDate = buildDateTime(2011, 3, 6); + + BigDecimal expectedValue; + expectedValue = FOURTEEN.divide(EIGHTY_NINE, NUMBER_OF_DECIMALS, ROUNDING_METHOD); + testCalculateNumberOfBillingCycles(startDate, phaseChangeDate, targetDate, 10, expectedValue); + + expectedValue = FOURTEEN.divide(NINETY, NUMBER_OF_DECIMALS, ROUNDING_METHOD); + testCalculateNumberOfBillingCycles(phaseChangeDate, targetDate, 10, expectedValue); + } + + @Test + public void testSinglePlan_WithPhaseChange_BeforeBillCycleDay() throws InvalidDateSequenceException { + DateTime startDate = buildDateTime(2011, 2, 3); + DateTime phaseChangeDate = buildDateTime(2011, 2, 17); + DateTime targetDate = buildDateTime(2011, 3, 1); + + BigDecimal expectedValue; + expectedValue = FOURTEEN.divide(EIGHTY_NINE, NUMBER_OF_DECIMALS, ROUNDING_METHOD); + testCalculateNumberOfBillingCycles(startDate, phaseChangeDate, targetDate, 3, expectedValue); + + expectedValue = FOURTEEN.divide(NINETY, NUMBER_OF_DECIMALS, ROUNDING_METHOD); + testCalculateNumberOfBillingCycles(phaseChangeDate, targetDate, 3, expectedValue); + } + + @Test + public void testSinglePlan_WithPhaseChange_OnBillCycleDay() throws InvalidDateSequenceException { + DateTime startDate = buildDateTime(2011, 2, 3); + DateTime phaseChangeDate = buildDateTime(2011, 2, 17); + DateTime targetDate = buildDateTime(2011, 3, 3); + + BigDecimal expectedValue; + expectedValue = FOURTEEN.divide(EIGHTY_NINE, NUMBER_OF_DECIMALS, ROUNDING_METHOD); + testCalculateNumberOfBillingCycles(startDate, phaseChangeDate, targetDate, 3, expectedValue); + + expectedValue = FOURTEEN.divide(NINETY, NUMBER_OF_DECIMALS, ROUNDING_METHOD).add(ONE); + testCalculateNumberOfBillingCycles(phaseChangeDate, targetDate, 3, expectedValue); + } + + @Test + public void testSinglePlan_WithPhaseChange_AfterBillCycleDay() throws InvalidDateSequenceException { + DateTime startDate = buildDateTime(2011, 2, 3); + DateTime phaseChangeDate = buildDateTime(2011, 2, 17); + DateTime targetDate = buildDateTime(2011, 3, 4); + + BigDecimal expectedValue; + expectedValue = FOURTEEN.divide(EIGHTY_NINE, NUMBER_OF_DECIMALS, ROUNDING_METHOD); + testCalculateNumberOfBillingCycles(startDate, phaseChangeDate, targetDate, 3, expectedValue); + + expectedValue = FOURTEEN.divide(NINETY, NUMBER_OF_DECIMALS, ROUNDING_METHOD).add(ONE); + testCalculateNumberOfBillingCycles(phaseChangeDate, targetDate, 3, expectedValue); + } + + @Test + public void testPlanChange_WithChangeOfBillCycleDayToLaterDay() throws InvalidDateSequenceException { + DateTime startDate = buildDateTime(2011, 2, 1); + DateTime planChangeDate = buildDateTime(2011, 2, 15); + DateTime targetDate = buildDateTime(2011, 3, 1); + + BigDecimal expectedValue = FOURTEEN.divide(EIGHTY_NINE, NUMBER_OF_DECIMALS, ROUNDING_METHOD); + testCalculateNumberOfBillingCycles(startDate, planChangeDate, targetDate, 1, expectedValue); + testCalculateNumberOfBillingCycles(planChangeDate, targetDate, 15, ONE); + } + + @Test + public void testPlanChange_WithChangeOfBillCycleDayToEarlierDay() throws InvalidDateSequenceException { + DateTime startDate = buildDateTime(2011, 2, 20); + DateTime planChangeDate = buildDateTime(2011, 3, 6); + DateTime targetDate = buildDateTime(2011, 3, 9); + + BigDecimal expectedValue = FOURTEEN.divide(EIGHTY_NINE, NUMBER_OF_DECIMALS, ROUNDING_METHOD); + testCalculateNumberOfBillingCycles(startDate, planChangeDate, targetDate, 20, expectedValue); + testCalculateNumberOfBillingCycles(planChangeDate, targetDate, 6, ONE); + } + + @Test + public void testSinglePlan_CrossingYearBoundary() throws InvalidDateSequenceException { + DateTime startDate = buildDateTime(2010, 12, 15); + DateTime targetDate = buildDateTime(2011, 1, 16); + + testCalculateNumberOfBillingCycles(startDate, targetDate, 15, ONE); + } + + @Test + public void testSinglePlan_LeapYear_StartingMidFebruary() throws InvalidDateSequenceException { + DateTime startDate = buildDateTime(2012, 2, 15); + DateTime targetDate = buildDateTime(2012, 3, 15); + + testCalculateNumberOfBillingCycles(startDate, targetDate, 15, ONE); + } + + @Test + public void testSinglePlan_LeapYear_StartingBeforeFebruary() throws InvalidDateSequenceException { + DateTime startDate = buildDateTime(2012, 1, 15); + DateTime targetDate = buildDateTime(2012, 2, 3); + + testCalculateNumberOfBillingCycles(startDate, targetDate, 15, ONE); + } + + @Test + public void testSinglePlan_LeapYear_IncludingAllOfFebruary() throws InvalidDateSequenceException { + DateTime startDate = buildDateTime(2012, 1, 30); + DateTime targetDate = buildDateTime(2012, 3, 1); + + testCalculateNumberOfBillingCycles(startDate, targetDate, 30, ONE); + } + + @Test + public void testSinglePlan_ChangeBCDTo31() throws InvalidDateSequenceException { + DateTime startDate = buildDateTime(2011, 2, 1); + DateTime planChangeDate = buildDateTime(2011, 2, 14); + DateTime targetDate = buildDateTime(2011, 3, 1); + + BigDecimal expectedValue; + + expectedValue = THIRTEEN.divide(EIGHTY_NINE, NUMBER_OF_DECIMALS, ROUNDING_METHOD); + testCalculateNumberOfBillingCycles(startDate, planChangeDate, targetDate, 1, expectedValue); + + expectedValue = ONE.add(FOURTEEN.divide(NINETY_TWO, NUMBER_OF_DECIMALS, ROUNDING_METHOD)); + testCalculateNumberOfBillingCycles(planChangeDate, targetDate, 31, expectedValue); + } + + @Test + public void testSinglePlan_ChangeBCD() throws InvalidDateSequenceException { + DateTime startDate = buildDateTime(2011, 2, 1); + DateTime planChangeDate = buildDateTime(2011, 2, 14); + DateTime targetDate = buildDateTime(2011, 5, 1); + + BigDecimal expectedValue; + + expectedValue = THIRTEEN.divide(EIGHTY_NINE, NUMBER_OF_DECIMALS, ROUNDING_METHOD); + testCalculateNumberOfBillingCycles(startDate, planChangeDate, targetDate, 1, expectedValue); + + expectedValue = ONE.add(THIRTEEN.divide(NINETY_TWO, NUMBER_OF_DECIMALS, ROUNDING_METHOD)); + testCalculateNumberOfBillingCycles(planChangeDate, targetDate, 27, expectedValue); + } + + @Test + public void testSinglePlan_LeapYearFebruaryProRation() throws InvalidDateSequenceException { + DateTime startDate = buildDateTime(2012, 2, 1); + DateTime endDate = buildDateTime(2012, 2, 15); + DateTime targetDate = buildDateTime(2012, 2, 19); + + BigDecimal expectedValue; + expectedValue = FOURTEEN.divide(NINETY, NUMBER_OF_DECIMALS, ROUNDING_METHOD); + + testCalculateNumberOfBillingCycles(startDate, endDate, targetDate, 1, expectedValue); + } + + @Test + public void testPlanChange_BeforeBillingDay() throws InvalidDateSequenceException { + DateTime startDate = buildDateTime(2011, 2, 7); + DateTime changeDate = buildDateTime(2011, 2, 15); + DateTime targetDate = buildDateTime(2011, 9, 21); + + BigDecimal expectedValue; + + expectedValue = EIGHT.divide(EIGHTY_NINE, NUMBER_OF_DECIMALS, ROUNDING_METHOD); + testCalculateNumberOfBillingCycles(startDate, changeDate, targetDate, 7, expectedValue); + + testCalculateNumberOfBillingCycles(changeDate, targetDate, 15, THREE); + } + + @Test + public void testPlanChange_OnBillingDay() throws InvalidDateSequenceException { + DateTime startDate = buildDateTime(2011, 2, 7); + DateTime changeDate = buildDateTime(2011, 5, 7); + DateTime targetDate = buildDateTime(2011, 7, 21); + + testCalculateNumberOfBillingCycles(startDate, changeDate, targetDate, 7, ONE); + + BigDecimal expectedValue; + expectedValue = EIGHT.divide(EIGHTY_NINE, NUMBER_OF_DECIMALS, ROUNDING_METHOD).add(ONE); + testCalculateNumberOfBillingCycles(changeDate, targetDate, 15, expectedValue); + } + + @Test + public void testPlanChange_AfterBillingDay() throws InvalidDateSequenceException { + DateTime startDate = buildDateTime(2011, 2, 7); + DateTime changeDate = buildDateTime(2011, 5, 10); + DateTime targetDate = buildDateTime(2011, 9, 21); + + BigDecimal expectedValue; + + expectedValue = ONE.add(THREE.divide(NINETY_TWO, NUMBER_OF_DECIMALS, ROUNDING_METHOD)); + testCalculateNumberOfBillingCycles(startDate, changeDate, targetDate, 7, expectedValue); + + expectedValue = FIVE.divide(EIGHTY_NINE, NUMBER_OF_DECIMALS, ROUNDING_METHOD).add(TWO); + testCalculateNumberOfBillingCycles(changeDate, targetDate, 15, expectedValue); + } + + @Test + public void testPlanChange_DoubleProRation() throws InvalidDateSequenceException { + DateTime startDate = buildDateTime(2011, 1, 31); + DateTime planChangeDate = buildDateTime(2011, 5, 10); + DateTime targetDate = buildDateTime(2011, 5, 21); + + BigDecimal expectedValue; + expectedValue = SEVEN.divide(NINETY_TWO, NUMBER_OF_DECIMALS, ROUNDING_METHOD); + expectedValue = expectedValue.add(ONE); + expectedValue = expectedValue.add(THREE.divide(NINETY_TWO, NUMBER_OF_DECIMALS, ROUNDING_METHOD)); + testCalculateNumberOfBillingCycles(startDate, planChangeDate, targetDate, 7, expectedValue); + + expectedValue = FIVE.divide(EIGHTY_NINE, NUMBER_OF_DECIMALS, ROUNDING_METHOD).add(ONE); + testCalculateNumberOfBillingCycles(planChangeDate, targetDate, 15, expectedValue); + } + + @Test + public void testStartTargetEnd() throws InvalidDateSequenceException { + DateTime startDate = buildDateTime(2010, 12, 15); + DateTime targetDate = buildDateTime(2011, 6, 15); + DateTime endDate = buildDateTime(2011, 6, 17); + + BigDecimal expectedValue = TWO.add(TWO.divide(NINETY_TWO, NUMBER_OF_DECIMALS, ROUNDING_METHOD)); + testCalculateNumberOfBillingCycles(startDate, endDate, targetDate, 15, expectedValue); + } +} \ No newline at end of file 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 new file mode 100644 index 0000000000..89e138ed91 --- /dev/null +++ b/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/quarterly/TrailingProRationTests.java @@ -0,0 +1,90 @@ +/* + * Copyright 2010-2011 Ning, Inc. + * + * Ning licenses this file to you under the Apache License, version 2.0 + * (the "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package com.ning.billing.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 org.joda.time.DateTime; +import org.testng.annotations.Test; + +import java.math.BigDecimal; + +@Test(groups = {"invoicing", "proRation"}) +public class TrailingProRationTests extends ProRationInAdvanceTestBase { + @Override + protected BillingPeriod getBillingPeriod() { + return BillingPeriod.QUARTERLY; + } + + @Test + public void testTargetDateOnStartDate() throws InvalidDateSequenceException { + DateTime startDate = buildDateTime(2010, 6, 17); + DateTime endDate = buildDateTime(2010, 9, 25); + DateTime targetDate = buildDateTime(2010, 6, 17); + + testCalculateNumberOfBillingCycles(startDate, endDate, targetDate, 17, ONE); + } + + @Test + public void testTargetDateInFirstBillingPeriod() throws InvalidDateSequenceException { + DateTime startDate = buildDateTime(2010, 6, 17); + DateTime endDate = buildDateTime(2010, 9, 25); + DateTime targetDate = buildDateTime(2010, 6, 20); + + testCalculateNumberOfBillingCycles(startDate, endDate, targetDate, 17, ONE); + } + + @Test + public void testTargetDateAtEndOfFirstBillingCycle() throws InvalidDateSequenceException { + DateTime startDate = buildDateTime(2010, 6, 17); + DateTime endDate = buildDateTime(2010, 9, 25); + DateTime targetDate = buildDateTime(2010, 9, 17); + + BigDecimal expectedValue = ONE.add(EIGHT.divide(NINETY_ONE, NUMBER_OF_DECIMALS, ROUNDING_METHOD)); + testCalculateNumberOfBillingCycles(startDate, endDate, targetDate, 17, expectedValue); + } + + @Test + public void testTargetDateInProRationPeriod() throws InvalidDateSequenceException { + DateTime startDate = buildDateTime(2010, 6, 17); + DateTime endDate = buildDateTime(2010, 9, 25); + DateTime targetDate = buildDateTime(2010, 9, 18); + + BigDecimal expectedValue = ONE.add(EIGHT.divide(NINETY_ONE, NUMBER_OF_DECIMALS, ROUNDING_METHOD)); + testCalculateNumberOfBillingCycles(startDate, endDate, targetDate, 17, expectedValue); + } + + @Test + public void testTargetDateOnEndDate() throws InvalidDateSequenceException { + DateTime startDate = buildDateTime(2010, 6, 17); + DateTime endDate = buildDateTime(2010, 9, 25); + + BigDecimal expectedValue = ONE.add(EIGHT.divide(NINETY_ONE, NUMBER_OF_DECIMALS, ROUNDING_METHOD)); + testCalculateNumberOfBillingCycles(startDate, endDate, endDate, 17, expectedValue); + } + + @Test + public void testTargetDateAfterEndDate() throws InvalidDateSequenceException { + DateTime startDate = buildDateTime(2010, 6, 17); + DateTime endDate = buildDateTime(2010, 9, 25); + DateTime targetDate = buildDateTime(2010, 9, 30); + + BigDecimal expectedValue = ONE.add(EIGHT.divide(NINETY_ONE, NUMBER_OF_DECIMALS, ROUNDING_METHOD)); + testCalculateNumberOfBillingCycles(startDate, endDate, targetDate, 17, expectedValue); + } +} \ No newline at end of file diff --git a/payment/pom.xml b/payment/pom.xml new file mode 100644 index 0000000000..d9b8f0cf8b --- /dev/null +++ b/payment/pom.xml @@ -0,0 +1,33 @@ + + + + + 4.0.0 + + com.ning.billing + killbill + 0.0.3-SNAPSHOT + ../pom.xml + + killbill-payment + killbill-payment + jar + + + + + diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000000..23f4a7e9a5 --- /dev/null +++ b/pom.xml @@ -0,0 +1,459 @@ + + + + + org.sonatype.oss + oss-parent + 5 + + 4.0.0 + com.ning.billing + killbill + pom + 0.0.3-SNAPSHOT + killbill + Library for managing recurring subscriptions and the associated billing + http://github.com/sbrossie/killbill + + + Apache License 2.0 + http://www.apache.org/licenses/LICENSE-2.0.html + repo + + + + scm:git:git://github.com/stephane/killbill.git + scm:git:git@github.com:sbrossie/killbill.git + http://github.com/sbrossie/killbill/tree/master + + + UTF-8 + + + analytics + api + catalog + account + entitlement + invoice + payment + util + + + + + com.ning.billing + killbill-api + ${project.version} + + + com.ning.billing + killbill-catalog + ${project.version} + + + com.ning.billing + killbill-util + ${project.version} + + + com.jolbox + bonecp + 0.7.1.RELEASE + + + com.google.guava + guava + 10.0 + provided + + + com.google.inject + guice + 3.0 + provided + + + com.mogwee + mogwee-executors + 1.1.0 + + + com.mysql + management + 5.0.11 + test + + + com.mysql + management-dbfiles + 5.0.11 + test + + + com.yammer.metrics + metrics-core + 2.0.0-BETA16 + + + com.yammer.metrics + metrics-guice + 2.0.0-BETA16 + + + com.ning.jdbi + jdbi-metrics + 1.0.1 + + + com.ning.jetty + ning-service-skeleton-core + 0.0.2 + + + commons-io + commons-io + 2.0.1 + + + commons-lang + commons-lang + 2.5 + + + joda-time + joda-time + 1.6.2 + + + + log4j + log4j + 1.2.16 + + + mysql + mysql-connector-java + 5.1.17 + runtime + + + org.antlr + stringtemplate + 3.2.1 + runtime + + + org.jdbi + jdbi + 2.27 + + + org.skife.config + config-magic + 0.9 + + + org.slf4j + slf4j-api + 1.6.2 + + + org.slf4j + jcl-over-slf4j + 1.6.2 + + + org.slf4j + jul-to-slf4j + 1.6.2 + + + org.slf4j + slf4j-log4j12 + 1.6.2 + + + org.testng + testng + 6.0 + test + + + + + + + com.ning.maven.plugins + maven-dependency-versions-check-plugin + 2.0.2 + + true + + + + none + + check + + + + + + com.ning.maven.plugins + maven-duplicate-finder-plugin + 1.0.2 + + false + + + about.html + + + + + verify + + check + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 2.3.2 + + 1.6 + 1.6 + + + + org.apache.maven.plugins + maven-dependency-plugin + 2.3 + + + analyze + + analyze-only + + + true + false + + + + + + org.apache.rat + apache-rat-plugin + 0.7 + + + verify + + check + + + true + true + true + + + .idea/** + **/.project + .git/** + .gitignore + API.txt + RELEASE.sh + deploy.sh + run.sh + run-local.sh + release-script + doc/** + src/site/** + *.log + README.* + TODO + logs/** + **/*.xsd + **/*.xml + **/*.stg + **/*.sql + **/*.properties + **/*.dont-let-git-remove-this-directory + **/test-output/** + + + + + + + + org.apache.maven.plugins + maven-gpg-plugin + 1.4 + + + sign-artifacts + verify + + sign + + + + + + org.apache.maven.plugins + maven-release-plugin + 2.2.1 + + forked-path + + + + org.apache.maven.scm + maven-scm-provider-gitexe + 1.4 + + + org.codehaus.plexus + plexus-utils + 1.5.9 + + + + + + org.apache.maven.plugins + maven-site-plugin + 3.0 + + + org.apache.maven.plugins + maven-surefire-plugin + 2.6 + + + file:${project.basedir}/src/test/resources/log4j.xml + + + + + org.apache.maven.plugins + maven-war-plugin + 2.1.1 + + true + + + + + + + + org.apache.maven.plugins + maven-changelog-plugin + 2.2 + + + org.apache.maven.plugins + maven-javadoc-plugin + 2.8 + + 1.6 + UTF-8 + 1g + + http://commons.apache.org/lang/api/ + http://download.oracle.com/javase/6/docs/api/ + + true + + + + html + + javadoc + + + + + + org.apache.maven.plugins + maven-jxr-plugin + 2.3 + + true + + + + org.apache.maven.plugins + maven-surefire-report-plugin + 2.6 + + + org.apache.maven.plugins + maven-project-info-reports-plugin + 2.4 + + + org.codehaus.mojo + cobertura-maven-plugin + 2.5.1 + + + org.codehaus.mojo + findbugs-maven-plugin + 2.3.2 + + Low + Max + + + + org.codehaus.mojo + taglist-maven-plugin + 2.4 + + + + + Todo Work + + + todo + ignoreCase + + + FIXME + exact + + + + + + + + + + + Github + http://github.com/sbrossie/killbill + + diff --git a/util/pom.xml b/util/pom.xml new file mode 100644 index 0000000000..6b005e9950 --- /dev/null +++ b/util/pom.xml @@ -0,0 +1,67 @@ + + + + + 4.0.0 + + com.ning.billing + killbill + 0.0.3-SNAPSHOT + ../pom.xml + + killbill-util + killbill-util + jar + + + com.ning.billing + killbill-api + + + com.jolbox + bonecp + + + com.google.inject + guice + 3.0 + + + org.skife.config + config-magic + + + com.ning.jdbi + jdbi-metrics + + + org.jdbi + jdbi + + + joda-time + joda-time + + + org.testng + testng + test + + + + + diff --git a/util/src/main/java/com/ning/billing/dbi/DBIProvider.java b/util/src/main/java/com/ning/billing/dbi/DBIProvider.java new file mode 100644 index 0000000000..a4a7b61dfe --- /dev/null +++ b/util/src/main/java/com/ning/billing/dbi/DBIProvider.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.dbi; + +import com.google.inject.Inject; +import com.google.inject.Provider; +import com.jolbox.bonecp.BoneCPConfig; +import com.jolbox.bonecp.BoneCPDataSource; +import com.ning.jdbi.metrics.JdbiGroupStrategy; +import com.ning.jdbi.metrics.MetricsTimingCollector; +import com.ning.jdbi.metrics.SqlJdbiGroupStrategy; +import com.yammer.metrics.core.MetricsRegistry; +import org.skife.jdbi.v2.DBI; +import org.skife.jdbi.v2.TimingCollector; +import org.skife.jdbi.v2.logging.Log4JLog; +import org.skife.jdbi.v2.tweak.SQLLog; + +import java.util.concurrent.TimeUnit; + +public class DBIProvider implements Provider +{ + private final MetricsRegistry metricsRegistry; + private final DbiConfig config; + + @Inject + public DBIProvider(final MetricsRegistry metricsRegistry, final DbiConfig config) + { + this.metricsRegistry = metricsRegistry; + this.config = config; + } + + @Override + public DBI get() + { + final BoneCPConfig dbConfig = new BoneCPConfig(); + dbConfig.setJdbcUrl(config.getJdbcUrl()); + dbConfig.setUsername(config.getUsername()); + dbConfig.setPassword(config.getPassword()); + dbConfig.setMinConnectionsPerPartition(config.getMinIdle()); + dbConfig.setMaxConnectionsPerPartition(config.getMaxActive()); + dbConfig.setConnectionTimeout(config.getConnectionTimeout().getPeriod(), config.getConnectionTimeout().getUnit()); + dbConfig.setPartitionCount(1); + dbConfig.setDefaultTransactionIsolation("READ_COMMITTED"); + dbConfig.setDisableJMX(false); + + final BoneCPDataSource ds = new BoneCPDataSource(dbConfig); + final DBI dbi = new DBI(ds); + final SQLLog log = new Log4JLog(); + dbi.setSQLLog(log); + + final JdbiGroupStrategy jdbiGroupStrategy = new SqlJdbiGroupStrategy(); + final TimingCollector timingCollector = new MetricsTimingCollector(metricsRegistry, jdbiGroupStrategy, TimeUnit.MILLISECONDS, TimeUnit.SECONDS); + dbi.setTimingCollector(timingCollector); + + return dbi; + } +} \ No newline at end of file diff --git a/util/src/main/java/com/ning/billing/dbi/DbiConfig.java b/util/src/main/java/com/ning/billing/dbi/DbiConfig.java new file mode 100644 index 0000000000..feb6ebdd42 --- /dev/null +++ b/util/src/main/java/com/ning/billing/dbi/DbiConfig.java @@ -0,0 +1,55 @@ +/* + * Copyright 2010-2011 Ning, Inc. + * + * Ning licenses this file to you under the Apache License, version 2.0 + * (the "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package com.ning.billing.dbi; + +import org.skife.config.Config; +import org.skife.config.Default; +import org.skife.config.Description; +import org.skife.config.TimeSpan; + +public interface DbiConfig +{ + @Description("The jdbc url for the database") + @Config("com.ning.billing.dbi.jdbc.url") + @Default("jdbc:mysql://127.0.0.1:3306/killbill") + String getJdbcUrl(); + + @Description("The jdbc user name for the database") + @Config("com.ning.billing.dbi.jdbc.user") + @Default("root") + String getUsername(); + + @Description("The jdbc password for the database") + @Config("com.ning.billing.dbi.jdbc.password") + @Default("root") + String getPassword(); + + @Description("The minimum allowed number of idle connections to the database") + @Config("com.ning.billing.dbi.jdbc.minIdle") + @Default("1") + int getMinIdle(); + + @Description("The maximum allowed number of active connections to the database") + @Config("com.ning.billing.dbi.jdbc.maxActive") + @Default("10") + int getMaxActive(); + + @Description("How long to wait before a connection attempt to the database is considered timed out") + @Config("com.ning.billing.dbi.jdbc.connectionTimeout") + @Default("10s") + TimeSpan getConnectionTimeout(); +} \ No newline at end of file diff --git a/util/src/main/java/com/ning/billing/util/Hostname.java b/util/src/main/java/com/ning/billing/util/Hostname.java new file mode 100644 index 0000000000..4f3dd835a7 --- /dev/null +++ b/util/src/main/java/com/ning/billing/util/Hostname.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; + +import java.net.InetAddress; +import java.net.UnknownHostException; + +public class Hostname { + + public static String get() { + try { + InetAddress addr = InetAddress.getLocalHost(); + return addr.getHostName(); + } catch (UnknownHostException e) { + e.printStackTrace(); + return "hostname-unknown"; + } + } +} 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 new file mode 100644 index 0000000000..6b5b2f19e0 --- /dev/null +++ b/util/src/main/java/com/ning/billing/util/clock/Clock.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.util.clock; + +import java.util.ArrayList; +import java.util.List; + +import org.joda.time.DateTime; +import org.joda.time.DateTimeZone; + +import com.ning.billing.catalog.api.IDuration; + +public class Clock implements IClock { + + @Override + public DateTime getNow(DateTimeZone tz) { + DateTime result = new DateTime(tz); + return result.minus(result.getMillisOfSecond()); + } + + @Override + public DateTime getUTCNow() { + return getNow(DateTimeZone.UTC); + } + + + public static DateTime addDuration(DateTime input, List durations) { + + DateTime result = input; + for (IDuration cur : durations) { + switch (cur.getUnit()) { + case DAYS: + result = result.plusDays(cur.getLength()); + break; + + case MONTHS: + result = result.plusMonths(cur.getLength()); + break; + + case YEARS: + result = result.plusYears(cur.getLength()); + break; + case UNLIMITED: + default: + throw new RuntimeException("Trying to move to unlimited time period"); + } + } + return result; + } + + public static DateTime addDuration(DateTime input, IDuration duration) { + List list = new ArrayList(); + list.add(duration); + return addDuration(input, list); + } +} diff --git a/util/src/main/java/com/ning/billing/util/clock/ClockMock.java b/util/src/main/java/com/ning/billing/util/clock/ClockMock.java new file mode 100644 index 0000000000..7e41d1c5e4 --- /dev/null +++ b/util/src/main/java/com/ning/billing/util/clock/ClockMock.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.util.clock; + +import java.util.ArrayList; +import java.util.List; + +import org.joda.time.DateTime; +import org.joda.time.DateTimeZone; + +import com.ning.billing.catalog.api.IDuration; + +// STEPH should really be in tests but not accessible from other sub modules +public class ClockMock extends Clock { + + private enum DeltaType { + DELTA_NONE, + DELTA_DURATION, + DELTA_ABS + } + + private long deltaFromRealityMs; + private List deltaFromRealityDuration; + private long deltaFromRealitDurationEpsilon; + private DeltaType deltaType; + + public ClockMock() { + deltaType = DeltaType.DELTA_NONE; + deltaFromRealityMs = 0; + deltaFromRealitDurationEpsilon = 0; + deltaFromRealityDuration = null; + } + + @Override + public synchronized DateTime getNow(DateTimeZone tz) { + return adjust(super.getNow(tz)); + } + + @Override + public synchronized DateTime getUTCNow() { + return getNow(DateTimeZone.UTC); + } + + public synchronized void setDeltaFromReality(IDuration delta, long epsilon) { + deltaType = DeltaType.DELTA_DURATION; + deltaFromRealityDuration = new ArrayList(); + deltaFromRealityDuration.add(delta); + deltaFromRealitDurationEpsilon = epsilon; + deltaFromRealityMs = 0; + } + + public synchronized void addDeltaFromReality(IDuration delta) { + if (deltaType != DeltaType.DELTA_DURATION) { + throw new RuntimeException("ClockMock should be set with type DELTA_DURATION"); + } + deltaFromRealityDuration.add(delta); + } + + public synchronized void setDeltaFromReality(long delta) { + deltaType = DeltaType.DELTA_ABS; + deltaFromRealityDuration = null; + deltaFromRealitDurationEpsilon = 0; + deltaFromRealityMs = delta; + } + + public synchronized void resetDeltaFromReality() { + deltaType = DeltaType.DELTA_NONE; + deltaFromRealityDuration = null; + deltaFromRealitDurationEpsilon = 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 (IDuration cur : deltaFromRealityDuration) { + + int length = cur.getLength(); + switch (cur.getUnit()) { + case DAYS: + result = result.plusDays(cur.getLength()); + break; + + case MONTHS: + result = result.plusMonths(cur.getLength()); + break; + + case YEARS: + result = result.plusYears(cur.getLength()); + break; + + case UNLIMITED: + default: + throw new RuntimeException("ClockMock is adjusting an unlimtited time period"); + } + } + if (deltaFromRealitDurationEpsilon != 0) { + result = result.plus(deltaFromRealitDurationEpsilon); + } + return result; + } + + private DateTime adjustFromAbsolute(DateTime input) { + return input.plus(deltaFromRealityMs); + } + +} diff --git a/util/src/main/java/com/ning/billing/util/clock/IClock.java b/util/src/main/java/com/ning/billing/util/clock/IClock.java new file mode 100644 index 0000000000..bf7997179b --- /dev/null +++ b/util/src/main/java/com/ning/billing/util/clock/IClock.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.util.clock; + +import org.joda.time.DateTime; +import org.joda.time.DateTimeZone; + +public interface IClock { + + public DateTime getNow(DateTimeZone tz); + + public DateTime getUTCNow(); + + + //public DateTime addDuration(DateTime input, IDuration duration); +} diff --git a/util/src/main/java/com/ning/billing/util/notification/INotification.java b/util/src/main/java/com/ning/billing/util/notification/INotification.java new file mode 100644 index 0000000000..8a812fce3b --- /dev/null +++ b/util/src/main/java/com/ning/billing/util/notification/INotification.java @@ -0,0 +1,21 @@ +/* + * Copyright 2010-2011 Ning, Inc. + * + * Ning licenses this file to you under the Apache License, version 2.0 + * (the "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package com.ning.billing.util.notification; + +public interface INotification { + +} diff --git a/util/src/main/java/com/ning/billing/util/notification/INotificationHandler.java b/util/src/main/java/com/ning/billing/util/notification/INotificationHandler.java new file mode 100644 index 0000000000..362265fbbb --- /dev/null +++ b/util/src/main/java/com/ning/billing/util/notification/INotificationHandler.java @@ -0,0 +1,22 @@ +/* + * Copyright 2010-2011 Ning, Inc. + * + * Ning licenses this file to you under the Apache License, version 2.0 + * (the "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package com.ning.billing.util.notification; + +public interface INotificationHandler { + + public void handle(T notification); +} diff --git a/util/src/main/java/com/ning/billing/util/notification/NotificationSystem.java b/util/src/main/java/com/ning/billing/util/notification/NotificationSystem.java new file mode 100644 index 0000000000..860d9f49dc --- /dev/null +++ b/util/src/main/java/com/ning/billing/util/notification/NotificationSystem.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.notification; + +import java.util.ArrayList; +import java.util.Hashtable; +import java.util.List; + +public class NotificationSystem { + Hashtable, List>> handlers = + new Hashtable, List>>(); + + public void register(INotificationHandler handler, Class clazz) { + List> hList = handlers.get(clazz); + if (hList == null) { + hList = new ArrayList>(); + handlers.put(clazz, hList); + } + hList.add(handler); + } + + public void publish(T notification) { + List> hList = handlers.get(notification.getClass()); + if (hList != null) { + for(INotificationHandler handler : hList) { + // Unfortunate cast but the hashtable contains many classes and generics are + // not up to it without an explicit cast. Cast is safe by construction though. + @SuppressWarnings("unchecked") + INotificationHandler handlerOfT = (INotificationHandler) handler; + handlerOfT.handle(notification); + } + } + } + + + +} diff --git a/util/src/test/java/com/ning/billing/util/notification/TestNotificationSystem.java b/util/src/test/java/com/ning/billing/util/notification/TestNotificationSystem.java new file mode 100644 index 0000000000..ef2d4d6f48 --- /dev/null +++ b/util/src/test/java/com/ning/billing/util/notification/TestNotificationSystem.java @@ -0,0 +1,55 @@ +/* + * Copyright 2010-2011 Ning, Inc. + * + * Ning licenses this file to you under the Apache License, version 2.0 + * (the "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF 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.notification; + +import static org.testng.AssertJUnit.*; +import org.testng.annotations.Test; + +public class TestNotificationSystem { + + private class TestNotify implements INotification {} + private class TestDontNotify implements INotification {} + private boolean notified; + + + @Test(enabled=true) + public void test(){ + NotificationSystem ns = new NotificationSystem(); + + ns.register(new INotificationHandler() { + + @Override + public void handle(TestNotify notification) { + notified = true; + + } + }, TestNotify.class); + + ns.register(new INotificationHandler() { + + @Override + public void handle(TestDontNotify notification) { + fail("This notification should not be called"); + + } + }, TestDontNotify.class); + + ns.publish(new TestNotify()); + + assertTrue(notified); + } +}