killbill-uncached
Changes
account/pom.xml 2(+1 -1)
api/pom.xml 2(+1 -1)
beatrix/pom.xml 2(+1 -1)
catalog/pom.xml 2(+1 -1)
currency/pom.xml 2(+1 -1)
entitlement/pom.xml 2(+1 -1)
entitlement/src/main/java/com/ning/billing/entitlement/api/DefaultSubscriptionApi.java 289(+138 -151)
entitlement/src/main/java/com/ning/billing/entitlement/api/DefaultSubscriptionBundleTimeline.java 724(+236 -488)
entitlement/src/main/java/com/ning/billing/entitlement/api/DefaultSubscriptionEvent.java 396(+396 -0)
entitlement/src/main/java/com/ning/billing/entitlement/api/svcs/DefaultAccountEntitlements.java 90(+90 -0)
entitlement/src/main/java/com/ning/billing/entitlement/api/svcs/DefaultAccountEventsStreams.java 108(+108 -0)
entitlement/src/main/java/com/ning/billing/entitlement/api/svcs/DefaultEntitlementInternalApi.java 101(+101 -0)
entitlement/src/main/java/com/ning/billing/entitlement/api/svcs/DefaultInternalBlockingApi.java 4(+2 -2)
entitlement/src/main/java/com/ning/billing/entitlement/dao/OptimizedProxyBlockingStateDao.java 101(+101 -0)
entitlement/src/main/java/com/ning/billing/entitlement/engine/core/DefaultEventsStream.java 186(+138 -48)
entitlement/src/main/java/com/ning/billing/entitlement/engine/core/EventsStreamBuilder.java 273(+166 -107)
entitlement/src/main/resources/com/ning/billing/entitlement/dao/BlockingStateSqlDao.sql.stg 24(+8 -16)
entitlement/src/test/java/com/ning/billing/entitlement/api/TestDefaultEntitlementApi.java 39(+35 -4)
entitlement/src/test/java/com/ning/billing/entitlement/api/TestDefaultSubscriptionApi.java 98(+98 -0)
entitlement/src/test/java/com/ning/billing/entitlement/api/TestDefaultSubscriptionBundleTimeline.java 537(+343 -194)
entitlement/src/test/java/com/ning/billing/entitlement/dao/TestDefaultBlockingStateDao.java 46(+28 -18)
entitlement/src/test/java/com/ning/billing/entitlement/engine/core/TestEntitlementUtils.java 71(+64 -7)
entitlement/src/test/java/com/ning/billing/entitlement/EntitlementTestSuiteWithEmbeddedDB.java 6(+6 -0)
entitlement/src/test/java/com/ning/billing/entitlement/glue/TestEntitlementModuleWithEmbeddedDB.java 2(+2 -0)
invoice/pom.xml 2(+1 -1)
jaxrs/pom.xml 2(+1 -1)
jaxrs/src/main/java/com/ning/billing/jaxrs/mappers/SubscriptionBillingApiExceptionMapper.java 2(+1 -1)
junction/pom.xml 12(+11 -1)
junction/src/main/java/com/ning/billing/junction/plumbing/billing/BlockingCalculator.java 62(+45 -17)
junction/src/main/java/com/ning/billing/junction/plumbing/billing/DefaultBillingEventSet.java 4(+0 -4)
junction/src/main/java/com/ning/billing/junction/plumbing/billing/DefaultInternalBillingApi.java 10(+5 -5)
junction/src/test/java/com/ning/billing/junction/glue/TestJunctionModuleWithEmbeddedDB.java 16(+16 -0)
junction/src/test/java/com/ning/billing/junction/plumbing/billing/TestBlockingCalculator.java 95(+61 -34)
junction/src/test/java/com/ning/billing/junction/plumbing/billing/TestDefaultInternalBillingApi.java 269(+269 -0)
junction/src/test/resources/catalog.xml 828(+828 -0)
NEWS 16(+16 -0)
osgi/pom.xml 2(+1 -1)
osgi-bundles/bundles/jruby/pom.xml 2(+1 -1)
osgi-bundles/bundles/logger/pom.xml 2(+1 -1)
osgi-bundles/bundles/pom.xml 2(+1 -1)
osgi-bundles/defaultbundles/pom.xml 2(+1 -1)
osgi-bundles/libs/killbill/pom.xml 2(+1 -1)
osgi-bundles/libs/pom.xml 2(+1 -1)
osgi-bundles/libs/slf4j-osgi/pom.xml 2(+1 -1)
osgi-bundles/pom.xml 2(+1 -1)
osgi-bundles/tests/beatrix/pom.xml 2(+1 -1)
osgi-bundles/tests/payment/pom.xml 2(+1 -1)
osgi-bundles/tests/pom.xml 2(+1 -1)
overdue/pom.xml 2(+1 -1)
payment/pom.xml 2(+1 -1)
pom.xml 4(+2 -2)
server/pom.xml 2(+1 -1)
subscription/pom.xml 2(+1 -1)
subscription/src/main/java/com/ning/billing/subscription/engine/dao/DefaultSubscriptionDao.java 160(+92 -68)
subscription/src/main/java/com/ning/billing/subscription/engine/dao/model/SubscriptionBundleModelDao.java 16(+16 -0)
subscription/src/main/java/com/ning/billing/subscription/engine/dao/model/SubscriptionEventModelDao.java 44(+44 -0)
subscription/src/main/java/com/ning/billing/subscription/engine/dao/model/SubscriptionModelDao.java 28(+28 -0)
subscription/src/main/java/com/ning/billing/subscription/glue/DefaultSubscriptionModule.java 4(+1 -3)
subscription/src/main/resources/com/ning/billing/subscription/engine/dao/BundleSqlDao.sql.stg 6(+3 -3)
subscription/src/main/resources/com/ning/billing/subscription/engine/dao/SubscriptionEventSqlDao.sql.stg 13(+7 -6)
subscription/src/main/resources/com/ning/billing/subscription/engine/dao/SubscriptionSqlDao.sql.stg 1(+1 -0)
tenant/pom.xml 2(+1 -1)
usage/pom.xml 2(+1 -1)
util/pom.xml 2(+1 -1)
Details
account/pom.xml 2(+1 -1)
diff --git a/account/pom.xml b/account/pom.xml
index a66537e..b5603ba 100644
--- a/account/pom.xml
+++ b/account/pom.xml
@@ -19,7 +19,7 @@
<parent>
<artifactId>killbill</artifactId>
<groupId>com.ning.billing</groupId>
- <version>0.8.3-SNAPSHOT</version>
+ <version>0.8.6-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>killbill-account</artifactId>
diff --git a/account/src/main/java/com/ning/billing/account/dao/AccountEmailModelDao.java b/account/src/main/java/com/ning/billing/account/dao/AccountEmailModelDao.java
index 4962a24..6b2bd31 100644
--- a/account/src/main/java/com/ning/billing/account/dao/AccountEmailModelDao.java
+++ b/account/src/main/java/com/ning/billing/account/dao/AccountEmailModelDao.java
@@ -54,6 +54,18 @@ public class AccountEmailModelDao extends EntityBase implements EntityModelDao<A
return isActive;
}
+ public void setAccountId(final UUID accountId) {
+ this.accountId = accountId;
+ }
+
+ public void setEmail(final String email) {
+ this.email = email;
+ }
+
+ public void setIsActive(final Boolean isActive) {
+ this.isActive = isActive;
+ }
+
@Override
public String toString() {
final StringBuilder sb = new StringBuilder();
diff --git a/account/src/main/java/com/ning/billing/account/dao/AccountModelDao.java b/account/src/main/java/com/ning/billing/account/dao/AccountModelDao.java
index 57365eb..2996fec 100644
--- a/account/src/main/java/com/ning/billing/account/dao/AccountModelDao.java
+++ b/account/src/main/java/com/ning/billing/account/dao/AccountModelDao.java
@@ -176,6 +176,82 @@ public class AccountModelDao extends EntityBase implements EntityModelDao<Accoun
return isNotifiedForInvoices;
}
+ public void setExternalKey(final String externalKey) {
+ this.externalKey = externalKey;
+ }
+
+ public void setEmail(final String email) {
+ this.email = email;
+ }
+
+ public void setName(final String name) {
+ this.name = name;
+ }
+
+ public void setFirstNameLength(final Integer firstNameLength) {
+ this.firstNameLength = firstNameLength;
+ }
+
+ public void setCurrency(final Currency currency) {
+ this.currency = currency;
+ }
+
+ public void setBillingCycleDayLocal(final int billingCycleDayLocal) {
+ this.billingCycleDayLocal = billingCycleDayLocal;
+ }
+
+ public void setPaymentMethodId(final UUID paymentMethodId) {
+ this.paymentMethodId = paymentMethodId;
+ }
+
+ public void setTimeZone(final DateTimeZone timeZone) {
+ this.timeZone = timeZone;
+ }
+
+ public void setLocale(final String locale) {
+ this.locale = locale;
+ }
+
+ public void setAddress1(final String address1) {
+ this.address1 = address1;
+ }
+
+ public void setAddress2(final String address2) {
+ this.address2 = address2;
+ }
+
+ public void setCompanyName(final String companyName) {
+ this.companyName = companyName;
+ }
+
+ public void setCity(final String city) {
+ this.city = city;
+ }
+
+ public void setStateOrProvince(final String stateOrProvince) {
+ this.stateOrProvince = stateOrProvince;
+ }
+
+ public void setCountry(final String country) {
+ this.country = country;
+ }
+
+ public void setPostalCode(final String postalCode) {
+ this.postalCode = postalCode;
+ }
+
+ public void setPhone(final String phone) {
+ this.phone = phone;
+ }
+
+ public void setMigrated(final Boolean migrated) {
+ this.migrated = migrated;
+ }
+
+ public void setIsNotifiedForInvoices(final Boolean isNotifiedForInvoices) {
+ this.isNotifiedForInvoices = isNotifiedForInvoices;
+ }
+
@Override
public String toString() {
final StringBuilder sb = new StringBuilder();
api/pom.xml 2(+1 -1)
diff --git a/api/pom.xml b/api/pom.xml
index fed08a0..ff7292d 100644
--- a/api/pom.xml
+++ b/api/pom.xml
@@ -19,7 +19,7 @@
<parent>
<artifactId>killbill</artifactId>
<groupId>com.ning.billing</groupId>
- <version>0.8.3-SNAPSHOT</version>
+ <version>0.8.6-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>killbill-internal-api</artifactId>
diff --git a/api/src/main/java/com/ning/billing/entitlement/AccountEntitlements.java b/api/src/main/java/com/ning/billing/entitlement/AccountEntitlements.java
new file mode 100644
index 0000000..3a192e9
--- /dev/null
+++ b/api/src/main/java/com/ning/billing/entitlement/AccountEntitlements.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2010-2013 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF 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;
+
+import java.util.Collection;
+import java.util.Map;
+import java.util.UUID;
+
+import com.ning.billing.account.api.Account;
+import com.ning.billing.entitlement.api.Entitlement;
+import com.ning.billing.subscription.api.user.SubscriptionBaseBundle;
+
+// Wrapper object to save on DAO calls
+public interface AccountEntitlements {
+
+ public Account getAccount();
+
+ // Map bundle id -> bundle
+ public Map<UUID, SubscriptionBaseBundle> getBundles();
+
+ // Map bundle id -> entitlements
+ public Map<UUID, Collection<Entitlement>> getEntitlements();
+}
diff --git a/api/src/main/java/com/ning/billing/entitlement/AccountEventsStreams.java b/api/src/main/java/com/ning/billing/entitlement/AccountEventsStreams.java
new file mode 100644
index 0000000..94e7817
--- /dev/null
+++ b/api/src/main/java/com/ning/billing/entitlement/AccountEventsStreams.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2010-2013 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF 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;
+
+import java.util.Collection;
+import java.util.Map;
+import java.util.UUID;
+
+import com.ning.billing.account.api.Account;
+import com.ning.billing.subscription.api.user.SubscriptionBaseBundle;
+
+// Wrapper object to save on DAO calls
+public interface AccountEventsStreams {
+
+ public Account getAccount();
+
+ // Map bundle id -> bundle
+ public Map<UUID, SubscriptionBaseBundle> getBundles();
+
+ // Map bundle id -> events streams
+ public Map<UUID, Collection<EventsStream>> getEventsStreams();
+}
diff --git a/api/src/main/java/com/ning/billing/entitlement/EntitlementInternalApi.java b/api/src/main/java/com/ning/billing/entitlement/EntitlementInternalApi.java
new file mode 100644
index 0000000..44953ea
--- /dev/null
+++ b/api/src/main/java/com/ning/billing/entitlement/EntitlementInternalApi.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2010-2013 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF 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;
+
+import java.util.UUID;
+
+import com.ning.billing.entitlement.api.EntitlementApiException;
+import com.ning.billing.util.callcontext.TenantContext;
+
+public interface EntitlementInternalApi {
+
+ public AccountEntitlements getAllEntitlementsForAccountId(UUID accountId, TenantContext context) throws EntitlementApiException;
+}
diff --git a/api/src/main/java/com/ning/billing/entitlement/EventsStream.java b/api/src/main/java/com/ning/billing/entitlement/EventsStream.java
new file mode 100644
index 0000000..8cb930f
--- /dev/null
+++ b/api/src/main/java/com/ning/billing/entitlement/EventsStream.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright 2010-2013 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF 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;
+
+import java.util.Collection;
+import java.util.UUID;
+
+import org.joda.time.DateTime;
+import org.joda.time.DateTimeZone;
+import org.joda.time.LocalDate;
+
+import com.ning.billing.callcontext.InternalTenantContext;
+import com.ning.billing.entitlement.api.BlockingState;
+import com.ning.billing.entitlement.api.Entitlement.EntitlementState;
+import com.ning.billing.subscription.api.SubscriptionBase;
+
+public interface EventsStream {
+
+ UUID getAccountId();
+
+ DateTimeZone getAccountTimeZone();
+
+ UUID getBundleId();
+
+ String getBundleExternalKey();
+
+ UUID getEntitlementId();
+
+ EntitlementState getEntitlementState();
+
+ LocalDate getEntitlementEffectiveEndDate();
+
+ SubscriptionBase getSubscription();
+
+ SubscriptionBase getBaseSubscription();
+
+ boolean isEntitlementActive();
+
+ boolean isBlockChange();
+
+ boolean isEntitlementCancelled();
+
+ boolean isSubscriptionCancelled();
+
+ Collection<BlockingState> getCurrentSubscriptionEntitlementBlockingStatesForServices();
+
+ Collection<BlockingState> getPendingEntitlementCancellationEvents();
+
+ BlockingState getEntitlementCancellationEvent();
+
+ // All blocking states for the account, associated bundle or subscription
+ Collection<BlockingState> getBlockingStates();
+
+ Collection<BlockingState> computeAddonsBlockingStatesForNextSubscriptionBaseEvent(DateTime effectiveDate);
+
+ Collection<BlockingState> computeAddonsBlockingStatesForFutureSubscriptionBaseEvents();
+
+ InternalTenantContext getInternalTenantContext();
+}
diff --git a/api/src/main/java/com/ning/billing/entity/EntityBase.java b/api/src/main/java/com/ning/billing/entity/EntityBase.java
index a579241..b0f4fea 100644
--- a/api/src/main/java/com/ning/billing/entity/EntityBase.java
+++ b/api/src/main/java/com/ning/billing/entity/EntityBase.java
@@ -24,9 +24,9 @@ import com.ning.billing.util.entity.Entity;
public abstract class EntityBase implements Entity {
- protected final UUID id;
- protected final DateTime createdDate;
- protected final DateTime updatedDate;
+ protected UUID id;
+ protected DateTime createdDate;
+ protected DateTime updatedDate;
// used to hydrate objects
public EntityBase(final UUID id) {
@@ -65,6 +65,18 @@ public abstract class EntityBase implements Entity {
return updatedDate;
}
+ public void setId(final UUID id) {
+ this.id = id;
+ }
+
+ public void setCreatedDate(final DateTime createdDate) {
+ this.createdDate = createdDate;
+ }
+
+ public void setUpdatedDate(final DateTime updatedDate) {
+ this.updatedDate = updatedDate;
+ }
+
@Override
public boolean equals(final Object o) {
if (this == o) {
diff --git a/api/src/main/java/com/ning/billing/events/BusInternalEvent.java b/api/src/main/java/com/ning/billing/events/BusInternalEvent.java
index 0a798ad..6867096 100644
--- a/api/src/main/java/com/ning/billing/events/BusInternalEvent.java
+++ b/api/src/main/java/com/ning/billing/events/BusInternalEvent.java
@@ -38,6 +38,7 @@ public interface BusInternalEvent extends BusEvent {
INVOICE_EMPTY,
OVERDUE_CHANGE,
PAYMENT_ERROR,
+ PAYMENT_PLUGIN_ERROR,
PAYMENT_INFO,
SUBSCRIPTION_TRANSITION,
USER_TAGDEFINITION_CREATION,
diff --git a/api/src/main/java/com/ning/billing/glue/EntitlementModule.java b/api/src/main/java/com/ning/billing/glue/EntitlementModule.java
index 456fb17..bd21d81 100644
--- a/api/src/main/java/com/ning/billing/glue/EntitlementModule.java
+++ b/api/src/main/java/com/ning/billing/glue/EntitlementModule.java
@@ -17,12 +17,15 @@
package com.ning.billing.glue;
public interface EntitlementModule {
+
public void installBlockingStateDao();
public void installBlockingApi();
public void installEntitlementApi();
+ public void installEntitlementInternalApi();
+
public void installSubscriptionApi();
public void installBlockingChecker();
diff --git a/api/src/main/java/com/ning/billing/glue/SubscriptionModule.java b/api/src/main/java/com/ning/billing/glue/SubscriptionModule.java
index a400b68..65185eb 100644
--- a/api/src/main/java/com/ning/billing/glue/SubscriptionModule.java
+++ b/api/src/main/java/com/ning/billing/glue/SubscriptionModule.java
@@ -27,5 +27,4 @@ public interface SubscriptionModule {
public void installSubscriptionInternalApi();
public void installSubscriptionTimelineApi();
-
}
diff --git a/api/src/main/java/com/ning/billing/junction/BillingEventSet.java b/api/src/main/java/com/ning/billing/junction/BillingEventSet.java
index 8dfca08..237e658 100644
--- a/api/src/main/java/com/ning/billing/junction/BillingEventSet.java
+++ b/api/src/main/java/com/ning/billing/junction/BillingEventSet.java
@@ -25,7 +25,4 @@ public interface BillingEventSet extends SortedSet<BillingEvent> {
public abstract boolean isAccountAutoInvoiceOff();
public abstract List<UUID> getSubscriptionIdsWithAutoInvoiceOff();
-
- public boolean isLast(BillingEvent event);
-
}
diff --git a/api/src/main/java/com/ning/billing/junction/BlockingInternalApi.java b/api/src/main/java/com/ning/billing/junction/BlockingInternalApi.java
index 0d30cf4..e7b9720 100644
--- a/api/src/main/java/com/ning/billing/junction/BlockingInternalApi.java
+++ b/api/src/main/java/com/ning/billing/junction/BlockingInternalApi.java
@@ -28,7 +28,7 @@ public interface BlockingInternalApi {
public BlockingState getBlockingStateForService(UUID blockableId, BlockingStateType blockingStateType, String serviceName, InternalTenantContext context);
- public List<BlockingState> getBlockingAll(UUID blockableId, BlockingStateType blockingStateType, InternalTenantContext context);
+ public List<BlockingState> getBlockingAllForAccount(InternalTenantContext context);
public void setBlockingState(BlockingState state, InternalCallContext context);
}
beatrix/pom.xml 2(+1 -1)
diff --git a/beatrix/pom.xml b/beatrix/pom.xml
index 4a6b0f8..fae6f5e 100644
--- a/beatrix/pom.xml
+++ b/beatrix/pom.xml
@@ -19,7 +19,7 @@
<parent>
<artifactId>killbill</artifactId>
<groupId>com.ning.billing</groupId>
- <version>0.8.3-SNAPSHOT</version>
+ <version>0.8.6-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>killbill-beatrix</artifactId>
diff --git a/beatrix/src/main/java/com/ning/billing/beatrix/extbus/BeatrixListener.java b/beatrix/src/main/java/com/ning/billing/beatrix/extbus/BeatrixListener.java
index ea3db2d..b21288c 100644
--- a/beatrix/src/main/java/com/ning/billing/beatrix/extbus/BeatrixListener.java
+++ b/beatrix/src/main/java/com/ning/billing/beatrix/extbus/BeatrixListener.java
@@ -48,6 +48,7 @@ import com.ning.billing.events.InvoiceCreationInternalEvent;
import com.ning.billing.events.OverdueChangeInternalEvent;
import com.ning.billing.events.PaymentErrorInternalEvent;
import com.ning.billing.events.PaymentInfoInternalEvent;
+import com.ning.billing.events.PaymentPluginErrorInternalEvent;
import com.ning.billing.events.SubscriptionInternalEvent;
import com.ning.billing.events.UserTagCreationInternalEvent;
import com.ning.billing.events.UserTagDeletionInternalEvent;
@@ -182,6 +183,13 @@ public class BeatrixListener {
eventBusType = ExtBusEventType.PAYMENT_FAILED;
break;
+ case PAYMENT_PLUGIN_ERROR:
+ PaymentPluginErrorInternalEvent realEventPayPluginErr = (PaymentPluginErrorInternalEvent) event;
+ objectType = ObjectType.PAYMENT;
+ objectId = realEventPayPluginErr.getPaymentId();
+ eventBusType = ExtBusEventType.PAYMENT_FAILED;
+ break;
+
case OVERDUE_CHANGE:
OverdueChangeInternalEvent realEventOC = (OverdueChangeInternalEvent) event;
objectType = ObjectType.ACCOUNT;
catalog/pom.xml 2(+1 -1)
diff --git a/catalog/pom.xml b/catalog/pom.xml
index 011fe1d..ed64b49 100644
--- a/catalog/pom.xml
+++ b/catalog/pom.xml
@@ -19,7 +19,7 @@
<parent>
<artifactId>killbill</artifactId>
<groupId>com.ning.billing</groupId>
- <version>0.8.3-SNAPSHOT</version>
+ <version>0.8.6-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>killbill-catalog</artifactId>
currency/pom.xml 2(+1 -1)
diff --git a/currency/pom.xml b/currency/pom.xml
index e3e6379..6af9d27 100644
--- a/currency/pom.xml
+++ b/currency/pom.xml
@@ -19,7 +19,7 @@
<parent>
<artifactId>killbill</artifactId>
<groupId>com.ning.billing</groupId>
- <version>0.8.3-SNAPSHOT</version>
+ <version>0.8.6-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
entitlement/pom.xml 2(+1 -1)
diff --git a/entitlement/pom.xml b/entitlement/pom.xml
index 1540e00..dbee812 100644
--- a/entitlement/pom.xml
+++ b/entitlement/pom.xml
@@ -19,7 +19,7 @@
<parent>
<artifactId>killbill</artifactId>
<groupId>com.ning.billing</groupId>
- <version>0.8.3-SNAPSHOT</version>
+ <version>0.8.6-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>killbill-entitlement</artifactId>
diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/api/DefaultEntitlement.java b/entitlement/src/main/java/com/ning/billing/entitlement/api/DefaultEntitlement.java
index 983381d..5576a98 100644
--- a/entitlement/src/main/java/com/ning/billing/entitlement/api/DefaultEntitlement.java
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/api/DefaultEntitlement.java
@@ -21,10 +21,10 @@ import java.util.Collection;
import java.util.UUID;
import org.joda.time.DateTime;
+import org.joda.time.DateTimeZone;
import org.joda.time.LocalDate;
import com.ning.billing.ErrorCode;
-import com.ning.billing.account.api.Account;
import com.ning.billing.callcontext.InternalCallContext;
import com.ning.billing.catalog.api.BillingActionPolicy;
import com.ning.billing.catalog.api.BillingPeriod;
@@ -36,12 +36,12 @@ import com.ning.billing.catalog.api.ProductCategory;
import com.ning.billing.clock.Clock;
import com.ning.billing.entitlement.DefaultEntitlementService;
import com.ning.billing.entitlement.EntitlementService;
+import com.ning.billing.entitlement.EventsStream;
import com.ning.billing.entitlement.block.BlockingChecker;
import com.ning.billing.entitlement.dao.BlockingStateDao;
import com.ning.billing.entitlement.engine.core.EntitlementNotificationKey;
import com.ning.billing.entitlement.engine.core.EntitlementNotificationKeyAction;
import com.ning.billing.entitlement.engine.core.EntitlementUtils;
-import com.ning.billing.entitlement.engine.core.EventsStream;
import com.ning.billing.entitlement.engine.core.EventsStreamBuilder;
import com.ning.billing.entity.EntityBase;
import com.ning.billing.junction.DefaultBlockingState;
@@ -88,7 +88,7 @@ public class DefaultEntitlement extends EntityBase implements Entitlement {
final SubscriptionBaseInternalApi subscriptionInternalApi, final BlockingChecker checker,
final NotificationQueueService notificationQueueService, final EntitlementUtils entitlementUtils,
final EntitlementDateHelper dateHelper, final Clock clock, final InternalCallContextFactory internalCallContextFactory) {
- super(eventsStream.getSubscription().getId(), eventsStream.getSubscription().getCreatedDate(), eventsStream.getSubscription().getUpdatedDate());
+ super(eventsStream.getEntitlementId(), eventsStream.getSubscription().getCreatedDate(), eventsStream.getSubscription().getUpdatedDate());
this.eventsStreamBuilder = eventsStreamBuilder;
this.eventsStream = eventsStream;
this.dateHelper = dateHelper;
@@ -116,18 +116,22 @@ public class DefaultEntitlement extends EntityBase implements Entitlement {
in.getInternalCallContextFactory());
}
- public Account getAccount() {
- return eventsStream.getAccount();
- }
-
public EventsStream getEventsStream() {
return eventsStream;
}
+ public DateTimeZone getAccountTimeZone() {
+ return eventsStream.getAccountTimeZone();
+ }
+
public SubscriptionBase getSubscriptionBase() {
return eventsStream.getSubscription();
}
+ public SubscriptionBase getBaseSubscription() {
+ return eventsStream.getBaseSubscription();
+ }
+
public EventsStreamBuilder getEventsStreamBuilder() {
return eventsStreamBuilder;
}
@@ -170,22 +174,22 @@ public class DefaultEntitlement extends EntityBase implements Entitlement {
@Override
public UUID getBaseEntitlementId() {
- return eventsStream.getSubscription().getId();
+ return eventsStream.getEntitlementId();
}
@Override
public UUID getBundleId() {
- return eventsStream.getSubscription().getBundleId();
+ return eventsStream.getBundleId();
}
@Override
public UUID getAccountId() {
- return eventsStream.getAccount().getId();
+ return eventsStream.getAccountId();
}
@Override
public String getExternalKey() {
- return eventsStream.getBundle().getExternalKey();
+ return eventsStream.getBundleExternalKey();
}
@Override
@@ -200,7 +204,7 @@ public class DefaultEntitlement extends EntityBase implements Entitlement {
@Override
public LocalDate getEffectiveStartDate() {
- return new LocalDate(getSubscriptionBase().getStartDate(), eventsStream.getAccount().getTimeZone());
+ return new LocalDate(getSubscriptionBase().getStartDate(), eventsStream.getAccountTimeZone());
}
@Override
@@ -285,15 +289,28 @@ public class DefaultEntitlement extends EntityBase implements Entitlement {
// Get the latest state from disk
refresh(callContext);
- if (eventsStream.isEntitlementCancelled() || eventsStream.isSubscriptionCancelled()) {
+ if (eventsStream.isSubscriptionCancelled()) {
throw new EntitlementApiException(ErrorCode.SUB_CANCEL_BAD_STATE, getId(), EntitlementState.CANCELLED);
}
- // Reactivate entitlements
- // See also https://github.com/killbill/killbill/issues/111
final InternalCallContext contextWithValidAccountRecordId = internalCallContextFactory.createInternalCallContext(getAccountId(), callContext);
- for (final BlockingState futureCancellation : eventsStream.getPendingEntitlementCancellationEvents()) {
- blockingStateDao.unactiveBlockingState(futureCancellation.getId(), contextWithValidAccountRecordId);
+ final Collection<BlockingState> pendingEntitlementCancellationEvents = eventsStream.getPendingEntitlementCancellationEvents();
+ if (eventsStream.isEntitlementCancelled()) {
+ final BlockingState cancellationEvent = eventsStream.getEntitlementCancellationEvent();
+ blockingStateDao.unactiveBlockingState(cancellationEvent.getId(), contextWithValidAccountRecordId);
+ } else if (pendingEntitlementCancellationEvents.size() > 0) {
+ // Reactivate entitlements
+ // See also https://github.com/killbill/killbill/issues/111
+ //
+ // Today we only support cancellation at SUBSCRIPTION level (Not ACCOUNT or BUNDLE), so we should really have only
+ // one future event in the list
+ //
+ for (final BlockingState futureCancellation : pendingEntitlementCancellationEvents) {
+ blockingStateDao.unactiveBlockingState(futureCancellation.getId(), contextWithValidAccountRecordId);
+ }
+ } else {
+ // Entitlement is NOT cancelled (or future cancelled), there is nothing to do
+ throw new EntitlementApiException(ErrorCode.SUB_CANCEL_BAD_STATE, getId(), EntitlementState.CANCELLED);
}
// If billing was previously cancelled, reactivate
@@ -319,7 +336,7 @@ public class DefaultEntitlement extends EntityBase implements Entitlement {
// (we don't want an entitlement cancel date one second or so after the subscription cancel date or add-ons cancellations
// computations won't work).
final InternalCallContext contextWithValidAccountRecordId = internalCallContextFactory.createInternalCallContext(getAccountId(), callContext);
- final LocalDate effectiveLocalDate = new LocalDate(localCancelDate, eventsStream.getAccount().getTimeZone());
+ final LocalDate effectiveLocalDate = new LocalDate(localCancelDate, eventsStream.getAccountTimeZone());
final DateTime effectiveDate = dateHelper.fromLocalDateAndReferenceTime(effectiveLocalDate, getSubscriptionBase().getStartDate(), contextWithValidAccountRecordId);
try {
@@ -341,10 +358,10 @@ public class DefaultEntitlement extends EntityBase implements Entitlement {
final LocalDate cancellationDate;
switch (entitlementPolicy) {
case IMMEDIATE:
- cancellationDate = new LocalDate(clock.getUTCNow(), eventsStream.getAccount().getTimeZone());
+ cancellationDate = new LocalDate(clock.getUTCNow(), eventsStream.getAccountTimeZone());
break;
case END_OF_TERM:
- cancellationDate = getSubscriptionBase().getChargedThroughDate() != null ? new LocalDate(getSubscriptionBase().getChargedThroughDate(), eventsStream.getAccount().getTimeZone()) : new LocalDate(clock.getUTCNow(), eventsStream.getAccount().getTimeZone());
+ cancellationDate = getSubscriptionBase().getChargedThroughDate() != null ? new LocalDate(getSubscriptionBase().getChargedThroughDate(), eventsStream.getAccountTimeZone()) : new LocalDate(clock.getUTCNow(), eventsStream.getAccountTimeZone());
break;
default:
throw new RuntimeException("Unsupported policy " + entitlementPolicy);
diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/api/DefaultEntitlementApi.java b/entitlement/src/main/java/com/ning/billing/entitlement/api/DefaultEntitlementApi.java
index 5be1c0e..dfd9cd6 100644
--- a/entitlement/src/main/java/com/ning/billing/entitlement/api/DefaultEntitlementApi.java
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/api/DefaultEntitlementApi.java
@@ -17,9 +17,7 @@
package com.ning.billing.entitlement.api;
import java.io.IOException;
-import java.util.LinkedList;
import java.util.List;
-import java.util.Map;
import java.util.UUID;
import javax.inject.Inject;
@@ -41,15 +39,16 @@ import com.ning.billing.callcontext.InternalTenantContext;
import com.ning.billing.catalog.api.BillingActionPolicy;
import com.ning.billing.catalog.api.PlanPhaseSpecifier;
import com.ning.billing.clock.Clock;
+import com.ning.billing.entitlement.AccountEventsStreams;
import com.ning.billing.entitlement.DefaultEntitlementService;
import com.ning.billing.entitlement.EntitlementService;
import com.ning.billing.entitlement.EntitlementTransitionType;
+import com.ning.billing.entitlement.EventsStream;
import com.ning.billing.entitlement.block.BlockingChecker;
import com.ning.billing.entitlement.dao.BlockingStateDao;
import com.ning.billing.entitlement.engine.core.EntitlementNotificationKey;
import com.ning.billing.entitlement.engine.core.EntitlementNotificationKeyAction;
import com.ning.billing.entitlement.engine.core.EntitlementUtils;
-import com.ning.billing.entitlement.engine.core.EventsStream;
import com.ning.billing.entitlement.engine.core.EventsStreamBuilder;
import com.ning.billing.junction.DefaultBlockingState;
import com.ning.billing.notificationq.api.NotificationEvent;
@@ -141,8 +140,8 @@ public class DefaultEntitlementApi implements EntitlementApi {
}
// Check the base entitlement state is not blocked
- if (eventsStreamForBaseSubscription.getCurrentBlockingAggregator().isBlockChange()) {
- throw new EntitlementApiException(new BlockingApiException(ErrorCode.BLOCK_BLOCKED_ACTION, BlockingChecker.ACTION_CHANGE, BlockingChecker.TYPE_SUBSCRIPTION, eventsStreamForBaseSubscription.getSubscription().getId().toString()));
+ if (eventsStreamForBaseSubscription.isBlockChange()) {
+ throw new EntitlementApiException(new BlockingApiException(ErrorCode.BLOCK_BLOCKED_ACTION, BlockingChecker.ACTION_CHANGE, BlockingChecker.TYPE_SUBSCRIPTION, eventsStreamForBaseSubscription.getEntitlementId().toString()));
}
final DateTime requestedDate = dateHelper.fromLocalDateAndReferenceTime(effectiveDate, eventsStreamForBaseSubscription.getSubscription().getStartDate(), eventsStreamForBaseSubscription.getInternalTenantContext());
@@ -184,8 +183,21 @@ public class DefaultEntitlementApi implements EntitlementApi {
@Override
public List<Entitlement> getAllEntitlementsForBundle(final UUID bundleId, final TenantContext tenantContext) throws EntitlementApiException {
- final InternalTenantContext context = internalCallContextFactory.createInternalTenantContext(tenantContext);
- return getAllEntitlementsForBundle(subscriptionInternalApi.getSubscriptionsForBundle(bundleId, context), tenantContext);
+ final InternalTenantContext internalContext = internalCallContextFactory.createInternalTenantContext(tenantContext);
+ final UUID accountId;
+ try {
+ accountId = subscriptionInternalApi.getBundleFromId(bundleId, internalContext).getAccountId();
+ } catch (SubscriptionBaseApiException e) {
+ throw new EntitlementApiException(e);
+ }
+
+ return ImmutableList.<Entitlement>copyOf(Iterables.<Entitlement>filter(getAllEntitlementsForAccountId(accountId, tenantContext),
+ new Predicate<Entitlement>() {
+ @Override
+ public boolean apply(final Entitlement input) {
+ return bundleId.equals(input.getBundleId());
+ }
+ }));
}
@Override
@@ -202,29 +214,20 @@ public class DefaultEntitlementApi implements EntitlementApi {
@Override
public List<Entitlement> getAllEntitlementsForAccountId(final UUID accountId, final TenantContext tenantContext) throws EntitlementApiException {
+ final EntitlementApi entitlementApi = this;
final InternalTenantContext context = internalCallContextFactory.createInternalTenantContext(accountId, tenantContext);
- final Map<UUID, List<SubscriptionBase>> subscriptionsPerBundle = subscriptionInternalApi.getSubscriptionsForAccount(context);
-
- final List<Entitlement> result = new LinkedList<Entitlement>();
- for (final UUID bundleId : subscriptionsPerBundle.keySet()) {
- final List<Entitlement> entitlements = getAllEntitlementsForBundle(subscriptionsPerBundle.get(bundleId), tenantContext);
- result.addAll(entitlements);
- }
- return result;
- }
- private List<Entitlement> getAllEntitlementsForBundle(final List<SubscriptionBase> subscriptions, final TenantContext context) {
- return Lists.transform(subscriptions,
- new Function<SubscriptionBase, Entitlement>() {
- @Override
- public Entitlement apply(final SubscriptionBase input) {
- try {
- return getEntitlementForId(input.getId(), context);
- } catch (EntitlementApiException e) {
- throw new RuntimeException("Failed to extract blocking state for subscription " + input.getId().toString());
- }
- }
- });
+ final AccountEventsStreams accountEventsStreams = eventsStreamBuilder.buildForAccount(context);
+ final List<EventsStream> eventsStreams = ImmutableList.<EventsStream>copyOf(Iterables.<EventsStream>concat(accountEventsStreams.getEventsStreams().values()));
+ return Lists.<EventsStream, Entitlement>transform(eventsStreams,
+ new Function<EventsStream, Entitlement>() {
+ @Override
+ public Entitlement apply(final EventsStream eventsStream) {
+ return new DefaultEntitlement(eventsStream, eventsStreamBuilder, entitlementApi,
+ blockingStateDao, subscriptionInternalApi, checker, notificationQueueService,
+ entitlementUtils, dateHelper, clock, internalCallContextFactory);
+ }
+ });
}
@Override
diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/api/DefaultSubscription.java b/entitlement/src/main/java/com/ning/billing/entitlement/api/DefaultSubscription.java
index 25842b7..3938659 100644
--- a/entitlement/src/main/java/com/ning/billing/entitlement/api/DefaultSubscription.java
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/api/DefaultSubscription.java
@@ -16,29 +16,32 @@
package com.ning.billing.entitlement.api;
-import java.util.List;
+import java.util.Collection;
import org.joda.time.DateTime;
import org.joda.time.LocalDate;
+import com.google.common.base.Predicate;
+import com.google.common.collect.Iterables;
+
public class DefaultSubscription extends DefaultEntitlement implements Subscription {
- private final List<BlockingState> blockingStates;
+ private final Collection<BlockingState> currentSubscriptionBlockingStatesForServices;
- DefaultSubscription(final DefaultEntitlement entitlement, final List<BlockingState> blockingStates) {
+ DefaultSubscription(final DefaultEntitlement entitlement) {
super(entitlement);
- this.blockingStates = blockingStates;
+ this.currentSubscriptionBlockingStatesForServices = eventsStream.getCurrentSubscriptionEntitlementBlockingStatesForServices();
}
@Override
public LocalDate getBillingStartDate() {
- return new LocalDate(getSubscriptionBase().getStartDate(), getAccount().getTimeZone());
+ return new LocalDate(getSubscriptionBase().getStartDate(), getAccountTimeZone());
}
@Override
public LocalDate getBillingEndDate() {
final DateTime futureOrCurrentEndDateForSubscription = getSubscriptionBase().getEndDate() != null ? getSubscriptionBase().getEndDate() : getSubscriptionBase().getFutureEndDate();
- final DateTime futureOrCurrentEndDateForBaseSubscription = getEventsStream().getBaseSubscription().getEndDate() != null ? getEventsStream().getBaseSubscription().getEndDate() : getEventsStream().getBaseSubscription().getFutureEndDate();
+ final DateTime futureOrCurrentEndDateForBaseSubscription = getBaseSubscription().getEndDate() != null ? getBaseSubscription().getEndDate() : getBaseSubscription().getFutureEndDate();
final DateTime futureOrCurrentEndDate;
if (futureOrCurrentEndDateForBaseSubscription != null && futureOrCurrentEndDateForBaseSubscription.isBefore(futureOrCurrentEndDateForSubscription)) {
@@ -47,25 +50,27 @@ public class DefaultSubscription extends DefaultEntitlement implements Subscript
futureOrCurrentEndDate = futureOrCurrentEndDateForSubscription;
}
- return futureOrCurrentEndDate != null ? new LocalDate(futureOrCurrentEndDate, getAccount().getTimeZone()) : null;
+ return futureOrCurrentEndDate != null ? new LocalDate(futureOrCurrentEndDate, getAccountTimeZone()) : null;
}
@Override
public LocalDate getChargedThroughDate() {
- return getSubscriptionBase().getChargedThroughDate() != null ? new LocalDate(getSubscriptionBase().getChargedThroughDate(), getAccount().getTimeZone()) : null;
+ return getSubscriptionBase().getChargedThroughDate() != null ? new LocalDate(getSubscriptionBase().getChargedThroughDate(), getAccountTimeZone()) : null;
}
@Override
public String getCurrentStateForService(final String serviceName) {
-
- if (blockingStates == null) {
+ if (currentSubscriptionBlockingStatesForServices == null) {
return null;
+ } else {
+ final BlockingState blockingState = Iterables.<BlockingState>tryFind(currentSubscriptionBlockingStatesForServices,
+ new Predicate<BlockingState>() {
+ @Override
+ public boolean apply(final BlockingState input) {
+ return serviceName.equals(input.getService());
+ }
+ }).orNull();
+ return blockingState == null ? null : blockingState.getService();
}
- for (BlockingState cur : blockingStates) {
- if (cur.getService().equals(serviceName)) {
- return cur.getStateName();
- }
- }
- return null;
}
}
diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/api/DefaultSubscriptionApi.java b/entitlement/src/main/java/com/ning/billing/entitlement/api/DefaultSubscriptionApi.java
index 850317f..253525e 100644
--- a/entitlement/src/main/java/com/ning/billing/entitlement/api/DefaultSubscriptionApi.java
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/api/DefaultSubscriptionApi.java
@@ -17,11 +17,11 @@
package com.ning.billing.entitlement.api;
import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashSet;
+import java.util.Comparator;
+import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
-import java.util.Set;
+import java.util.Map;
import java.util.UUID;
import javax.inject.Inject;
@@ -29,217 +29,204 @@ import javax.inject.Inject;
import org.joda.time.DateTimeZone;
import com.ning.billing.ErrorCode;
-import com.ning.billing.account.api.Account;
-import com.ning.billing.account.api.AccountApiException;
-import com.ning.billing.account.api.AccountInternalApi;
+import com.ning.billing.ObjectType;
import com.ning.billing.callcontext.InternalTenantContext;
-import com.ning.billing.clock.Clock;
-import com.ning.billing.entitlement.block.BlockingChecker;
-import com.ning.billing.entitlement.dao.BlockingStateDao;
+import com.ning.billing.entitlement.AccountEntitlements;
+import com.ning.billing.entitlement.EntitlementInternalApi;
import com.ning.billing.subscription.api.SubscriptionBaseInternalApi;
import com.ning.billing.subscription.api.user.SubscriptionBaseApiException;
import com.ning.billing.subscription.api.user.SubscriptionBaseBundle;
+import com.ning.billing.util.cache.Cachable.CacheType;
+import com.ning.billing.util.cache.CacheControllerDispatcher;
import com.ning.billing.util.callcontext.InternalCallContextFactory;
import com.ning.billing.util.callcontext.TenantContext;
+import com.ning.billing.util.customfield.ShouldntHappenException;
+import com.ning.billing.util.dao.NonEntityDao;
-import com.google.common.base.Function;
+import com.google.common.base.Optional;
import com.google.common.base.Predicate;
-import com.google.common.collect.Collections2;
-import com.google.common.collect.LinkedListMultimap;
-import com.google.common.collect.ListMultimap;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Ordering;
public class DefaultSubscriptionApi implements SubscriptionApi {
+ private static final Comparator<SubscriptionBundle> SUBSCRIPTION_BUNDLE_COMPARATOR = new Comparator<SubscriptionBundle>() {
+ @Override
+ public int compare(final SubscriptionBundle o1, final SubscriptionBundle o2) {
+ final int compared = o1.getOriginalCreatedDate().compareTo(o2.getOriginalCreatedDate());
+ if (compared != 0) {
+ return compared;
+ } else {
+ final int compared2 = o1.getUpdatedDate().compareTo(o2.getUpdatedDate());
+ if (compared2 != 0) {
+ return compared2;
+ } else {
+ // Default, stable, ordering
+ return o1.getId().compareTo(o2.getId());
+ }
+ }
+ }
+ };
+
+ private final EntitlementInternalApi entitlementInternalApi;
private final SubscriptionBaseInternalApi subscriptionInternalApi;
- private final EntitlementApi entitlementApi;
- private final BlockingChecker checker;
- private final BlockingStateDao blockingStateDao;
- private final EntitlementDateHelper dateHelper;
- private final Clock clock;
private final InternalCallContextFactory internalCallContextFactory;
- private final AccountInternalApi accountApi;
+ private final NonEntityDao nonEntityDao;
+ private final CacheControllerDispatcher cacheControllerDispatcher;
@Inject
- public DefaultSubscriptionApi(final SubscriptionBaseInternalApi subscriptionInternalApi, final EntitlementApi entitlementApi, final BlockingChecker checker, final BlockingStateDao blockingStateDao, final AccountInternalApi accountApi, final Clock clock, final InternalCallContextFactory internalCallContextFactory) {
+ public DefaultSubscriptionApi(final EntitlementInternalApi entitlementInternalApi, final SubscriptionBaseInternalApi subscriptionInternalApi,
+ final InternalCallContextFactory internalCallContextFactory, final NonEntityDao nonEntityDao, final CacheControllerDispatcher cacheControllerDispatcher) {
+ this.entitlementInternalApi = entitlementInternalApi;
this.subscriptionInternalApi = subscriptionInternalApi;
- this.entitlementApi = entitlementApi;
- this.accountApi = accountApi;
- this.checker = checker;
- this.blockingStateDao = blockingStateDao;
- this.dateHelper = new EntitlementDateHelper(accountApi, clock);
- ;
- this.clock = clock;
this.internalCallContextFactory = internalCallContextFactory;
+ this.nonEntityDao = nonEntityDao;
+ this.cacheControllerDispatcher = cacheControllerDispatcher;
}
@Override
public Subscription getSubscriptionForEntitlementId(final UUID entitlementId, final TenantContext context) throws SubscriptionApiException {
+ final Long accountRecordId = nonEntityDao.retrieveAccountRecordIdFromObject(entitlementId, ObjectType.SUBSCRIPTION, cacheControllerDispatcher.getCacheController(CacheType.ACCOUNT_RECORD_ID));
+ final UUID accountId = nonEntityDao.retrieveIdFromObject(accountRecordId, ObjectType.ACCOUNT);
+
+ // Retrieve entitlements
+ final AccountEntitlements accountEntitlements;
try {
- final Entitlement entitlement = entitlementApi.getEntitlementForId(entitlementId, context);
- final InternalTenantContext contextWithValidAccountRecordId = internalCallContextFactory.createInternalTenantContext(entitlement.getAccountId(), context);
- return fromEntitlement(entitlement, contextWithValidAccountRecordId);
+ accountEntitlements = entitlementInternalApi.getAllEntitlementsForAccountId(accountId, context);
} catch (EntitlementApiException e) {
throw new SubscriptionApiException(e);
}
+
+ // Build subscriptions
+ final Iterable<Subscription> accountSubscriptions = Iterables.<Subscription>concat(buildSubscriptionsFromEntitlements(accountEntitlements).values());
+
+ return Iterables.<Subscription>find(accountSubscriptions,
+ new Predicate<Subscription>() {
+ @Override
+ public boolean apply(final Subscription subscription) {
+ return subscription.getId().equals(entitlementId);
+ }
+ });
}
@Override
public SubscriptionBundle getSubscriptionBundle(final UUID bundleId, final TenantContext context) throws SubscriptionApiException {
-
- try {
-
- final List<Entitlement> entitlements = entitlementApi.getAllEntitlementsForBundle(bundleId, context);
- if (entitlements.isEmpty()) {
- throw new SubscriptionApiException(ErrorCode.SUB_GET_INVALID_BUNDLE_ID, bundleId);
- }
- return getSubscriptionBundleFromEntitlements(bundleId, entitlements, context);
- } catch (EntitlementApiException e) {
- throw new SubscriptionApiException(e);
- } catch (SubscriptionBaseApiException e) {
- throw new SubscriptionApiException(e);
- } catch (AccountApiException e) {
- throw new SubscriptionApiException(e);
+ final Long accountRecordId = nonEntityDao.retrieveAccountRecordIdFromObject(bundleId, ObjectType.BUNDLE, cacheControllerDispatcher.getCacheController(CacheType.ACCOUNT_RECORD_ID));
+ final UUID accountId = nonEntityDao.retrieveIdFromObject(accountRecordId, ObjectType.ACCOUNT);
+
+ final Optional<SubscriptionBundle> bundleOptional = Iterables.<SubscriptionBundle>tryFind(getSubscriptionBundlesForAccount(accountId, context),
+ new Predicate<SubscriptionBundle>() {
+ @Override
+ public boolean apply(final SubscriptionBundle bundle) {
+ return bundle.getId().equals(bundleId);
+ }
+ });
+ if (!bundleOptional.isPresent()) {
+ throw new SubscriptionApiException(ErrorCode.SUB_GET_INVALID_BUNDLE_ID, bundleId);
+ } else {
+ return bundleOptional.get();
}
}
-
@Override
public List<SubscriptionBundle> getSubscriptionBundlesForAccountIdAndExternalKey(final UUID accountId, final String externalKey, final TenantContext context) throws SubscriptionApiException {
-
- try {
-
- final List<Entitlement> entitlements = entitlementApi.getAllEntitlementsForAccountIdAndExternalKey(accountId, externalKey, context);
- if (entitlements.isEmpty()) {
- throw new SubscriptionApiException(ErrorCode.SUB_GET_INVALID_BUNDLE_KEY, externalKey);
- }
- return getSubscriptionBundles(entitlements, context);
- } catch (EntitlementApiException e) {
- throw new SubscriptionApiException(e);
- } catch (SubscriptionBaseApiException e) {
- throw new SubscriptionApiException(e);
- } catch (AccountApiException e) {
- throw new SubscriptionApiException(e);
- }
+ return ImmutableList.<SubscriptionBundle>copyOf(Iterables.<SubscriptionBundle>filter(getSubscriptionBundlesForAccount(accountId, context),
+ new Predicate<SubscriptionBundle>() {
+ @Override
+ public boolean apply(final SubscriptionBundle bundle) {
+ return bundle.getExternalKey().equals(externalKey);
+ }
+ }));
}
@Override
- public SubscriptionBundle getActiveSubscriptionBundleForExternalKey(final String externalKey,
- final TenantContext context) throws SubscriptionApiException {
-
+ public SubscriptionBundle getActiveSubscriptionBundleForExternalKey(final String externalKey, final TenantContext context) throws SubscriptionApiException {
final InternalTenantContext internalContext = internalCallContextFactory.createInternalTenantContext(context);
try {
final SubscriptionBaseBundle baseBundle = subscriptionInternalApi.getActiveBundleForKey(externalKey, internalContext);
- final List<Entitlement> allEntitlementsForBundle = entitlementApi.getAllEntitlementsForBundle(baseBundle.getId(), context);
-
- return getSubscriptionBundleFromEntitlements(baseBundle.getId(), allEntitlementsForBundle, context);
+ return getSubscriptionBundle(baseBundle.getId(), context);
} catch (SubscriptionBaseApiException e) {
throw new SubscriptionApiException(e);
- } catch (EntitlementApiException e) {
- throw new SubscriptionApiException(e);
- } catch (AccountApiException e) {
- throw new SubscriptionApiException(e);
}
}
@Override
public List<SubscriptionBundle> getSubscriptionBundlesForExternalKey(final String externalKey, final TenantContext context) throws SubscriptionApiException {
-
final InternalTenantContext internalContext = internalCallContextFactory.createInternalTenantContext(context);
- try {
- final List<SubscriptionBaseBundle> baseBundles = subscriptionInternalApi.getBundlesForKey(externalKey, internalContext);
- final List<SubscriptionBundle> result = new ArrayList<SubscriptionBundle>(baseBundles.size());
- for (SubscriptionBaseBundle cur : baseBundles) {
- final List<Entitlement> allEntitlementsForBundle = entitlementApi.getAllEntitlementsForBundle(cur.getId(), context);
- final SubscriptionBundle bundle = getSubscriptionBundleFromEntitlements(cur.getId(), allEntitlementsForBundle, context);
- result.add(bundle);
- }
- return result;
- } catch (SubscriptionBaseApiException e) {
- throw new SubscriptionApiException(e);
- } catch (EntitlementApiException e) {
- throw new SubscriptionApiException(e);
- } catch (AccountApiException e) {
- throw new SubscriptionApiException(e);
+ final List<SubscriptionBaseBundle> baseBundles = subscriptionInternalApi.getBundlesForKey(externalKey, internalContext);
+
+ final List<SubscriptionBundle> result = new ArrayList<SubscriptionBundle>(baseBundles.size());
+ for (final SubscriptionBaseBundle cur : baseBundles) {
+ final SubscriptionBundle bundle = getSubscriptionBundle(cur.getId(), context);
+ result.add(bundle);
}
+
+ return result;
}
@Override
- public List<SubscriptionBundle> getSubscriptionBundlesForAccountId(final UUID accountId,
- final TenantContext context) throws SubscriptionApiException {
- try {
+ public List<SubscriptionBundle> getSubscriptionBundlesForAccountId(final UUID accountId, final TenantContext context) throws SubscriptionApiException {
+ return getSubscriptionBundlesForAccount(accountId, context);
+ }
- final List<Entitlement> entitlements = entitlementApi.getAllEntitlementsForAccountId(accountId, context);
- if (entitlements.isEmpty()) {
- return Collections.emptyList();
- }
- return getSubscriptionBundles(entitlements, context);
+ private List<SubscriptionBundle> getSubscriptionBundlesForAccount(final UUID accountId, final TenantContext tenantContext) throws SubscriptionApiException {
+ // Retrieve entitlements
+ final AccountEntitlements accountEntitlements;
+ try {
+ accountEntitlements = entitlementInternalApi.getAllEntitlementsForAccountId(accountId, tenantContext);
} catch (EntitlementApiException e) {
throw new SubscriptionApiException(e);
- } catch (SubscriptionBaseApiException e) {
- throw new SubscriptionApiException(e);
- } catch (AccountApiException e) {
- throw new SubscriptionApiException(e);
- }
- }
-
- private List<SubscriptionBundle> getSubscriptionBundles(final List<Entitlement> entitlements, final TenantContext context) throws SubscriptionBaseApiException, AccountApiException {
- final ListMultimap<UUID, Entitlement> perBundleEntitlements = LinkedListMultimap.create();
- for (Entitlement cur : entitlements) {
- perBundleEntitlements.put(cur.getBundleId(), cur);
}
- final List<SubscriptionBundle> result = new ArrayList<SubscriptionBundle>(perBundleEntitlements.keySet().size());
- for (UUID bundleId : perBundleEntitlements.keySet()) {
- final List<Entitlement> allEntitlementsForBundle = perBundleEntitlements.get(bundleId);
- final SubscriptionBundle bundle = getSubscriptionBundleFromEntitlements(bundleId, allEntitlementsForBundle, context);
- result.add(bundle);
+ // Build subscriptions
+ final Map<UUID, List<Subscription>> subscriptionsPerBundle = buildSubscriptionsFromEntitlements(accountEntitlements);
+
+ final DateTimeZone accountTimeZone = accountEntitlements.getAccount().getTimeZone();
+
+ // Build subscription bundles
+ final List<SubscriptionBundle> bundles = new LinkedList<SubscriptionBundle>();
+ for (final UUID bundleId : subscriptionsPerBundle.keySet()) {
+ final List<Subscription> subscriptionsForBundle = subscriptionsPerBundle.get(bundleId);
+ final String externalKey = subscriptionsForBundle.get(0).getExternalKey();
+
+ final SubscriptionBundleTimeline timeline = new DefaultSubscriptionBundleTimeline(accountTimeZone,
+ accountId,
+ bundleId,
+ externalKey,
+ accountEntitlements.getEntitlements().get(bundleId));
+
+ final SubscriptionBaseBundle baseBundle = accountEntitlements.getBundles().get(bundleId);
+ final SubscriptionBundle subscriptionBundle = new DefaultSubscriptionBundle(bundleId,
+ accountId,
+ externalKey,
+ subscriptionsForBundle,
+ timeline,
+ baseBundle.getOriginalCreatedDate(),
+ baseBundle.getCreatedDate(),
+ baseBundle.getUpdatedDate());
+ bundles.add(subscriptionBundle);
}
- return result;
- }
- private Subscription fromEntitlement(final Entitlement entitlement, final InternalTenantContext internalTenantContext) {
-
- final List<BlockingState> states = blockingStateDao.getBlockingState(entitlement.getId(), BlockingStateType.SUBSCRIPTION, internalTenantContext);
- final Subscription result = new DefaultSubscription((DefaultEntitlement) entitlement, states);
- return result;
+ // Sort the results for predictability
+ return Ordering.<SubscriptionBundle>from(SUBSCRIPTION_BUNDLE_COMPARATOR).sortedCopy(bundles);
}
- private SubscriptionBundle getSubscriptionBundleFromEntitlements(final UUID bundleId, final List<Entitlement> entitlements, final TenantContext context) throws SubscriptionBaseApiException, AccountApiException {
- final InternalTenantContext internalTenantContext = internalCallContextFactory.createInternalTenantContext(context);
- final SubscriptionBaseBundle baseBundle = subscriptionInternalApi.getBundleFromId(bundleId, internalTenantContext);
- final List<Subscription> subscriptions = new ArrayList<Subscription>(entitlements.size());
- subscriptions.addAll(Collections2.transform(entitlements, new Function<Entitlement, Subscription>() {
- @Override
- public Subscription apply(final Entitlement input) {
- return fromEntitlement(input, internalTenantContext);
+ private Map<UUID, List<Subscription>> buildSubscriptionsFromEntitlements(final AccountEntitlements accountEntitlements) {
+ final Map<UUID, List<Subscription>> subscriptionsPerBundle = new HashMap<UUID, List<Subscription>>();
+ for (final UUID bundleId : accountEntitlements.getEntitlements().keySet()) {
+ if (subscriptionsPerBundle.get(bundleId) == null) {
+ subscriptionsPerBundle.put(bundleId, new LinkedList<Subscription>());
}
- }));
-
- final Account account = accountApi.getAccountById(baseBundle.getAccountId(), internalTenantContext);
-
- final InternalTenantContext internalTenantContextWithAccountRecordId = internalCallContextFactory.createInternalTenantContext(account.getId(), context);
- final DateTimeZone accountTimeZone = account.getTimeZone();
-
- final List<BlockingState> allBlockingStatesPerAccountRecordId = blockingStateDao.getBlockingAllForAccountRecordId(internalTenantContextWithAccountRecordId);
- final Set<UUID> allEntitlementIds = new HashSet<UUID>(Collections2.<Entitlement, UUID>transform(entitlements, new Function<Entitlement, UUID>() {
- @Override
- public UUID apply(final Entitlement input) {
- return input.getId();
- }
- }));
-
- final List<BlockingState> filteredBlockingStates = new LinkedList<BlockingState>(Collections2.filter(allBlockingStatesPerAccountRecordId, new Predicate<BlockingState>() {
- @Override
- public boolean apply(final BlockingState input) {
- return input.getType() == BlockingStateType.ACCOUNT ||
- (input.getType() == BlockingStateType.SUBSCRIPTION_BUNDLE && input.getBlockedId().equals(bundleId)) ||
- (input.getType() == BlockingStateType.SUBSCRIPTION && allEntitlementIds.contains(input.getBlockedId()));
+ for (final Entitlement entitlement : accountEntitlements.getEntitlements().get(bundleId)) {
+ if (entitlement instanceof DefaultEntitlement) {
+ subscriptionsPerBundle.get(bundleId).add(new DefaultSubscription((DefaultEntitlement) entitlement));
+ } else {
+ throw new ShouldntHappenException("Entitlement should be a DefaultEntitlement instance");
+ }
}
- }));
-
- final SubscriptionBundleTimeline timeline = new DefaultSubscriptionBundleTimeline(accountTimeZone, account.getId(), bundleId, baseBundle.getExternalKey(), entitlements, filteredBlockingStates);
- final DefaultSubscriptionBundle bundle = new DefaultSubscriptionBundle(bundleId, baseBundle.getAccountId(), baseBundle.getExternalKey(), subscriptions, timeline, baseBundle.getOriginalCreatedDate() , baseBundle.getCreatedDate(), baseBundle.getUpdatedDate());
- return bundle;
+ }
+ return subscriptionsPerBundle;
}
}
diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/api/DefaultSubscriptionBundleTimeline.java b/entitlement/src/main/java/com/ning/billing/entitlement/api/DefaultSubscriptionBundleTimeline.java
index 796eb06..c182b4c 100644
--- a/entitlement/src/main/java/com/ning/billing/entitlement/api/DefaultSubscriptionBundleTimeline.java
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/api/DefaultSubscriptionBundleTimeline.java
@@ -21,18 +21,19 @@ import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
+import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
-import java.util.ListIterator;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import java.util.UUID;
+import javax.annotation.Nullable;
+
import org.joda.time.DateTime;
import org.joda.time.DateTimeZone;
-import org.joda.time.LocalDate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -56,13 +57,8 @@ import com.google.common.collect.ImmutableList;
public class DefaultSubscriptionBundleTimeline implements SubscriptionBundleTimeline {
-
private final Logger logger = LoggerFactory.getLogger(DefaultSubscriptionBundleTimeline.class);
- // STEPH This is added to give us confidence the timeline we generate behaves as expected. Could be removed at some point
- private final static String TIMELINE_WARN_LOG = "Sanity Timeline: ";
-
-
public static final String BILLING_SERVICE_NAME = "billing-service";
public static final String ENT_BILLING_SERVICE_NAME = "entitlement+billing-service";
@@ -71,8 +67,18 @@ public class DefaultSubscriptionBundleTimeline implements SubscriptionBundleTime
private final UUID bundleId;
private final String externalKey;
+ public DefaultSubscriptionBundleTimeline(final DateTimeZone accountTimeZone, final UUID accountId, final UUID bundleId, final String externalKey, final Collection<Entitlement> entitlements) {
+ final Collection<BlockingState> blockingStates = new HashSet<BlockingState>();
+ for (final Entitlement entitlement : entitlements) {
+ blockingStates.addAll(((DefaultEntitlement) entitlement).getEventsStream().getBlockingStates());
+ }
+ this.accountId = accountId;
+ this.bundleId = bundleId;
+ this.externalKey = externalKey;
+ this.events = computeEvents(entitlements, new LinkedList<BlockingState>(blockingStates), accountTimeZone);
+ }
- public DefaultSubscriptionBundleTimeline(final DateTimeZone accountTimeZone, final UUID accountId, final UUID bundleId, final String externalKey, final List<Entitlement> entitlements, List<BlockingState> allBlockingStates) {
+ public DefaultSubscriptionBundleTimeline(final DateTimeZone accountTimeZone, final UUID accountId, final UUID bundleId, final String externalKey, final List<Entitlement> entitlements, final List<BlockingState> allBlockingStates) {
this.accountId = accountId;
this.bundleId = bundleId;
this.externalKey = externalKey;
@@ -85,8 +91,7 @@ public class DefaultSubscriptionBundleTimeline implements SubscriptionBundleTime
// - base subscription events are already ordered for each Entitlement and so when we reorder at the bundle level we try not to break that initial ordering
// - blocking state events occur at various level (account, bundle and subscription) so for higher level, we need to dispatch that on each subscription.
//
- private List<SubscriptionEvent> computeEvents(final List<Entitlement> entitlements, List<BlockingState> allBlockingStates, final DateTimeZone accountTimeZone) {
-
+ private List<SubscriptionEvent> computeEvents(final Collection<Entitlement> entitlements, final List<BlockingState> allBlockingStates, final DateTimeZone accountTimeZone) {
// Extract ids for all entitlement in the list
final Set<UUID> allEntitlementUUIDs = new TreeSet(Collections2.transform(entitlements, new Function<Entitlement, UUID>() {
@Override
@@ -131,23 +136,44 @@ public class DefaultSubscriptionBundleTimeline implements SubscriptionBundleTime
if (createdDateComp != 0) {
return createdDateComp;
}
- logger.warn(TIMELINE_WARN_LOG + "Detected two identical blockingStates events for blockableId = " + o1.getBlockedId() +
- ", type = " + o1.getType() + ", ");
- // Underministic-- not sure that will ever happen. Once we are confident this never happens we should thrown IllegalException
+
+ // Non deterministic -- not sure that will ever happen. Once we are confident this never happens, we should throw ShouldntHappenException
return 0;
}
});
- for (BlockingState bs : allBlockingStates) {
-
+ for (final BlockingState bs : allBlockingStates) {
final List<SubscriptionEvent> newEvents = new ArrayList<SubscriptionEvent>();
- int index = insertFromBlockingEvent(accountTimeZone, allEntitlementUUIDs, result, bs, bs.getEffectiveDate(), newEvents);
+ final int index = insertFromBlockingEvent(accountTimeZone, allEntitlementUUIDs, result, bs, bs.getEffectiveDate(), newEvents);
insertAfterIndex(result, newEvents, index);
}
+
reOrderSubscriptionEventsOnSameDateByType(result);
+
+ removeOverlappingSubscriptionEvents(result);
+
return result;
}
+ // Make sure the argument supports the remove operation - hence expect a LinkedList, not a List
+ private void removeOverlappingSubscriptionEvents(final LinkedList<SubscriptionEvent> events) {
+ final Iterator<SubscriptionEvent> iterator = events.iterator();
+ final Map<String, DefaultSubscriptionEvent> prevPerService = new HashMap<String, DefaultSubscriptionEvent>();
+ while (iterator.hasNext()) {
+ final DefaultSubscriptionEvent current = (DefaultSubscriptionEvent) iterator.next();
+ final DefaultSubscriptionEvent prev = prevPerService.get(current.getServiceName());
+ if (prev != null) {
+ if (current.overlaps(prev)) {
+ iterator.remove();
+ } else {
+ prevPerService.put(current.getServiceName(), current);
+ }
+ } else {
+ prevPerService.put(current.getServiceName(), current);
+ }
+ }
+ }
+
//
// All events have been inserted and should be at the right place, except that we want to ensure that events for a given subscription,
// and for a given time are ordered by SubscriptionEventType.
@@ -163,20 +189,20 @@ public class DefaultSubscriptionBundleTimeline implements SubscriptionBundleTime
protected void reOrderSubscriptionEventsOnSameDateByType(final List<SubscriptionEvent> events) {
final int size = events.size();
for (int i = 0; i < size; i++) {
- final DefaultSubscriptionEvent cur = (DefaultSubscriptionEvent) events.get(i);
- final DefaultSubscriptionEvent next = (i < (size - 1)) ? (DefaultSubscriptionEvent) events.get(i + 1) : null;
+ final SubscriptionEvent cur = events.get(i);
+ final SubscriptionEvent next = (i < (size - 1)) ? events.get(i + 1) : null;
final boolean shouldSwap = (next != null && shouldSwap(cur, next, true));
final boolean shouldReverseSort = (next == null || shouldSwap);
int currentIndex = i;
if (shouldSwap) {
- Collections.swap(events, i, i+1);
+ Collections.swap(events, i, i + 1);
}
if (shouldReverseSort) {
while (currentIndex >= 1) {
- final DefaultSubscriptionEvent revCur = (DefaultSubscriptionEvent) events.get(currentIndex);
- final DefaultSubscriptionEvent other = (DefaultSubscriptionEvent) events.get(currentIndex - 1);
+ final SubscriptionEvent revCur = events.get(currentIndex);
+ final SubscriptionEvent other = events.get(currentIndex - 1);
if (shouldSwap(revCur, other, false)) {
Collections.swap(events, currentIndex, currentIndex - 1);
}
@@ -189,23 +215,19 @@ public class DefaultSubscriptionBundleTimeline implements SubscriptionBundleTime
}
}
-
- private boolean shouldSwap(DefaultSubscriptionEvent cur, DefaultSubscriptionEvent other, boolean isAscending) {
-
+ private boolean shouldSwap(final SubscriptionEvent cur, final SubscriptionEvent other, final boolean isAscending) {
// For a given date, order by subscriptionId, and within subscription by event type
final int idComp = cur.getEntitlementId().compareTo(other.getEntitlementId());
return (cur.getEffectiveDate().compareTo(other.getEffectiveDate()) == 0 &&
((isAscending &&
((idComp > 0) ||
(idComp == 0 && cur.getSubscriptionEventType().ordinal() > other.getSubscriptionEventType().ordinal()))) ||
- (!isAscending &&
- ((idComp < 0) ||
- (idComp == 0 && cur.getSubscriptionEventType().ordinal() < other.getSubscriptionEventType().ordinal())))));
+ (!isAscending &&
+ ((idComp < 0) ||
+ (idComp == 0 && cur.getSubscriptionEventType().ordinal() < other.getSubscriptionEventType().ordinal())))));
}
-
- private void insertAfterIndex(final LinkedList<SubscriptionEvent> original, final List<SubscriptionEvent> newEvents, int index) {
-
+ private void insertAfterIndex(final LinkedList<SubscriptionEvent> original, final List<SubscriptionEvent> newEvents, final int index) {
final boolean firstPosition = (index == -1);
final boolean lastPosition = (index == original.size() - 1);
if (lastPosition || firstPosition) {
@@ -226,11 +248,9 @@ public class DefaultSubscriptionBundleTimeline implements SubscriptionBundleTime
// reOrderSubscriptionEventsOnSameDateByType would reorder them anyway if this was not the case.
//
private int insertFromBlockingEvent(final DateTimeZone accountTimeZone, final Set<UUID> allEntitlementUUIDs, final List<SubscriptionEvent> result, final BlockingState bs, final DateTime bsEffectiveDate, final List<SubscriptionEvent> newEvents) {
-
-
// Keep the current state per entitlement
final Map<UUID, TargetState> targetStates = new HashMap<UUID, TargetState>();
- for (UUID cur : allEntitlementUUIDs) {
+ for (final UUID cur : allEntitlementUUIDs) {
targetStates.put(cur, new TargetState());
}
@@ -242,7 +262,7 @@ public class DefaultSubscriptionBundleTimeline implements SubscriptionBundleTime
// Where we need to insert in that stream
DefaultSubscriptionEvent curInsertion = null;
while (it.hasNext()) {
- DefaultSubscriptionEvent cur = (DefaultSubscriptionEvent) it.next();
+ final DefaultSubscriptionEvent cur = (DefaultSubscriptionEvent) it.next();
final int compEffectiveDate = bsEffectiveDate.compareTo(cur.getEffectiveDateTime());
final boolean shouldContinue = (compEffectiveDate >= 0);
if (!shouldContinue) {
@@ -277,13 +297,12 @@ public class DefaultSubscriptionBundleTimeline implements SubscriptionBundleTime
ImmutableList.<UUID>copyOf(allEntitlementUUIDs);
// For each target compute the new events that should be inserted in the stream
- for (UUID target : targetEntitlementIds) {
-
+ for (final UUID target : targetEntitlementIds) {
final SubscriptionEvent[] prevNext = findPrevNext(result, target, curInsertion);
final TargetState curTargetState = targetStates.get(target);
final List<SubscriptionEventType> eventTypes = curTargetState.addStateAndReturnEventTypes(bs);
- for (SubscriptionEventType t : eventTypes) {
+ for (final SubscriptionEventType t : eventTypes) {
newEvents.add(toSubscriptionEvent(prevNext[0], prevNext[1], target, bs, t, accountTimeZone));
}
}
@@ -292,12 +311,11 @@ public class DefaultSubscriptionBundleTimeline implements SubscriptionBundleTime
// Extract prev and next events in the stream events for that particular target subscription from the insertionEvent
private SubscriptionEvent[] findPrevNext(final List<SubscriptionEvent> events, final UUID targetEntitlementId, final SubscriptionEvent insertionEvent) {
-
// Find prev/next event for the same entitlement
final SubscriptionEvent[] result = new DefaultSubscriptionEvent[2];
if (insertionEvent == null) {
result[0] = null;
- result[1] = events.size() > 0 ? events.get(0) : null;
+ result[1] = !events.isEmpty() ? events.get(0) : null;
return result;
}
@@ -325,7 +343,7 @@ public class DefaultSubscriptionBundleTimeline implements SubscriptionBundleTime
}
// Compute the initial stream of events based on the subscription base events
- private LinkedList<SubscriptionEvent> computeSubscriptionBaseEvents(final List<Entitlement> entitlements, final DateTimeZone accountTimeZone) {
+ private LinkedList<SubscriptionEvent> computeSubscriptionBaseEvents(final Collection<Entitlement> entitlements, final DateTimeZone accountTimeZone) {
final LinkedList<SubscriptionEvent> result = new LinkedList<SubscriptionEvent>();
for (final Entitlement cur : entitlements) {
final SubscriptionBase base = ((DefaultEntitlement) cur).getSubscriptionBase();
@@ -338,64 +356,35 @@ public class DefaultSubscriptionBundleTimeline implements SubscriptionBundleTime
}
}
}
- sanitizeForBaseRecreateEvents(result);
- return result;
- }
- //
- // Old version of code would use CANCEL/RE_CREATE to simulate PAUSE_BILLING/RESUME_BILLING
- // (Relies on the assumption that there is no blocking_state event matching that CACNEL event so:
- // 1. The STOP_BILLING (coming from the row CANCEL event) should be transformed into a PAUSE_BILLING
- // 2. We also add a PAUSE_ENTITLEMENT at the same time as the PAUSE_BILLING
- //
- private void sanitizeForBaseRecreateEvents(final LinkedList<SubscriptionEvent> input) {
- final Collection<UUID> guiltyEntitlementIds = new TreeSet<UUID>();
- final ListIterator<SubscriptionEvent> it = input.listIterator(input.size());
- while (it.hasPrevious()) {
- final SubscriptionEvent cur = it.previous();
- if (cur.getSubscriptionEventType() == SubscriptionEventType.RESUME_BILLING) {
- guiltyEntitlementIds.add(cur.getEntitlementId());
- continue;
- }
- if (cur.getSubscriptionEventType() == SubscriptionEventType.STOP_BILLING &&
- guiltyEntitlementIds.contains(cur.getEntitlementId())) {
- guiltyEntitlementIds.remove(cur.getEntitlementId());
- final SubscriptionEvent correctedBillingEvent = new DefaultSubscriptionEvent((DefaultSubscriptionEvent) cur, SubscriptionEventType.PAUSE_BILLING);
- it.set(correctedBillingEvent);
-
- // Old versions of the code won't have an associated event in blocking_states - we need to add one on the fly
- final SubscriptionEvent correctedEntitlementEvent = new DefaultSubscriptionEvent((DefaultSubscriptionEvent) cur, SubscriptionEventType.PAUSE_ENTITLEMENT);
- it.add(correctedEntitlementEvent);
- }
- }
+ return result;
}
- private void insertSubscriptionEvent(final SubscriptionEvent event, final LinkedList<SubscriptionEvent> result) {
+ private void insertSubscriptionEvent(final SubscriptionEvent event, final List<SubscriptionEvent> result) {
int index = 0;
- for (SubscriptionEvent cur : result) {
- int compEffectiveDate = event.getEffectiveDate().compareTo(cur.getEffectiveDate());
+ for (final SubscriptionEvent cur : result) {
+ final int compEffectiveDate = event.getEffectiveDate().compareTo(cur.getEffectiveDate());
if (compEffectiveDate < 0) {
// EffectiveDate is less than cur -> insert here
break;
} else if (compEffectiveDate == 0) {
-
- int compUUID = event.getEntitlementId().compareTo(cur.getEntitlementId());
+ final int compUUID = event.getEntitlementId().compareTo(cur.getEntitlementId());
if (compUUID < 0) {
// Same EffectiveDate but subscription are different, no need top sort further just return something deterministic
break;
} else if (compUUID == 0) {
-
- int eventOrder = event.getSubscriptionEventType().ordinal() - cur.getSubscriptionEventType().ordinal();
+ final int eventOrder = event.getSubscriptionEventType().ordinal() - cur.getSubscriptionEventType().ordinal();
if (eventOrder < 0) {
// Same EffectiveDate and same subscription, order by SubscriptionEventType;
break;
}
- // Two identical event for the same subscription at the same time, this sounds like some data issue
+ // Two identical events for the same subscription in the same day, trust createdDate
if (eventOrder == 0) {
- logger.warn(TIMELINE_WARN_LOG + "Detected identical events type = " + event.getSubscriptionEventType() + " ids = " +
- event.getId() + ", " + cur.getId() + " for subscription " + cur.getEntitlementId());
- break;
+ final int compCreatedDate = (((DefaultSubscriptionEvent) event).getCreatedDate()).compareTo(((DefaultSubscriptionEvent) cur).getCreatedDate());
+ if (compCreatedDate <= 0) {
+ break;
+ }
}
}
}
@@ -404,8 +393,74 @@ public class DefaultSubscriptionBundleTimeline implements SubscriptionBundleTime
result.add(index, event);
}
+ private SubscriptionEvent toSubscriptionEvent(@Nullable final SubscriptionEvent prev, @Nullable final SubscriptionEvent next,
+ final UUID entitlementId, final BlockingState in, final SubscriptionEventType eventType, final DateTimeZone accountTimeZone) {
+ final Product prevProduct;
+ final Plan prevPlan;
+ final PlanPhase prevPlanPhase;
+ final PriceList prevPriceList;
+ final BillingPeriod prevBillingPeriod;
+ // Enforce prev = null for start events
+ if (prev == null || SubscriptionEventType.START_ENTITLEMENT.equals(eventType) || SubscriptionEventType.START_BILLING.equals(eventType)) {
+ prevProduct = null;
+ prevPlan = null;
+ prevPlanPhase = null;
+ prevPriceList = null;
+ prevBillingPeriod = null;
+ } else {
+ // We look for the next for the 'prev' meaning we we are headed to, but if this is null -- for example on cancellation we get the prev which gives the correct state.
+ prevProduct = (prev.getNextProduct() != null ? prev.getNextProduct() : prev.getPrevProduct());
+ prevPlan = (prev.getNextPlan() != null ? prev.getNextPlan() : prev.getPrevPlan());
+ prevPlanPhase = (prev.getNextPhase() != null ? prev.getNextPhase() : prev.getPrevPhase());
+ prevPriceList = (prev.getNextPriceList() != null ? prev.getNextPriceList() : prev.getPrevPriceList());
+ prevBillingPeriod = (prev.getNextBillingPeriod() != null ? prev.getNextBillingPeriod() : prev.getPrevBillingPeriod());
+ }
+
+ final Product nextProduct;
+ final Plan nextPlan;
+ final PlanPhase nextPlanPhase;
+ final PriceList nextPriceList;
+ final BillingPeriod nextBillingPeriod;
+ if (SubscriptionEventType.PAUSE_ENTITLEMENT.equals(eventType) || SubscriptionEventType.PAUSE_BILLING.equals(eventType) ||
+ SubscriptionEventType.RESUME_ENTITLEMENT.equals(eventType) || SubscriptionEventType.RESUME_BILLING.equals(eventType) ||
+ (SubscriptionEventType.SERVICE_STATE_CHANGE.equals(eventType) && (prev == null || (!SubscriptionEventType.STOP_ENTITLEMENT.equals(prev.getSubscriptionEventType()) && !SubscriptionEventType.STOP_BILLING.equals(prev.getSubscriptionEventType()))))) {
+ // Enforce next = prev for pause/resume events as well as service changes
+ nextProduct = prevProduct;
+ nextPlan = prevPlan;
+ nextPlanPhase = prevPlanPhase;
+ nextPriceList = prevPriceList;
+ nextBillingPeriod = prevBillingPeriod;
+ } else if (next == null) {
+ // Enforce next = null for stop events
+ if (prev == null || SubscriptionEventType.STOP_ENTITLEMENT.equals(eventType) || SubscriptionEventType.STOP_BILLING.equals(eventType)) {
+ nextProduct = null;
+ nextPlan = null;
+ nextPlanPhase = null;
+ nextPriceList = null;
+ nextBillingPeriod = null;
+ } else {
+ nextProduct = prev.getNextProduct();
+ nextPlan = prev.getNextPlan();
+ nextPlanPhase = prev.getNextPhase();
+ nextPriceList = prev.getNextPriceList();
+ nextBillingPeriod = prev.getNextBillingPeriod();
+ }
+ } else {
+ nextProduct = next.getNextProduct();
+ nextPlan = next.getNextPlan();
+ nextPlanPhase = next.getNextPhase();
+ nextPriceList = next.getNextPriceList();
+ nextBillingPeriod = next.getNextBillingPeriod();
+ }
+
+ // See https://github.com/killbill/killbill/issues/135
+ final String serviceName;
+ if (DefaultEntitlementService.ENTITLEMENT_SERVICE_NAME.equals(in.getService())) {
+ serviceName = getServiceName(eventType);
+ } else {
+ serviceName = in.getService();
+ }
- private SubscriptionEvent toSubscriptionEvent(final SubscriptionEvent prev, final SubscriptionEvent next, final UUID entitlementId, final BlockingState in, final SubscriptionEventType eventType, final DateTimeZone accountTimeZone) {
return new DefaultSubscriptionEvent(in.getId(),
entitlementId,
in.getEffectiveDate(),
@@ -413,24 +468,22 @@ public class DefaultSubscriptionBundleTimeline implements SubscriptionBundleTime
eventType,
in.isBlockEntitlement(),
in.isBlockBilling(),
- in.getService(),
+ serviceName,
in.getStateName(),
- // We look for the next for the 'prev' meaning we we are headed to, but if this is null -- for example on cancellation we get the prev which gives the correct state.
- prev != null ? (prev.getNextProduct() != null ? prev.getNextProduct() : prev.getPrevProduct()) : null,
- prev != null ? (prev.getNextPlan() != null ? prev.getNextPlan() : prev.getPrevPlan()) : null,
- prev != null ? (prev.getNextPhase() != null ? prev.getNextPhase() : prev.getPrevPhase()) : null,
- prev != null ? (prev.getNextPriceList() != null ? prev.getNextPriceList() : prev.getPrevPriceList()) : null,
- prev != null ? (prev.getNextBillingPeriod() != null ? prev.getNextBillingPeriod() : prev.getPrevBillingPeriod()) : null,
- next != null ? next.getPrevProduct() : null,
- next != null ? next.getPrevPlan() : null,
- next != null ? next.getPrevPhase() : null,
- next != null ? next.getPrevPriceList() : null,
- next != null ? next.getPrevBillingPeriod() : null,
+ prevProduct,
+ prevPlan,
+ prevPlanPhase,
+ prevPriceList,
+ prevBillingPeriod,
+ nextProduct,
+ nextPlan,
+ nextPlanPhase,
+ nextPriceList,
+ nextBillingPeriod,
in.getCreatedDate(),
accountTimeZone);
}
-
private SubscriptionEvent toSubscriptionEvent(final SubscriptionBaseTransition in, final SubscriptionEventType eventType, final DateTimeZone accountTimeZone) {
return new DefaultSubscriptionEvent(in.getId(),
in.getSubscriptionId(),
@@ -488,11 +541,6 @@ public class DefaultSubscriptionBundleTimeline implements SubscriptionBundleTime
return ImmutableList.<SubscriptionEventType>of(SubscriptionEventType.STOP_BILLING);
case PHASE:
return ImmutableList.<SubscriptionEventType>of(SubscriptionEventType.PHASE);
- // This is the old way of pausing billing; not used any longer, but kept for compatibility reason. We return both RESUME_ENTITLEMENT and RESUME_BILLING
- // and will rely on the sanitizeForBaseRecreateEvents method to transform the STOP_BILLING (coming from CANCEL) into the correct events.
- //
- case RE_CREATE:
- return ImmutableList.<SubscriptionEventType>of(SubscriptionEventType.RESUME_ENTITLEMENT, SubscriptionEventType.RESUME_BILLING);
/*
* Those can be ignored:
*/
@@ -526,415 +574,115 @@ public class DefaultSubscriptionBundleTimeline implements SubscriptionBundleTime
return events;
}
-//
-// Internal class to keep the state associated with each subscription
-//
-private final static class TargetState {
-
- private boolean isEntitlementStarted;
- private boolean isEntitlementStopped;
- private boolean isBillingStarted;
- private boolean isBillingStopped;
- private Map<String, BlockingState> perServiceBlockingState;
-
- public TargetState() {
- this.isEntitlementStarted = false;
- this.isEntitlementStopped = false;
- this.isBillingStarted = false;
- this.isBillingStopped = false;
- this.perServiceBlockingState = new HashMap<String, BlockingState>();
- }
-
- public void setEntitlementStarted() {
- isEntitlementStarted = true;
- }
-
- public void setEntitlementStopped() {
- isEntitlementStopped = true;
- }
-
- public void setBillingStarted() {
- isBillingStarted = true;
- }
-
- public void setBillingStopped() {
- isBillingStopped = true;
- }
-
- public void addEntitlementEvent(final SubscriptionEvent e) {
- final BlockingState converted = new DefaultBlockingState(e.getEntitlementId(), BlockingStateType.SUBSCRIPTION,
- e.getServiceStateName(), e.getServiceName(), false, e.isBlockedEntitlement(), e.isBlockedBilling(),
- ((DefaultSubscriptionEvent) e).getEffectiveDateTime());
- perServiceBlockingState.put(converted.getService(), converted);
-
- }
-
//
- // From the current state of that subscription, compute the effect of the new state based on the incoming blockingState event
+ // Internal class to keep the state associated with each subscription
//
- private List<SubscriptionEventType> addStateAndReturnEventTypes(final BlockingState bs) {
-
- // Turn off isBlockedEntitlement and isBlockedBilling if there was not start event
- final BlockingState fixedBlockingState = new DefaultBlockingState(bs.getBlockedId(),
- bs.getType(),
- bs.getStateName(),
- bs.getService(),
- bs.isBlockChange(),
- (bs.isBlockEntitlement() && isEntitlementStarted && !isEntitlementStopped),
- (bs.isBlockBilling() && isBillingStarted && !isBillingStopped),
- bs.getEffectiveDate());
-
- final List<SubscriptionEventType> result = new ArrayList<SubscriptionEventType>(4);
- if (fixedBlockingState.getStateName().equals(DefaultEntitlementApi.ENT_STATE_CANCELLED)) {
- isEntitlementStopped = true;
- result.add(SubscriptionEventType.STOP_ENTITLEMENT);
- return result;
- }
+ private static final class TargetState {
- //
- // We look at the effect of the incoming event for the specific service, and then recompute the state after so we can compare if anything has changed
- // across all services
- //
- final BlockingAggregator stateBefore = getState();
- perServiceBlockingState.put(fixedBlockingState.getService(), fixedBlockingState);
- final BlockingAggregator stateAfter = getState();
+ private boolean isEntitlementStarted;
+ private boolean isEntitlementStopped;
+ private boolean isBillingStarted;
+ private boolean isBillingStopped;
+ private Map<String, BlockingState> perServiceBlockingState;
- final boolean shouldResumeEntitlement = isEntitlementStarted && !isEntitlementStopped && stateBefore.isBlockEntitlement() && !stateAfter.isBlockEntitlement();
- if (shouldResumeEntitlement) {
- result.add(SubscriptionEventType.RESUME_ENTITLEMENT);
- }
- final boolean shouldResumeBilling = isBillingStarted && !isBillingStopped && stateBefore.isBlockBilling() && !stateAfter.isBlockBilling();
- if (shouldResumeBilling) {
- result.add(SubscriptionEventType.RESUME_BILLING);
+ public TargetState() {
+ this.isEntitlementStarted = false;
+ this.isEntitlementStopped = false;
+ this.isBillingStarted = false;
+ this.isBillingStopped = false;
+ this.perServiceBlockingState = new HashMap<String, BlockingState>();
}
- final boolean shouldBlockEntitlement = isEntitlementStarted && !isEntitlementStopped && !stateBefore.isBlockEntitlement() && stateAfter.isBlockEntitlement();
- if (shouldBlockEntitlement) {
- result.add(SubscriptionEventType.PAUSE_ENTITLEMENT);
- }
- final boolean shouldBlockBilling = isBillingStarted && !isBillingStopped && !stateBefore.isBlockBilling() && stateAfter.isBlockBilling();
- if (shouldBlockBilling) {
- result.add(SubscriptionEventType.PAUSE_BILLING);
+ public void setEntitlementStarted() {
+ isEntitlementStarted = true;
}
- if (!shouldResumeEntitlement && !shouldBlockEntitlement && !shouldBlockEntitlement && !shouldBlockBilling && !fixedBlockingState.getService().equals(DefaultEntitlementService.ENTITLEMENT_SERVICE_NAME)) {
- result.add(SubscriptionEventType.SERVICE_STATE_CHANGE);
+ public void setEntitlementStopped() {
+ isEntitlementStopped = true;
}
- return result;
- }
- private BlockingAggregator getState() {
- final DefaultBlockingAggregator aggrBefore = new DefaultBlockingAggregator();
- for (BlockingState cur : perServiceBlockingState.values()) {
- aggrBefore.or(cur);
+ public void setBillingStarted() {
+ isBillingStarted = true;
}
- return aggrBefore;
- }
-}
-
-
-protected static final class DefaultSubscriptionEvent implements SubscriptionEvent {
-
- private final UUID id;
- private final UUID entitlementId;
- private final DateTime effectiveDate;
- private final DateTime requestedDate;
- private final SubscriptionEventType eventType;
- private final boolean isBlockingEntitlement;
- private final boolean isBlockingBilling;
- private final String serviceName;
- private final String serviceStateName;
- private final Product prevProduct;
- private final Plan prevPlan;
- private final PlanPhase prevPlanPhase;
- private final PriceList prevPriceList;
- private final BillingPeriod prevBillingPeriod;
- private final Product nextProduct;
- private final Plan nextPlan;
- private final PlanPhase nextPlanPhase;
- private final PriceList nextPriceList;
- private final BillingPeriod nextBillingPeriod;
- private final DateTime createdDate;
- private final DateTimeZone accountTimeZone;
-
-
- public DefaultSubscriptionEvent(final UUID id,
- final UUID entitlementId,
- final DateTime effectiveDate,
- final DateTime requestedDate,
- final SubscriptionEventType eventType,
- final boolean blockingEntitlement,
- final boolean blockingBilling,
- final String serviceName,
- final String serviceStateName,
- final Product prevProduct,
- final Plan prevPlan,
- final PlanPhase prevPlanPhase,
- final PriceList prevPriceList,
- final BillingPeriod prevBillingPeriod,
- final Product nextProduct,
- final Plan nextPlan,
- final PlanPhase nextPlanPhase,
- final PriceList nextPriceList,
- final BillingPeriod nextBillingPeriod,
- final DateTime createDate,
- final DateTimeZone accountTimeZone) {
- this.id = id;
- this.entitlementId = entitlementId;
- this.effectiveDate = effectiveDate;
- this.requestedDate = requestedDate;
- this.eventType = eventType;
- this.isBlockingEntitlement = blockingEntitlement;
- this.isBlockingBilling = blockingBilling;
- this.serviceName = serviceName;
- this.serviceStateName = serviceStateName;
- this.prevProduct = prevProduct;
- this.prevPlan = prevPlan;
- this.prevPlanPhase = prevPlanPhase;
- this.prevPriceList = prevPriceList;
- this.prevBillingPeriod = prevBillingPeriod;
- this.nextProduct = nextProduct;
- this.nextPlan = nextPlan;
- this.nextPlanPhase = nextPlanPhase;
- this.nextPriceList = nextPriceList;
- this.nextBillingPeriod = nextBillingPeriod;
- this.createdDate = createDate;
- this.accountTimeZone = accountTimeZone;
- }
-
- private DefaultSubscriptionEvent(DefaultSubscriptionEvent copy, SubscriptionEventType newEventType) {
- this(copy.getId(),
- copy.getEntitlementId(),
- copy.getEffectiveDateTime(),
- copy.getRequestedDateTime(),
- newEventType,
- copy.isBlockedEntitlement(),
- copy.isBlockedBilling(),
- copy.getServiceName(),
- copy.getServiceStateName(),
- copy.getPrevProduct(),
- copy.getPrevPlan(),
- copy.getPrevPhase(),
- copy.getPrevPriceList(),
- copy.getPrevBillingPeriod(),
- copy.getNextProduct(),
- copy.getNextPlan(),
- copy.getNextPhase(),
- copy.getNextPriceList(),
- copy.getNextBillingPeriod(),
- copy.getCreatedDate(),
- copy.getAccountTimeZone());
- }
-
- public DateTimeZone getAccountTimeZone() {
- return accountTimeZone;
- }
-
- public DateTime getEffectiveDateTime() {
- return effectiveDate;
- }
-
- public DateTime getRequestedDateTime() {
- return requestedDate;
- }
-
- @Override
- public UUID getId() {
- return id;
- }
-
- @Override
- public UUID getEntitlementId() {
- return entitlementId;
- }
- @Override
- public LocalDate getEffectiveDate() {
- return effectiveDate != null ? new LocalDate(effectiveDate, accountTimeZone) : null;
- }
-
- @Override
- public LocalDate getRequestedDate() {
- return requestedDate != null ? new LocalDate(requestedDate, accountTimeZone) : null;
- }
-
- @Override
- public SubscriptionEventType getSubscriptionEventType() {
- return eventType;
- }
-
- @Override
- public boolean isBlockedBilling() {
- return isBlockingBilling;
- }
-
- @Override
- public boolean isBlockedEntitlement() {
- return isBlockingEntitlement;
- }
-
- @Override
- public String getServiceName() {
- return serviceName;
- }
-
- @Override
- public String getServiceStateName() {
- return serviceStateName;
- }
-
- @Override
- public Product getPrevProduct() {
- return prevProduct;
- }
-
- @Override
- public Plan getPrevPlan() {
- return prevPlan;
- }
-
- @Override
- public PlanPhase getPrevPhase() {
- return prevPlanPhase;
- }
-
- @Override
- public PriceList getPrevPriceList() {
- return prevPriceList;
- }
-
- @Override
- public BillingPeriod getPrevBillingPeriod() {
- return prevBillingPeriod;
- }
-
- @Override
- public Product getNextProduct() {
- return nextProduct;
- }
+ public void setBillingStopped() {
+ isBillingStopped = true;
+ }
- @Override
- public Plan getNextPlan() {
- return nextPlan;
- }
+ public void addEntitlementEvent(final SubscriptionEvent e) {
+ final BlockingState converted = new DefaultBlockingState(e.getEntitlementId(), BlockingStateType.SUBSCRIPTION,
+ e.getServiceStateName(), e.getServiceName(), false, e.isBlockedEntitlement(), e.isBlockedBilling(),
+ ((DefaultSubscriptionEvent) e).getEffectiveDateTime());
+ perServiceBlockingState.put(converted.getService(), converted);
+ }
- @Override
- public PlanPhase getNextPhase() {
- return nextPlanPhase;
- }
+ //
+ // From the current state of that subscription, compute the effect of the new state based on the incoming blockingState event
+ //
+ private List<SubscriptionEventType> addStateAndReturnEventTypes(final BlockingState bs) {
+ // Turn off isBlockedEntitlement and isBlockedBilling if there was not start event
+ final BlockingState fixedBlockingState = new DefaultBlockingState(bs.getBlockedId(),
+ bs.getType(),
+ bs.getStateName(),
+ bs.getService(),
+ bs.isBlockChange(),
+ (bs.isBlockEntitlement() && isEntitlementStarted && !isEntitlementStopped),
+ (bs.isBlockBilling() && isBillingStarted && !isBillingStopped),
+ bs.getEffectiveDate());
+
+ final List<SubscriptionEventType> result = new ArrayList<SubscriptionEventType>(4);
+ if (fixedBlockingState.getStateName().equals(DefaultEntitlementApi.ENT_STATE_CANCELLED)) {
+ isEntitlementStopped = true;
+ result.add(SubscriptionEventType.STOP_ENTITLEMENT);
+ return result;
+ }
- @Override
- public PriceList getNextPriceList() {
- return nextPriceList;
- }
+ //
+ // We look at the effect of the incoming event for the specific service, and then recompute the state after so we can compare if anything has changed
+ // across all services
+ //
+ final BlockingAggregator stateBefore = getState();
+ if (DefaultEntitlementService.ENTITLEMENT_SERVICE_NAME.equals(fixedBlockingState.getService())) {
+ // Some blocking states will be added as entitlement-service and billing-service via addEntitlementEvent
+ // (see above). Because of it, we need to multiplex entitlement events here.
+ // TODO - this is magic and fragile. We should revisit how we create this state machine.
+ perServiceBlockingState.put(DefaultEntitlementService.ENTITLEMENT_SERVICE_NAME, fixedBlockingState);
+ perServiceBlockingState.put(DefaultSubscriptionBundleTimeline.BILLING_SERVICE_NAME, fixedBlockingState);
+ } else {
+ perServiceBlockingState.put(fixedBlockingState.getService(), fixedBlockingState);
+ }
+ final BlockingAggregator stateAfter = getState();
- @Override
- public BillingPeriod getNextBillingPeriod() {
- return nextBillingPeriod;
- }
+ final boolean shouldResumeEntitlement = isEntitlementStarted && !isEntitlementStopped && stateBefore.isBlockEntitlement() && !stateAfter.isBlockEntitlement();
+ if (shouldResumeEntitlement) {
+ result.add(SubscriptionEventType.RESUME_ENTITLEMENT);
+ }
+ final boolean shouldResumeBilling = isBillingStarted && !isBillingStopped && stateBefore.isBlockBilling() && !stateAfter.isBlockBilling();
+ if (shouldResumeBilling) {
+ result.add(SubscriptionEventType.RESUME_BILLING);
+ }
- public DateTime getCreatedDate() {
- return createdDate;
- }
+ final boolean shouldBlockEntitlement = isEntitlementStarted && !isEntitlementStopped && !stateBefore.isBlockEntitlement() && stateAfter.isBlockEntitlement();
+ if (shouldBlockEntitlement) {
+ result.add(SubscriptionEventType.PAUSE_ENTITLEMENT);
+ }
+ final boolean shouldBlockBilling = isBillingStarted && !isBillingStopped && !stateBefore.isBlockBilling() && stateAfter.isBlockBilling();
+ if (shouldBlockBilling) {
+ result.add(SubscriptionEventType.PAUSE_BILLING);
+ }
- @Override
- public boolean equals(final Object o) {
- if (this == o) {
- return true;
- }
- if (o == null || getClass() != o.getClass()) {
- return false;
+ if (!shouldResumeEntitlement && !shouldBlockEntitlement && !shouldBlockEntitlement && !shouldBlockBilling && !fixedBlockingState.getService().equals(DefaultEntitlementService.ENTITLEMENT_SERVICE_NAME)) {
+ result.add(SubscriptionEventType.SERVICE_STATE_CHANGE);
+ }
+ return result;
}
- final DefaultSubscriptionEvent that = (DefaultSubscriptionEvent) o;
-
- if (isBlockingBilling != that.isBlockingBilling) {
- return false;
- }
- if (isBlockingEntitlement != that.isBlockingEntitlement) {
- return false;
- }
- if (createdDate != null ? !createdDate.equals(that.createdDate) : that.createdDate != null) {
- return false;
- }
- if (effectiveDate != null ? !effectiveDate.equals(that.effectiveDate) : that.effectiveDate != null) {
- return false;
- }
- if (entitlementId != null ? !entitlementId.equals(that.entitlementId) : that.entitlementId != null) {
- return false;
- }
- if (eventType != that.eventType) {
- return false;
- }
- if (id != null ? !id.equals(that.id) : that.id != null) {
- return false;
- }
- if (nextBillingPeriod != that.nextBillingPeriod) {
- return false;
- }
- if (nextPlan != null ? !nextPlan.equals(that.nextPlan) : that.nextPlan != null) {
- return false;
- }
- if (nextPlanPhase != null ? !nextPlanPhase.equals(that.nextPlanPhase) : that.nextPlanPhase != null) {
- return false;
- }
- if (nextPriceList != null ? !nextPriceList.equals(that.nextPriceList) : that.nextPriceList != null) {
- return false;
- }
- if (nextProduct != null ? !nextProduct.equals(that.nextProduct) : that.nextProduct != null) {
- return false;
- }
- if (prevBillingPeriod != that.prevBillingPeriod) {
- return false;
- }
- if (prevPlan != null ? !prevPlan.equals(that.prevPlan) : that.prevPlan != null) {
- return false;
- }
- if (prevPlanPhase != null ? !prevPlanPhase.equals(that.prevPlanPhase) : that.prevPlanPhase != null) {
- return false;
- }
- if (prevPriceList != null ? !prevPriceList.equals(that.prevPriceList) : that.prevPriceList != null) {
- return false;
- }
- if (prevProduct != null ? !prevProduct.equals(that.prevProduct) : that.prevProduct != null) {
- return false;
- }
- if (requestedDate != null ? !requestedDate.equals(that.requestedDate) : that.requestedDate != null) {
- return false;
- }
- if (serviceName != null ? !serviceName.equals(that.serviceName) : that.serviceName != null) {
- return false;
- }
- if (serviceStateName != null ? !serviceStateName.equals(that.serviceStateName) : that.serviceStateName != null) {
- return false;
+ private BlockingAggregator getState() {
+ final DefaultBlockingAggregator aggrBefore = new DefaultBlockingAggregator();
+ for (final BlockingState cur : perServiceBlockingState.values()) {
+ aggrBefore.or(cur);
+ }
+ return aggrBefore;
}
-
- return true;
}
-
- @Override
- public int hashCode() {
- int result = id != null ? id.hashCode() : 0;
- result = 31 * result + (entitlementId != null ? entitlementId.hashCode() : 0);
- result = 31 * result + (effectiveDate != null ? effectiveDate.hashCode() : 0);
- result = 31 * result + (requestedDate != null ? requestedDate.hashCode() : 0);
- result = 31 * result + (eventType != null ? eventType.hashCode() : 0);
- result = 31 * result + (isBlockingEntitlement ? 1 : 0);
- result = 31 * result + (isBlockingBilling ? 1 : 0);
- result = 31 * result + (serviceName != null ? serviceName.hashCode() : 0);
- result = 31 * result + (serviceStateName != null ? serviceStateName.hashCode() : 0);
- result = 31 * result + (prevProduct != null ? prevProduct.hashCode() : 0);
- result = 31 * result + (prevPlan != null ? prevPlan.hashCode() : 0);
- result = 31 * result + (prevPlanPhase != null ? prevPlanPhase.hashCode() : 0);
- result = 31 * result + (prevPriceList != null ? prevPriceList.hashCode() : 0);
- result = 31 * result + (prevBillingPeriod != null ? prevBillingPeriod.hashCode() : 0);
- result = 31 * result + (nextProduct != null ? nextProduct.hashCode() : 0);
- result = 31 * result + (nextPlan != null ? nextPlan.hashCode() : 0);
- result = 31 * result + (nextPlanPhase != null ? nextPlanPhase.hashCode() : 0);
- result = 31 * result + (nextPriceList != null ? nextPriceList.hashCode() : 0);
- result = 31 * result + (nextBillingPeriod != null ? nextBillingPeriod.hashCode() : 0);
- result = 31 * result + (createdDate != null ? createdDate.hashCode() : 0);
- return result;
- }
-}
}
diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/api/DefaultSubscriptionEvent.java b/entitlement/src/main/java/com/ning/billing/entitlement/api/DefaultSubscriptionEvent.java
new file mode 100644
index 0000000..fbd7ab6
--- /dev/null
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/api/DefaultSubscriptionEvent.java
@@ -0,0 +1,396 @@
+/*
+ * Copyright 2010-2013 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.ning.billing.entitlement.api;
+
+import java.util.UUID;
+
+import org.joda.time.DateTime;
+import org.joda.time.DateTimeZone;
+import org.joda.time.LocalDate;
+
+import com.ning.billing.catalog.api.BillingPeriod;
+import com.ning.billing.catalog.api.Plan;
+import com.ning.billing.catalog.api.PlanPhase;
+import com.ning.billing.catalog.api.PriceList;
+import com.ning.billing.catalog.api.Product;
+
+public class DefaultSubscriptionEvent implements SubscriptionEvent {
+
+ private final UUID id;
+ private final UUID entitlementId;
+ private final DateTime effectiveDate;
+ private final DateTime requestedDate;
+ private final SubscriptionEventType eventType;
+ private final boolean isBlockingEntitlement;
+ private final boolean isBlockingBilling;
+ private final String serviceName;
+ private final String serviceStateName;
+ private final Product prevProduct;
+ private final Plan prevPlan;
+ private final PlanPhase prevPlanPhase;
+ private final PriceList prevPriceList;
+ private final BillingPeriod prevBillingPeriod;
+ private final Product nextProduct;
+ private final Plan nextPlan;
+ private final PlanPhase nextPlanPhase;
+ private final PriceList nextPriceList;
+ private final BillingPeriod nextBillingPeriod;
+ private final DateTime createdDate;
+ private final DateTimeZone accountTimeZone;
+
+ public DefaultSubscriptionEvent(final UUID id,
+ final UUID entitlementId,
+ final DateTime effectiveDate,
+ final DateTime requestedDate,
+ final SubscriptionEventType eventType,
+ final boolean blockingEntitlement,
+ final boolean blockingBilling,
+ final String serviceName,
+ final String serviceStateName,
+ final Product prevProduct,
+ final Plan prevPlan,
+ final PlanPhase prevPlanPhase,
+ final PriceList prevPriceList,
+ final BillingPeriod prevBillingPeriod,
+ final Product nextProduct,
+ final Plan nextPlan,
+ final PlanPhase nextPlanPhase,
+ final PriceList nextPriceList,
+ final BillingPeriod nextBillingPeriod,
+ final DateTime createDate,
+ final DateTimeZone accountTimeZone) {
+ this.id = id;
+ this.entitlementId = entitlementId;
+ this.effectiveDate = effectiveDate;
+ this.requestedDate = requestedDate;
+ this.eventType = eventType;
+ this.isBlockingEntitlement = blockingEntitlement;
+ this.isBlockingBilling = blockingBilling;
+ this.serviceName = serviceName;
+ this.serviceStateName = serviceStateName;
+ this.prevProduct = prevProduct;
+ this.prevPlan = prevPlan;
+ this.prevPlanPhase = prevPlanPhase;
+ this.prevPriceList = prevPriceList;
+ this.prevBillingPeriod = prevBillingPeriod;
+ this.nextProduct = nextProduct;
+ this.nextPlan = nextPlan;
+ this.nextPlanPhase = nextPlanPhase;
+ this.nextPriceList = nextPriceList;
+ this.nextBillingPeriod = nextBillingPeriod;
+ this.createdDate = createDate;
+ this.accountTimeZone = accountTimeZone;
+ }
+
+ public DefaultSubscriptionEvent(final DefaultSubscriptionEvent copy, final SubscriptionEventType newEventType) {
+ this(copy.getId(),
+ copy.getEntitlementId(),
+ copy.getEffectiveDateTime(),
+ copy.getRequestedDateTime(),
+ newEventType,
+ copy.isBlockedEntitlement(),
+ copy.isBlockedBilling(),
+ copy.getServiceName(),
+ copy.getServiceStateName(),
+ copy.getPrevProduct(),
+ copy.getPrevPlan(),
+ copy.getPrevPhase(),
+ copy.getPrevPriceList(),
+ copy.getPrevBillingPeriod(),
+ copy.getNextProduct(),
+ copy.getNextPlan(),
+ copy.getNextPhase(),
+ copy.getNextPriceList(),
+ copy.getNextBillingPeriod(),
+ copy.getCreatedDate(),
+ copy.getAccountTimeZone());
+ }
+
+ public DateTimeZone getAccountTimeZone() {
+ return accountTimeZone;
+ }
+
+ public DateTime getEffectiveDateTime() {
+ return effectiveDate;
+ }
+
+ public DateTime getRequestedDateTime() {
+ return requestedDate;
+ }
+
+ @Override
+ public UUID getId() {
+ return id;
+ }
+
+ @Override
+ public UUID getEntitlementId() {
+ return entitlementId;
+ }
+
+ @Override
+ public LocalDate getEffectiveDate() {
+ return effectiveDate != null ? new LocalDate(effectiveDate, accountTimeZone) : null;
+ }
+
+ @Override
+ public LocalDate getRequestedDate() {
+ return requestedDate != null ? new LocalDate(requestedDate, accountTimeZone) : null;
+ }
+
+ @Override
+ public SubscriptionEventType getSubscriptionEventType() {
+ return eventType;
+ }
+
+ @Override
+ public boolean isBlockedBilling() {
+ return isBlockingBilling;
+ }
+
+ @Override
+ public boolean isBlockedEntitlement() {
+ return isBlockingEntitlement;
+ }
+
+ @Override
+ public String getServiceName() {
+ return serviceName;
+ }
+
+ @Override
+ public String getServiceStateName() {
+ return serviceStateName;
+ }
+
+ @Override
+ public Product getPrevProduct() {
+ return prevProduct;
+ }
+
+ @Override
+ public Plan getPrevPlan() {
+ return prevPlan;
+ }
+
+ @Override
+ public PlanPhase getPrevPhase() {
+ return prevPlanPhase;
+ }
+
+ @Override
+ public PriceList getPrevPriceList() {
+ return prevPriceList;
+ }
+
+ @Override
+ public BillingPeriod getPrevBillingPeriod() {
+ return prevBillingPeriod;
+ }
+
+ @Override
+ public Product getNextProduct() {
+ return nextProduct;
+ }
+
+ @Override
+ public Plan getNextPlan() {
+ return nextPlan;
+ }
+
+ @Override
+ public PlanPhase getNextPhase() {
+ return nextPlanPhase;
+ }
+
+ @Override
+ public PriceList getNextPriceList() {
+ return nextPriceList;
+ }
+
+ @Override
+ public BillingPeriod getNextBillingPeriod() {
+ return nextBillingPeriod;
+ }
+
+ public DateTime getCreatedDate() {
+ return createdDate;
+ }
+
+ @Override
+ public boolean equals(final Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+
+ final DefaultSubscriptionEvent that = (DefaultSubscriptionEvent) o;
+
+ if (isBlockingBilling != that.isBlockingBilling) {
+ return false;
+ }
+ if (isBlockingEntitlement != that.isBlockingEntitlement) {
+ return false;
+ }
+ if (createdDate != null ? !createdDate.equals(that.createdDate) : that.createdDate != null) {
+ return false;
+ }
+ if (effectiveDate != null ? !effectiveDate.equals(that.effectiveDate) : that.effectiveDate != null) {
+ return false;
+ }
+ if (entitlementId != null ? !entitlementId.equals(that.entitlementId) : that.entitlementId != null) {
+ return false;
+ }
+ if (eventType != that.eventType) {
+ return false;
+ }
+ if (id != null ? !id.equals(that.id) : that.id != null) {
+ return false;
+ }
+ if (nextBillingPeriod != that.nextBillingPeriod) {
+ return false;
+ }
+ if (nextPlan != null ? !nextPlan.equals(that.nextPlan) : that.nextPlan != null) {
+ return false;
+ }
+ if (nextPlanPhase != null ? !nextPlanPhase.equals(that.nextPlanPhase) : that.nextPlanPhase != null) {
+ return false;
+ }
+ if (nextPriceList != null ? !nextPriceList.equals(that.nextPriceList) : that.nextPriceList != null) {
+ return false;
+ }
+ if (nextProduct != null ? !nextProduct.equals(that.nextProduct) : that.nextProduct != null) {
+ return false;
+ }
+ if (prevBillingPeriod != that.prevBillingPeriod) {
+ return false;
+ }
+ if (prevPlan != null ? !prevPlan.equals(that.prevPlan) : that.prevPlan != null) {
+ return false;
+ }
+ if (prevPlanPhase != null ? !prevPlanPhase.equals(that.prevPlanPhase) : that.prevPlanPhase != null) {
+ return false;
+ }
+ if (prevPriceList != null ? !prevPriceList.equals(that.prevPriceList) : that.prevPriceList != null) {
+ return false;
+ }
+ if (prevProduct != null ? !prevProduct.equals(that.prevProduct) : that.prevProduct != null) {
+ return false;
+ }
+ if (requestedDate != null ? !requestedDate.equals(that.requestedDate) : that.requestedDate != null) {
+ return false;
+ }
+ if (serviceName != null ? !serviceName.equals(that.serviceName) : that.serviceName != null) {
+ return false;
+ }
+ if (serviceStateName != null ? !serviceStateName.equals(that.serviceStateName) : that.serviceStateName != null) {
+ return false;
+ }
+
+ return true;
+ }
+
+ public boolean overlaps(final DefaultSubscriptionEvent that) {
+ if (this == that) {
+ return true;
+ }
+ if (that == null || getClass() != that.getClass()) {
+ return false;
+ }
+
+ if (isBlockingBilling != that.isBlockingBilling) {
+ return false;
+ }
+ if (isBlockingEntitlement != that.isBlockingEntitlement) {
+ return false;
+ }
+ if (effectiveDate != null ? effectiveDate.compareTo(that.effectiveDate) < 0 : that.effectiveDate != null) {
+ return false;
+ }
+ if (entitlementId != null ? !entitlementId.equals(that.entitlementId) : that.entitlementId != null) {
+ return false;
+ }
+ if (eventType != that.eventType) {
+ return false;
+ }
+ if (nextBillingPeriod != that.nextBillingPeriod) {
+ return false;
+ }
+ if (nextPlan != null ? !nextPlan.equals(that.nextPlan) : that.nextPlan != null) {
+ return false;
+ }
+ if (nextPlanPhase != null ? !nextPlanPhase.equals(that.nextPlanPhase) : that.nextPlanPhase != null) {
+ return false;
+ }
+ if (nextPriceList != null ? !nextPriceList.equals(that.nextPriceList) : that.nextPriceList != null) {
+ return false;
+ }
+ if (nextProduct != null ? !nextProduct.equals(that.nextProduct) : that.nextProduct != null) {
+ return false;
+ }
+ if (prevBillingPeriod != that.prevBillingPeriod) {
+ return false;
+ }
+ if (prevPlan != null ? !prevPlan.equals(that.prevPlan) : that.prevPlan != null) {
+ return false;
+ }
+ if (prevPlanPhase != null ? !prevPlanPhase.equals(that.prevPlanPhase) : that.prevPlanPhase != null) {
+ return false;
+ }
+ if (prevPriceList != null ? !prevPriceList.equals(that.prevPriceList) : that.prevPriceList != null) {
+ return false;
+ }
+ if (prevProduct != null ? !prevProduct.equals(that.prevProduct) : that.prevProduct != null) {
+ return false;
+ }
+ if (serviceName != null ? !serviceName.equals(that.serviceName) : that.serviceName != null) {
+ return false;
+ }
+ if (serviceStateName != null ? !serviceStateName.equals(that.serviceStateName) : that.serviceStateName != null) {
+ return false;
+ }
+
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = id != null ? id.hashCode() : 0;
+ result = 31 * result + (entitlementId != null ? entitlementId.hashCode() : 0);
+ result = 31 * result + (effectiveDate != null ? effectiveDate.hashCode() : 0);
+ result = 31 * result + (requestedDate != null ? requestedDate.hashCode() : 0);
+ result = 31 * result + (eventType != null ? eventType.hashCode() : 0);
+ result = 31 * result + (isBlockingEntitlement ? 1 : 0);
+ result = 31 * result + (isBlockingBilling ? 1 : 0);
+ result = 31 * result + (serviceName != null ? serviceName.hashCode() : 0);
+ result = 31 * result + (serviceStateName != null ? serviceStateName.hashCode() : 0);
+ result = 31 * result + (prevProduct != null ? prevProduct.hashCode() : 0);
+ result = 31 * result + (prevPlan != null ? prevPlan.hashCode() : 0);
+ result = 31 * result + (prevPlanPhase != null ? prevPlanPhase.hashCode() : 0);
+ result = 31 * result + (prevPriceList != null ? prevPriceList.hashCode() : 0);
+ result = 31 * result + (prevBillingPeriod != null ? prevBillingPeriod.hashCode() : 0);
+ result = 31 * result + (nextProduct != null ? nextProduct.hashCode() : 0);
+ result = 31 * result + (nextPlan != null ? nextPlan.hashCode() : 0);
+ result = 31 * result + (nextPlanPhase != null ? nextPlanPhase.hashCode() : 0);
+ result = 31 * result + (nextPriceList != null ? nextPriceList.hashCode() : 0);
+ result = 31 * result + (nextBillingPeriod != null ? nextBillingPeriod.hashCode() : 0);
+ result = 31 * result + (createdDate != null ? createdDate.hashCode() : 0);
+ return result;
+ }
+}
diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/api/svcs/DefaultAccountEntitlements.java b/entitlement/src/main/java/com/ning/billing/entitlement/api/svcs/DefaultAccountEntitlements.java
new file mode 100644
index 0000000..72898be
--- /dev/null
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/api/svcs/DefaultAccountEntitlements.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright 2010-2013 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF 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.svcs;
+
+import java.util.Collection;
+import java.util.Map;
+import java.util.UUID;
+
+import com.ning.billing.account.api.Account;
+import com.ning.billing.entitlement.AccountEntitlements;
+import com.ning.billing.entitlement.AccountEventsStreams;
+import com.ning.billing.entitlement.api.Entitlement;
+import com.ning.billing.subscription.api.user.SubscriptionBaseBundle;
+
+public class DefaultAccountEntitlements implements AccountEntitlements {
+
+ private final AccountEventsStreams accountEventsStreams;
+ private final Map<UUID, Collection<Entitlement>> entitlements;
+
+ public DefaultAccountEntitlements(final AccountEventsStreams accountEventsStreams, final Map<UUID, Collection<Entitlement>> entitlements) {
+ this.accountEventsStreams = accountEventsStreams;
+ this.entitlements = entitlements;
+ }
+
+ @Override
+ public Account getAccount() {
+ return accountEventsStreams.getAccount();
+ }
+
+ @Override
+ public Map<UUID, SubscriptionBaseBundle> getBundles() {
+ return accountEventsStreams.getBundles();
+ }
+
+ @Override
+ public Map<UUID, Collection<Entitlement>> getEntitlements() {
+ return entitlements;
+ }
+
+ @Override
+ public String toString() {
+ final StringBuilder sb = new StringBuilder("DefaultAccountEntitlements{");
+ sb.append("accountEventsStreams=").append(accountEventsStreams);
+ sb.append(", entitlements=").append(entitlements);
+ 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 DefaultAccountEntitlements that = (DefaultAccountEntitlements) o;
+
+ if (accountEventsStreams != null ? !accountEventsStreams.equals(that.accountEventsStreams) : that.accountEventsStreams != null) {
+ return false;
+ }
+ if (entitlements != null ? !entitlements.equals(that.entitlements) : that.entitlements != null) {
+ return false;
+ }
+
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = accountEventsStreams != null ? accountEventsStreams.hashCode() : 0;
+ result = 31 * result + (entitlements != null ? entitlements.hashCode() : 0);
+ return result;
+ }
+}
diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/api/svcs/DefaultAccountEventsStreams.java b/entitlement/src/main/java/com/ning/billing/entitlement/api/svcs/DefaultAccountEventsStreams.java
new file mode 100644
index 0000000..d05df80
--- /dev/null
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/api/svcs/DefaultAccountEventsStreams.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright 2010-2013 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF 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.svcs;
+
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.UUID;
+
+import com.ning.billing.account.api.Account;
+import com.ning.billing.entitlement.AccountEventsStreams;
+import com.ning.billing.entitlement.EventsStream;
+import com.ning.billing.subscription.api.user.SubscriptionBaseBundle;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+
+public class DefaultAccountEventsStreams implements AccountEventsStreams {
+
+ private final Account account;
+ private final Map<UUID, Collection<EventsStream>> eventsStreams;
+ private final Map<UUID, SubscriptionBaseBundle> bundles = new HashMap<UUID, SubscriptionBaseBundle>();
+
+ public DefaultAccountEventsStreams(final Account account,
+ final Iterable<SubscriptionBaseBundle> bundles,
+ final Map<UUID, Collection<EventsStream>> eventsStreams) {
+ this.account = account;
+ this.eventsStreams = eventsStreams;
+ for (final SubscriptionBaseBundle baseBundle : bundles) {
+ this.bundles.put(baseBundle.getId(), baseBundle);
+ }
+ }
+
+ public DefaultAccountEventsStreams(final Account account) {
+ this(account, ImmutableList.<SubscriptionBaseBundle>of(), ImmutableMap.<UUID, Collection<EventsStream>>of());
+ }
+
+ @Override
+ public Account getAccount() {
+ return account;
+ }
+
+ @Override
+ public Map<UUID, SubscriptionBaseBundle> getBundles() {
+ return bundles;
+ }
+
+ @Override
+ public Map<UUID, Collection<EventsStream>> getEventsStreams() {
+ return eventsStreams;
+ }
+
+ @Override
+ public String toString() {
+ final StringBuilder sb = new StringBuilder("DefaultAccountEventsStreams{");
+ sb.append("account=").append(account);
+ sb.append(", eventsStreams=").append(eventsStreams);
+ sb.append(", bundles=").append(bundles);
+ 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 DefaultAccountEventsStreams that = (DefaultAccountEventsStreams) o;
+
+ if (account != null ? !account.equals(that.account) : that.account != null) {
+ return false;
+ }
+ if (bundles != null ? !bundles.equals(that.bundles) : that.bundles != null) {
+ return false;
+ }
+ if (eventsStreams != null ? !eventsStreams.equals(that.eventsStreams) : that.eventsStreams != null) {
+ return false;
+ }
+
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = account != null ? account.hashCode() : 0;
+ result = 31 * result + (eventsStreams != null ? eventsStreams.hashCode() : 0);
+ result = 31 * result + (bundles != null ? bundles.hashCode() : 0);
+ return result;
+ }
+}
diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/api/svcs/DefaultEntitlementInternalApi.java b/entitlement/src/main/java/com/ning/billing/entitlement/api/svcs/DefaultEntitlementInternalApi.java
new file mode 100644
index 0000000..f941e9b
--- /dev/null
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/api/svcs/DefaultEntitlementInternalApi.java
@@ -0,0 +1,101 @@
+/*
+ * Copyright 2010-2013 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF 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.svcs;
+
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.Map;
+import java.util.UUID;
+
+import javax.inject.Inject;
+
+import com.ning.billing.account.api.AccountInternalApi;
+import com.ning.billing.callcontext.InternalTenantContext;
+import com.ning.billing.clock.Clock;
+import com.ning.billing.entitlement.AccountEntitlements;
+import com.ning.billing.entitlement.AccountEventsStreams;
+import com.ning.billing.entitlement.EntitlementInternalApi;
+import com.ning.billing.entitlement.EventsStream;
+import com.ning.billing.entitlement.api.DefaultEntitlement;
+import com.ning.billing.entitlement.api.Entitlement;
+import com.ning.billing.entitlement.api.EntitlementApi;
+import com.ning.billing.entitlement.api.EntitlementApiException;
+import com.ning.billing.entitlement.api.EntitlementDateHelper;
+import com.ning.billing.entitlement.block.BlockingChecker;
+import com.ning.billing.entitlement.dao.BlockingStateDao;
+import com.ning.billing.entitlement.engine.core.EntitlementUtils;
+import com.ning.billing.entitlement.engine.core.EventsStreamBuilder;
+import com.ning.billing.notificationq.api.NotificationQueueService;
+import com.ning.billing.subscription.api.SubscriptionBaseInternalApi;
+import com.ning.billing.util.callcontext.InternalCallContextFactory;
+import com.ning.billing.util.callcontext.TenantContext;
+
+public class DefaultEntitlementInternalApi implements EntitlementInternalApi {
+
+ private final EntitlementApi entitlementApi;
+ private final SubscriptionBaseInternalApi subscriptionInternalApi;
+ private final Clock clock;
+ private final InternalCallContextFactory internalCallContextFactory;
+ private final BlockingChecker checker;
+ private final BlockingStateDao blockingStateDao;
+ private final EntitlementDateHelper dateHelper;
+ private final EventsStreamBuilder eventsStreamBuilder;
+ private final EntitlementUtils entitlementUtils;
+ private final NotificationQueueService notificationQueueService;
+
+ @Inject
+ public DefaultEntitlementInternalApi(final EntitlementApi entitlementApi, final InternalCallContextFactory internalCallContextFactory,
+ final SubscriptionBaseInternalApi subscriptionInternalApi,
+ final AccountInternalApi accountApi, final BlockingStateDao blockingStateDao, final Clock clock,
+ final BlockingChecker checker, final NotificationQueueService notificationQueueService,
+ final EventsStreamBuilder eventsStreamBuilder, final EntitlementUtils entitlementUtils) {
+ this.entitlementApi = entitlementApi;
+ this.internalCallContextFactory = internalCallContextFactory;
+ this.subscriptionInternalApi = subscriptionInternalApi;
+ this.clock = clock;
+ this.checker = checker;
+ this.blockingStateDao = blockingStateDao;
+ this.notificationQueueService = notificationQueueService;
+ this.eventsStreamBuilder = eventsStreamBuilder;
+ this.entitlementUtils = entitlementUtils;
+ this.dateHelper = new EntitlementDateHelper(accountApi, clock);
+ }
+
+ @Override
+ public AccountEntitlements getAllEntitlementsForAccountId(final UUID accountId, final TenantContext tenantContext) throws EntitlementApiException {
+ final InternalTenantContext context = internalCallContextFactory.createInternalTenantContext(accountId, tenantContext);
+
+ final AccountEventsStreams accountEventsStreams = eventsStreamBuilder.buildForAccount(context);
+
+ final Map<UUID, Collection<Entitlement>> entitlementsPerBundle = new HashMap<UUID, Collection<Entitlement>>();
+ for (final UUID bundleId : accountEventsStreams.getEventsStreams().keySet()) {
+ if (entitlementsPerBundle.get(bundleId) == null) {
+ entitlementsPerBundle.put(bundleId, new LinkedList<Entitlement>());
+ }
+
+ for (final EventsStream eventsStream : accountEventsStreams.getEventsStreams().get(bundleId)) {
+ final Entitlement entitlement = new DefaultEntitlement(eventsStream, eventsStreamBuilder, entitlementApi,
+ blockingStateDao, subscriptionInternalApi, checker, notificationQueueService,
+ entitlementUtils, dateHelper, clock, internalCallContextFactory);
+ entitlementsPerBundle.get(bundleId).add(entitlement);
+ }
+ }
+
+ return new DefaultAccountEntitlements(accountEventsStreams, entitlementsPerBundle);
+ }
+}
diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/api/svcs/DefaultInternalBlockingApi.java b/entitlement/src/main/java/com/ning/billing/entitlement/api/svcs/DefaultInternalBlockingApi.java
index 22f7434..2ca84c0 100644
--- a/entitlement/src/main/java/com/ning/billing/entitlement/api/svcs/DefaultInternalBlockingApi.java
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/api/svcs/DefaultInternalBlockingApi.java
@@ -55,8 +55,8 @@ public class DefaultInternalBlockingApi implements BlockingInternalApi {
}
@Override
- public List<BlockingState> getBlockingAll(final UUID overdueableId, final BlockingStateType blockingStateType, final InternalTenantContext context) {
- return dao.getBlockingAll(overdueableId, blockingStateType, context);
+ public List<BlockingState> getBlockingAllForAccount(final InternalTenantContext context) {
+ return dao.getBlockingAllForAccountRecordId(context);
}
@Override
diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/dao/BlockingStateDao.java b/entitlement/src/main/java/com/ning/billing/entitlement/dao/BlockingStateDao.java
index aaa286b..3dd9cdd 100644
--- a/entitlement/src/main/java/com/ning/billing/entitlement/dao/BlockingStateDao.java
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/dao/BlockingStateDao.java
@@ -51,27 +51,6 @@ public interface BlockingStateDao extends EntityDao<BlockingStateModelDao, Block
public List<BlockingState> getBlockingState(UUID blockableId, BlockingStateType blockingStateType, InternalTenantContext context);
/**
- * Returns the state history for that specific service
- *
- * @param blockableId id of the blockable object
- * @param blockingStateType blockable object type
- * @param serviceName name of the service
- * @param context call context
- * @return list of blocking states for that blockable object and service
- */
- public List<BlockingState> getBlockingHistoryForService(UUID blockableId, BlockingStateType blockingStateType, String serviceName, InternalTenantContext context);
-
- /**
- * Return all the events (past and future) across all services
- *
- * @param blockableId id of the blockable object
- * @param blockingStateType blockable object type
- * @param context call context
- * @return list of blocking states for that blockable object
- */
- public List<BlockingState> getBlockingAll(UUID blockableId, BlockingStateType blockingStateType, InternalTenantContext context);
-
- /**
* Return all events (past and future) across all services) for a given callcontext (account_record_id)
*
* @param context call context
diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/dao/BlockingStateModelDao.java b/entitlement/src/main/java/com/ning/billing/entitlement/dao/BlockingStateModelDao.java
index 7727fa5..7ad96b8 100644
--- a/entitlement/src/main/java/com/ning/billing/entitlement/dao/BlockingStateModelDao.java
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/dao/BlockingStateModelDao.java
@@ -30,14 +30,14 @@ import com.ning.billing.util.entity.dao.EntityModelDao;
public class BlockingStateModelDao extends EntityBase implements EntityModelDao<BlockingState>{
- private final UUID blockableId;
- private final BlockingStateType type;
- private final String state;
- private final String service;
- private final Boolean blockChange;
- private final Boolean blockEntitlement;
- private final Boolean blockBilling;
- private final DateTime effectiveDate;
+ private UUID blockableId;
+ private BlockingStateType type;
+ private String state;
+ private String service;
+ private Boolean blockChange;
+ private Boolean blockEntitlement;
+ private Boolean blockBilling;
+ private DateTime effectiveDate;
private boolean isActive;
public BlockingStateModelDao(final UUID id, final UUID blockableId, final BlockingStateType blockingStateType, final String state, final String service, final Boolean blockChange, final Boolean blockEntitlement,
@@ -95,6 +95,42 @@ public class BlockingStateModelDao extends EntityBase implements EntityModelDao<
return effectiveDate;
}
+ public void setBlockableId(final UUID blockableId) {
+ this.blockableId = blockableId;
+ }
+
+ public void setType(final BlockingStateType type) {
+ this.type = type;
+ }
+
+ public void setState(final String state) {
+ this.state = state;
+ }
+
+ public void setService(final String service) {
+ this.service = service;
+ }
+
+ public void setBlockChange(final Boolean blockChange) {
+ this.blockChange = blockChange;
+ }
+
+ public void setBlockEntitlement(final Boolean blockEntitlement) {
+ this.blockEntitlement = blockEntitlement;
+ }
+
+ public void setBlockBilling(final Boolean blockBilling) {
+ this.blockBilling = blockBilling;
+ }
+
+ public void setEffectiveDate(final DateTime effectiveDate) {
+ this.effectiveDate = effectiveDate;
+ }
+
+ public void setIsActive(final boolean isActive) {
+ this.isActive = isActive;
+ }
+
// TODO required for jdbi binder
public boolean getIsActive() {
return isActive;
diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/dao/BlockingStateSqlDao.java b/entitlement/src/main/java/com/ning/billing/entitlement/dao/BlockingStateSqlDao.java
index e95ebe9..c69758b 100644
--- a/entitlement/src/main/java/com/ning/billing/entitlement/dao/BlockingStateSqlDao.java
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/dao/BlockingStateSqlDao.java
@@ -57,23 +57,16 @@ public interface BlockingStateSqlDao extends EntitySqlDao<BlockingStateModelDao,
@Bind("effectiveDate") Date effectiveDate,
@BindBean final InternalTenantContext context);
-
@SqlQuery
public abstract List<BlockingStateModelDao> getBlockingHistoryForService(@Bind("blockableId") UUID blockableId,
@Bind("service") String serviceName,
@BindBean final InternalTenantContext context);
- @SqlQuery
- public abstract List<BlockingStateModelDao> getBlockingAll(@Bind("blockableId") UUID blockableId,
- @BindBean final InternalTenantContext context);
-
-
@SqlUpdate
@Audited(ChangeType.UPDATE)
public void unactiveEvent(@Bind("id") String id,
@BindBean final InternalCallContext context);
-
public class BlockingHistorySqlMapper extends MapperBase implements ResultSetMapper<BlockingStateModelDao> {
@Override
diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/dao/DefaultBlockingStateDao.java b/entitlement/src/main/java/com/ning/billing/entitlement/dao/DefaultBlockingStateDao.java
index 1292cbf..7f21843 100644
--- a/entitlement/src/main/java/com/ning/billing/entitlement/dao/DefaultBlockingStateDao.java
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/dao/DefaultBlockingStateDao.java
@@ -111,44 +111,6 @@ public class DefaultBlockingStateDao extends EntityDaoBase<BlockingStateModelDao
}
@Override
- public List<BlockingState> getBlockingHistoryForService(final UUID blockableId, final BlockingStateType blockingStateType, final String serviceName, final InternalTenantContext context) {
- return transactionalSqlDao.execute(new EntitySqlDaoTransactionWrapper<List<BlockingState>>() {
- @Override
- public List<BlockingState> inTransaction(final EntitySqlDaoWrapperFactory<EntitySqlDao> entitySqlDaoWrapperFactory) throws Exception {
- final BlockingStateSqlDao sqlDao = entitySqlDaoWrapperFactory.become(BlockingStateSqlDao.class);
- final List<BlockingStateModelDao> models = sqlDao.getBlockingHistoryForService(blockableId, serviceName, context);
- final Collection<BlockingStateModelDao> modelsFiltered = filterBlockingStates(models, blockingStateType);
- return new ArrayList<BlockingState>(Collections2.transform(modelsFiltered,
- new Function<BlockingStateModelDao, BlockingState>() {
- @Override
- public BlockingState apply(@Nullable final BlockingStateModelDao src) {
- return BlockingStateModelDao.toBlockingState(src);
- }
- }));
- }
- });
- }
-
- @Override
- public List<BlockingState> getBlockingAll(final UUID blockableId, final BlockingStateType blockingStateType, final InternalTenantContext context) {
- return transactionalSqlDao.execute(new EntitySqlDaoTransactionWrapper<List<BlockingState>>() {
- @Override
- public List<BlockingState> inTransaction(final EntitySqlDaoWrapperFactory<EntitySqlDao> entitySqlDaoWrapperFactory) throws Exception {
- final BlockingStateSqlDao sqlDao = entitySqlDaoWrapperFactory.become(BlockingStateSqlDao.class);
- final List<BlockingStateModelDao> models = sqlDao.getBlockingAll(blockableId, context);
- final Collection<BlockingStateModelDao> modelsFiltered = filterBlockingStates(models, blockingStateType);
- return new ArrayList<BlockingState>(Collections2.transform(modelsFiltered,
- new Function<BlockingStateModelDao, BlockingState>() {
- @Override
- public BlockingState apply(@Nullable final BlockingStateModelDao src) {
- return BlockingStateModelDao.toBlockingState(src);
- }
- }));
- }
- });
- }
-
- @Override
public List<BlockingState> getBlockingAllForAccountRecordId(final InternalTenantContext context) {
return transactionalSqlDao.execute(new EntitySqlDaoTransactionWrapper<List<BlockingState>>() {
@Override
diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/dao/OptimizedProxyBlockingStateDao.java b/entitlement/src/main/java/com/ning/billing/entitlement/dao/OptimizedProxyBlockingStateDao.java
new file mode 100644
index 0000000..4f71fba
--- /dev/null
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/dao/OptimizedProxyBlockingStateDao.java
@@ -0,0 +1,101 @@
+/*
+ * Copyright 2010-2013 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF 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.dao;
+
+import java.util.LinkedList;
+import java.util.List;
+
+import javax.annotation.Nullable;
+
+import org.skife.jdbi.v2.IDBI;
+
+import com.ning.billing.account.api.Account;
+import com.ning.billing.callcontext.InternalTenantContext;
+import com.ning.billing.catalog.api.ProductCategory;
+import com.ning.billing.clock.Clock;
+import com.ning.billing.entitlement.EventsStream;
+import com.ning.billing.entitlement.api.BlockingState;
+import com.ning.billing.entitlement.api.BlockingStateType;
+import com.ning.billing.entitlement.api.EntitlementApiException;
+import com.ning.billing.entitlement.engine.core.EventsStreamBuilder;
+import com.ning.billing.subscription.api.SubscriptionBase;
+import com.ning.billing.subscription.api.SubscriptionBaseInternalApi;
+import com.ning.billing.subscription.api.user.SubscriptionBaseBundle;
+import com.ning.billing.util.cache.CacheControllerDispatcher;
+import com.ning.billing.util.dao.NonEntityDao;
+
+import com.google.common.collect.ImmutableList;
+
+public class OptimizedProxyBlockingStateDao extends ProxyBlockingStateDao {
+
+ public OptimizedProxyBlockingStateDao(final EventsStreamBuilder eventsStreamBuilder, final SubscriptionBaseInternalApi subscriptionBaseInternalApi,
+ final IDBI dbi, final Clock clock, final CacheControllerDispatcher cacheControllerDispatcher,
+ final NonEntityDao nonEntityDao) {
+ super(eventsStreamBuilder, subscriptionBaseInternalApi, dbi, clock, cacheControllerDispatcher, nonEntityDao);
+ }
+
+ /**
+ * Retrieve blocking states for a given subscription
+ * <p/>
+ * If the specified subscription is not an add-on, we already have the blocking states
+ * (they are all on disk) - we simply return them and there is nothing to do.
+ * Otherwise, for add-ons, we will need to compute the blocking states not on disk.
+ * <p/>
+ * This is a special method for EventsStreamBuilder to save some DAO calls.
+ *
+ * @param subscriptionBlockingStatesOnDisk
+ * blocking states on disk for that subscription
+ * @param allBlockingStatesOnDiskForAccount
+ * all blocking states on disk for that account
+ * @param account account associated with the subscription
+ * @param bundle bundle associated with the subscription
+ * @param baseSubscription base subscription (ProductCategory.BASE) associated with that bundle
+ * @param subscription subscription for which to build blocking states
+ * @param allSubscriptionsForBundle all subscriptions associated with that bundle
+ * @param context call context
+ * @return blocking states for that subscription
+ * @throws EntitlementApiException
+ */
+ public List<BlockingState> getBlockingHistory(final List<BlockingState> subscriptionBlockingStatesOnDisk,
+ final List<BlockingState> allBlockingStatesOnDiskForAccount,
+ final Account account,
+ final SubscriptionBaseBundle bundle,
+ @Nullable final SubscriptionBase baseSubscription,
+ final SubscriptionBase subscription,
+ final List<SubscriptionBase> allSubscriptionsForBundle,
+ final InternalTenantContext context) throws EntitlementApiException {
+ // blockable id points to a subscription, but make sure it's an add-on
+ if (!ProductCategory.ADD_ON.equals(subscription.getCategory())) {
+ // blockable id points to a base or standalone subscription, there is nothing to do
+ return subscriptionBlockingStatesOnDisk;
+ }
+
+ // Find all base entitlements that we care about (for which we want to find future cancelled add-ons)
+ final Iterable<EventsStream> eventsStreams = ImmutableList.<EventsStream>of(eventsStreamBuilder.buildForEntitlement(allBlockingStatesOnDiskForAccount,
+ account,
+ bundle,
+ baseSubscription,
+ allSubscriptionsForBundle,
+ context));
+
+ return addBlockingStatesNotOnDisk(subscription.getId(),
+ BlockingStateType.SUBSCRIPTION,
+ new LinkedList<BlockingState>(subscriptionBlockingStatesOnDisk),
+ ImmutableList.<SubscriptionBase>of(baseSubscription),
+ eventsStreams);
+ }
+}
diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/dao/ProxyBlockingStateDao.java b/entitlement/src/main/java/com/ning/billing/entitlement/dao/ProxyBlockingStateDao.java
index 13f3cd5..bef2260 100644
--- a/entitlement/src/main/java/com/ning/billing/entitlement/dao/ProxyBlockingStateDao.java
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/dao/ProxyBlockingStateDao.java
@@ -18,8 +18,10 @@ package com.ning.billing.entitlement.dao;
import java.util.Collection;
import java.util.Comparator;
+import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
+import java.util.Map;
import java.util.UUID;
import javax.annotation.Nullable;
@@ -36,22 +38,20 @@ import com.ning.billing.callcontext.InternalTenantContext;
import com.ning.billing.catalog.api.ProductCategory;
import com.ning.billing.clock.Clock;
import com.ning.billing.entitlement.EntitlementService;
+import com.ning.billing.entitlement.EventsStream;
import com.ning.billing.entitlement.api.BlockingState;
import com.ning.billing.entitlement.api.BlockingStateType;
import com.ning.billing.entitlement.api.DefaultEntitlementApi;
-import com.ning.billing.entitlement.api.Entitlement.EntitlementState;
import com.ning.billing.entitlement.api.EntitlementApiException;
-import com.ning.billing.entitlement.engine.core.EventsStream;
import com.ning.billing.entitlement.engine.core.EventsStreamBuilder;
import com.ning.billing.subscription.api.SubscriptionBase;
import com.ning.billing.subscription.api.SubscriptionBaseInternalApi;
-import com.ning.billing.subscription.api.user.SubscriptionBaseApiException;
import com.ning.billing.util.cache.CacheControllerDispatcher;
+import com.ning.billing.util.customfield.ShouldntHappenException;
import com.ning.billing.util.dao.NonEntityDao;
import com.ning.billing.util.entity.Pagination;
import com.google.common.base.Predicate;
-import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
import com.google.common.collect.Ordering;
@@ -60,29 +60,125 @@ public class ProxyBlockingStateDao implements BlockingStateDao {
private static final Logger log = LoggerFactory.getLogger(ProxyBlockingStateDao.class);
- public static final Ordering<BlockingState> BLOCKING_STATE_ORDERING = Ordering.<BlockingState>from(new Comparator<BlockingState>() {
+ // Ordering is critical here, especially for Junction
+ public static List<BlockingState> sortedCopy(final Iterable<BlockingState> blockingStates) {
+ final List<BlockingState> blockingStatesSomewhatSorted = BLOCKING_STATE_ORDERING_WITH_TIES_UNHANDLED.immutableSortedCopy(blockingStates);
+
+ final List<BlockingState> result = new LinkedList<BlockingState>();
+
+ // Take care of the ties
+ final Iterator<BlockingState> iterator = blockingStatesSomewhatSorted.iterator();
+ BlockingState prev = null;
+ while (iterator.hasNext()) {
+ final BlockingState current = iterator.next();
+ if (iterator.hasNext()) {
+ final BlockingState next = iterator.next();
+ if (prev != null && current.getEffectiveDate().equals(next.getEffectiveDate()) && current.getBlockedId().equals(next.getBlockedId())) {
+ // Same date, same blockable id
+
+ // Make sure block billing transitions are respected first
+ BlockingState prevCandidate = insertTiedBlockingStatesInTheRightOrder(result, current, next, prev.isBlockBilling(), current.isBlockBilling(), next.isBlockBilling());
+ if (prevCandidate == null) {
+ // Then respect block entitlement transitions
+ prevCandidate = insertTiedBlockingStatesInTheRightOrder(result, current, next, prev.isBlockEntitlement(), current.isBlockEntitlement(), next.isBlockEntitlement());
+ if (prevCandidate == null) {
+ // And finally block changes transitions
+ prevCandidate = insertTiedBlockingStatesInTheRightOrder(result, current, next, prev.isBlockChange(), current.isBlockChange(), next.isBlockChange());
+ if (prevCandidate == null) {
+ // Trust the creation date (see BLOCKING_STATE_ORDERING_WITH_TIES_UNHANDLED below)
+ result.add(current);
+ result.add(next);
+ prev = next;
+ } else {
+ prev = prevCandidate;
+ }
+ } else {
+ prev = prevCandidate;
+ }
+ } else {
+ prev = prevCandidate;
+ }
+ } else {
+ result.add(current);
+ result.add(next);
+ prev = next;
+ }
+ } else {
+ // End of the list
+ result.add(current);
+ }
+ }
+
+ return result;
+ }
+
+ private static BlockingState insertTiedBlockingStatesInTheRightOrder(final Collection<BlockingState> result,
+ final BlockingState current,
+ final BlockingState next,
+ final boolean prevBlocked,
+ final boolean currentBlocked,
+ final boolean nextBlocked) {
+ final BlockingState prev;
+
+ if (prevBlocked && currentBlocked && nextBlocked) {
+ // Tricky use case, bail
+ return null;
+ } else if (prevBlocked && currentBlocked && !nextBlocked) {
+ result.add(next);
+ result.add(current);
+ prev = current;
+ } else if (prevBlocked && !currentBlocked && nextBlocked) {
+ result.add(current);
+ result.add(next);
+ prev = next;
+ } else if (prevBlocked && !currentBlocked && !nextBlocked) {
+ // Tricky use case, bail
+ return null;
+ } else if (!prevBlocked && currentBlocked && nextBlocked) {
+ // Tricky use case, bail
+ return null;
+ } else if (!prevBlocked && currentBlocked && !nextBlocked) {
+ result.add(current);
+ result.add(next);
+ prev = next;
+ } else if (!prevBlocked && !currentBlocked && nextBlocked) {
+ result.add(next);
+ result.add(current);
+ prev = current;
+ } else if (!prevBlocked && !currentBlocked && !nextBlocked) {
+ // Tricky use case, bail
+ return null;
+ } else {
+ throw new ShouldntHappenException("Marker exception for code clarity");
+ }
+
+ return prev;
+ }
+
+ private static final Ordering<BlockingState> BLOCKING_STATE_ORDERING_WITH_TIES_UNHANDLED = Ordering.<BlockingState>from(new Comparator<BlockingState>() {
@Override
public int compare(final BlockingState o1, final BlockingState o2) {
- final int blockableIdComparison = o1.getBlockedId().compareTo(o2.getBlockedId());
- if (blockableIdComparison == 0) {
- // effective_date column NOT NULL
- final int comparison = o1.getEffectiveDate().compareTo(o2.getEffectiveDate());
- if (comparison == 0) {
- // Keep a stable ordering for ties
- return o1.getCreatedDate().compareTo(o2.getCreatedDate());
+ // effective_date column NOT NULL
+ final int effectiveDateComparison = o1.getEffectiveDate().compareTo(o2.getEffectiveDate());
+ if (effectiveDateComparison != 0) {
+ return effectiveDateComparison;
+ } else {
+ final int blockableIdComparison = o1.getBlockedId().compareTo(o2.getBlockedId());
+ if (blockableIdComparison != 0) {
+ return blockableIdComparison;
} else {
- return comparison;
+ // Same date, same blockable id, just respect the created date for now (see sortedCopyOf method above)
+ return o1.getCreatedDate().compareTo(o2.getCreatedDate());
}
- } else {
- return blockableIdComparison;
}
}
});
- private final EventsStreamBuilder eventsStreamBuilder;
private final SubscriptionBaseInternalApi subscriptionInternalApi;
private final Clock clock;
- private final DefaultBlockingStateDao delegate;
+
+ protected final EventsStreamBuilder eventsStreamBuilder;
+ protected final DefaultBlockingStateDao delegate;
@Inject
public ProxyBlockingStateDao(final EventsStreamBuilder eventsStreamBuilder, final SubscriptionBaseInternalApi subscriptionBaseInternalApi,
@@ -145,21 +241,9 @@ public class ProxyBlockingStateDao implements BlockingStateDao {
}
@Override
- public List<BlockingState> getBlockingHistoryForService(final UUID blockableId, final BlockingStateType blockingStateType, final String serviceName, final InternalTenantContext context) {
- final List<BlockingState> statesOnDisk = delegate.getBlockingHistoryForService(blockableId, blockingStateType, serviceName, context);
- return addBlockingStatesNotOnDisk(blockableId, blockingStateType, statesOnDisk, context);
- }
-
- @Override
- public List<BlockingState> getBlockingAll(final UUID blockableId, final BlockingStateType blockingStateType, final InternalTenantContext context) {
- final List<BlockingState> statesOnDisk = delegate.getBlockingAll(blockableId, blockingStateType, context);
- return addBlockingStatesNotOnDisk(blockableId, blockingStateType, statesOnDisk, context);
- }
-
- @Override
public List<BlockingState> getBlockingAllForAccountRecordId(final InternalTenantContext context) {
final List<BlockingState> statesOnDisk = delegate.getBlockingAllForAccountRecordId(context);
- return addBlockingStatesNotOnDisk(null, null, statesOnDisk, context);
+ return addBlockingStatesNotOnDisk(statesOnDisk, context);
}
@Override
@@ -174,64 +258,37 @@ public class ProxyBlockingStateDao implements BlockingStateDao {
// Add blocking states for add-ons, which would be impacted by a future cancellation or change of their base plan
// See DefaultEntitlement#blockAddOnsIfRequired
- private List<BlockingState> addBlockingStatesNotOnDisk(@Nullable final UUID blockableId,
- @Nullable final BlockingStateType blockingStateType,
- final List<BlockingState> blockingStatesOnDisk,
+ private List<BlockingState> addBlockingStatesNotOnDisk(final List<BlockingState> blockingStatesOnDisk,
final InternalTenantContext context) {
final Collection<BlockingState> blockingStatesOnDiskCopy = new LinkedList<BlockingState>(blockingStatesOnDisk);
// Find all base entitlements that we care about (for which we want to find future cancelled add-ons)
final Iterable<SubscriptionBase> baseSubscriptionsToConsider;
+ final Iterable<EventsStream> eventsStreams;
try {
- if (blockingStateType == null) {
- // We're coming from getBlockingAllForAccountRecordId
- final Iterable<SubscriptionBase> subscriptions = Iterables.<SubscriptionBase>concat(subscriptionInternalApi.getSubscriptionsForAccount(context).values());
- baseSubscriptionsToConsider = Iterables.<SubscriptionBase>filter(subscriptions,
- new Predicate<SubscriptionBase>() {
- @Override
- public boolean apply(final SubscriptionBase input) {
- return ProductCategory.BASE.equals(input.getCategory()) &&
- !EntitlementState.CANCELLED.equals(input.getState());
- }
- });
- } else if (BlockingStateType.SUBSCRIPTION.equals(blockingStateType)) {
- // We're coming from getBlockingHistoryForService / getBlockingAll
- final SubscriptionBase subscription = subscriptionInternalApi.getSubscriptionFromId(blockableId, context);
-
- // blockable id points to a subscription, but make sure it's an add-on
- if (ProductCategory.ADD_ON.equals(subscription.getCategory())) {
- final SubscriptionBase baseSubscription = subscriptionInternalApi.getBaseSubscription(subscription.getBundleId(), context);
- baseSubscriptionsToConsider = ImmutableList.<SubscriptionBase>of(baseSubscription);
- } else {
- // blockable id points to a base or standalone subscription, there is nothing to do
- return blockingStatesOnDisk;
- }
- } else {
- // blockable id points to an account or bundle, in which case there are no extra blocking states to add
- return blockingStatesOnDisk;
- }
- } catch (SubscriptionBaseApiException e) {
- log.error("Error retrieving subscriptions for account record id " + context.getAccountRecordId(), e);
- throw new RuntimeException(e);
- }
-
- // Retrieve the cancellation blocking state on disk, if it exists (will be used later)
- final BlockingState cancellationBlockingStateOnDisk = findEntitlementCancellationBlockingState(blockableId, blockingStatesOnDiskCopy);
-
- final List<EventsStream> eventsStreams;
- try {
- if (blockingStateType == null) {
- // We're coming from getBlockingAllForAccountRecordId
- eventsStreams = eventsStreamBuilder.buildForAccount(context);
- } else {
- // We're coming from getBlockingHistoryForService / getBlockingAll
- eventsStreams = ImmutableList.<EventsStream>of(eventsStreamBuilder.buildForEntitlement(baseSubscriptionsToConsider.iterator().next().getId(), context));
- }
+ final Map<UUID, List<SubscriptionBase>> subscriptions = subscriptionInternalApi.getSubscriptionsForAccount(context);
+ baseSubscriptionsToConsider = Iterables.<SubscriptionBase>filter(Iterables.<SubscriptionBase>concat(subscriptions.values()),
+ new Predicate<SubscriptionBase>() {
+ @Override
+ public boolean apply(final SubscriptionBase input) {
+ return ProductCategory.BASE.equals(input.getCategory());
+ }
+ });
+ eventsStreams = Iterables.<EventsStream>concat(eventsStreamBuilder.buildForAccount(subscriptions, context).getEventsStreams().values());
} catch (EntitlementApiException e) {
log.error("Error computing blocking states for addons for account record id " + context.getAccountRecordId(), e);
throw new RuntimeException(e);
}
+ return addBlockingStatesNotOnDisk(null, null, blockingStatesOnDiskCopy, baseSubscriptionsToConsider, eventsStreams);
+ }
+
+ // Special signature for OptimizedProxyBlockingStateDao
+ protected List<BlockingState> addBlockingStatesNotOnDisk(@Nullable final UUID blockableId,
+ @Nullable final BlockingStateType blockingStateType,
+ final Collection<BlockingState> blockingStatesOnDiskCopy,
+ final Iterable<SubscriptionBase> baseSubscriptionsToConsider,
+ final Iterable<EventsStream> eventsStreams) {
// Compute the blocking states not on disk for all base subscriptions
final DateTime now = clock.getUTCNow();
for (final SubscriptionBase baseSubscription : baseSubscriptionsToConsider) {
@@ -243,20 +300,23 @@ public class ProxyBlockingStateDao implements BlockingStateDao {
}
});
- // First, check to see if the base entitlement is cancelled. If so, cancel the
+ // First, check to see if the base entitlement is cancelled
final Collection<BlockingState> blockingStatesNotOnDisk = eventsStream.computeAddonsBlockingStatesForFutureSubscriptionBaseEvents();
// Inject the extra blocking states into the stream if needed
for (final BlockingState blockingState : blockingStatesNotOnDisk) {
// If this entitlement is actually already cancelled, add the cancellation event we computed
// only if it's prior to the blocking state on disk (e.g. add-on future cancelled but base plan cancelled earlier).
- final boolean overrideCancellationBlockingStateOnDisk = cancellationBlockingStateOnDisk != null &&
- isEntitlementCancellationBlockingState(blockingState) &&
- blockingState.getEffectiveDate().isBefore(cancellationBlockingStateOnDisk.getEffectiveDate());
+ BlockingState cancellationBlockingStateOnDisk = null;
+ boolean overrideCancellationBlockingStateOnDisk = false;
+ if (isEntitlementCancellationBlockingState(blockingState)) {
+ cancellationBlockingStateOnDisk = findEntitlementCancellationBlockingState(blockingState.getBlockedId(), blockingStatesOnDiskCopy);
+ overrideCancellationBlockingStateOnDisk = cancellationBlockingStateOnDisk != null && blockingState.getEffectiveDate().isBefore(cancellationBlockingStateOnDisk.getEffectiveDate());
+ }
if ((
blockingStateType == null ||
- // In case we're coming from getBlockingHistoryForService / getBlockingAll, make sure we don't add
+ // In case we're coming from OptimizedProxyBlockingStateDao, make sure we don't add
// blocking states for other add-ons on that base subscription
(BlockingStateType.SUBSCRIPTION.equals(blockingStateType) && blockingState.getBlockedId().equals(blockableId))
) && (
@@ -273,7 +333,7 @@ public class ProxyBlockingStateDao implements BlockingStateDao {
}
// Return the sorted list
- return BLOCKING_STATE_ORDERING.immutableSortedCopy(blockingStatesOnDiskCopy);
+ return sortedCopy(blockingStatesOnDiskCopy);
}
private BlockingState findEntitlementCancellationBlockingState(@Nullable final UUID blockedId, final Iterable<BlockingState> blockingStatesOnDisk) {
diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/engine/core/EventsStreamBuilder.java b/entitlement/src/main/java/com/ning/billing/entitlement/engine/core/EventsStreamBuilder.java
index 926464e..1dcf932 100644
--- a/entitlement/src/main/java/com/ning/billing/entitlement/engine/core/EventsStreamBuilder.java
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/engine/core/EventsStreamBuilder.java
@@ -16,16 +16,18 @@
package com.ning.billing.entitlement.engine.core;
+import java.util.Collection;
import java.util.HashMap;
+import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.UUID;
+import javax.annotation.Nullable;
import javax.inject.Inject;
import javax.inject.Singleton;
-import org.joda.time.DateTime;
import org.skife.jdbi.v2.IDBI;
import com.ning.billing.ObjectType;
@@ -35,14 +37,16 @@ import com.ning.billing.account.api.AccountInternalApi;
import com.ning.billing.callcontext.InternalTenantContext;
import com.ning.billing.catalog.api.ProductCategory;
import com.ning.billing.clock.Clock;
-import com.ning.billing.entitlement.EntitlementService;
+import com.ning.billing.entitlement.AccountEventsStreams;
+import com.ning.billing.entitlement.EventsStream;
import com.ning.billing.entitlement.api.BlockingState;
import com.ning.billing.entitlement.api.BlockingStateType;
import com.ning.billing.entitlement.api.EntitlementApiException;
+import com.ning.billing.entitlement.api.svcs.DefaultAccountEventsStreams;
import com.ning.billing.entitlement.block.BlockingChecker;
-import com.ning.billing.entitlement.block.BlockingChecker.BlockingAggregator;
-import com.ning.billing.entitlement.dao.BlockingStateDao;
import com.ning.billing.entitlement.dao.DefaultBlockingStateDao;
+import com.ning.billing.entitlement.dao.OptimizedProxyBlockingStateDao;
+import com.ning.billing.entitlement.dao.ProxyBlockingStateDao;
import com.ning.billing.subscription.api.SubscriptionBase;
import com.ning.billing.subscription.api.SubscriptionBaseInternalApi;
import com.ning.billing.subscription.api.user.SubscriptionBaseApiException;
@@ -52,43 +56,40 @@ import com.ning.billing.util.callcontext.InternalCallContextFactory;
import com.ning.billing.util.callcontext.TenantContext;
import com.ning.billing.util.dao.NonEntityDao;
+import com.google.common.base.Objects;
import com.google.common.base.Predicate;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
-import static com.ning.billing.entitlement.dao.ProxyBlockingStateDao.BLOCKING_STATE_ORDERING;
-
@Singleton
public class EventsStreamBuilder {
private final AccountInternalApi accountInternalApi;
private final SubscriptionBaseInternalApi subscriptionInternalApi;
private final BlockingChecker checker;
- private final BlockingStateDao blockingStateDao;
+ private final OptimizedProxyBlockingStateDao blockingStateDao;
private final DefaultBlockingStateDao defaultBlockingStateDao;
private final Clock clock;
- private final NonEntityDao nonEntityDao;
private final InternalCallContextFactory internalCallContextFactory;
@Inject
public EventsStreamBuilder(final AccountInternalApi accountInternalApi, final SubscriptionBaseInternalApi subscriptionInternalApi,
final BlockingChecker checker, final IDBI dbi, final Clock clock,
final CacheControllerDispatcher cacheControllerDispatcher,
- final BlockingStateDao blockingStateDao, final NonEntityDao nonEntityDao,
+ final NonEntityDao nonEntityDao,
final InternalCallContextFactory internalCallContextFactory) {
this.accountInternalApi = accountInternalApi;
this.subscriptionInternalApi = subscriptionInternalApi;
this.checker = checker;
this.clock = clock;
- this.blockingStateDao = blockingStateDao;
- this.nonEntityDao = nonEntityDao;
this.internalCallContextFactory = internalCallContextFactory;
this.defaultBlockingStateDao = new DefaultBlockingStateDao(dbi, clock, cacheControllerDispatcher, nonEntityDao);
+ this.blockingStateDao = new OptimizedProxyBlockingStateDao(this, subscriptionInternalApi, dbi, clock, cacheControllerDispatcher, nonEntityDao);
}
public EventsStream refresh(final EventsStream eventsStream, final TenantContext tenantContext) throws EntitlementApiException {
- return buildForEntitlement(eventsStream.getSubscription().getId(), tenantContext);
+ return buildForEntitlement(eventsStream.getEntitlementId(), tenantContext);
}
public EventsStream buildForBaseSubscription(final UUID bundleId, final TenantContext tenantContext) throws EntitlementApiException {
@@ -108,25 +109,29 @@ public class EventsStreamBuilder {
return buildForEntitlement(entitlementId, internalTenantContext);
}
- public List<EventsStream> buildForAccount(final InternalTenantContext internalTenantContext) throws EntitlementApiException {
+ public AccountEventsStreams buildForAccount(final InternalTenantContext internalTenantContext) throws EntitlementApiException {
// Retrieve the subscriptions (map bundle id -> subscriptions)
final Map<UUID, List<SubscriptionBase>> subscriptions = subscriptionInternalApi.getSubscriptionsForAccount(internalTenantContext);
- if (subscriptions.isEmpty()) {
- // Bail early
- return ImmutableList.<EventsStream>of();
- }
+ return buildForAccount(subscriptions, internalTenantContext);
+ }
+ // Special signature for ProxyBlockingStateDao to save a DAO call
+ public AccountEventsStreams buildForAccount(final Map<UUID, List<SubscriptionBase>> subscriptions, final InternalTenantContext internalTenantContext) throws EntitlementApiException {
// Retrieve the account
- final UUID accountId = nonEntityDao.retrieveIdFromObject(internalTenantContext.getAccountRecordId(), ObjectType.ACCOUNT);
final Account account;
try {
- account = accountInternalApi.getAccountById(accountId, internalTenantContext);
+ account = accountInternalApi.getAccountByRecordId(internalTenantContext.getAccountRecordId(), internalTenantContext);
} catch (AccountApiException e) {
throw new EntitlementApiException(e);
}
+ if (subscriptions.isEmpty()) {
+ // Bail early
+ return new DefaultAccountEventsStreams(account);
+ }
+
// Retrieve the bundles
- final List<SubscriptionBaseBundle> bundles = subscriptionInternalApi.getBundlesForAccount(accountId, internalTenantContext);
+ final List<SubscriptionBaseBundle> bundles = subscriptionInternalApi.getBundlesForAccount(account.getId(), internalTenantContext);
// Map bundle id -> bundles
final Map<UUID, SubscriptionBaseBundle> bundlesPerId = new HashMap<UUID, SubscriptionBaseBundle>();
for (final SubscriptionBaseBundle bundle : bundles) {
@@ -134,20 +139,31 @@ public class EventsStreamBuilder {
}
// Retrieve the blocking states
- final List<BlockingState> blockingStatesForAccount = BLOCKING_STATE_ORDERING.immutableSortedCopy(defaultBlockingStateDao.getBlockingAllForAccountRecordId(internalTenantContext));
- // Copy fully the list (avoid lazy loading)
- final List<BlockingState> accountEntitlementStates = ImmutableList.<BlockingState>copyOf(Iterables.<BlockingState>filter(blockingStatesForAccount,
- new Predicate<BlockingState>() {
- @Override
- public boolean apply(final BlockingState input) {
- return BlockingStateType.ACCOUNT.equals(input.getType()) &&
- EntitlementService.ENTITLEMENT_SERVICE_NAME.equals(input.getService()) &&
- accountId.equals(input.getBlockedId());
- }
- }));
+ final List<BlockingState> blockingStatesForAccount = defaultBlockingStateDao.getBlockingAllForAccountRecordId(internalTenantContext);
+
+ // Optimization: build lookup tables for blocking states states
+ final Collection<BlockingState> accountBlockingStates = new LinkedList<BlockingState>();
+ final Map<UUID, List<BlockingState>> blockingStatesPerSubscription = new HashMap<UUID, List<BlockingState>>();
+ final Map<UUID, List<BlockingState>> blockingStatesPerBundle = new HashMap<UUID, List<BlockingState>>();
+ for (final BlockingState blockingState : blockingStatesForAccount) {
+ if (BlockingStateType.SUBSCRIPTION.equals(blockingState.getType())) {
+ if (blockingStatesPerSubscription.get(blockingState.getBlockedId()) == null) {
+ blockingStatesPerSubscription.put(blockingState.getBlockedId(), new LinkedList<BlockingState>());
+ }
+ blockingStatesPerSubscription.get(blockingState.getBlockedId()).add(blockingState);
+ } else if (BlockingStateType.SUBSCRIPTION_BUNDLE.equals(blockingState.getType())) {
+ if (blockingStatesPerBundle.get(blockingState.getBlockedId()) == null) {
+ blockingStatesPerBundle.put(blockingState.getBlockedId(), new LinkedList<BlockingState>());
+ }
+ blockingStatesPerBundle.get(blockingState.getBlockedId()).add(blockingState);
+ } else if (BlockingStateType.ACCOUNT.equals(blockingState.getType()) &&
+ account.getId().equals(blockingState.getBlockedId())) {
+ accountBlockingStates.add(blockingState);
+ }
+ }
// Build the EventsStream objects
- final List<EventsStream> results = new LinkedList<EventsStream>();
+ final Map<UUID, Collection<EventsStream>> entitlementsPerBundle = new HashMap<UUID, Collection<EventsStream>>();
for (final UUID bundleId : subscriptions.keySet()) {
final SubscriptionBaseBundle bundle = bundlesPerId.get(bundleId);
final List<SubscriptionBase> allSubscriptionsForBundle = subscriptions.get(bundleId);
@@ -158,34 +174,46 @@ public class EventsStreamBuilder {
return ProductCategory.BASE.equals(input.getLastActiveProduct().getCategory());
}
}).orNull();
- // Copy fully the list (avoid lazy loading)
- final List<BlockingState> bundleEntitlementStates = ImmutableList.<BlockingState>copyOf(Iterables.<BlockingState>filter(blockingStatesForAccount,
- new Predicate<BlockingState>() {
- @Override
- public boolean apply(final BlockingState input) {
- return BlockingStateType.SUBSCRIPTION_BUNDLE.equals(input.getType()) &&
- EntitlementService.ENTITLEMENT_SERVICE_NAME.equals(input.getService()) &&
- bundle.getId().equals(input.getBlockedId());
- }
- }));
-
- for (final SubscriptionBase subscriptionBase : allSubscriptionsForBundle) {
- // Copy fully the list (avoid lazy loading)
- final List<BlockingState> subscriptionEntitlementStates = ImmutableList.<BlockingState>copyOf(Iterables.<BlockingState>filter(blockingStatesForAccount,
- new Predicate<BlockingState>() {
- @Override
- public boolean apply(final BlockingState input) {
- return BlockingStateType.SUBSCRIPTION.equals(input.getType()) &&
- EntitlementService.ENTITLEMENT_SERVICE_NAME.equals(input.getService()) &&
- subscriptionBase.getId().equals(input.getBlockedId());
- }
- }));
-
- results.add(buildForEntitlement(account, bundle, baseSubscription, subscriptionBase, allSubscriptionsForBundle, subscriptionEntitlementStates, bundleEntitlementStates, accountEntitlementStates, internalTenantContext));
+ final List<BlockingState> bundleBlockingStates = Objects.firstNonNull(blockingStatesPerBundle.get(bundleId), ImmutableList.<BlockingState>of());
+
+ if (entitlementsPerBundle.get(bundleId) == null) {
+ entitlementsPerBundle.put(bundleId, new LinkedList<EventsStream>());
+ }
+
+ for (final SubscriptionBase subscription : allSubscriptionsForBundle) {
+ final List<BlockingState> subscriptionBlockingStatesOnDisk = Objects.firstNonNull(blockingStatesPerSubscription.get(subscription.getId()), ImmutableList.<BlockingState>of());
+
+ // We cannot always use blockingStatesForAccount here: we need subscriptionBlockingStates to contain the events not on disk when building an EventsStream
+ // for an add-on - which means going through the magic of ProxyBlockingStateDao, which will recursively
+ // create EventsStream objects. To avoid an infinite recursion, bypass ProxyBlockingStateDao when it's not
+ // needed, i.e. if this EventStream is for a standalone or a base subscription
+ final List<BlockingState> subscriptionBlockingStates;
+ if (baseSubscription == null || subscription.getId().equals(baseSubscription.getId())) {
+ subscriptionBlockingStates = subscriptionBlockingStatesOnDisk;
+ } else {
+ subscriptionBlockingStates = blockingStateDao.getBlockingHistory(subscriptionBlockingStatesOnDisk,
+ blockingStatesForAccount,
+ account,
+ bundle,
+ baseSubscription,
+ subscription,
+ allSubscriptionsForBundle,
+ internalTenantContext);
+
+ }
+
+ // Merge the BlockingStates
+ final Collection<BlockingState> blockingStateSet = new LinkedHashSet<BlockingState>(accountBlockingStates);
+ blockingStateSet.addAll(bundleBlockingStates);
+ blockingStateSet.addAll(subscriptionBlockingStates);
+ final List<BlockingState> blockingStates = ProxyBlockingStateDao.sortedCopy(blockingStateSet);
+
+ final EventsStream eventStream = buildForEntitlement(account, bundle, baseSubscription, subscription, allSubscriptionsForBundle, blockingStates, internalTenantContext);
+ entitlementsPerBundle.get(bundleId).add(eventStream);
}
}
- return results;
+ return new DefaultAccountEventsStreams(account, bundles, entitlementsPerBundle);
}
public EventsStream buildForEntitlement(final UUID entitlementId, final InternalTenantContext internalTenantContext) throws EntitlementApiException {
@@ -208,14 +236,6 @@ public class EventsStreamBuilder {
throw new EntitlementApiException(e);
}
- return buildForEntitlement(bundle, baseSubscription, subscription, allSubscriptionsForBundle, internalTenantContext);
- }
-
- private EventsStream buildForEntitlement(final SubscriptionBaseBundle bundle,
- final SubscriptionBase baseSubscription,
- final SubscriptionBase subscription,
- final List<SubscriptionBase> allSubscriptionsForBundle,
- final InternalTenantContext internalTenantContext) throws EntitlementApiException {
final Account account;
try {
account = accountInternalApi.getAccountById(bundle.getAccountId(), internalTenantContext);
@@ -223,58 +243,97 @@ public class EventsStreamBuilder {
throw new EntitlementApiException(e);
}
- final List<BlockingState> bundleEntitlementStates = BLOCKING_STATE_ORDERING.immutableSortedCopy(defaultBlockingStateDao.getBlockingHistoryForService(bundle.getId(), BlockingStateType.SUBSCRIPTION_BUNDLE, EntitlementService.ENTITLEMENT_SERVICE_NAME, internalTenantContext));
- final List<BlockingState> accountEntitlementStates = BLOCKING_STATE_ORDERING.immutableSortedCopy(defaultBlockingStateDao.getBlockingHistoryForService(account.getId(), BlockingStateType.ACCOUNT, EntitlementService.ENTITLEMENT_SERVICE_NAME, internalTenantContext));
- // TODO PIERRE Explain the magic
- final List<BlockingState> subscriptionEntitlementStates = subscription.getId().equals(baseSubscription.getId()) ?
- BLOCKING_STATE_ORDERING.immutableSortedCopy(defaultBlockingStateDao.getBlockingHistoryForService(subscription.getId(), BlockingStateType.SUBSCRIPTION, EntitlementService.ENTITLEMENT_SERVICE_NAME, internalTenantContext)) :
- BLOCKING_STATE_ORDERING.immutableSortedCopy(blockingStateDao.getBlockingHistoryForService(subscription.getId(), BlockingStateType.SUBSCRIPTION, EntitlementService.ENTITLEMENT_SERVICE_NAME, internalTenantContext));
+ // Retrieve the blocking states
+ final List<BlockingState> blockingStatesForAccount = defaultBlockingStateDao.getBlockingAllForAccountRecordId(internalTenantContext);
- return buildForEntitlement(account, bundle, baseSubscription, subscription, allSubscriptionsForBundle, subscriptionEntitlementStates, bundleEntitlementStates, accountEntitlementStates, internalTenantContext);
+ return buildForEntitlement(blockingStatesForAccount, account, bundle, baseSubscription, subscription, allSubscriptionsForBundle, internalTenantContext);
}
- private EventsStream buildForEntitlement(final Account account,
+ // Special signature for OptimizedProxyBlockingStateDao to save some DAO calls
+ public EventsStream buildForEntitlement(final List<BlockingState> blockingStatesForAccount,
+ final Account account,
+ final SubscriptionBaseBundle bundle,
+ final SubscriptionBase baseSubscription,
+ final List<SubscriptionBase> allSubscriptionsForBundle,
+ final InternalTenantContext internalTenantContext) throws EntitlementApiException {
+ return buildForEntitlement(blockingStatesForAccount, account, bundle, baseSubscription, baseSubscription, allSubscriptionsForBundle, internalTenantContext);
+ }
+
+ private EventsStream buildForEntitlement(final List<BlockingState> blockingStatesForAccount,
+ final Account account,
final SubscriptionBaseBundle bundle,
- final SubscriptionBase baseSubscription,
+ @Nullable final SubscriptionBase baseSubscription,
final SubscriptionBase subscription,
final List<SubscriptionBase> allSubscriptionsForBundle,
- final List<BlockingState> subscriptionEntitlementStates,
- final List<BlockingState> bundleEntitlementStates,
- final List<BlockingState> accountEntitlementStates,
final InternalTenantContext internalTenantContext) throws EntitlementApiException {
- final BlockingAggregator blockingAggregator = checker.getBlockedStatus(filterCurrentBlockableStatePerService(accountEntitlementStates),
- filterCurrentBlockableStatePerService(bundleEntitlementStates),
- filterCurrentBlockableStatePerService(subscriptionEntitlementStates),
- internalTenantContext);
-
- return new EventsStream(account,
- bundle,
- subscriptionEntitlementStates,
- bundleEntitlementStates,
- accountEntitlementStates,
- blockingAggregator,
- baseSubscription,
- subscription,
- allSubscriptionsForBundle,
- internalTenantContext,
- clock.getUTCNow());
- }
-
- private List<BlockingState> filterCurrentBlockableStatePerService(final Iterable<BlockingState> allBlockingStates) {
- final DateTime now = clock.getUTCNow();
-
- final Map<String, BlockingState> currentBlockingStatePerService = new HashMap<String, BlockingState>();
- for (final BlockingState blockingState : allBlockingStates) {
- if (blockingState.getEffectiveDate().isAfter(now)) {
- continue;
+ // Optimization: build lookup tables for blocking states states
+ final Collection<BlockingState> accountBlockingStates = new LinkedList<BlockingState>();
+ final Map<UUID, List<BlockingState>> blockingStatesPerSubscription = new HashMap<UUID, List<BlockingState>>();
+ final Map<UUID, List<BlockingState>> blockingStatesPerBundle = new HashMap<UUID, List<BlockingState>>();
+ for (final BlockingState blockingState : blockingStatesForAccount) {
+ if (BlockingStateType.SUBSCRIPTION.equals(blockingState.getType())) {
+ if (blockingStatesPerSubscription.get(blockingState.getBlockedId()) == null) {
+ blockingStatesPerSubscription.put(blockingState.getBlockedId(), new LinkedList<BlockingState>());
+ }
+ blockingStatesPerSubscription.get(blockingState.getBlockedId()).add(blockingState);
+ } else if (BlockingStateType.SUBSCRIPTION_BUNDLE.equals(blockingState.getType())) {
+ if (blockingStatesPerBundle.get(blockingState.getBlockedId()) == null) {
+ blockingStatesPerBundle.put(blockingState.getBlockedId(), new LinkedList<BlockingState>());
+ }
+ blockingStatesPerBundle.get(blockingState.getBlockedId()).add(blockingState);
+ } else if (BlockingStateType.ACCOUNT.equals(blockingState.getType()) &&
+ account.getId().equals(blockingState.getBlockedId())) {
+ accountBlockingStates.add(blockingState);
}
+ }
- if (currentBlockingStatePerService.get(blockingState.getService()) == null ||
- currentBlockingStatePerService.get(blockingState.getService()).getEffectiveDate().isBefore(blockingState.getEffectiveDate())) {
- currentBlockingStatePerService.put(blockingState.getService(), blockingState);
- }
+ final List<BlockingState> bundleBlockingStates = Objects.firstNonNull(blockingStatesPerBundle.get(subscription.getBundleId()), ImmutableList.<BlockingState>of());
+ final List<BlockingState> subscriptionBlockingStatesOnDisk = Objects.firstNonNull(blockingStatesPerSubscription.get(subscription.getId()), ImmutableList.<BlockingState>of());
+
+ // We cannot always use blockingStatesForAccount here: we need subscriptionBlockingStates to contain the events not on disk when building an EventsStream
+ // for an add-on - which means going through the magic of ProxyBlockingStateDao, which will recursively
+ // create EventsStream objects. To avoid an infinite recursion, bypass ProxyBlockingStateDao when it's not
+ // needed, i.e. if this EventStream is for a standalone or a base subscription
+ final Collection<BlockingState> subscriptionBlockingStates;
+ if (baseSubscription == null || subscription.getId().equals(baseSubscription.getId())) {
+ // Note: we come here during the recursion from OptimizedProxyBlockingStateDao#getBlockingHistory
+ // (called by blockingStateDao.getBlockingHistory below)
+ subscriptionBlockingStates = subscriptionBlockingStatesOnDisk;
+ } else {
+ subscriptionBlockingStates = blockingStateDao.getBlockingHistory(ImmutableList.<BlockingState>copyOf(subscriptionBlockingStatesOnDisk),
+ blockingStatesForAccount,
+ account,
+ bundle,
+ baseSubscription,
+ subscription,
+ allSubscriptionsForBundle,
+ internalTenantContext);
}
- return ImmutableList.<BlockingState>copyOf(currentBlockingStatePerService.values());
+ // Merge the BlockingStates
+ final Collection<BlockingState> blockingStateSet = new LinkedHashSet<BlockingState>(accountBlockingStates);
+ blockingStateSet.addAll(bundleBlockingStates);
+ blockingStateSet.addAll(subscriptionBlockingStates);
+ final List<BlockingState> blockingStates = ProxyBlockingStateDao.sortedCopy(blockingStateSet);
+
+ return buildForEntitlement(account, bundle, baseSubscription, subscription, allSubscriptionsForBundle, blockingStates, internalTenantContext);
+ }
+
+ private EventsStream buildForEntitlement(final Account account,
+ final SubscriptionBaseBundle bundle,
+ @Nullable final SubscriptionBase baseSubscription,
+ final SubscriptionBase subscription,
+ final List<SubscriptionBase> allSubscriptionsForBundle,
+ final List<BlockingState> blockingStates,
+ final InternalTenantContext internalTenantContext) throws EntitlementApiException {
+ return new DefaultEventsStream(account,
+ bundle,
+ blockingStates,
+ checker,
+ baseSubscription,
+ subscription,
+ allSubscriptionsForBundle,
+ internalTenantContext,
+ clock.getUTCNow());
}
}
diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/glue/DefaultEntitlementModule.java b/entitlement/src/main/java/com/ning/billing/entitlement/glue/DefaultEntitlementModule.java
index d95248b..975419d 100644
--- a/entitlement/src/main/java/com/ning/billing/entitlement/glue/DefaultEntitlementModule.java
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/glue/DefaultEntitlementModule.java
@@ -19,11 +19,13 @@ package com.ning.billing.entitlement.glue;
import org.skife.config.ConfigSource;
import com.ning.billing.entitlement.DefaultEntitlementService;
+import com.ning.billing.entitlement.EntitlementInternalApi;
import com.ning.billing.entitlement.EntitlementService;
import com.ning.billing.entitlement.api.DefaultEntitlementApi;
import com.ning.billing.entitlement.api.DefaultSubscriptionApi;
import com.ning.billing.entitlement.api.EntitlementApi;
import com.ning.billing.entitlement.api.SubscriptionApi;
+import com.ning.billing.entitlement.api.svcs.DefaultEntitlementInternalApi;
import com.ning.billing.entitlement.api.svcs.DefaultInternalBlockingApi;
import com.ning.billing.entitlement.block.BlockingChecker;
import com.ning.billing.entitlement.block.DefaultBlockingChecker;
@@ -38,7 +40,6 @@ import com.google.inject.AbstractModule;
public class DefaultEntitlementModule extends AbstractModule implements EntitlementModule {
-
public DefaultEntitlementModule(final ConfigSource configSource) {
}
@@ -47,6 +48,7 @@ public class DefaultEntitlementModule extends AbstractModule implements Entitlem
installBlockingStateDao();
installBlockingApi();
installEntitlementApi();
+ installEntitlementInternalApi();
installSubscriptionApi();
installBlockingChecker();
bind(EntitlementService.class).to(DefaultEntitlementService.class).asEagerSingleton();
@@ -70,6 +72,11 @@ public class DefaultEntitlementModule extends AbstractModule implements Entitlem
}
@Override
+ public void installEntitlementInternalApi() {
+ bind(EntitlementInternalApi.class).to(DefaultEntitlementInternalApi.class).asEagerSingleton();
+ }
+
+ @Override
public void installSubscriptionApi() {
bind(SubscriptionApi.class).to(DefaultSubscriptionApi.class).asEagerSingleton();
}
diff --git a/entitlement/src/main/resources/com/ning/billing/entitlement/dao/BlockingStateSqlDao.sql.stg b/entitlement/src/main/resources/com/ning/billing/entitlement/dao/BlockingStateSqlDao.sql.stg
index 25067a4..62d8de4 100644
--- a/entitlement/src/main/resources/com/ning/billing/entitlement/dao/BlockingStateSqlDao.sql.stg
+++ b/entitlement/src/main/resources/com/ning/billing/entitlement/dao/BlockingStateSqlDao.sql.stg
@@ -5,6 +5,10 @@ tableName() ::= "blocking_states"
andCheckSoftDeletionWithComma(prefix) ::= "and <prefix>is_active"
+defaultOrderBy(prefix) ::= <<
+order by <prefix>effective_date ASC, <recordIdField(prefix)> ASC
+>>
+
tableFields(prefix) ::= <<
<prefix>blockable_id
, <prefix>type
@@ -50,7 +54,7 @@ and effective_date \<= :effectiveDate
and is_active
<AND_CHECK_TENANT()>
-- We want the current state, hence the order desc and limit 1
-order by record_id desc
+order by effective_date desc, record_id desc
limit 1
;
>>
@@ -71,8 +75,8 @@ getBlockingState() ::= <<
group by service
) tmp
on t.record_id = tmp.record_id
- order by t.record_id asc
- ;
+ <defaultOrderBy("t.")>
+ ;
>>
getBlockingHistoryForService() ::= <<
@@ -84,19 +88,7 @@ where blockable_id = :blockableId
and service = :service
and is_active
<AND_CHECK_TENANT()>
-order by record_id asc
-;
->>
-
-getBlockingAll() ::= <<
-select
-<allTableFields()>
-from
-<tableName()>
-where blockable_id = :blockableId
-and is_active
-<AND_CHECK_TENANT()>
-order by record_id asc
+<defaultOrderBy()>
;
>>
diff --git a/entitlement/src/test/java/com/ning/billing/entitlement/api/TestDefaultEntitlementApi.java b/entitlement/src/test/java/com/ning/billing/entitlement/api/TestDefaultEntitlementApi.java
index e2ff2d8..caa7178 100644
--- a/entitlement/src/test/java/com/ning/billing/entitlement/api/TestDefaultEntitlementApi.java
+++ b/entitlement/src/test/java/com/ning/billing/entitlement/api/TestDefaultEntitlementApi.java
@@ -62,15 +62,12 @@ public class TestDefaultEntitlementApi extends EntitlementTestSuiteWithEmbeddedD
testListener.pushExpectedEvent(NextEvent.CREATE);
final Entitlement addOnEntitlement = entitlementApi.addEntitlement(entitlement.getBundleId(), addOnSpec, initialDate, callContext);
assertListenerStatus();
-
- /*
- // TODO It looks like we don't check if there is a future cancellation. Maybe we should?
try {
entitlement.uncancelEntitlement(callContext);
Assert.fail("Entitlement hasn't been cancelled yet");
} catch (final EntitlementApiException e) {
Assert.assertEquals(e.getCode(), ErrorCode.SUB_CANCEL_BAD_STATE.getCode());
- }*/
+ }
clock.addDays(3);
@@ -108,6 +105,40 @@ public class TestDefaultEntitlementApi extends EntitlementTestSuiteWithEmbeddedD
}
}
+
+ @Test(groups = "slow")
+ public void testUncancelEffectiveCancelledEntitlement() throws AccountApiException, EntitlementApiException {
+ final LocalDate initialDate = new LocalDate(2013, 8, 7);
+ clock.setDay(initialDate);
+
+ final Account account = accountApi.createAccount(getAccountData(7), callContext);
+
+ final PlanPhaseSpecifier spec = new PlanPhaseSpecifier("Shotgun", ProductCategory.BASE, BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, null);
+
+ // Keep the same object for the whole test, to make sure we refresh its state before r/w calls
+ testListener.pushExpectedEvent(NextEvent.CREATE);
+ final Entitlement entitlement = entitlementApi.createBaseEntitlement(account.getId(), spec, account.getExternalKey(), initialDate, callContext);
+ assertListenerStatus();
+
+ testListener.pushExpectedEvent(NextEvent.PHASE);
+ clock.addDays(30);
+ assertListenerStatus();
+ subscriptionInternalApi.setChargedThroughDate(entitlement.getId(), clock.getUTCNow().plusMonths(1), internalCallContext);
+
+ final LocalDate entitlementCancelledDate = clock.getToday(account.getTimeZone());
+ testListener.pushExpectedEvent(NextEvent.BLOCK);
+ final Entitlement cancelledEntitlement = entitlement.cancelEntitlementWithDateOverrideBillingPolicy(clock.getToday(account.getTimeZone()), BillingActionPolicy.END_OF_TERM, callContext);
+ assertListenerStatus();
+ Assert.assertEquals(cancelledEntitlement.getEffectiveEndDate(), entitlementCancelledDate);
+
+ testListener.pushExpectedEvent(NextEvent.UNCANCEL);
+ cancelledEntitlement.uncancelEntitlement(callContext);
+ assertListenerStatus();
+
+ final Entitlement reactivatedEntitlement = entitlementApi.getEntitlementForId(cancelledEntitlement.getId(), callContext);
+ Assert.assertNull(reactivatedEntitlement.getEffectiveEndDate());
+ }
+
@Test(groups = "slow")
public void testCreateEntitlementWithCheck() throws AccountApiException, EntitlementApiException {
final LocalDate initialDate = new LocalDate(2013, 8, 7);
diff --git a/entitlement/src/test/java/com/ning/billing/entitlement/api/TestDefaultSubscriptionApi.java b/entitlement/src/test/java/com/ning/billing/entitlement/api/TestDefaultSubscriptionApi.java
index ea79a11..bcfd0f1 100644
--- a/entitlement/src/test/java/com/ning/billing/entitlement/api/TestDefaultSubscriptionApi.java
+++ b/entitlement/src/test/java/com/ning/billing/entitlement/api/TestDefaultSubscriptionApi.java
@@ -17,8 +17,10 @@
package com.ning.billing.entitlement.api;
import java.util.List;
+import java.util.UUID;
import org.joda.time.LocalDate;
+import org.testng.Assert;
import org.testng.annotations.Test;
import com.ning.billing.account.api.Account;
@@ -29,12 +31,56 @@ import com.ning.billing.catalog.api.PlanPhaseSpecifier;
import com.ning.billing.catalog.api.PriceListSet;
import com.ning.billing.catalog.api.ProductCategory;
import com.ning.billing.entitlement.EntitlementTestSuiteWithEmbeddedDB;
+import com.ning.billing.junction.DefaultBlockingState;
+import com.ning.billing.util.api.AuditLevel;
+import com.ning.billing.util.audit.AuditLog;
+import com.ning.billing.util.audit.ChangeType;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertNull;
public class TestDefaultSubscriptionApi extends EntitlementTestSuiteWithEmbeddedDB {
+ @Test(groups = "slow", description = "Verify blocking states are exposed in SubscriptionBundle")
+ public void testBlockingStatesInTimelineApi() throws Exception {
+ final LocalDate initialDate = new LocalDate(2013, 8, 7);
+ clock.setDay(initialDate);
+
+ final Account account = accountApi.createAccount(getAccountData(7), callContext);
+ final PlanPhaseSpecifier spec = new PlanPhaseSpecifier("Shotgun", ProductCategory.BASE, BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, null);
+ testListener.pushExpectedEvents(NextEvent.CREATE, NextEvent.CREATE, NextEvent.BLOCK);
+ final Entitlement entitlement1 = entitlementApi.createBaseEntitlement(account.getId(), spec, UUID.randomUUID().toString(), initialDate, callContext);
+ // Sleep 1 sec so created date are apparts from each other and ordering in the bundle does not default on the UUID which is random.
+ try {Thread.sleep(1000); } catch (InterruptedException ignore) {};
+ final Entitlement entitlement2 = entitlementApi.createBaseEntitlement(account.getId(), spec, UUID.randomUUID().toString(), initialDate, callContext);
+ entitlementUtils.setBlockingStateAndPostBlockingTransitionEvent(new DefaultBlockingState(account.getId(), BlockingStateType.ACCOUNT, "stateName", "service", false, false, false, clock.getUTCNow()),
+ internalCallContextFactory.createInternalCallContext(account.getId(), callContext));
+ assertListenerStatus();
+
+ final List<SubscriptionBundle> bundles = subscriptionApi.getSubscriptionBundlesForAccountId(account.getId(), callContext);
+ Assert.assertEquals(bundles.size(), 2);
+
+ // This will test the ordering as well
+ subscriptionBundleChecker(bundles, initialDate, entitlement1, 0);
+ subscriptionBundleChecker(bundles, initialDate, entitlement2, 1);
+ }
+
+ private void subscriptionBundleChecker(final List<SubscriptionBundle> bundles, final LocalDate initialDate, final Entitlement entitlement, final int idx) {
+ Assert.assertEquals(bundles.get(idx).getId(), entitlement.getBundleId());
+ Assert.assertEquals(bundles.get(idx).getSubscriptions().size(), 1);
+ Assert.assertEquals(bundles.get(idx).getSubscriptions().get(0).getId(), entitlement.getId());
+ Assert.assertEquals(bundles.get(idx).getTimeline().getSubscriptionEvents().size(), 4);
+ Assert.assertEquals(bundles.get(idx).getTimeline().getSubscriptionEvents().get(0).getEffectiveDate(), initialDate);
+ Assert.assertEquals(bundles.get(idx).getTimeline().getSubscriptionEvents().get(0).getSubscriptionEventType(), SubscriptionEventType.START_ENTITLEMENT);
+ Assert.assertEquals(bundles.get(idx).getTimeline().getSubscriptionEvents().get(1).getEffectiveDate(), initialDate);
+ Assert.assertEquals(bundles.get(idx).getTimeline().getSubscriptionEvents().get(1).getSubscriptionEventType(), SubscriptionEventType.START_BILLING);
+ Assert.assertEquals(bundles.get(idx).getTimeline().getSubscriptionEvents().get(2).getEffectiveDate(), initialDate);
+ Assert.assertEquals(bundles.get(idx).getTimeline().getSubscriptionEvents().get(2).getSubscriptionEventType(), SubscriptionEventType.SERVICE_STATE_CHANGE);
+ Assert.assertEquals(bundles.get(idx).getTimeline().getSubscriptionEvents().get(2).getServiceName(), "service");
+ Assert.assertEquals(bundles.get(idx).getTimeline().getSubscriptionEvents().get(2).getServiceStateName(), "stateName");
+ Assert.assertEquals(bundles.get(idx).getTimeline().getSubscriptionEvents().get(3).getEffectiveDate(), new LocalDate(2013, 9, 6));
+ Assert.assertEquals(bundles.get(idx).getTimeline().getSubscriptionEvents().get(3).getSubscriptionEventType(), SubscriptionEventType.PHASE);
+ }
@Test(groups = "slow")
public void testWithMultipleBundle() throws AccountApiException, SubscriptionApiException, EntitlementApiException {
@@ -132,4 +178,56 @@ public class TestDefaultSubscriptionApi extends EntitlementTestSuiteWithEmbedded
assertNull(thirdBundle.getSubscriptions().get(0).getBillingEndDate());
assertEquals(thirdBundle.getOriginalCreatedDate().compareTo(firstbundle.getCreatedDate()), 0);
}
+
+ @Test(groups = "slow", description = "Test for https://github.com/killbill/killbill/issues/136")
+ public void testAuditLogsForEntitlementAndSubscriptionBaseObjects() throws AccountApiException, EntitlementApiException, SubscriptionApiException {
+ final LocalDate initialDate = new LocalDate(2013, 8, 7);
+ clock.setDay(initialDate);
+
+ final Account account = accountApi.createAccount(getAccountData(7), callContext);
+
+ // Create entitlement
+ testListener.pushExpectedEvent(NextEvent.CREATE);
+ final PlanPhaseSpecifier spec = new PlanPhaseSpecifier("Shotgun", ProductCategory.BASE, BillingPeriod.ANNUAL, PriceListSet.DEFAULT_PRICELIST_NAME, null);
+ final Entitlement baseEntitlement = entitlementApi.createBaseEntitlement(account.getId(), spec, account.getExternalKey(), initialDate, callContext);
+ assertListenerStatus();
+
+ // Get the phase event out of the way
+ testListener.pushExpectedEvents(NextEvent.PHASE);
+ clock.setDay(new LocalDate(2013, 9, 7));
+ assertListenerStatus();
+
+ final LocalDate pauseDate = new LocalDate(2013, 9, 17);
+ entitlementApi.pause(baseEntitlement.getBundleId(), pauseDate, callContext);
+
+ final LocalDate resumeDate = new LocalDate(2013, 12, 24);
+ entitlementApi.resume(baseEntitlement.getBundleId(), resumeDate, callContext);
+
+ final LocalDate cancelDate = new LocalDate(2013, 12, 27);
+ baseEntitlement.cancelEntitlementWithDate(cancelDate, true, callContext);
+
+ testListener.pushExpectedEvents(NextEvent.PAUSE, NextEvent.BLOCK, NextEvent.RESUME, NextEvent.BLOCK, NextEvent.CANCEL, NextEvent.BLOCK);
+ clock.setDay(cancelDate.plusDays(1));
+ assertListenerStatus();
+
+ final SubscriptionBundle bundle = subscriptionApi.getSubscriptionBundle(baseEntitlement.getBundleId(), callContext);
+ final List<SubscriptionEvent> transitions = bundle.getTimeline().getSubscriptionEvents();
+ assertEquals(transitions.size(), 9);
+ checkSubscriptionEventAuditLog(transitions, 0, SubscriptionEventType.START_ENTITLEMENT);
+ checkSubscriptionEventAuditLog(transitions, 1, SubscriptionEventType.START_BILLING);
+ checkSubscriptionEventAuditLog(transitions, 2, SubscriptionEventType.PHASE);
+ checkSubscriptionEventAuditLog(transitions, 3, SubscriptionEventType.PAUSE_ENTITLEMENT);
+ checkSubscriptionEventAuditLog(transitions, 4, SubscriptionEventType.PAUSE_BILLING);
+ checkSubscriptionEventAuditLog(transitions, 5, SubscriptionEventType.RESUME_ENTITLEMENT);
+ checkSubscriptionEventAuditLog(transitions, 6, SubscriptionEventType.RESUME_BILLING);
+ checkSubscriptionEventAuditLog(transitions, 7, SubscriptionEventType.STOP_ENTITLEMENT);
+ checkSubscriptionEventAuditLog(transitions, 8, SubscriptionEventType.STOP_BILLING);
+ }
+
+ private void checkSubscriptionEventAuditLog(final List<SubscriptionEvent> transitions, final int idx, final SubscriptionEventType expectedType) {
+ assertEquals(transitions.get(idx).getSubscriptionEventType(), expectedType);
+ final List<AuditLog> auditLogs = auditUserApi.getAuditLogs(transitions.get(idx).getId(), transitions.get(idx).getSubscriptionEventType().getObjectType(), AuditLevel.FULL, callContext);
+ assertEquals(auditLogs.size(), 1);
+ assertEquals(auditLogs.get(0).getChangeType(), ChangeType.INSERT);
+ }
}
diff --git a/entitlement/src/test/java/com/ning/billing/entitlement/api/TestDefaultSubscriptionBundleTimeline.java b/entitlement/src/test/java/com/ning/billing/entitlement/api/TestDefaultSubscriptionBundleTimeline.java
index 359f73c..fd225ad 100644
--- a/entitlement/src/test/java/com/ning/billing/entitlement/api/TestDefaultSubscriptionBundleTimeline.java
+++ b/entitlement/src/test/java/com/ning/billing/entitlement/api/TestDefaultSubscriptionBundleTimeline.java
@@ -29,7 +29,6 @@ import org.testng.Assert;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.Test;
-import com.ning.billing.catalog.api.BillingPeriod;
import com.ning.billing.catalog.api.CatalogApiException;
import com.ning.billing.catalog.api.Plan;
import com.ning.billing.catalog.api.PlanPhase;
@@ -45,6 +44,7 @@ import com.ning.billing.subscription.events.SubscriptionBaseEvent.EventType;
import com.ning.billing.subscription.events.user.ApiEventType;
import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertNull;
public class TestDefaultSubscriptionBundleTimeline extends EntitlementTestSuiteNoDB {
@@ -56,7 +56,6 @@ public class TestDefaultSubscriptionBundleTimeline extends EntitlementTestSuiteN
bundleId = UUID.randomUUID();
}
-
public class TestSubscriptionBundleTimeline extends DefaultSubscriptionBundleTimeline {
public TestSubscriptionBundleTimeline(final DateTimeZone accountTimeZone, final UUID accountId, final UUID bundleId, final String externalKey, final List<Entitlement> entitlements, final List<BlockingState> allBlockingStates) {
@@ -65,33 +64,33 @@ public class TestDefaultSubscriptionBundleTimeline extends EntitlementTestSuiteN
public SubscriptionEvent createEvent(final UUID subscriptionId, final SubscriptionEventType type, final DateTime effectiveDate) {
return new DefaultSubscriptionEvent(UUID.randomUUID(),
- subscriptionId,
- effectiveDate,
- null,
- type,
- true,
- true,
- "foo",
- "bar",
- null,
- null,
- null,
- null,
- null,
- null,
- null,
- null,
- null,
- null,
- null,
- null);
+ subscriptionId,
+ effectiveDate,
+ null,
+ type,
+ true,
+ true,
+ "foo",
+ "bar",
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null);
}
}
@Test(groups = "fast")
public void testReOrderSubscriptionEventsOnInvalidOrder1() {
- TestSubscriptionBundleTimeline timeline = new TestSubscriptionBundleTimeline(null, null, null, null, new ArrayList<Entitlement>(), new ArrayList<BlockingState>());
+ final TestSubscriptionBundleTimeline timeline = new TestSubscriptionBundleTimeline(null, null, null, null, new ArrayList<Entitlement>(), new ArrayList<BlockingState>());
final List<SubscriptionEvent> events = new ArrayList<SubscriptionEvent>();
final UUID subscriptionId = UUID.randomUUID();
@@ -109,10 +108,9 @@ public class TestDefaultSubscriptionBundleTimeline extends EntitlementTestSuiteN
Assert.assertEquals(events.get(3).getSubscriptionEventType(), SubscriptionEventType.STOP_BILLING);
}
-
@Test(groups = "fast")
public void testReOrderSubscriptionEventsOnInvalidOrder2() {
- TestSubscriptionBundleTimeline timeline = new TestSubscriptionBundleTimeline(null, null, null, null, new ArrayList<Entitlement>(), new ArrayList<BlockingState>());
+ final TestSubscriptionBundleTimeline timeline = new TestSubscriptionBundleTimeline(null, null, null, null, new ArrayList<Entitlement>(), new ArrayList<BlockingState>());
final List<SubscriptionEvent> events = new ArrayList<SubscriptionEvent>();
final UUID subscriptionId = UUID.randomUUID();
@@ -132,7 +130,7 @@ public class TestDefaultSubscriptionBundleTimeline extends EntitlementTestSuiteN
@Test(groups = "fast")
public void testReOrderSubscriptionEventsOnInvalidOrder3() {
- TestSubscriptionBundleTimeline timeline = new TestSubscriptionBundleTimeline(null, null, null, null, new ArrayList<Entitlement>(), new ArrayList<BlockingState>());
+ final TestSubscriptionBundleTimeline timeline = new TestSubscriptionBundleTimeline(null, null, null, null, new ArrayList<Entitlement>(), new ArrayList<BlockingState>());
final List<SubscriptionEvent> events = new ArrayList<SubscriptionEvent>();
final UUID subscriptionId = UUID.randomUUID();
@@ -150,10 +148,9 @@ public class TestDefaultSubscriptionBundleTimeline extends EntitlementTestSuiteN
Assert.assertEquals(events.get(3).getSubscriptionEventType(), SubscriptionEventType.STOP_BILLING);
}
-
@Test(groups = "fast")
public void testReOrderSubscriptionEventsOnInvalidOrderAndDifferentSubscriptionsSameDates1() {
- TestSubscriptionBundleTimeline timeline = new TestSubscriptionBundleTimeline(null, null, null, null, new ArrayList<Entitlement>(), new ArrayList<BlockingState>());
+ final TestSubscriptionBundleTimeline timeline = new TestSubscriptionBundleTimeline(null, null, null, null, new ArrayList<Entitlement>(), new ArrayList<BlockingState>());
final List<SubscriptionEvent> events = new ArrayList<SubscriptionEvent>();
final UUID subscriptionId = UUID.fromString("60b64e0c-cefd-48c3-8de9-c731a9558165");
@@ -180,7 +177,7 @@ public class TestDefaultSubscriptionBundleTimeline extends EntitlementTestSuiteN
@Test(groups = "fast")
public void testReOrderSubscriptionEventsOnInvalidOrderAndDifferentSubscriptionsSameDates2() {
- TestSubscriptionBundleTimeline timeline = new TestSubscriptionBundleTimeline(null, null, null, null, new ArrayList<Entitlement>(), new ArrayList<BlockingState>());
+ final TestSubscriptionBundleTimeline timeline = new TestSubscriptionBundleTimeline(null, null, null, null, new ArrayList<Entitlement>(), new ArrayList<BlockingState>());
final List<SubscriptionEvent> events = new ArrayList<SubscriptionEvent>();
final UUID subscriptionId = UUID.fromString("35b3b340-31b2-46ea-b062-e9fc9fab3bc9");
@@ -208,7 +205,7 @@ public class TestDefaultSubscriptionBundleTimeline extends EntitlementTestSuiteN
@Test(groups = "fast")
public void testReOrderSubscriptionEventsOnInvalidOrderAndDifferentSubscriptionsDates() {
- TestSubscriptionBundleTimeline timeline = new TestSubscriptionBundleTimeline(null, null, null, null, new ArrayList<Entitlement>(), new ArrayList<BlockingState>());
+ final TestSubscriptionBundleTimeline timeline = new TestSubscriptionBundleTimeline(null, null, null, null, new ArrayList<Entitlement>(), new ArrayList<BlockingState>());
final List<SubscriptionEvent> events = new ArrayList<SubscriptionEvent>();
final UUID subscriptionId = UUID.randomUUID();
@@ -242,13 +239,11 @@ public class TestDefaultSubscriptionBundleTimeline extends EntitlementTestSuiteN
Assert.assertEquals(events.get(7).getSubscriptionEventType(), SubscriptionEventType.PAUSE_BILLING);
Assert.assertEquals(events.get(8).getSubscriptionEventType(), SubscriptionEventType.STOP_ENTITLEMENT);
Assert.assertEquals(events.get(9).getSubscriptionEventType(), SubscriptionEventType.STOP_BILLING);
-
-
}
@Test(groups = "fast")
public void testReOrderSubscriptionEventsOnCorrectOrder() {
- TestSubscriptionBundleTimeline timeline = new TestSubscriptionBundleTimeline(null, null, null, null, new ArrayList<Entitlement>(), new ArrayList<BlockingState>());
+ final TestSubscriptionBundleTimeline timeline = new TestSubscriptionBundleTimeline(null, null, null, null, new ArrayList<Entitlement>(), new ArrayList<BlockingState>());
final List<SubscriptionEvent> events = new ArrayList<SubscriptionEvent>();
final UUID subscriptionId = UUID.randomUUID();
@@ -268,7 +263,6 @@ public class TestDefaultSubscriptionBundleTimeline extends EntitlementTestSuiteN
@Test(groups = "fast")
public void testOneEntitlementNoBlockingStates() throws CatalogApiException {
-
clock.setDay(new LocalDate(2013, 1, 1));
final DateTimeZone accountTimeZone = DateTimeZone.UTC;
@@ -276,7 +270,6 @@ public class TestDefaultSubscriptionBundleTimeline extends EntitlementTestSuiteN
final UUID bundleId = UUID.randomUUID();
final String externalKey = "foo";
-
final UUID entitlementId = UUID.randomUUID();
final List<SubscriptionBaseTransition> allTransitions = new ArrayList<SubscriptionBaseTransition>();
@@ -291,24 +284,22 @@ public class TestDefaultSubscriptionBundleTimeline extends EntitlementTestSuiteN
final SubscriptionBaseTransition tr2 = createTransition(entitlementId, EventType.PHASE, null, requestedDate, effectiveDate, clock.getUTCNow(), "trial", "phase");
allTransitions.add(tr2);
-
effectiveDate = effectiveDate.plusDays(15);
clock.addDays(15);
final SubscriptionBaseTransition tr3 = createTransition(entitlementId, EventType.API_USER, ApiEventType.CANCEL, requestedDate, effectiveDate, clock.getUTCNow(), "phase", null);
allTransitions.add(tr3);
-
final List<Entitlement> entitlements = new ArrayList<Entitlement>();
final Entitlement entitlement = createEntitlement(entitlementId, allTransitions);
entitlements.add(entitlement);
- final DefaultSubscriptionBundleTimeline timeline = new DefaultSubscriptionBundleTimeline(accountTimeZone, accountId, bundleId, externalKey, entitlements, Collections.<BlockingState>emptyList());
+ final SubscriptionBundleTimeline timeline = new DefaultSubscriptionBundleTimeline(accountTimeZone, accountId, bundleId, externalKey, entitlements, Collections.<BlockingState>emptyList());
assertEquals(timeline.getAccountId(), accountId);
assertEquals(timeline.getBundleId(), bundleId);
assertEquals(timeline.getExternalKey(), externalKey);
- List<SubscriptionEvent> events = timeline.getSubscriptionEvents();
+ final List<SubscriptionEvent> events = timeline.getSubscriptionEvents();
assertEquals(events.size(), 4);
assertEquals(events.get(0).getEffectiveDate().compareTo(new LocalDate(tr1.getEffectiveTransitionTime(), accountTimeZone)), 0);
@@ -321,14 +312,23 @@ public class TestDefaultSubscriptionBundleTimeline extends EntitlementTestSuiteN
assertEquals(events.get(2).getSubscriptionEventType(), SubscriptionEventType.PHASE);
assertEquals(events.get(3).getSubscriptionEventType(), SubscriptionEventType.STOP_BILLING);
+ assertEquals(events.get(0).getServiceName(), DefaultEntitlementService.ENTITLEMENT_SERVICE_NAME);
+ assertEquals(events.get(1).getServiceName(), DefaultSubscriptionBundleTimeline.BILLING_SERVICE_NAME);
+ assertEquals(events.get(2).getServiceName(), DefaultSubscriptionBundleTimeline.ENT_BILLING_SERVICE_NAME);
+ assertEquals(events.get(3).getServiceName(), DefaultSubscriptionBundleTimeline.BILLING_SERVICE_NAME);
+
+ assertNull(events.get(0).getPrevPhase());
assertEquals(events.get(0).getNextPhase().getName(), "trial");
+ assertNull(events.get(1).getPrevPhase());
assertEquals(events.get(1).getNextPhase().getName(), "trial");
+ assertEquals(events.get(2).getPrevPhase().getName(), "trial");
assertEquals(events.get(2).getNextPhase().getName(), "phase");
- assertEquals(events.get(3).getNextPhase(), null);
+ assertEquals(events.get(3).getPrevPhase().getName(), "phase");
+ assertNull(events.get(3).getNextPhase());
}
- @Test(groups = "fast")
- public void testOneEntitlementWithRecreate() throws CatalogApiException {
+ @Test(groups = "fast", description = "Test for https://github.com/killbill/killbill/issues/135")
+ public void testOneEntitlementWithPauseResume() throws CatalogApiException {
clock.setDay(new LocalDate(2013, 1, 1));
final DateTimeZone accountTimeZone = DateTimeZone.UTC;
@@ -339,6 +339,7 @@ public class TestDefaultSubscriptionBundleTimeline extends EntitlementTestSuiteN
final UUID entitlementId = UUID.randomUUID();
final List<SubscriptionBaseTransition> allTransitions = new ArrayList<SubscriptionBaseTransition>();
+ final List<BlockingState> blockingStates = new ArrayList<BlockingState>();
final DateTime requestedDate = new DateTime();
DateTime effectiveDate = new DateTime(2013, 1, 1, 15, 43, 25, 0, DateTimeZone.UTC);
@@ -350,57 +351,112 @@ public class TestDefaultSubscriptionBundleTimeline extends EntitlementTestSuiteN
final SubscriptionBaseTransition tr2 = createTransition(entitlementId, EventType.PHASE, null, requestedDate, effectiveDate, clock.getUTCNow(), "trial", "phase");
allTransitions.add(tr2);
+ effectiveDate = effectiveDate.plusDays(12);
+ clock.addDays(12);
+ final BlockingState bs1 = new DefaultBlockingState(UUID.randomUUID(), entitlementId, BlockingStateType.SUBSCRIPTION,
+ "NothingUseful1", DefaultEntitlementService.ENTITLEMENT_SERVICE_NAME,
+ true, true, true, effectiveDate, clock.getUTCNow(), clock.getUTCNow());
+
+ blockingStates.add(bs1);
+
+ effectiveDate = effectiveDate.plusDays(42);
+ clock.addDays(42);
+ final BlockingState bs2 = new DefaultBlockingState(UUID.randomUUID(), entitlementId, BlockingStateType.SUBSCRIPTION,
+ "NothingUseful2", DefaultEntitlementService.ENTITLEMENT_SERVICE_NAME,
+ false, false, false, effectiveDate, clock.getUTCNow(), clock.getUTCNow());
+
+ blockingStates.add(bs2);
+
effectiveDate = effectiveDate.plusDays(15);
clock.addDays(15);
- final SubscriptionBaseTransition tr3 = createTransition(entitlementId, EventType.API_USER, ApiEventType.CANCEL, requestedDate, effectiveDate, clock.getUTCNow(), "phase", null);
- allTransitions.add(tr3);
+ final String service = "boo";
+ final BlockingState bs3 = new DefaultBlockingState(UUID.randomUUID(), entitlementId, BlockingStateType.SUBSCRIPTION,
+ "NothingUseful3", service,
+ false, false, true, effectiveDate, clock.getUTCNow(), clock.getUTCNow());
+
+ blockingStates.add(bs3);
effectiveDate = effectiveDate.plusDays(15);
clock.addDays(15);
- final SubscriptionBaseTransition tr4 = createTransition(entitlementId, EventType.API_USER, ApiEventType.RE_CREATE, requestedDate, effectiveDate, clock.getUTCNow(), null, "phase");
- allTransitions.add(tr4);
+ final BlockingState bs4 = new DefaultBlockingState(UUID.randomUUID(), entitlementId, BlockingStateType.SUBSCRIPTION,
+ "NothingUseful4", service,
+ false, false, false, effectiveDate, clock.getUTCNow(), clock.getUTCNow());
+
+ blockingStates.add(bs4);
final List<Entitlement> entitlements = new ArrayList<Entitlement>();
final Entitlement entitlement = createEntitlement(entitlementId, allTransitions);
entitlements.add(entitlement);
- final SubscriptionBundleTimeline timeline = new DefaultSubscriptionBundleTimeline(accountTimeZone, accountId, bundleId, externalKey, entitlements, Collections.<BlockingState>emptyList());
+ final SubscriptionBundleTimeline timeline = new DefaultSubscriptionBundleTimeline(accountTimeZone, accountId, bundleId, externalKey, entitlements, blockingStates);
assertEquals(timeline.getAccountId(), accountId);
assertEquals(timeline.getBundleId(), bundleId);
assertEquals(timeline.getExternalKey(), externalKey);
final List<SubscriptionEvent> events = timeline.getSubscriptionEvents();
- assertEquals(events.size(), 7);
+ assertEquals(events.size(), 9);
assertEquals(events.get(0).getEffectiveDate().compareTo(new LocalDate(tr1.getEffectiveTransitionTime(), accountTimeZone)), 0);
assertEquals(events.get(1).getEffectiveDate().compareTo(new LocalDate(tr1.getEffectiveTransitionTime(), accountTimeZone)), 0);
assertEquals(events.get(2).getEffectiveDate().compareTo(new LocalDate(tr2.getEffectiveTransitionTime(), accountTimeZone)), 0);
- assertEquals(events.get(3).getEffectiveDate().compareTo(new LocalDate(tr3.getEffectiveTransitionTime(), accountTimeZone)), 0);
- assertEquals(events.get(4).getEffectiveDate().compareTo(new LocalDate(tr3.getEffectiveTransitionTime(), accountTimeZone)), 0);
- assertEquals(events.get(5).getEffectiveDate().compareTo(new LocalDate(tr4.getEffectiveTransitionTime(), accountTimeZone)), 0);
- assertEquals(events.get(6).getEffectiveDate().compareTo(new LocalDate(tr4.getEffectiveTransitionTime(), accountTimeZone)), 0);
+ assertEquals(events.get(3).getEffectiveDate().compareTo(new LocalDate(bs1.getEffectiveDate(), accountTimeZone)), 0);
+ assertEquals(events.get(4).getEffectiveDate().compareTo(new LocalDate(bs1.getEffectiveDate(), accountTimeZone)), 0);
+ assertEquals(events.get(5).getEffectiveDate().compareTo(new LocalDate(bs2.getEffectiveDate(), accountTimeZone)), 0);
+ assertEquals(events.get(6).getEffectiveDate().compareTo(new LocalDate(bs2.getEffectiveDate(), accountTimeZone)), 0);
+ assertEquals(events.get(7).getEffectiveDate().compareTo(new LocalDate(bs3.getEffectiveDate(), accountTimeZone)), 0);
+ assertEquals(events.get(8).getEffectiveDate().compareTo(new LocalDate(bs4.getEffectiveDate(), accountTimeZone)), 0);
assertEquals(events.get(0).getSubscriptionEventType(), SubscriptionEventType.START_ENTITLEMENT);
assertEquals(events.get(1).getSubscriptionEventType(), SubscriptionEventType.START_BILLING);
+
assertEquals(events.get(2).getSubscriptionEventType(), SubscriptionEventType.PHASE);
+
assertEquals(events.get(3).getSubscriptionEventType(), SubscriptionEventType.PAUSE_ENTITLEMENT);
assertEquals(events.get(4).getSubscriptionEventType(), SubscriptionEventType.PAUSE_BILLING);
assertEquals(events.get(5).getSubscriptionEventType(), SubscriptionEventType.RESUME_ENTITLEMENT);
assertEquals(events.get(6).getSubscriptionEventType(), SubscriptionEventType.RESUME_BILLING);
+ assertEquals(events.get(7).getSubscriptionEventType(), SubscriptionEventType.SERVICE_STATE_CHANGE);
+ assertEquals(events.get(8).getSubscriptionEventType(), SubscriptionEventType.SERVICE_STATE_CHANGE);
+
+ assertEquals(events.get(0).getServiceName(), DefaultEntitlementService.ENTITLEMENT_SERVICE_NAME);
+ assertEquals(events.get(1).getServiceName(), DefaultSubscriptionBundleTimeline.BILLING_SERVICE_NAME);
+
+ assertEquals(events.get(2).getServiceName(), DefaultSubscriptionBundleTimeline.ENT_BILLING_SERVICE_NAME);
+
+ assertEquals(events.get(3).getServiceName(), DefaultEntitlementService.ENTITLEMENT_SERVICE_NAME);
+ assertEquals(events.get(4).getServiceName(), DefaultSubscriptionBundleTimeline.BILLING_SERVICE_NAME);
+ assertEquals(events.get(5).getServiceName(), DefaultEntitlementService.ENTITLEMENT_SERVICE_NAME);
+ assertEquals(events.get(6).getServiceName(), DefaultSubscriptionBundleTimeline.BILLING_SERVICE_NAME);
+
+ assertEquals(events.get(7).getServiceName(), service);
+ assertEquals(events.get(8).getServiceName(), service);
+
+ assertNull(events.get(0).getPrevPhase());
assertEquals(events.get(0).getNextPhase().getName(), "trial");
+ assertNull(events.get(1).getPrevPhase());
assertEquals(events.get(1).getNextPhase().getName(), "trial");
+
+ assertEquals(events.get(2).getPrevPhase().getName(), "trial");
assertEquals(events.get(2).getNextPhase().getName(), "phase");
- assertEquals(events.get(3).getNextPhase(), null);
- assertEquals(events.get(4).getNextPhase(), null);
+
+ assertEquals(events.get(3).getPrevPhase().getName(), "phase");
+ assertEquals(events.get(3).getNextPhase().getName(), "phase");
+ assertEquals(events.get(4).getPrevPhase().getName(), "phase");
+ assertEquals(events.get(4).getNextPhase().getName(), "phase");
+ assertEquals(events.get(5).getPrevPhase().getName(), "phase");
assertEquals(events.get(5).getNextPhase().getName(), "phase");
+ assertEquals(events.get(6).getPrevPhase().getName(), "phase");
assertEquals(events.get(6).getNextPhase().getName(), "phase");
+ assertEquals(events.get(7).getPrevPhase().getName(), "phase");
+ assertEquals(events.get(7).getNextPhase().getName(), "phase");
+ assertEquals(events.get(8).getPrevPhase().getName(), "phase");
+ assertEquals(events.get(8).getNextPhase().getName(), "phase");
}
@Test(groups = "fast")
public void testOneEntitlementWithInitialBlockingState() throws CatalogApiException {
-
clock.setDay(new LocalDate(2013, 1, 1));
final DateTimeZone accountTimeZone = DateTimeZone.UTC;
@@ -408,7 +464,6 @@ public class TestDefaultSubscriptionBundleTimeline extends EntitlementTestSuiteN
final UUID bundleId = UUID.randomUUID();
final String externalKey = "foo";
-
final UUID entitlementId = UUID.randomUUID();
final List<SubscriptionBaseTransition> allTransitions = new ArrayList<SubscriptionBaseTransition>();
@@ -431,31 +486,29 @@ public class TestDefaultSubscriptionBundleTimeline extends EntitlementTestSuiteN
final SubscriptionBaseTransition tr2 = createTransition(entitlementId, EventType.PHASE, null, requestedDate, effectiveDate, clock.getUTCNow(), "trial", "phase");
allTransitions.add(tr2);
-
+ final String service = "boo";
final BlockingState bs2 = new DefaultBlockingState(UUID.randomUUID(), entitlementId, BlockingStateType.SUBSCRIPTION,
- "NothingUseful", "boo",
+ "NothingUseful", service,
false, false, false, clock.getUTCNow(), clock.getUTCNow(), clock.getUTCNow());
blockingStates.add(bs2);
-
effectiveDate = effectiveDate.plusDays(15);
clock.addDays(15);
final SubscriptionBaseTransition tr3 = createTransition(entitlementId, EventType.API_USER, ApiEventType.CANCEL, requestedDate, effectiveDate, clock.getUTCNow(), "phase", null);
allTransitions.add(tr3);
-
final List<Entitlement> entitlements = new ArrayList<Entitlement>();
final Entitlement entitlement = createEntitlement(entitlementId, allTransitions);
entitlements.add(entitlement);
- final DefaultSubscriptionBundleTimeline timeline = new DefaultSubscriptionBundleTimeline(accountTimeZone, accountId, bundleId, externalKey, entitlements, blockingStates);
+ final SubscriptionBundleTimeline timeline = new DefaultSubscriptionBundleTimeline(accountTimeZone, accountId, bundleId, externalKey, entitlements, blockingStates);
assertEquals(timeline.getAccountId(), accountId);
assertEquals(timeline.getBundleId(), bundleId);
assertEquals(timeline.getExternalKey(), externalKey);
- List<SubscriptionEvent> events = timeline.getSubscriptionEvents();
+ final List<SubscriptionEvent> events = timeline.getSubscriptionEvents();
assertEquals(events.size(), 5);
assertEquals(events.get(0).getEffectiveDate().compareTo(new LocalDate(tr1.getEffectiveTransitionTime(), accountTimeZone)), 0);
@@ -470,18 +523,26 @@ public class TestDefaultSubscriptionBundleTimeline extends EntitlementTestSuiteN
assertEquals(events.get(3).getSubscriptionEventType(), SubscriptionEventType.SERVICE_STATE_CHANGE);
assertEquals(events.get(4).getSubscriptionEventType(), SubscriptionEventType.STOP_BILLING);
+ assertEquals(events.get(0).getServiceName(), DefaultEntitlementService.ENTITLEMENT_SERVICE_NAME);
+ assertEquals(events.get(1).getServiceName(), DefaultSubscriptionBundleTimeline.BILLING_SERVICE_NAME);
+ assertEquals(events.get(2).getServiceName(), DefaultSubscriptionBundleTimeline.ENT_BILLING_SERVICE_NAME);
+ assertEquals(events.get(3).getServiceName(), service);
+ assertEquals(events.get(4).getServiceName(), DefaultSubscriptionBundleTimeline.BILLING_SERVICE_NAME);
+
+ assertNull(events.get(0).getPrevPhase());
assertEquals(events.get(0).getNextPhase().getName(), "trial");
+ assertNull(events.get(1).getPrevPhase());
assertEquals(events.get(1).getNextPhase().getName(), "trial");
+ assertEquals(events.get(2).getPrevPhase().getName(), "trial");
assertEquals(events.get(2).getNextPhase().getName(), "phase");
+ assertEquals(events.get(3).getPrevPhase().getName(), "phase");
assertEquals(events.get(3).getNextPhase().getName(), "phase");
- assertEquals(events.get(4).getNextPhase(), null);
+ assertEquals(events.get(4).getPrevPhase().getName(), "phase");
+ assertNull(events.get(4).getNextPhase());
}
-
-
@Test(groups = "fast")
public void testOneEntitlementWithBlockingStatesSubscription() throws CatalogApiException {
-
clock.setDay(new LocalDate(2013, 1, 1));
final DateTimeZone accountTimeZone = DateTimeZone.UTC;
@@ -489,7 +550,6 @@ public class TestDefaultSubscriptionBundleTimeline extends EntitlementTestSuiteN
final UUID bundleId = UUID.randomUUID();
final String externalKey = "foo";
-
final UUID entitlementId = UUID.randomUUID();
final List<SubscriptionBaseTransition> allTransitions = new ArrayList<SubscriptionBaseTransition>();
@@ -526,10 +586,9 @@ public class TestDefaultSubscriptionBundleTimeline extends EntitlementTestSuiteN
final Entitlement entitlement = createEntitlement(entitlementId, allTransitions);
entitlements.add(entitlement);
+ final SubscriptionBundleTimeline timeline = new DefaultSubscriptionBundleTimeline(accountTimeZone, accountId, bundleId, externalKey, entitlements, blockingStates);
- final DefaultSubscriptionBundleTimeline timeline = new DefaultSubscriptionBundleTimeline(accountTimeZone, accountId, bundleId, externalKey, entitlements, blockingStates);
-
- List<SubscriptionEvent> events = timeline.getSubscriptionEvents();
+ final List<SubscriptionEvent> events = timeline.getSubscriptionEvents();
assertEquals(events.size(), 6);
assertEquals(events.get(0).getEffectiveDate().compareTo(new LocalDate(tr1.getEffectiveTransitionTime(), accountTimeZone)), 0);
@@ -546,24 +605,32 @@ public class TestDefaultSubscriptionBundleTimeline extends EntitlementTestSuiteN
assertEquals(events.get(4).getSubscriptionEventType(), SubscriptionEventType.STOP_ENTITLEMENT);
assertEquals(events.get(5).getSubscriptionEventType(), SubscriptionEventType.STOP_BILLING);
- assertEquals(events.get(0).getPrevPhase(), null);
+ assertEquals(events.get(0).getServiceName(), DefaultEntitlementService.ENTITLEMENT_SERVICE_NAME);
+ assertEquals(events.get(1).getServiceName(), DefaultSubscriptionBundleTimeline.BILLING_SERVICE_NAME);
+ assertEquals(events.get(2).getServiceName(), DefaultSubscriptionBundleTimeline.ENT_BILLING_SERVICE_NAME);
+ assertEquals(events.get(3).getServiceName(), DefaultEntitlementService.ENTITLEMENT_SERVICE_NAME);
+ assertEquals(events.get(4).getServiceName(), DefaultEntitlementService.ENTITLEMENT_SERVICE_NAME);
+ assertEquals(events.get(5).getServiceName(), DefaultSubscriptionBundleTimeline.BILLING_SERVICE_NAME);
+
+ assertNull(events.get(0).getPrevPhase());
assertEquals(events.get(0).getNextPhase().getName(), "trial");
- assertEquals(events.get(1).getPrevPhase(), null);
+ assertNull(events.get(1).getPrevPhase());
assertEquals(events.get(1).getNextPhase().getName(), "trial");
+
assertEquals(events.get(2).getPrevPhase().getName(), "trial");
assertEquals(events.get(2).getNextPhase().getName(), "phase");
+
assertEquals(events.get(3).getPrevPhase().getName(), "phase");
assertEquals(events.get(3).getNextPhase().getName(), "phase");
+
assertEquals(events.get(4).getPrevPhase().getName(), "phase");
- assertEquals(events.get(4).getNextPhase(), null);
+ assertNull(events.get(4).getNextPhase());
assertEquals(events.get(5).getPrevPhase().getName(), "phase");
- assertEquals(events.get(5).getNextPhase(), null);
+ assertNull(events.get(5).getNextPhase());
}
-
@Test(groups = "fast")
public void testWithMultipleEntitlements() throws CatalogApiException {
-
clock.setDay(new LocalDate(2013, 1, 1));
final DateTimeZone accountTimeZone = DateTimeZone.UTC;
@@ -571,7 +638,6 @@ public class TestDefaultSubscriptionBundleTimeline extends EntitlementTestSuiteN
final UUID bundleId = UUID.randomUUID();
final String externalKey = "foo";
-
final UUID entitlementId1 = UUID.fromString("cf5a597a-cf15-45d3-8f02-95371be7f927");
final UUID entitlementId2 = UUID.fromString("e37cc97a-7b98-4ab6-a29a-7259e45c3366");
@@ -584,7 +650,6 @@ public class TestDefaultSubscriptionBundleTimeline extends EntitlementTestSuiteN
final SubscriptionBaseTransition ent1Tr1 = createTransition(entitlementId1, EventType.API_USER, ApiEventType.CREATE, requestedDate, effectiveDate, clock.getUTCNow(), null, "trial1");
allTransitions1.add(ent1Tr1);
-
effectiveDate = effectiveDate.plusDays(15);
clock.addDays(15);
final SubscriptionBaseTransition ent2Tr1 = createTransition(entitlementId2, EventType.API_USER, ApiEventType.TRANSFER, requestedDate, effectiveDate, clock.getUTCNow(), null, "phase2");
@@ -619,10 +684,9 @@ public class TestDefaultSubscriptionBundleTimeline extends EntitlementTestSuiteN
final Entitlement entitlement2 = createEntitlement(entitlementId2, allTransitions2);
entitlements.add(entitlement2);
+ final SubscriptionBundleTimeline timeline = new DefaultSubscriptionBundleTimeline(accountTimeZone, accountId, bundleId, externalKey, entitlements, blockingStates);
- final DefaultSubscriptionBundleTimeline timeline = new DefaultSubscriptionBundleTimeline(accountTimeZone, accountId, bundleId, externalKey, entitlements, blockingStates);
-
- List<SubscriptionEvent> events = timeline.getSubscriptionEvents();
+ final List<SubscriptionEvent> events = timeline.getSubscriptionEvents();
assertEquals(events.size(), 9);
assertEquals(events.get(0).getEffectiveDate().compareTo(new LocalDate(ent1Tr1.getEffectiveTransitionTime(), accountTimeZone)), 0);
@@ -651,13 +715,27 @@ public class TestDefaultSubscriptionBundleTimeline extends EntitlementTestSuiteN
assertEquals(events.get(7).getSubscriptionEventType(), SubscriptionEventType.STOP_ENTITLEMENT);
assertEquals(events.get(8).getSubscriptionEventType(), SubscriptionEventType.STOP_BILLING);
- assertEquals(events.get(0).getPrevPhase(), null);
+ assertEquals(events.get(0).getServiceName(), DefaultEntitlementService.ENTITLEMENT_SERVICE_NAME);
+ assertEquals(events.get(1).getServiceName(), DefaultSubscriptionBundleTimeline.BILLING_SERVICE_NAME);
+ assertEquals(events.get(2).getServiceName(), DefaultEntitlementService.ENTITLEMENT_SERVICE_NAME);
+ assertEquals(events.get(3).getServiceName(), DefaultSubscriptionBundleTimeline.BILLING_SERVICE_NAME);
+
+ assertEquals(events.get(4).getServiceName(), DefaultSubscriptionBundleTimeline.ENT_BILLING_SERVICE_NAME);
+
+ assertEquals(events.get(5).getServiceName(), DefaultEntitlementService.ENTITLEMENT_SERVICE_NAME);
+ assertEquals(events.get(6).getServiceName(), DefaultEntitlementService.ENTITLEMENT_SERVICE_NAME);
+
+ assertEquals(events.get(7).getServiceName(), DefaultEntitlementService.ENTITLEMENT_SERVICE_NAME);
+ assertEquals(events.get(8).getServiceName(), DefaultSubscriptionBundleTimeline.BILLING_SERVICE_NAME);
+
+ assertNull(events.get(0).getPrevPhase());
assertEquals(events.get(0).getNextPhase().getName(), "trial1");
- assertEquals(events.get(1).getPrevPhase(), null);
+ assertNull(events.get(1).getPrevPhase());
assertEquals(events.get(1).getNextPhase().getName(), "trial1");
- assertEquals(events.get(2).getPrevPhase(), null);
+
+ assertNull(events.get(2).getPrevPhase());
assertEquals(events.get(2).getNextPhase().getName(), "phase2");
- assertEquals(events.get(3).getPrevPhase(), null);
+ assertNull(events.get(3).getPrevPhase());
assertEquals(events.get(3).getNextPhase().getName(), "phase2");
assertEquals(events.get(4).getPrevPhase().getName(), "trial1");
@@ -667,20 +745,17 @@ public class TestDefaultSubscriptionBundleTimeline extends EntitlementTestSuiteN
assertEquals(events.get(5).getNextPhase().getName(), "phase1");
assertEquals(events.get(6).getPrevPhase().getName(), "phase2");
- assertEquals(events.get(6).getNextPhase(), null);
+ assertEquals(events.get(6).getNextPhase().getName(), "phase2");
assertEquals(events.get(7).getPrevPhase().getName(), "phase1");
- assertEquals(events.get(7).getNextPhase(), null);
+ assertNull(events.get(7).getNextPhase());
assertEquals(events.get(8).getPrevPhase().getName(), "phase1");
- assertEquals(events.get(8).getNextPhase(), null);
-
+ assertNull(events.get(8).getNextPhase());
}
-
-
@Test(groups = "fast")
- public void testWithOverdueOfflineAndClear() throws CatalogApiException {
+ public void testWithOverdueOffline() throws CatalogApiException {
clock.setDay(new LocalDate(2013, 1, 1));
final DateTimeZone accountTimeZone = DateTimeZone.UTC;
@@ -704,45 +779,24 @@ public class TestDefaultSubscriptionBundleTimeline extends EntitlementTestSuiteN
final SubscriptionBaseTransition tr2 = createTransition(entitlementId, EventType.PHASE, null, requestedDate, effectiveDate, clock.getUTCNow(), "trial", "phase");
allTransitions.add(tr2);
- effectiveDate = effectiveDate.plusDays(6); // 2013-02-06
- clock.addDays(6);
+ effectiveDate = effectiveDate.plusDays(40); // 2013-03-12
+ clock.addDays(40);
final SubscriptionBaseTransition tr3 = createTransition(entitlementId, EventType.API_USER, ApiEventType.CANCEL, requestedDate, effectiveDate, clock.getUTCNow(), "phase", null);
allTransitions.add(tr3);
- effectiveDate = effectiveDate.plusDays(22);// 2013-02-28
- clock.addDays(22);
- final SubscriptionBaseTransition tr4 = createTransition(entitlementId, EventType.API_USER, ApiEventType.RE_CREATE, requestedDate, effectiveDate, clock.getUTCNow(), null, "phase");
- allTransitions.add(tr4);
-
- effectiveDate = effectiveDate.plusDays(12); // 2013-03-12
- clock.addDays(12);
- final SubscriptionBaseTransition tr5 = createTransition(entitlementId, EventType.API_USER, ApiEventType.CANCEL, requestedDate, effectiveDate, clock.getUTCNow(), "phase", null);
- allTransitions.add(tr5);
-
+ final String service = "overdue-service";
final BlockingState bs1 = new DefaultBlockingState(UUID.randomUUID(), entitlementId, BlockingStateType.ACCOUNT,
- "OFFLINE", "overdue-service",
+ "OFFLINE", service,
true, true, false, effectiveDate, clock.getUTCNow(), clock.getUTCNow());
blockingStates.add(bs1);
-
final BlockingState bs2 = new DefaultBlockingState(UUID.randomUUID(), entitlementId, BlockingStateType.SUBSCRIPTION,
DefaultEntitlementApi.ENT_STATE_CANCELLED, DefaultEntitlementService.ENTITLEMENT_SERVICE_NAME,
true, true, false, effectiveDate, clock.getUTCNow(), clock.getUTCNow());
blockingStates.add(bs2);
-
- effectiveDate = effectiveDate.plusDays(12); // 2013-03-24
- clock.addDays(12);
-
- final BlockingState bs3 = new DefaultBlockingState(UUID.randomUUID(), entitlementId, BlockingStateType.ACCOUNT,
- "__KILLBILL__CLEAR__OVERDUE__STATE__", "overdue-service",
- false, false, false, effectiveDate, clock.getUTCNow(), clock.getUTCNow());
-
- blockingStates.add(bs3);
-
-
final List<Entitlement> entitlements = new ArrayList<Entitlement>();
final Entitlement entitlement = createEntitlement(entitlementId, allTransitions);
entitlements.add(entitlement);
@@ -754,48 +808,132 @@ public class TestDefaultSubscriptionBundleTimeline extends EntitlementTestSuiteN
assertEquals(timeline.getExternalKey(), externalKey);
final List<SubscriptionEvent> events = timeline.getSubscriptionEvents();
- assertEquals(events.size(), 11);
+ assertEquals(events.size(), 6);
assertEquals(events.get(0).getEffectiveDate().compareTo(new LocalDate(tr1.getEffectiveTransitionTime(), accountTimeZone)), 0);
assertEquals(events.get(1).getEffectiveDate().compareTo(new LocalDate(tr1.getEffectiveTransitionTime(), accountTimeZone)), 0);
assertEquals(events.get(2).getEffectiveDate().compareTo(new LocalDate(tr2.getEffectiveTransitionTime(), accountTimeZone)), 0);
assertEquals(events.get(3).getEffectiveDate().compareTo(new LocalDate(tr3.getEffectiveTransitionTime(), accountTimeZone)), 0);
assertEquals(events.get(4).getEffectiveDate().compareTo(new LocalDate(tr3.getEffectiveTransitionTime(), accountTimeZone)), 0);
- assertEquals(events.get(5).getEffectiveDate().compareTo(new LocalDate(tr4.getEffectiveTransitionTime(), accountTimeZone)), 0);
- assertEquals(events.get(6).getEffectiveDate().compareTo(new LocalDate(tr4.getEffectiveTransitionTime(), accountTimeZone)), 0);
- assertEquals(events.get(7).getEffectiveDate().compareTo(new LocalDate(tr5.getEffectiveTransitionTime(), accountTimeZone)), 0);
- assertEquals(events.get(8).getEffectiveDate().compareTo(new LocalDate(tr5.getEffectiveTransitionTime(), accountTimeZone)), 0);
- assertEquals(events.get(9).getEffectiveDate().compareTo(new LocalDate(tr5.getEffectiveTransitionTime(), accountTimeZone)), 0);
- assertEquals(events.get(10).getEffectiveDate().compareTo(new LocalDate(bs3.getEffectiveDate(), accountTimeZone)), 0);
+ assertEquals(events.get(5).getEffectiveDate().compareTo(new LocalDate(bs1.getEffectiveDate(), accountTimeZone)), 0);
assertEquals(events.get(0).getSubscriptionEventType(), SubscriptionEventType.START_ENTITLEMENT);
assertEquals(events.get(1).getSubscriptionEventType(), SubscriptionEventType.START_BILLING);
assertEquals(events.get(2).getSubscriptionEventType(), SubscriptionEventType.PHASE);
- assertEquals(events.get(3).getSubscriptionEventType(), SubscriptionEventType.PAUSE_ENTITLEMENT);
- assertEquals(events.get(4).getSubscriptionEventType(), SubscriptionEventType.PAUSE_BILLING);
- assertEquals(events.get(5).getSubscriptionEventType(), SubscriptionEventType.RESUME_ENTITLEMENT);
- assertEquals(events.get(6).getSubscriptionEventType(), SubscriptionEventType.RESUME_BILLING);
- assertEquals(events.get(7).getSubscriptionEventType(), SubscriptionEventType.STOP_ENTITLEMENT);
- assertEquals(events.get(8).getSubscriptionEventType(), SubscriptionEventType.STOP_BILLING);
- assertEquals(events.get(9).getSubscriptionEventType(), SubscriptionEventType.SERVICE_STATE_CHANGE);
- assertEquals(events.get(10).getSubscriptionEventType(), SubscriptionEventType.SERVICE_STATE_CHANGE);
+ assertEquals(events.get(3).getSubscriptionEventType(), SubscriptionEventType.STOP_ENTITLEMENT);
+ assertEquals(events.get(4).getSubscriptionEventType(), SubscriptionEventType.STOP_BILLING);
+ assertEquals(events.get(5).getSubscriptionEventType(), SubscriptionEventType.SERVICE_STATE_CHANGE);
+
+ assertEquals(events.get(0).getServiceName(), DefaultEntitlementService.ENTITLEMENT_SERVICE_NAME);
+ assertEquals(events.get(1).getServiceName(), DefaultSubscriptionBundleTimeline.BILLING_SERVICE_NAME);
+ assertEquals(events.get(2).getServiceName(), DefaultSubscriptionBundleTimeline.ENT_BILLING_SERVICE_NAME);
+ assertEquals(events.get(3).getServiceName(), DefaultEntitlementService.ENTITLEMENT_SERVICE_NAME);
+ assertEquals(events.get(4).getServiceName(), DefaultSubscriptionBundleTimeline.BILLING_SERVICE_NAME);
+ assertEquals(events.get(5).getServiceName(), service);
+ assertNull(events.get(0).getPrevPhase());
assertEquals(events.get(0).getNextPhase().getName(), "trial");
+ assertNull(events.get(1).getPrevPhase());
assertEquals(events.get(1).getNextPhase().getName(), "trial");
+
+ assertEquals(events.get(2).getPrevPhase().getName(), "trial");
assertEquals(events.get(2).getNextPhase().getName(), "phase");
- assertEquals(events.get(3).getNextPhase(), null);
- assertEquals(events.get(4).getNextPhase(), null);
- assertEquals(events.get(5).getNextPhase().getName(), "phase");
- assertEquals(events.get(6).getNextPhase().getName(), "phase");
- assertEquals(events.get(7).getNextPhase(), null);
- assertEquals(events.get(8).getNextPhase(), null);
- assertEquals(events.get(9).getNextPhase(), null);
- assertEquals(events.get(10).getNextPhase(), null);
+
+ assertEquals(events.get(3).getPrevPhase().getName(), "phase");
+ assertNull(events.get(3).getNextPhase());
+ assertEquals(events.get(4).getPrevPhase().getName(), "phase");
+ assertNull(events.get(4).getNextPhase());
+
+ assertEquals(events.get(5).getPrevPhase().getName(), "phase");
+ assertNull(events.get(5).getNextPhase());
}
+ @Test(groups = "fast", description = "Test for https://github.com/killbill/killbill/issues/134")
+ public void testRemoveOverlappingBlockingStates() throws CatalogApiException {
+ clock.setDay(new LocalDate(2013, 1, 1));
+
+ final DateTimeZone accountTimeZone = DateTimeZone.UTC;
+ final UUID accountId = UUID.randomUUID();
+ final UUID bundleId = UUID.randomUUID();
+ final String externalKey = "foo";
+
+ final UUID entitlementId = UUID.randomUUID();
+
+ final List<SubscriptionBaseTransition> allTransitions = new ArrayList<SubscriptionBaseTransition>();
+ final List<BlockingState> blockingStates = new ArrayList<BlockingState>();
+
+ final DateTime requestedDate = new DateTime();
+ DateTime effectiveDate = new DateTime(2013, 1, 1, 15, 43, 25, 0, DateTimeZone.UTC);
+ final SubscriptionBaseTransition tr1 = createTransition(entitlementId, EventType.API_USER, ApiEventType.CREATE, requestedDate, effectiveDate, clock.getUTCNow(), null, "trial");
+ allTransitions.add(tr1);
+
+ // Overlapping ENT_STATE_BLOCKED - should merge
+ effectiveDate = effectiveDate.plusDays(5);
+ clock.addDays(5);
+ final BlockingState bs1 = new DefaultBlockingState(UUID.randomUUID(), entitlementId, BlockingStateType.SUBSCRIPTION,
+ DefaultEntitlementApi.ENT_STATE_BLOCKED, DefaultEntitlementService.ENTITLEMENT_SERVICE_NAME,
+ true, true, false, effectiveDate, clock.getUTCNow(), clock.getUTCNow());
+ blockingStates.add(bs1);
+
+ effectiveDate = effectiveDate.plusDays(1);
+ clock.addDays(1);
+ final BlockingState bs2 = new DefaultBlockingState(UUID.randomUUID(), bundleId, BlockingStateType.SUBSCRIPTION_BUNDLE,
+ DefaultEntitlementApi.ENT_STATE_BLOCKED, DefaultEntitlementService.ENTITLEMENT_SERVICE_NAME,
+ true, true, false, effectiveDate, clock.getUTCNow(), clock.getUTCNow());
+
+ blockingStates.add(bs2);
+
+ // Overlapping ENT_STATE_CANCELLED - should merge
+ effectiveDate = effectiveDate.plusDays(1);
+ clock.addDays(1);
+ final BlockingState bs3 = new DefaultBlockingState(UUID.randomUUID(), accountId, BlockingStateType.ACCOUNT,
+ DefaultEntitlementApi.ENT_STATE_CANCELLED, DefaultEntitlementService.ENTITLEMENT_SERVICE_NAME,
+ true, true, false, effectiveDate, clock.getUTCNow(), clock.getUTCNow());
+
+ blockingStates.add(bs3);
+ final BlockingState bs4 = new DefaultBlockingState(UUID.randomUUID(), bundleId, BlockingStateType.SUBSCRIPTION_BUNDLE,
+ DefaultEntitlementApi.ENT_STATE_CANCELLED, DefaultEntitlementService.ENTITLEMENT_SERVICE_NAME,
+ true, true, false, effectiveDate, clock.getUTCNow(), clock.getUTCNow());
+
+ blockingStates.add(bs4);
+
+ final List<Entitlement> entitlements = new ArrayList<Entitlement>();
+ final Entitlement entitlement = createEntitlement(entitlementId, allTransitions);
+ entitlements.add(entitlement);
- private DefaultEntitlement createEntitlement(final UUID entitlementId, final List<SubscriptionBaseTransition> allTransitions) {
+ final SubscriptionBundleTimeline timeline = new DefaultSubscriptionBundleTimeline(accountTimeZone, accountId, bundleId, externalKey, entitlements, blockingStates);
+ final List<SubscriptionEvent> events = timeline.getSubscriptionEvents();
+ assertEquals(events.size(), 4);
+
+ assertEquals(events.get(0).getEffectiveDate().compareTo(new LocalDate(tr1.getEffectiveTransitionTime(), accountTimeZone)), 0);
+ assertEquals(events.get(1).getEffectiveDate().compareTo(new LocalDate(tr1.getEffectiveTransitionTime(), accountTimeZone)), 0);
+ assertEquals(events.get(2).getEffectiveDate().compareTo(new LocalDate(bs1.getEffectiveDate(), accountTimeZone)), 0);
+ assertEquals(events.get(3).getEffectiveDate().compareTo(new LocalDate(bs3.getEffectiveDate(), accountTimeZone)), 0);
+
+ assertEquals(events.get(0).getSubscriptionEventType(), SubscriptionEventType.START_ENTITLEMENT);
+ assertEquals(events.get(1).getSubscriptionEventType(), SubscriptionEventType.START_BILLING);
+ assertEquals(events.get(2).getSubscriptionEventType(), SubscriptionEventType.PAUSE_ENTITLEMENT);
+ assertEquals(events.get(3).getSubscriptionEventType(), SubscriptionEventType.STOP_ENTITLEMENT);
+
+ assertEquals(events.get(0).getServiceName(), DefaultEntitlementService.ENTITLEMENT_SERVICE_NAME);
+ assertEquals(events.get(1).getServiceName(), DefaultSubscriptionBundleTimeline.BILLING_SERVICE_NAME);
+ assertEquals(events.get(2).getServiceName(), DefaultEntitlementService.ENTITLEMENT_SERVICE_NAME);
+ assertEquals(events.get(3).getServiceName(), DefaultEntitlementService.ENTITLEMENT_SERVICE_NAME);
+
+ assertNull(events.get(0).getPrevPhase());
+ assertEquals(events.get(0).getNextPhase().getName(), "trial");
+ assertNull(events.get(1).getPrevPhase());
+ assertEquals(events.get(1).getNextPhase().getName(), "trial");
+
+ assertEquals(events.get(2).getPrevPhase().getName(), "trial");
+ assertEquals(events.get(2).getNextPhase().getName(), "trial");
+
+ assertEquals(events.get(3).getPrevPhase().getName(), "trial");
+ assertNull(events.get(3).getNextPhase());
+ }
+
+ private Entitlement createEntitlement(final UUID entitlementId, final List<SubscriptionBaseTransition> allTransitions) {
final DefaultEntitlement result = Mockito.mock(DefaultEntitlement.class);
Mockito.when(result.getId()).thenReturn(entitlementId);
@@ -805,13 +943,6 @@ public class TestDefaultSubscriptionBundleTimeline extends EntitlementTestSuiteN
return result;
}
- private String computePhaseName(final UUID entitlementId, final EventType eventType, final ApiEventType apiEventType) {
- return "phase-" +
- entitlementId.toString() +
- "-" +
- (eventType == EventType.API_USER ? apiEventType : eventType);
- }
-
private SubscriptionBaseTransition createTransition(final UUID entitlementId,
final EventType eventType,
final ApiEventType apiEventType,
@@ -821,58 +952,76 @@ public class TestDefaultSubscriptionBundleTimeline extends EntitlementTestSuiteN
final String prevPhaseName,
final String nextPhaseName
) throws CatalogApiException {
+ final PlanPhase prevPhase;
+ final Plan prevPlan;
+ final Product prevProduct;
+ final PriceList prevPriceList;
+ if (prevPhaseName == null) {
+ prevPhase = null;
+ prevPlan = null;
+ prevProduct = null;
+ prevPriceList = null;
+ } else {
+ prevPhase = Mockito.mock(PlanPhase.class);
+ Mockito.when(prevPhase.getName()).thenReturn(prevPhaseName);
+ prevProduct = Mockito.mock(Product.class);
+ Mockito.when(prevProduct.getName()).thenReturn("product");
- final PlanPhase prevPhase = prevPhaseName != null ? Mockito.mock(PlanPhase.class) : null;
- if (prevPhase != null) {
- Mockito.when(prevPhase.getName()).thenReturn(prevPhaseName);
+ prevPlan = Mockito.mock(Plan.class);
+ Mockito.when(prevPlan.getName()).thenReturn("plan");
+ Mockito.when(prevPlan.getProduct()).thenReturn(prevProduct);
+
+ prevPriceList = Mockito.mock(PriceList.class);
+ Mockito.when(prevPriceList.getName()).thenReturn("pricelist");
}
- final PlanPhase nextPhase = nextPhaseName != null ? Mockito.mock(PlanPhase.class) : null;
- if (nextPhase != null) {
+ final PlanPhase nextPhase;
+ final Plan nextPlan;
+ final Product nextProduct;
+ final PriceList nextPriceList;
+ if (nextPhaseName == null) {
+ nextPhase = null;
+ nextPlan = null;
+ nextProduct = null;
+ nextPriceList = null;
+ } else {
+ nextPhase = Mockito.mock(PlanPhase.class);
Mockito.when(nextPhase.getName()).thenReturn(nextPhaseName);
+
+ nextProduct = Mockito.mock(Product.class);
+ Mockito.when(nextProduct.getName()).thenReturn("product");
+
+ nextPlan = Mockito.mock(Plan.class);
+ Mockito.when(nextPlan.getName()).thenReturn("plan");
+ Mockito.when(nextPlan.getProduct()).thenReturn(nextProduct);
+
+ nextPriceList = Mockito.mock(PriceList.class);
+ Mockito.when(nextPriceList.getName()).thenReturn("pricelist");
}
- //catalogService.getCurrentCatalog().findCurrentPhase("pistol-monthly-trial");
- final Plan plan = Mockito.mock(Plan.class);
- Mockito.when(plan.getName()).thenReturn("plan");
-
-
- //catalogService.getCurrentCatalog().findCurrentPlan("pistol-monthly");
- final Product product = Mockito.mock(Product.class);
- Mockito.when(product.getName()).thenReturn("product");
-
- //catalogService.getCurrentCatalog().findCurrentProduct("Pistol");
-
- final PriceList priceList = Mockito.mock(PriceList.class);
- Mockito.when(priceList.getName()).thenReturn("pricelist");
-
- //catalogService.getCurrentCatalog().findCurrentPricelist(PriceListSet.DEFAULT_PRICELIST_NAME);
- final BillingPeriod billingPeriod = BillingPeriod.MONTHLY;
-
- final SubscriptionBaseTransition transition = new SubscriptionBaseTransitionData(UUID.randomUUID(),
- entitlementId,
- bundleId,
- eventType,
- apiEventType,
- requestedDate,
- effectiveDate,
- null,
- null,
- null,
- plan,
- prevPhase,
- priceList,
- null,
- null,
- null,
- plan,
- nextPhase,
- priceList,
- 1L,
- createdDate,
- UUID.randomUUID(),
- true);
- return transition;
+ return new SubscriptionBaseTransitionData(UUID.randomUUID(),
+ entitlementId,
+ bundleId,
+ eventType,
+ apiEventType,
+ requestedDate,
+ effectiveDate,
+ null,
+ null,
+ null,
+ prevPlan,
+ prevPhase,
+ prevPriceList,
+ null,
+ null,
+ null,
+ nextPlan,
+ nextPhase,
+ nextPriceList,
+ 1L,
+ createdDate,
+ UUID.randomUUID(),
+ true);
}
}
diff --git a/entitlement/src/test/java/com/ning/billing/entitlement/block/TestBlockingApi.java b/entitlement/src/test/java/com/ning/billing/entitlement/block/TestBlockingApi.java
index e227e1d..57073a4 100644
--- a/entitlement/src/test/java/com/ning/billing/entitlement/block/TestBlockingApi.java
+++ b/entitlement/src/test/java/com/ning/billing/entitlement/block/TestBlockingApi.java
@@ -23,11 +23,15 @@ import org.testng.Assert;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;
+import com.ning.billing.account.api.Account;
import com.ning.billing.api.TestApiListener.NextEvent;
+import com.ning.billing.callcontext.InternalCallContext;
import com.ning.billing.entitlement.EntitlementTestSuiteWithEmbeddedDB;
import com.ning.billing.entitlement.api.BlockingState;
import com.ning.billing.entitlement.api.BlockingStateType;
import com.ning.billing.junction.DefaultBlockingState;
+import com.ning.billing.util.callcontext.CallOrigin;
+import com.ning.billing.util.callcontext.UserType;
import com.google.common.base.Predicate;
import com.google.common.collect.Collections2;
@@ -77,6 +81,9 @@ public class TestBlockingApi extends EntitlementTestSuiteWithEmbeddedDB {
final boolean blockEntitlement = false;
final boolean blockBilling = false;
+ final Account account = accountApi.createAccount(getAccountData(7), callContext);
+ final InternalCallContext internalCallContext = internalCallContextFactory.createInternalCallContext(account.getId(), "TestBlockingApi", CallOrigin.TEST, UserType.SYSTEM, UUID.randomUUID());
+
testListener.pushExpectedEvent(NextEvent.BLOCK);
final BlockingState state1 = new DefaultBlockingState(uuid, BlockingStateType.ACCOUNT, overdueStateName, service, blockChange, blockEntitlement, blockBilling, clock.getUTCNow());
blockingInternalApi.setBlockingState(state1, internalCallContext);
@@ -90,7 +97,7 @@ public class TestBlockingApi extends EntitlementTestSuiteWithEmbeddedDB {
blockingInternalApi.setBlockingState(state2, internalCallContext);
assertListenerStatus();
- final List<BlockingState> blockingAll = blockingInternalApi.getBlockingAll(uuid, BlockingStateType.ACCOUNT, internalCallContext);
+ final List<BlockingState> blockingAll = blockingInternalApi.getBlockingAllForAccount(internalCallContext);
final List<BlockingState> history = ImmutableList.<BlockingState>copyOf(Collections2.<BlockingState>filter(blockingAll,
new Predicate<BlockingState>() {
@Override
diff --git a/entitlement/src/test/java/com/ning/billing/entitlement/dao/MockBlockingStateDao.java b/entitlement/src/test/java/com/ning/billing/entitlement/dao/MockBlockingStateDao.java
index f2bff3d..5f1faf6 100644
--- a/entitlement/src/test/java/com/ning/billing/entitlement/dao/MockBlockingStateDao.java
+++ b/entitlement/src/test/java/com/ning/billing/entitlement/dao/MockBlockingStateDao.java
@@ -32,6 +32,7 @@ import com.ning.billing.entitlement.api.BlockingStateType;
import com.ning.billing.entitlement.api.EntitlementApiException;
import com.ning.billing.util.entity.dao.MockEntityDaoBase;
+import com.google.common.base.Objects;
import com.google.common.base.Predicate;
import com.google.common.collect.Collections2;
import com.google.common.collect.ImmutableList;
@@ -39,12 +40,13 @@ import com.google.common.collect.ImmutableList;
public class MockBlockingStateDao extends MockEntityDaoBase<BlockingStateModelDao, BlockingState, EntitlementApiException> implements BlockingStateDao {
private final Map<UUID, List<BlockingState>> blockingStates = new HashMap<UUID, List<BlockingState>>();
+ private final Map<Long, List<BlockingState>> blockingStatesPerAccountRecordId = new HashMap<Long, List<BlockingState>>();
- // TODO This mock class should also check that events are past or present except for getBlockingAll
+ // TODO This mock class should also check that events are past or present
@Override
public BlockingState getBlockingStateForService(final UUID blockableId, final BlockingStateType blockingStateType, final String serviceName, final InternalTenantContext context) {
- final List<BlockingState> states = getBlockingAll(blockableId, blockingStateType, context);
+ final List<BlockingState> states = blockingStates.get(blockableId);
if (states == null) {
return null;
}
@@ -75,32 +77,8 @@ public class MockBlockingStateDao extends MockEntityDaoBase<BlockingStateModelDa
}
@Override
- public List<BlockingState> getBlockingHistoryForService(final UUID overdueableId, final BlockingStateType blockingStateType, final String serviceName, final InternalTenantContext context) {
- final List<BlockingState> states = blockingStates.get(overdueableId);
- if (states == null) {
- return new ArrayList<BlockingState>();
- }
- final ImmutableList<BlockingState> filtered = ImmutableList.<BlockingState>copyOf(Collections2.filter(states, new Predicate<BlockingState>() {
- @Override
- public boolean apply(@Nullable final BlockingState input) {
- return input.getService().equals(serviceName);
- }
- }));
-
- // Note! The returned list cannot be immutable!
- return states == null ? new ArrayList<BlockingState>() : new ArrayList<BlockingState>(filtered);
- }
-
- @Override
- public List<BlockingState> getBlockingAll(final UUID blockableId, final BlockingStateType blockingStateType, final InternalTenantContext context) {
- final List<BlockingState> states = blockingStates.get(blockableId);
- // Note! The returned list cannot be immutable!
- return states == null ? new ArrayList<BlockingState>() : states;
- }
-
- @Override
public List<BlockingState> getBlockingAllForAccountRecordId(final InternalTenantContext context) {
- throw new UnsupportedOperationException();
+ return Objects.firstNonNull(blockingStatesPerAccountRecordId.get(context.getAccountRecordId()), ImmutableList.<BlockingState>of());
}
@Override
@@ -109,6 +87,11 @@ public class MockBlockingStateDao extends MockEntityDaoBase<BlockingStateModelDa
blockingStates.put(state.getBlockedId(), new ArrayList<BlockingState>());
}
blockingStates.get(state.getBlockedId()).add(state);
+
+ if (blockingStatesPerAccountRecordId.get(context.getAccountRecordId()) == null) {
+ blockingStatesPerAccountRecordId.put(context.getAccountRecordId(), new ArrayList<BlockingState>());
+ }
+ blockingStatesPerAccountRecordId.get(context.getAccountRecordId()).add(state);
}
@Override
@@ -116,11 +99,8 @@ public class MockBlockingStateDao extends MockEntityDaoBase<BlockingStateModelDa
throw new UnsupportedOperationException();
}
- public synchronized void setBlockingStates(final UUID blockedId, final List<BlockingState> states) {
- blockingStates.put(blockedId, states);
- }
-
public synchronized void clear() {
blockingStates.clear();
+ blockingStatesPerAccountRecordId.clear();
}
}
diff --git a/entitlement/src/test/java/com/ning/billing/entitlement/dao/TestBlockingDao.java b/entitlement/src/test/java/com/ning/billing/entitlement/dao/TestBlockingDao.java
index db37ccd..264e810 100644
--- a/entitlement/src/test/java/com/ning/billing/entitlement/dao/TestBlockingDao.java
+++ b/entitlement/src/test/java/com/ning/billing/entitlement/dao/TestBlockingDao.java
@@ -21,8 +21,10 @@ import java.util.UUID;
import org.joda.time.LocalDate;
import org.testng.Assert;
+import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;
+import com.ning.billing.account.api.Account;
import com.ning.billing.entitlement.EntitlementTestSuiteWithEmbeddedDB;
import com.ning.billing.entitlement.api.BlockingState;
import com.ning.billing.entitlement.api.BlockingStateType;
@@ -30,9 +32,16 @@ import com.ning.billing.junction.DefaultBlockingState;
public class TestBlockingDao extends EntitlementTestSuiteWithEmbeddedDB {
- @Test(groups = "slow")
- public void testDao() {
+ @BeforeMethod(groups = "slow")
+ public void setUp() throws Exception {
+ final Account account = accountApi.createAccount(getAccountData(7), callContext);
+ // Override the context with the right account record id
+ internalCallContext = internalCallContextFactory.createInternalCallContext(account.getId(), callContext);
+ }
+
+ @Test(groups = "slow", description = "Check BlockingStateDao with a single service")
+ public void testDaoWithOneService() {
final UUID uuid = UUID.randomUUID();
final String overdueStateName = "WayPassedItMan";
final String service = "TEST";
@@ -54,15 +63,15 @@ public class TestBlockingDao extends EntitlementTestSuiteWithEmbeddedDB {
Assert.assertEquals(blockingStateDao.getBlockingStateForService(uuid, BlockingStateType.ACCOUNT, service, internalCallContext).getStateName(), state2.getStateName());
- final List<BlockingState> states = blockingStateDao.getBlockingHistoryForService(uuid, BlockingStateType.ACCOUNT, service, internalCallContext);
+ final List<BlockingState> states = blockingStateDao.getBlockingAllForAccountRecordId(internalCallContext);
Assert.assertEquals(states.size(), 2);
Assert.assertEquals(states.get(0).getStateName(), overdueStateName);
Assert.assertEquals(states.get(1).getStateName(), overdueStateName2);
}
- @Test(groups = "slow")
- public void testDaoHistory() throws Exception {
+ @Test(groups = "slow", description = "Check BlockingStateDao with multiple services")
+ public void testDaoWithMultipleServices() throws Exception {
final UUID uuid = UUID.randomUUID();
final String overdueStateName = "WayPassedItMan";
final String service1 = "TEST";
@@ -81,7 +90,7 @@ public class TestBlockingDao extends EntitlementTestSuiteWithEmbeddedDB {
final BlockingState state2 = new DefaultBlockingState(uuid, BlockingStateType.ACCOUNT, overdueStateName2, service2, blockChange, blockEntitlement, blockBilling, clock.getUTCNow());
blockingStateDao.setBlockingState(state2, clock, internalCallContext);
- final List<BlockingState> history2 = blockingStateDao.getBlockingAll(uuid, BlockingStateType.ACCOUNT, internalCallContext);
+ final List<BlockingState> history2 = blockingStateDao.getBlockingAllForAccountRecordId(internalCallContext);
Assert.assertEquals(history2.size(), 2);
Assert.assertEquals(history2.get(0).getStateName(), overdueStateName);
Assert.assertEquals(history2.get(1).getStateName(), overdueStateName2);
diff --git a/entitlement/src/test/java/com/ning/billing/entitlement/dao/TestDefaultBlockingStateDao.java b/entitlement/src/test/java/com/ning/billing/entitlement/dao/TestDefaultBlockingStateDao.java
index ed26f65..06989c9 100644
--- a/entitlement/src/test/java/com/ning/billing/entitlement/dao/TestDefaultBlockingStateDao.java
+++ b/entitlement/src/test/java/com/ning/billing/entitlement/dao/TestDefaultBlockingStateDao.java
@@ -22,6 +22,7 @@ import java.util.UUID;
import org.joda.time.DateTime;
import org.joda.time.DateTimeZone;
import org.testng.Assert;
+import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;
import com.ning.billing.account.api.Account;
@@ -38,12 +39,21 @@ import com.ning.billing.junction.DefaultBlockingState;
public class TestDefaultBlockingStateDao extends EntitlementTestSuiteWithEmbeddedDB {
+ private Account account;
+
+ @BeforeMethod(groups = "slow")
+ public void setUp() throws Exception {
+ account = accountApi.createAccount(getAccountData(7), callContext);
+
+ // Override the context with the right account record id
+ internalCallContext = internalCallContextFactory.createInternalCallContext(account.getId(), callContext);
+ }
+
@Test(groups = "slow", description = "Verify we don't insert extra add-on events")
public void testUnnecessaryEventsAreNotAdded() throws Exception {
// This is a simple smoke test at the dao level only to make sure we do sane
// things in case there are no future add-on cancellation events to add in the stream.
// See TestEntitlementUtils for a more comprehensive test
- final Account account = accountApi.createAccount(getAccountData(7), callContext);
final PlanPhaseSpecifier spec = new PlanPhaseSpecifier("Shotgun", ProductCategory.BASE, BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, null);
testListener.pushExpectedEvent(NextEvent.CREATE);
final Entitlement entitlement = entitlementApi.createBaseEntitlement(account.getId(), spec, account.getExternalKey(), clock.getUTCToday(), callContext);
@@ -54,14 +64,14 @@ public class TestDefaultBlockingStateDao extends EntitlementTestSuiteWithEmbedde
final String service = "service";
// Verify initial state
- Assert.assertEquals(blockingStateDao.getBlockingAll(entitlement.getId(), type, internalCallContext).size(), 0);
+ Assert.assertEquals(blockingStateDao.getBlockingAllForAccountRecordId(internalCallContext).size(), 0);
// Set a state
final DateTime stateDateTime = new DateTime(2013, 5, 6, 10, 11, 12, DateTimeZone.UTC);
final BlockingState blockingState = new DefaultBlockingState(entitlement.getId(), type, state, service, false, false, false, stateDateTime);
blockingStateDao.setBlockingState(blockingState, clock, internalCallContext);
- Assert.assertEquals(blockingStateDao.getBlockingAll(entitlement.getId(), type, internalCallContext).size(), 1);
+ Assert.assertEquals(blockingStateDao.getBlockingAllForAccountRecordId(internalCallContext).size(), 1);
}
// See https://github.com/killbill/killbill/issues/111
@@ -75,7 +85,7 @@ public class TestDefaultBlockingStateDao extends EntitlementTestSuiteWithEmbedde
final String serviceB = "service-B";
// Verify initial state
- Assert.assertEquals(blockingStateDao.getBlockingAll(blockableId, type, internalCallContext).size(), 0);
+ Assert.assertEquals(blockingStateDao.getBlockingAllForAccountRecordId(internalCallContext).size(), 0);
// Note: the checkers below rely on record_id ordering, not effective date
@@ -83,7 +93,7 @@ public class TestDefaultBlockingStateDao extends EntitlementTestSuiteWithEmbedde
final DateTime stateDateTime = new DateTime(2013, 5, 6, 10, 11, 12, DateTimeZone.UTC);
final BlockingState blockingState1 = new DefaultBlockingState(blockableId, type, state, serviceA, false, false, false, stateDateTime);
blockingStateDao.setBlockingState(blockingState1, clock, internalCallContext);
- final List<BlockingState> blockingStates1 = blockingStateDao.getBlockingAll(blockableId, type, internalCallContext);
+ final List<BlockingState> blockingStates1 = blockingStateDao.getBlockingAllForAccountRecordId(internalCallContext);
Assert.assertEquals(blockingStates1.size(), 1);
Assert.assertEquals(blockingStates1.get(0).getBlockedId(), blockableId);
Assert.assertEquals(blockingStates1.get(0).getStateName(), state);
@@ -92,7 +102,7 @@ public class TestDefaultBlockingStateDao extends EntitlementTestSuiteWithEmbedde
// Set the same state again - no change
blockingStateDao.setBlockingState(blockingState1, clock, internalCallContext);
- final List<BlockingState> blockingStates2 = blockingStateDao.getBlockingAll(blockableId, type, internalCallContext);
+ final List<BlockingState> blockingStates2 = blockingStateDao.getBlockingAllForAccountRecordId(internalCallContext);
Assert.assertEquals(blockingStates2.size(), 1);
Assert.assertEquals(blockingStates2.get(0).getBlockedId(), blockableId);
Assert.assertEquals(blockingStates2.get(0).getStateName(), state);
@@ -102,7 +112,7 @@ public class TestDefaultBlockingStateDao extends EntitlementTestSuiteWithEmbedde
// Set the state for service B
final BlockingState blockingState2 = new DefaultBlockingState(blockableId, type, state, serviceB, false, false, false, stateDateTime);
blockingStateDao.setBlockingState(blockingState2, clock, internalCallContext);
- final List<BlockingState> blockingStates3 = blockingStateDao.getBlockingAll(blockableId, type, internalCallContext);
+ final List<BlockingState> blockingStates3 = blockingStateDao.getBlockingAllForAccountRecordId(internalCallContext);
Assert.assertEquals(blockingStates3.size(), 2);
Assert.assertEquals(blockingStates3.get(0).getBlockedId(), blockableId);
Assert.assertEquals(blockingStates3.get(0).getStateName(), state);
@@ -117,7 +127,7 @@ public class TestDefaultBlockingStateDao extends EntitlementTestSuiteWithEmbedde
final DateTime stateDateTime2 = new DateTime(2013, 6, 6, 10, 11, 12, DateTimeZone.UTC);
final BlockingState blockingState3 = new DefaultBlockingState(blockableId, type, state, serviceA, false, false, false, stateDateTime2);
blockingStateDao.setBlockingState(blockingState3, clock, internalCallContext);
- final List<BlockingState> blockingStates4 = blockingStateDao.getBlockingAll(blockableId, type, internalCallContext);
+ final List<BlockingState> blockingStates4 = blockingStateDao.getBlockingAllForAccountRecordId(internalCallContext);
Assert.assertEquals(blockingStates4.size(), 2);
Assert.assertEquals(blockingStates4.get(0).getBlockedId(), blockableId);
Assert.assertEquals(blockingStates4.get(0).getStateName(), state);
@@ -132,31 +142,31 @@ public class TestDefaultBlockingStateDao extends EntitlementTestSuiteWithEmbedde
final DateTime stateDateTime3 = new DateTime(2013, 2, 6, 10, 11, 12, DateTimeZone.UTC);
final BlockingState blockingState4 = new DefaultBlockingState(blockableId, type, state, serviceA, false, false, false, stateDateTime3);
blockingStateDao.setBlockingState(blockingState4, clock, internalCallContext);
- final List<BlockingState> blockingStates5 = blockingStateDao.getBlockingAll(blockableId, type, internalCallContext);
+ final List<BlockingState> blockingStates5 = blockingStateDao.getBlockingAllForAccountRecordId(internalCallContext);
Assert.assertEquals(blockingStates5.size(), 2);
Assert.assertEquals(blockingStates5.get(0).getBlockedId(), blockableId);
Assert.assertEquals(blockingStates5.get(0).getStateName(), state);
- Assert.assertEquals(blockingStates5.get(0).getService(), serviceB);
- Assert.assertEquals(blockingStates5.get(0).getEffectiveDate(), stateDateTime);
+ Assert.assertEquals(blockingStates5.get(0).getService(), serviceA);
+ Assert.assertEquals(blockingStates5.get(0).getEffectiveDate(), stateDateTime3);
Assert.assertEquals(blockingStates5.get(1).getBlockedId(), blockableId);
Assert.assertEquals(blockingStates5.get(1).getStateName(), state);
- Assert.assertEquals(blockingStates5.get(1).getService(), serviceA);
- Assert.assertEquals(blockingStates5.get(1).getEffectiveDate(), stateDateTime3);
+ Assert.assertEquals(blockingStates5.get(1).getService(), serviceB);
+ Assert.assertEquals(blockingStates5.get(1).getEffectiveDate(), stateDateTime);
// Set a new state for service A
final DateTime state2DateTime = new DateTime(2013, 12, 6, 10, 11, 12, DateTimeZone.UTC);
final BlockingState blockingState5 = new DefaultBlockingState(blockableId, type, state2, serviceA, false, false, false, state2DateTime);
blockingStateDao.setBlockingState(blockingState5, clock, internalCallContext);
- final List<BlockingState> blockingStates6 = blockingStateDao.getBlockingAll(blockableId, type, internalCallContext);
+ final List<BlockingState> blockingStates6 = blockingStateDao.getBlockingAllForAccountRecordId(internalCallContext);
Assert.assertEquals(blockingStates6.size(), 3);
Assert.assertEquals(blockingStates6.get(0).getBlockedId(), blockableId);
Assert.assertEquals(blockingStates6.get(0).getStateName(), state);
- Assert.assertEquals(blockingStates6.get(0).getService(), serviceB);
- Assert.assertEquals(blockingStates6.get(0).getEffectiveDate(), stateDateTime);
+ Assert.assertEquals(blockingStates6.get(0).getService(), serviceA);
+ Assert.assertEquals(blockingStates6.get(0).getEffectiveDate(), stateDateTime3);
Assert.assertEquals(blockingStates6.get(1).getBlockedId(), blockableId);
Assert.assertEquals(blockingStates6.get(1).getStateName(), state);
- Assert.assertEquals(blockingStates6.get(1).getService(), serviceA);
- Assert.assertEquals(blockingStates6.get(1).getEffectiveDate(), stateDateTime3);
+ Assert.assertEquals(blockingStates6.get(1).getService(), serviceB);
+ Assert.assertEquals(blockingStates6.get(1).getEffectiveDate(), stateDateTime);
Assert.assertEquals(blockingStates6.get(2).getBlockedId(), blockableId);
Assert.assertEquals(blockingStates6.get(2).getStateName(), state2);
Assert.assertEquals(blockingStates6.get(2).getService(), serviceA);
diff --git a/entitlement/src/test/java/com/ning/billing/entitlement/engine/core/TestEntitlementUtils.java b/entitlement/src/test/java/com/ning/billing/entitlement/engine/core/TestEntitlementUtils.java
index a0c387c..034a147 100644
--- a/entitlement/src/test/java/com/ning/billing/entitlement/engine/core/TestEntitlementUtils.java
+++ b/entitlement/src/test/java/com/ning/billing/entitlement/engine/core/TestEntitlementUtils.java
@@ -18,6 +18,7 @@ package com.ning.billing.entitlement.engine.core;
import java.util.Collection;
import java.util.List;
+import java.util.UUID;
import javax.annotation.Nullable;
@@ -29,6 +30,7 @@ import org.testng.annotations.Test;
import com.ning.billing.account.api.Account;
import com.ning.billing.api.TestApiListener.NextEvent;
+import com.ning.billing.callcontext.InternalTenantContext;
import com.ning.billing.catalog.api.BillingActionPolicy;
import com.ning.billing.catalog.api.BillingPeriod;
import com.ning.billing.catalog.api.PlanPhaseSpecifier;
@@ -36,6 +38,7 @@ import com.ning.billing.catalog.api.PriceListSet;
import com.ning.billing.catalog.api.ProductCategory;
import com.ning.billing.entitlement.EntitlementService;
import com.ning.billing.entitlement.EntitlementTestSuiteWithEmbeddedDB;
+import com.ning.billing.entitlement.EventsStream;
import com.ning.billing.entitlement.api.BlockingState;
import com.ning.billing.entitlement.api.BlockingStateType;
import com.ning.billing.entitlement.api.DefaultEntitlement;
@@ -46,6 +49,9 @@ import com.ning.billing.entitlement.api.EntitlementApiException;
import com.ning.billing.entitlement.dao.BlockingStateSqlDao;
import com.google.common.base.Objects;
+import com.google.common.base.Predicate;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterables;
public class TestEntitlementUtils extends EntitlementTestSuiteWithEmbeddedDB {
@@ -66,6 +72,9 @@ public class TestEntitlementUtils extends EntitlementTestSuiteWithEmbeddedDB {
clock.setDay(initialDate);
account = accountApi.createAccount(getAccountData(7), callContext);
+ // Override the context with the right account record id
+ internalCallContext = internalCallContextFactory.createInternalCallContext(account.getId(), callContext);
+
testListener.pushExpectedEvents(NextEvent.CREATE, NextEvent.CREATE);
// Create base entitlement
@@ -272,7 +281,7 @@ public class TestEntitlementUtils extends EntitlementTestSuiteWithEmbeddedDB {
// Verify the blocking states DAO adds events not on disk for the first add-on...
checkBlockingStatesDAO(changedBaseEntitlement, addOnEntitlement, baseEffectiveCancellationOrChangeDate, false);
// ...but not for the second one
- final List<BlockingState> blockingStatesForSecondAddOn = blockingStateDao.getBlockingAll(secondAddOnEntitlement.getId(), BlockingStateType.SUBSCRIPTION, internalCallContext);
+ final List<BlockingState> blockingStatesForSecondAddOn = blockingStatesForBlockedId(secondAddOnEntitlement.getId());
Assert.assertEquals(blockingStatesForSecondAddOn.size(), 0);
}
@@ -354,7 +363,14 @@ public class TestEntitlementUtils extends EntitlementTestSuiteWithEmbeddedDB {
// Test the "read" path
private void checkFutureBlockingStatesToCancel(final DefaultEntitlement baseEntitlement, @Nullable final DefaultEntitlement addOnEntitlement, @Nullable final DateTime effectiveCancellationDateTime) throws EntitlementApiException {
- final Collection<BlockingState> blockingStatesForCancellation = computeFutureBlockingStatesForAssociatedAddons(baseEntitlement);
+ final Collection<BlockingState> blockingStatesForCancellationViaEntitlement = computeFutureBlockingStatesForAssociatedAddonsViaEntitlement(baseEntitlement);
+ doCheckFutureBlockingStatesToCancel(addOnEntitlement, effectiveCancellationDateTime, blockingStatesForCancellationViaEntitlement);
+
+ final Collection<BlockingState> blockingStatesForCancellationViaAccount = computeFutureBlockingStatesForAssociatedAddonsViaAccount(baseEntitlement);
+ doCheckFutureBlockingStatesToCancel(addOnEntitlement, effectiveCancellationDateTime, blockingStatesForCancellationViaAccount);
+ }
+
+ private void doCheckFutureBlockingStatesToCancel(final DefaultEntitlement addOnEntitlement, final DateTime effectiveCancellationDateTime, final Collection<BlockingState> blockingStatesForCancellation) {
if (addOnEntitlement == null || effectiveCancellationDateTime == null) {
Assert.assertEquals(blockingStatesForCancellation.size(), 0);
} else {
@@ -370,7 +386,14 @@ public class TestEntitlementUtils extends EntitlementTestSuiteWithEmbeddedDB {
// Test the "write" path
private void checkActualBlockingStatesToCancel(final DefaultEntitlement baseEntitlement, final DefaultEntitlement addOnEntitlement, @Nullable final DateTime effectiveCancellationDateTime, final boolean approximateDateCheck) throws EntitlementApiException {
- final Collection<BlockingState> blockingStatesForCancellation = computeBlockingStatesForAssociatedAddons(baseEntitlement, Objects.firstNonNull(effectiveCancellationDateTime, initialDate.toDateTimeAtStartOfDay()));
+ final Collection<BlockingState> blockingStatesForCancellationViaEntitlement = computeBlockingStatesForAssociatedAddonsViaEntitlement(baseEntitlement, Objects.firstNonNull(effectiveCancellationDateTime, initialDate.toDateTimeAtStartOfDay()));
+ doCheckActualBlockingStatesToCancel(addOnEntitlement, effectiveCancellationDateTime, approximateDateCheck, blockingStatesForCancellationViaEntitlement);
+
+ final Collection<BlockingState> blockingStatesForCancellationViaAccount = computeBlockingStatesForAssociatedAddonsViaAccount(baseEntitlement, Objects.firstNonNull(effectiveCancellationDateTime, initialDate.toDateTimeAtStartOfDay()));
+ doCheckActualBlockingStatesToCancel(addOnEntitlement, effectiveCancellationDateTime, approximateDateCheck, blockingStatesForCancellationViaAccount);
+ }
+
+ private void doCheckActualBlockingStatesToCancel(final DefaultEntitlement addOnEntitlement, final DateTime effectiveCancellationDateTime, final boolean approximateDateCheck, final Collection<BlockingState> blockingStatesForCancellation) {
if (effectiveCancellationDateTime == null) {
Assert.assertEquals(blockingStatesForCancellation.size(), 0);
} else {
@@ -396,7 +419,7 @@ public class TestEntitlementUtils extends EntitlementTestSuiteWithEmbeddedDB {
// Test the DAO
private void checkBlockingStatesDAO(final DefaultEntitlement baseEntitlement, final DefaultEntitlement addOnEntitlement, final LocalDate effectiveBaseCancellationDate, final LocalDate effectiveAddOnCancellationDate, final boolean isBaseCancelled) {
- final List<BlockingState> blockingStatesForBaseEntitlement = blockingStateDao.getBlockingAll(baseEntitlement.getId(), BlockingStateType.SUBSCRIPTION, internalCallContext);
+ final List<BlockingState> blockingStatesForBaseEntitlement = blockingStatesForBlockedId(baseEntitlement.getId());
Assert.assertEquals(blockingStatesForBaseEntitlement.size(), isBaseCancelled ? 1 : 0);
if (isBaseCancelled) {
Assert.assertEquals(blockingStatesForBaseEntitlement.get(0).getBlockedId(), baseEntitlement.getId());
@@ -406,7 +429,7 @@ public class TestEntitlementUtils extends EntitlementTestSuiteWithEmbeddedDB {
Assert.assertEquals(blockingStatesForBaseEntitlement.get(0).getStateName(), DefaultEntitlementApi.ENT_STATE_CANCELLED);
}
- final List<BlockingState> blockingStatesForAddOn = blockingStateDao.getBlockingAll(addOnEntitlement.getId(), BlockingStateType.SUBSCRIPTION, internalCallContext);
+ final List<BlockingState> blockingStatesForAddOn = blockingStatesForBlockedId(addOnEntitlement.getId());
Assert.assertEquals(blockingStatesForAddOn.size(), 1);
Assert.assertEquals(blockingStatesForAddOn.get(0).getBlockedId(), addOnEntitlement.getId());
Assert.assertEquals(blockingStatesForAddOn.get(0).getEffectiveDate().toLocalDate(), effectiveAddOnCancellationDate);
@@ -415,13 +438,47 @@ public class TestEntitlementUtils extends EntitlementTestSuiteWithEmbeddedDB {
Assert.assertEquals(blockingStatesForAddOn.get(0).getStateName(), DefaultEntitlementApi.ENT_STATE_CANCELLED);
}
- private Collection<BlockingState> computeFutureBlockingStatesForAssociatedAddons(final DefaultEntitlement baseEntitlement) throws EntitlementApiException {
+ private Collection<BlockingState> computeFutureBlockingStatesForAssociatedAddonsViaEntitlement(final DefaultEntitlement baseEntitlement) throws EntitlementApiException {
final EventsStream eventsStream = eventsStreamBuilder.buildForEntitlement(baseEntitlement.getId(), callContext);
return eventsStream.computeAddonsBlockingStatesForFutureSubscriptionBaseEvents();
}
- private Collection<BlockingState> computeBlockingStatesForAssociatedAddons(final DefaultEntitlement baseEntitlement, final DateTime effectiveDate) throws EntitlementApiException {
+ private Collection<BlockingState> computeFutureBlockingStatesForAssociatedAddonsViaAccount(final DefaultEntitlement baseEntitlement) throws EntitlementApiException {
+ final InternalTenantContext context = internalCallContextFactory.createInternalTenantContext(baseEntitlement.getAccountId(), callContext);
+ final EventsStream eventsStream = Iterables.<EventsStream>find(Iterables.<EventsStream>concat(eventsStreamBuilder.buildForAccount(context).getEventsStreams().values()),
+ new Predicate<EventsStream>() {
+ @Override
+ public boolean apply(final EventsStream input) {
+ return input.getSubscription().getId().equals(baseEntitlement.getId());
+ }
+ });
+ return eventsStream.computeAddonsBlockingStatesForFutureSubscriptionBaseEvents();
+ }
+
+ private Collection<BlockingState> computeBlockingStatesForAssociatedAddonsViaEntitlement(final DefaultEntitlement baseEntitlement, final DateTime effectiveDate) throws EntitlementApiException {
final EventsStream eventsStream = eventsStreamBuilder.buildForEntitlement(baseEntitlement.getId(), callContext);
return eventsStream.computeAddonsBlockingStatesForNextSubscriptionBaseEvent(effectiveDate);
}
+
+ private Collection<BlockingState> computeBlockingStatesForAssociatedAddonsViaAccount(final DefaultEntitlement baseEntitlement, final DateTime effectiveDate) throws EntitlementApiException {
+ final InternalTenantContext context = internalCallContextFactory.createInternalTenantContext(baseEntitlement.getAccountId(), callContext);
+ final EventsStream eventsStream = Iterables.<EventsStream>find(Iterables.<EventsStream>concat(eventsStreamBuilder.buildForAccount(context).getEventsStreams().values()),
+ new Predicate<EventsStream>() {
+ @Override
+ public boolean apply(final EventsStream input) {
+ return input.getSubscription().getId().equals(baseEntitlement.getId());
+ }
+ });
+ return eventsStream.computeAddonsBlockingStatesForNextSubscriptionBaseEvent(effectiveDate);
+ }
+
+ private List<BlockingState> blockingStatesForBlockedId(final UUID blockedId) {
+ return ImmutableList.<BlockingState>copyOf(Iterables.<BlockingState>filter(blockingStateDao.getBlockingAllForAccountRecordId(internalCallContext),
+ new Predicate<BlockingState>() {
+ @Override
+ public boolean apply(final BlockingState input) {
+ return input.getBlockedId().equals(blockedId);
+ }
+ }));
+ }
}
diff --git a/entitlement/src/test/java/com/ning/billing/entitlement/EntitlementTestSuiteWithEmbeddedDB.java b/entitlement/src/test/java/com/ning/billing/entitlement/EntitlementTestSuiteWithEmbeddedDB.java
index 2227fd6..e0396c1 100644
--- a/entitlement/src/test/java/com/ning/billing/entitlement/EntitlementTestSuiteWithEmbeddedDB.java
+++ b/entitlement/src/test/java/com/ning/billing/entitlement/EntitlementTestSuiteWithEmbeddedDB.java
@@ -52,6 +52,8 @@ import com.ning.billing.subscription.api.SubscriptionBaseInternalApi;
import com.ning.billing.subscription.api.SubscriptionBaseService;
import com.ning.billing.subscription.engine.core.DefaultSubscriptionBaseService;
import com.ning.billing.tag.TagInternalApi;
+import com.ning.billing.util.api.AuditUserApi;
+import com.ning.billing.util.callcontext.InternalCallContextFactory;
import com.ning.billing.util.svcsapi.bus.BusService;
import com.ning.billing.util.tag.dao.TagDao;
@@ -106,6 +108,10 @@ public class EntitlementTestSuiteWithEmbeddedDB extends GuicyKillbillTestSuiteWi
protected EntitlementUtils entitlementUtils;
@Inject
protected EventsStreamBuilder eventsStreamBuilder;
+ @Inject
+ protected AuditUserApi auditUserApi;
+ @Inject
+ protected InternalCallContextFactory internalCallContextFactory;
protected Catalog catalog;
diff --git a/entitlement/src/test/java/com/ning/billing/entitlement/glue/TestEntitlementModuleWithEmbeddedDB.java b/entitlement/src/test/java/com/ning/billing/entitlement/glue/TestEntitlementModuleWithEmbeddedDB.java
index 2369cb6..0b40737 100644
--- a/entitlement/src/test/java/com/ning/billing/entitlement/glue/TestEntitlementModuleWithEmbeddedDB.java
+++ b/entitlement/src/test/java/com/ning/billing/entitlement/glue/TestEntitlementModuleWithEmbeddedDB.java
@@ -25,6 +25,7 @@ import com.ning.billing.api.TestListenerStatus;
import com.ning.billing.catalog.glue.CatalogModule;
import com.ning.billing.entitlement.EntitlementTestListenerStatus;
import com.ning.billing.subscription.glue.DefaultSubscriptionModule;
+import com.ning.billing.util.glue.AuditModule;
import com.ning.billing.util.glue.BusModule;
import com.ning.billing.util.glue.MetricsModule;
import com.ning.billing.util.glue.NonEntityDaoModule;
@@ -49,6 +50,7 @@ public class TestEntitlementModuleWithEmbeddedDB extends TestEntitlementModule {
install(new CatalogModule(configSource));
install(new NotificationQueueModule(configSource));
install(new DefaultSubscriptionModule(configSource));
+ install(new AuditModule());
bind(TestListenerStatus.class).to(EntitlementTestListenerStatus.class).asEagerSingleton();
bind(TestApiListener.class).asEagerSingleton();
invoice/pom.xml 2(+1 -1)
diff --git a/invoice/pom.xml b/invoice/pom.xml
index e26016f..030497f 100644
--- a/invoice/pom.xml
+++ b/invoice/pom.xml
@@ -19,7 +19,7 @@
<parent>
<artifactId>killbill</artifactId>
<groupId>com.ning.billing</groupId>
- <version>0.8.3-SNAPSHOT</version>
+ <version>0.8.6-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>killbill-invoice</artifactId>
diff --git a/invoice/src/main/java/com/ning/billing/invoice/dao/InvoiceItemModelDao.java b/invoice/src/main/java/com/ning/billing/invoice/dao/InvoiceItemModelDao.java
index a7df12c..7aa6e6c 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/dao/InvoiceItemModelDao.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/dao/InvoiceItemModelDao.java
@@ -133,6 +133,58 @@ public class InvoiceItemModelDao extends EntityBase implements EntityModelDao<In
return linkedItemId;
}
+ public void setType(final InvoiceItemType type) {
+ this.type = type;
+ }
+
+ public void setInvoiceId(final UUID invoiceId) {
+ this.invoiceId = invoiceId;
+ }
+
+ public void setAccountId(final UUID accountId) {
+ this.accountId = accountId;
+ }
+
+ public void setBundleId(final UUID bundleId) {
+ this.bundleId = bundleId;
+ }
+
+ public void setSubscriptionId(final UUID subscriptionId) {
+ this.subscriptionId = subscriptionId;
+ }
+
+ public void setPlanName(final String planName) {
+ this.planName = planName;
+ }
+
+ public void setPhaseName(final String phaseName) {
+ this.phaseName = phaseName;
+ }
+
+ public void setStartDate(final LocalDate startDate) {
+ this.startDate = startDate;
+ }
+
+ public void setEndDate(final LocalDate endDate) {
+ this.endDate = endDate;
+ }
+
+ public void setAmount(final BigDecimal amount) {
+ this.amount = amount;
+ }
+
+ public void setRate(final BigDecimal rate) {
+ this.rate = rate;
+ }
+
+ public void setCurrency(final Currency currency) {
+ this.currency = currency;
+ }
+
+ public void setLinkedItemId(final UUID linkedItemId) {
+ this.linkedItemId = linkedItemId;
+ }
+
@Override
public String toString() {
final StringBuilder sb = new StringBuilder();
diff --git a/invoice/src/main/java/com/ning/billing/invoice/dao/InvoiceModelDao.java b/invoice/src/main/java/com/ning/billing/invoice/dao/InvoiceModelDao.java
index cf3956d..569959e 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/dao/InvoiceModelDao.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/dao/InvoiceModelDao.java
@@ -119,6 +119,38 @@ public class InvoiceModelDao extends EntityBase implements EntityModelDao<Invoic
return migrated;
}
+ public void setAccountId(final UUID accountId) {
+ this.accountId = accountId;
+ }
+
+ public void setInvoiceNumber(final Integer invoiceNumber) {
+ this.invoiceNumber = invoiceNumber;
+ }
+
+ public void setInvoiceDate(final LocalDate invoiceDate) {
+ this.invoiceDate = invoiceDate;
+ }
+
+ public void setTargetDate(final LocalDate targetDate) {
+ this.targetDate = targetDate;
+ }
+
+ public void setCurrency(final Currency currency) {
+ this.currency = currency;
+ }
+
+ public void setMigrated(final boolean migrated) {
+ this.migrated = migrated;
+ }
+
+ public void setInvoiceItems(final List<InvoiceItemModelDao> invoiceItems) {
+ this.invoiceItems = invoiceItems;
+ }
+
+ public void setInvoicePayments(final List<InvoicePaymentModelDao> invoicePayments) {
+ this.invoicePayments = invoicePayments;
+ }
+
@Override
public String toString() {
final StringBuilder sb = new StringBuilder();
diff --git a/invoice/src/main/java/com/ning/billing/invoice/dao/InvoicePaymentModelDao.java b/invoice/src/main/java/com/ning/billing/invoice/dao/InvoicePaymentModelDao.java
index 21dddd4..f36b39c 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/dao/InvoicePaymentModelDao.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/dao/InvoicePaymentModelDao.java
@@ -99,6 +99,42 @@ public class InvoicePaymentModelDao extends EntityBase implements EntityModelDao
return linkedInvoicePaymentId;
}
+ public void setType(final InvoicePaymentType type) {
+ this.type = type;
+ }
+
+ public void setInvoiceId(final UUID invoiceId) {
+ this.invoiceId = invoiceId;
+ }
+
+ public void setPaymentId(final UUID paymentId) {
+ this.paymentId = paymentId;
+ }
+
+ public void setPaymentDate(final DateTime paymentDate) {
+ this.paymentDate = paymentDate;
+ }
+
+ public void setAmount(final BigDecimal amount) {
+ this.amount = amount;
+ }
+
+ public void setCurrency(final Currency currency) {
+ this.currency = currency;
+ }
+
+ public void setProcessedCurrency(final Currency processedCurrency) {
+ this.processedCurrency = processedCurrency;
+ }
+
+ public void setPaymentCookieId(final UUID paymentCookieId) {
+ this.paymentCookieId = paymentCookieId;
+ }
+
+ public void setLinkedInvoicePaymentId(final UUID linkedInvoicePaymentId) {
+ this.linkedInvoicePaymentId = linkedInvoicePaymentId;
+ }
+
@Override
public String toString() {
final StringBuilder sb = new StringBuilder();
diff --git a/invoice/src/main/java/com/ning/billing/invoice/generator/InvoiceDateUtils.java b/invoice/src/main/java/com/ning/billing/invoice/generator/InvoiceDateUtils.java
index 8a439b1..ff1591c 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/generator/InvoiceDateUtils.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/generator/InvoiceDateUtils.java
@@ -60,6 +60,7 @@ public class InvoiceDateUtils {
return days.divide(daysInPeriod, 2 * NUMBER_OF_DECIMALS, ROUNDING_METHOD);
}
+
public static BigDecimal calculateProRationBeforeFirstBillingPeriod(final LocalDate startDate, final LocalDate nextBillingCycleDate,
final BillingPeriod billingPeriod) {
final LocalDate previousBillingCycleDate = nextBillingCycleDate.plusMonths(-billingPeriod.getNumberOfMonths());
@@ -73,10 +74,129 @@ public class InvoiceDateUtils {
return numberOfMonths / numberOfMonthsInPeriod;
}
+ public static LocalDate calculateLastBillingCycleDateBefore(final LocalDate date, final LocalDate previousBillCycleDate,
+ final int billingCycleDay, final BillingPeriod billingPeriod) {
+ LocalDate proposedDate = previousBillCycleDate;
+
+ int numberOfPeriods = 0;
+ while (!proposedDate.isAfter(date)) {
+ proposedDate = previousBillCycleDate.plusMonths(numberOfPeriods * billingPeriod.getNumberOfMonths());
+ numberOfPeriods += 1;
+ }
+
+ proposedDate = proposedDate.plusMonths(-billingPeriod.getNumberOfMonths());
+
+ if (proposedDate.dayOfMonth().get() < billingCycleDay) {
+ final int lastDayOfTheMonth = proposedDate.dayOfMonth().getMaximumValue();
+ if (lastDayOfTheMonth < billingCycleDay) {
+ proposedDate = new LocalDate(proposedDate.getYear(), proposedDate.getMonthOfYear(), lastDayOfTheMonth);
+ } else {
+ proposedDate = new LocalDate(proposedDate.getYear(), proposedDate.getMonthOfYear(), billingCycleDay);
+ }
+ }
+
+ if (proposedDate.isBefore(previousBillCycleDate)) {
+ // Make sure not to go too far in the past
+ return previousBillCycleDate;
+ } else {
+ return proposedDate;
+ }
+ }
+
+ public static LocalDate calculateEffectiveEndDate(final LocalDate billCycleDate, final LocalDate targetDate,
+ final BillingPeriod billingPeriod) {
+ if (targetDate.isBefore(billCycleDate)) {
+ return billCycleDate;
+ }
+
+ final int numberOfMonthsInPeriod = billingPeriod.getNumberOfMonths();
+ int numberOfPeriods = 0;
+ LocalDate proposedDate = billCycleDate;
+
+ while (!proposedDate.isAfter(targetDate)) {
+ proposedDate = billCycleDate.plusMonths(numberOfPeriods * numberOfMonthsInPeriod);
+ numberOfPeriods += 1;
+ }
+
+ return proposedDate;
+ }
+
+ public static LocalDate calculateEffectiveEndDate(final LocalDate billCycleDate, final LocalDate targetDate,
+ final LocalDate endDate, final BillingPeriod billingPeriod) {
+ if (targetDate.isBefore(endDate)) {
+ if (targetDate.isBefore(billCycleDate)) {
+ return billCycleDate;
+ }
+
+ final int numberOfMonthsInPeriod = billingPeriod.getNumberOfMonths();
+ int numberOfPeriods = 0;
+ LocalDate proposedDate = billCycleDate;
+
+ while (!proposedDate.isAfter(targetDate)) {
+ proposedDate = billCycleDate.plusMonths(numberOfPeriods * numberOfMonthsInPeriod);
+ numberOfPeriods += 1;
+ }
+
+ // the current period includes the target date
+ // check to see whether the end date truncates the period
+ if (endDate.isBefore(proposedDate)) {
+ return endDate;
+ } else {
+ return proposedDate;
+ }
+ } else {
+ return endDate;
+ }
+ }
+
+
public static BigDecimal calculateProRationAfterLastBillingCycleDate(final LocalDate endDate, final LocalDate previousBillThroughDate,
final BillingPeriod billingPeriod) {
// Note: assumption is that previousBillThroughDate is correctly aligned with the billing cycle day
final LocalDate nextBillThroughDate = previousBillThroughDate.plusMonths(billingPeriod.getNumberOfMonths());
return calculateProrationBetweenDates(previousBillThroughDate, endDate, previousBillThroughDate, nextBillThroughDate);
}
+
+ /*
+ public static LocalDate calculateBillingCycleDateOnOrAfter(final LocalDate date, final DateTimeZone accountTimeZone,
+ final int billingCycleDayLocal) {
+ final DateTime tmp = date.toDateTimeAtStartOfDay(accountTimeZone);
+ final DateTime proposedDateTime = calculateBillingCycleDateOnOrAfter(tmp, billingCycleDayLocal);
+
+ return new LocalDate(proposedDateTime, accountTimeZone);
+ }
+
+ public static LocalDate calculateBillingCycleDateAfter(final LocalDate date, final DateTimeZone accountTimeZone,
+ final int billingCycleDayLocal) {
+ final DateTime tmp = date.toDateTimeAtStartOfDay(accountTimeZone);
+ final DateTime proposedDateTime = calculateBillingCycleDateAfter(tmp, billingCycleDayLocal);
+
+ return new LocalDate(proposedDateTime, accountTimeZone);
+ }
+ */
+
+ public static LocalDate calculateBillingCycleDateOnOrAfter(final LocalDate date, final int billingCycleDayLocal) {
+ final int lastDayOfMonth = date.dayOfMonth().getMaximumValue();
+
+ final LocalDate fixedDate;
+ if (billingCycleDayLocal > lastDayOfMonth) {
+ fixedDate = new LocalDate(date.getYear(), date.getMonthOfYear(), lastDayOfMonth, date.getChronology());
+ } else {
+ fixedDate = new LocalDate(date.getYear(), date.getMonthOfYear(), billingCycleDayLocal, date.getChronology());
+ }
+
+ LocalDate proposedDate = fixedDate;
+ while (proposedDate.isBefore(date)) {
+ proposedDate = proposedDate.plusMonths(1);
+ }
+ return proposedDate;
+ }
+
+ public static LocalDate calculateBillingCycleDateAfter(final LocalDate date, final int billingCycleDayLocal) {
+ LocalDate proposedDate = calculateBillingCycleDateOnOrAfter(date, billingCycleDayLocal);
+ if (date.compareTo(proposedDate) == 0) {
+ proposedDate = proposedDate.plusMonths(1);
+ }
+ return proposedDate;
+ }
}
diff --git a/invoice/src/main/java/com/ning/billing/invoice/InvoiceDispatcher.java b/invoice/src/main/java/com/ning/billing/invoice/InvoiceDispatcher.java
index 506278a..2c94cb9 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/InvoiceDispatcher.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/InvoiceDispatcher.java
@@ -169,11 +169,11 @@ public class InvoiceDispatcher {
// Make sure to first set the BCD if needed then get the account object (to have the BCD set)
final BillingEventSet billingEvents = billingApi.getBillingEventsForAccountAndUpdateAccountBCD(accountId, context);
- final Account account = accountApi.getAccountById(accountId, context);
+ final Account account = accountApi.getAccountById(accountId, context);
final DateAndTimeZoneContext dateAndTimeZoneContext = billingEvents.iterator().hasNext() ?
new DateAndTimeZoneContext(billingEvents.iterator().next().getEffectiveDate(), account.getTimeZone(), clock) :
- new DateAndTimeZoneContext(null, account.getTimeZone(), clock);
+ null;
List<Invoice> invoices = new ArrayList<Invoice>();
@@ -189,8 +189,8 @@ public class InvoiceDispatcher {
final Currency targetCurrency = account.getCurrency();
- final LocalDate targetDate = dateAndTimeZoneContext.computeTargetDate(targetDateTime);
- final Invoice invoice = generator.generateInvoice(accountId, billingEvents, invoices, targetDate, targetCurrency);
+ final LocalDate targetDate = dateAndTimeZoneContext != null ? dateAndTimeZoneContext.computeTargetDate(targetDateTime) : null;
+ final Invoice invoice = targetDate != null ? generator.generateInvoice(accountId, billingEvents, invoices, targetDate, targetCurrency) : null;
if (invoice == null) {
log.info("Generated null invoice for accountId {} and targetDate {} (targetDateTime {})", new Object[]{accountId, targetDate, targetDateTime});
if (!dryRun) {
diff --git a/invoice/src/main/java/com/ning/billing/invoice/model/InAdvanceBillingMode.java b/invoice/src/main/java/com/ning/billing/invoice/model/InAdvanceBillingMode.java
index b2725e2..e3b4cd3 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/model/InAdvanceBillingMode.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/model/InAdvanceBillingMode.java
@@ -27,8 +27,10 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.ning.billing.catalog.api.BillingPeriod;
-import com.ning.billing.invoice.generator.BillingIntervalDetail;
+import static com.ning.billing.invoice.generator.InvoiceDateUtils.calculateBillingCycleDateOnOrAfter;
+import static com.ning.billing.invoice.generator.InvoiceDateUtils.calculateEffectiveEndDate;
+import static com.ning.billing.invoice.generator.InvoiceDateUtils.calculateLastBillingCycleDateBefore;
import static com.ning.billing.invoice.generator.InvoiceDateUtils.calculateNumberOfWholeBillingPeriods;
import static com.ning.billing.invoice.generator.InvoiceDateUtils.calculateProRationAfterLastBillingCycleDate;
import static com.ning.billing.invoice.generator.InvoiceDateUtils.calculateProRationBeforeFirstBillingPeriod;
@@ -50,8 +52,7 @@ public class InAdvanceBillingMode implements BillingMode {
final List<RecurringInvoiceItemData> results = new ArrayList<RecurringInvoiceItemData>();
- final BillingIntervalDetail billingIntervalDetail = new BillingIntervalDetail(startDate, endDate, targetDate, billingCycleDayLocal, billingPeriod);
-
+ final LocalDate firstBillingCycleDate = calculateBillingCycleDateOnOrAfter(startDate, billingCycleDayLocal);
// We are not billing for less than a day (we could...)
if (endDate != null && endDate.equals(startDate)) {
@@ -61,7 +62,7 @@ public class InAdvanceBillingMode implements BillingMode {
// If there is an endDate and that endDate is before our first coming firstBillingCycleDate, all we have to do
// is to charge for that period
//
- if (endDate != null && !endDate.isAfter(billingIntervalDetail.getFirstBillingCycleDate())) {
+ if (endDate != null && !endDate.isAfter(firstBillingCycleDate)) {
final BigDecimal leadingProRationPeriods = calculateProRationBeforeFirstBillingPeriod(startDate, endDate, billingPeriod);
final RecurringInvoiceItemData itemData = new RecurringInvoiceItemData(startDate, endDate, leadingProRationPeriods);
results.add(itemData);
@@ -73,11 +74,11 @@ public class InAdvanceBillingMode implements BillingMode {
// i) The first firstBillingCycleDate is strictly after our start date AND
// ii) The endDate is is not null and is strictly after our firstBillingCycleDate (previous check)
//
- if (billingIntervalDetail.getFirstBillingCycleDate().isAfter(startDate)) {
- final BigDecimal leadingProRationPeriods = calculateProRationBeforeFirstBillingPeriod(startDate, billingIntervalDetail.getFirstBillingCycleDate(), billingPeriod);
+ if (firstBillingCycleDate.isAfter(startDate)) {
+ final BigDecimal leadingProRationPeriods = calculateProRationBeforeFirstBillingPeriod(startDate, firstBillingCycleDate, billingPeriod);
if (leadingProRationPeriods != null && leadingProRationPeriods.compareTo(BigDecimal.ZERO) > 0) {
// Not common - add info in the logs for debugging purposes
- final RecurringInvoiceItemData itemData = new RecurringInvoiceItemData(startDate, billingIntervalDetail.getFirstBillingCycleDate(), leadingProRationPeriods);
+ final RecurringInvoiceItemData itemData = new RecurringInvoiceItemData(startDate, firstBillingCycleDate, leadingProRationPeriods);
log.info("Adding pro-ration: {}", itemData);
results.add(itemData);
}
@@ -88,13 +89,18 @@ public class InAdvanceBillingMode implements BillingMode {
// - If endDate != null and targetDate is after endDate => this is the endDate and will lead to a trailing pro-ration
// - If not, this is the last billingCycleDate calculation right after the targetDate
//
- final LocalDate effectiveEndDate = billingIntervalDetail.getEffectiveEndDate();
+ final LocalDate effectiveEndDate;
+ if (endDate != null) {
+ effectiveEndDate = calculateEffectiveEndDate(firstBillingCycleDate, targetDate, endDate, billingPeriod);
+ } else {
+ effectiveEndDate = calculateEffectiveEndDate(firstBillingCycleDate, targetDate, billingPeriod);
+ }
//
// Based on what we calculated previously, code recompute one more time the numberOfWholeBillingPeriods
//
- final LocalDate lastBillingCycleDate = billingIntervalDetail.getLastBillingCycleDate();
- final int numberOfWholeBillingPeriods = calculateNumberOfWholeBillingPeriods(billingIntervalDetail.getFirstBillingCycleDate(), lastBillingCycleDate, billingPeriod);
+ final LocalDate lastBillingCycleDate = calculateLastBillingCycleDateBefore(effectiveEndDate, firstBillingCycleDate, billingCycleDayLocal, billingPeriod);
+ final int numberOfWholeBillingPeriods = calculateNumberOfWholeBillingPeriods(firstBillingCycleDate, lastBillingCycleDate, billingPeriod);
final int numberOfMonthsPerBillingPeriod = billingPeriod.getNumberOfMonths();
for (int i = 0; i < numberOfWholeBillingPeriods; i++) {
diff --git a/invoice/src/test/java/com/ning/billing/invoice/generator/TestInvoiceDateUtils.java b/invoice/src/test/java/com/ning/billing/invoice/generator/TestInvoiceDateUtils.java
index 5728847..2ee9dff 100644
--- a/invoice/src/test/java/com/ning/billing/invoice/generator/TestInvoiceDateUtils.java
+++ b/invoice/src/test/java/com/ning/billing/invoice/generator/TestInvoiceDateUtils.java
@@ -18,6 +18,7 @@ package com.ning.billing.invoice.generator;
import java.math.BigDecimal;
+import org.joda.time.DateTimeZone;
import org.joda.time.LocalDate;
import org.testng.Assert;
import org.testng.annotations.Test;
@@ -28,6 +29,22 @@ import com.ning.billing.invoice.InvoiceTestSuiteNoDB;
public class TestInvoiceDateUtils extends InvoiceTestSuiteNoDB {
@Test(groups = "fast")
+ public void testLastBCDShouldNotBeBeforePreviousBCD() throws Exception {
+ final LocalDate from = new LocalDate("2012-07-16");
+ final LocalDate previousBCD = new LocalDate("2012-08-15");
+ final int bcdLocal = 15;
+ final LocalDate lastBCD = InvoiceDateUtils.calculateLastBillingCycleDateBefore(from, previousBCD, bcdLocal, BillingPeriod.MONTHLY);
+ Assert.assertEquals(lastBCD, new LocalDate("2012-08-15"));
+ }
+
+ @Test(groups = "fast")
+ public void testNextBCDShouldNotBeInThePast() throws Exception {
+ final LocalDate from = new LocalDate("2012-07-16");
+ final LocalDate to = InvoiceDateUtils.calculateBillingCycleDateOnOrAfter(from, 15);
+ Assert.assertEquals(to, new LocalDate("2012-08-15"));
+ }
+
+ @Test(groups = "fast")
public void testProRationAfterLastBillingCycleDate() throws Exception {
final LocalDate endDate = new LocalDate("2012-06-02");
final LocalDate previousBillThroughDate = new LocalDate("2012-03-02");
@@ -36,6 +53,67 @@ public class TestInvoiceDateUtils extends InvoiceTestSuiteNoDB {
}
@Test(groups = "fast")
+ public void testBeforeBCDWithAfter() throws Exception {
+ final LocalDate from = new LocalDate("2012-03-02");
+ final LocalDate to = InvoiceDateUtils.calculateBillingCycleDateAfter(from, 3);
+ Assert.assertEquals(to, new LocalDate("2012-03-03"));
+ }
+
+ @Test(groups = "fast")
+ public void testEqualBCDWithAfter() throws Exception {
+ final LocalDate from = new LocalDate("2012-03-03");
+ final LocalDate to = InvoiceDateUtils.calculateBillingCycleDateAfter(from, 3);
+ Assert.assertEquals(to, new LocalDate("2012-04-03"));
+ }
+
+ @Test(groups = "fast")
+ public void testAfterBCDWithAfter() throws Exception {
+ final LocalDate from = new LocalDate("2012-03-04");
+ final LocalDate to = InvoiceDateUtils.calculateBillingCycleDateAfter(from, 3);
+ Assert.assertEquals(to, new LocalDate("2012-04-03"));
+ }
+
+ @Test(groups = "fast")
+ public void testBeforeBCDWithOnOrAfter() throws Exception {
+ final LocalDate from = new LocalDate("2012-03-02");
+ final LocalDate to = InvoiceDateUtils.calculateBillingCycleDateOnOrAfter(from, 3);
+ Assert.assertEquals(to, new LocalDate("2012-03-03"));
+ }
+
+ @Test(groups = "fast")
+ public void testEqualBCDWithOnOrAfter() throws Exception {
+ final LocalDate from = new LocalDate("2012-03-03");
+ final LocalDate to = InvoiceDateUtils.calculateBillingCycleDateOnOrAfter(from, 3);
+ Assert.assertEquals(to, new LocalDate("2012-03-03"));
+ }
+
+ @Test(groups = "fast")
+ public void testAfterBCDWithOnOrAfter() throws Exception {
+ final LocalDate from = new LocalDate("2012-03-04");
+ final LocalDate to = InvoiceDateUtils.calculateBillingCycleDateOnOrAfter(from, 3);
+ Assert.assertEquals(to, new LocalDate("2012-04-03"));
+ }
+
+ @Test(groups = "fast")
+ public void testEffectiveEndDate() throws Exception {
+ final LocalDate firstBCD = new LocalDate(2012, 7, 16);
+ final LocalDate targetDate = new LocalDate(2012, 8, 16);
+ final BillingPeriod billingPeriod = BillingPeriod.MONTHLY;
+ final LocalDate effectiveEndDate = InvoiceDateUtils.calculateEffectiveEndDate(firstBCD, targetDate, billingPeriod);
+ // TODO should that be 2012-09-15?
+ Assert.assertEquals(effectiveEndDate, new LocalDate(2012, 9, 16));
+ }
+
+ @Test(groups = "fast")
+ public void testLastBCD() throws Exception {
+ final LocalDate firstBCD = new LocalDate(2012, 7, 16);
+ final LocalDate effectiveEndDate = new LocalDate(2012, 9, 15);
+ final BillingPeriod billingPeriod = BillingPeriod.MONTHLY;
+ final LocalDate lastBCD = InvoiceDateUtils.calculateLastBillingCycleDateBefore(effectiveEndDate, firstBCD, 16, billingPeriod);
+ Assert.assertEquals(lastBCD, new LocalDate(2012, 8, 16));
+ }
+
+ @Test(groups = "fast")
public void testCalculateNbOfBillingPeriods() throws Exception {
final LocalDate firstBCD = new LocalDate(2012, 7, 16);
final LocalDate lastBCD = new LocalDate(2012, 9, 16);
diff --git a/invoice/src/test/java/com/ning/billing/invoice/MockBillingEventSet.java b/invoice/src/test/java/com/ning/billing/invoice/MockBillingEventSet.java
index f32427d..6037eeb 100644
--- a/invoice/src/test/java/com/ning/billing/invoice/MockBillingEventSet.java
+++ b/invoice/src/test/java/com/ning/billing/invoice/MockBillingEventSet.java
@@ -36,11 +36,6 @@ public class MockBillingEventSet extends TreeSet<BillingEvent> implements Billin
}
@Override
- public boolean isLast(final BillingEvent event) {
- return event == last();
- }
-
- @Override
public boolean isAccountAutoInvoiceOff() {
return isAccountInvoiceOff;
}
diff --git a/invoice/src/test/java/com/ning/billing/invoice/TestInvoiceDispatcher.java b/invoice/src/test/java/com/ning/billing/invoice/TestInvoiceDispatcher.java
index 03d7450..728bcce 100644
--- a/invoice/src/test/java/com/ning/billing/invoice/TestInvoiceDispatcher.java
+++ b/invoice/src/test/java/com/ning/billing/invoice/TestInvoiceDispatcher.java
@@ -194,7 +194,7 @@ public class TestInvoiceDispatcher extends InvoiceTestSuiteWithEmbeddedDB {
final LocalDate endDate = new LocalDate("2012-11-26");
- ((ClockMock) clock).setTime(new DateTime(2012, 10, 26, 1, 12, 23, DateTimeZone.UTC));
+ ((ClockMock) clock).setTime(new DateTime(2012, 10, 13, 1, 12, 23, DateTimeZone.UTC));
final DateAndTimeZoneContext dateAndTimeZoneContext = new DateAndTimeZoneContext(clock.getUTCNow(), DateTimeZone.forID("Pacific/Pitcairn"), clock);
@@ -217,10 +217,6 @@ public class TestInvoiceDispatcher extends InvoiceTestSuiteWithEmbeddedDB {
final LocalDate receivedTargetDate = new LocalDate(receivedDate, DateTimeZone.forID("Pacific/Pitcairn"));
Assert.assertEquals(receivedTargetDate, endDate);
- Assert.assertTrue(receivedDate.compareTo(new DateTime(2012, 11, 26, 9 /* 1 + 8 for Pitcairn */, 12, 23, DateTimeZone.UTC)) >= 0);
- Assert.assertTrue(receivedDate.compareTo(new DateTime(2012, 11, 26, 9, 13, 0, DateTimeZone.UTC)) <= 0);
-
+ Assert.assertTrue(receivedDate.compareTo(new DateTime(2012, 11, 27, 1, 12, 23, DateTimeZone.UTC)) <= 0);
}
-
- //MDW add a test to cover when the account auto-invoice-off tag is present
}
jaxrs/pom.xml 2(+1 -1)
diff --git a/jaxrs/pom.xml b/jaxrs/pom.xml
index c4b1329..68b4477 100644
--- a/jaxrs/pom.xml
+++ b/jaxrs/pom.xml
@@ -19,7 +19,7 @@
<parent>
<artifactId>killbill</artifactId>
<groupId>com.ning.billing</groupId>
- <version>0.8.3-SNAPSHOT</version>
+ <version>0.8.6-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>killbill-jaxrs</artifactId>
diff --git a/jaxrs/src/main/java/com/ning/billing/jaxrs/json/RefundJson.java b/jaxrs/src/main/java/com/ning/billing/jaxrs/json/RefundJson.java
index 73a83dd..1c17625 100644
--- a/jaxrs/src/main/java/com/ning/billing/jaxrs/json/RefundJson.java
+++ b/jaxrs/src/main/java/com/ning/billing/jaxrs/json/RefundJson.java
@@ -42,6 +42,7 @@ public class RefundJson extends JsonBase {
private final Boolean isAdjusted;
private final DateTime requestedDate;
private final DateTime effectiveDate;
+ private final String status;
private final List<InvoiceItemJson> adjustments;
@JsonCreator
@@ -49,6 +50,7 @@ public class RefundJson extends JsonBase {
@JsonProperty("paymentId") final String paymentId,
@JsonProperty("amount") final BigDecimal amount,
@JsonProperty("currency") final String currency,
+ @JsonProperty("status") final String status,
@JsonProperty("adjusted") final Boolean isAdjusted,
@JsonProperty("requestedDate") final DateTime requestedDate,
@JsonProperty("effectiveDate") final DateTime effectiveDate,
@@ -59,6 +61,7 @@ public class RefundJson extends JsonBase {
this.paymentId = paymentId;
this.amount = amount;
this.currency = currency;
+ this.status = status;
this.isAdjusted = isAdjusted;
this.requestedDate = requestedDate;
this.effectiveDate = effectiveDate;
@@ -71,7 +74,7 @@ public class RefundJson extends JsonBase {
public RefundJson(final Refund refund, @Nullable final List<InvoiceItem> adjustments, @Nullable final List<AuditLog> auditLogs) {
this(refund.getId().toString(), refund.getPaymentId().toString(), refund.getRefundAmount(), refund.getCurrency().toString(),
- refund.isAdjusted(), refund.getEffectiveDate(), refund.getEffectiveDate(),
+ refund.getRefundStatus().toString(), refund.isAdjusted(), refund.getEffectiveDate(), refund.getEffectiveDate(),
adjustments == null ? null : ImmutableList.<InvoiceItemJson>copyOf(Collections2.transform(adjustments, new Function<InvoiceItem, InvoiceItemJson>() {
@Override
public InvoiceItemJson apply(@Nullable final InvoiceItem input) {
@@ -113,6 +116,8 @@ public class RefundJson extends JsonBase {
return adjustments;
}
+ public String getStatus() { return status; }
+
@Override
public String toString() {
final StringBuilder sb = new StringBuilder();
@@ -121,6 +126,7 @@ public class RefundJson extends JsonBase {
sb.append(", paymentId='").append(paymentId).append('\'');
sb.append(", amount=").append(amount);
sb.append(", currency=").append(currency);
+ sb.append(", status=").append(status);
sb.append(", isAdjusted=").append(isAdjusted);
sb.append(", requestedDate=").append(requestedDate);
sb.append(", effectiveDate=").append(effectiveDate);
@@ -135,6 +141,7 @@ public class RefundJson extends JsonBase {
result = 31 * result + (paymentId != null ? paymentId.hashCode() : 0);
result = 31 * result + (amount != null ? amount.hashCode() : 0);
result = 31 * result + (currency != null ? currency.hashCode() : 0);
+ result = 31 * result + (status != null ? status.hashCode() : 0);
result = 31 * result + (isAdjusted != null ? isAdjusted.hashCode() : 0);
result = 31 * result + (requestedDate != null ? requestedDate.hashCode() : 0);
result = 31 * result + (effectiveDate != null ? effectiveDate.hashCode() : 0);
@@ -222,6 +229,14 @@ public class RefundJson extends JsonBase {
return false;
}
+ if (status == null) {
+ if (other.status != null) {
+ return false;
+ }
+ } else if (!status.equals(other.status)) {
+ return false;
+ }
+
if (adjustments == null) {
if (other.adjustments != null) {
return false;
diff --git a/jaxrs/src/main/java/com/ning/billing/jaxrs/mappers/AccountApiExceptionMapper.java b/jaxrs/src/main/java/com/ning/billing/jaxrs/mappers/AccountApiExceptionMapper.java
index 26a1908..a9484fb 100644
--- a/jaxrs/src/main/java/com/ning/billing/jaxrs/mappers/AccountApiExceptionMapper.java
+++ b/jaxrs/src/main/java/com/ning/billing/jaxrs/mappers/AccountApiExceptionMapper.java
@@ -54,8 +54,10 @@ public class AccountApiExceptionMapper extends ExceptionMapperBase implements Ex
return buildBadRequestResponse(exception, uriInfo);
} else if (exception.getCode() == ErrorCode.ACCOUNT_UPDATE_FAILED.getCode()) {
return buildInternalErrorResponse(exception, uriInfo);
+ } else if (exception.getCode() == ErrorCode.ACCOUNT_DOES_NOT_EXIST_FOR_RECORD_ID.getCode()) {
+ return buildNotFoundResponse(exception, uriInfo);
} else {
- return buildBadRequestResponse(exception, uriInfo);
+ return fallback(exception, uriInfo);
}
}
}
diff --git a/jaxrs/src/main/java/com/ning/billing/jaxrs/mappers/BlockingApiExceptionMapper.java b/jaxrs/src/main/java/com/ning/billing/jaxrs/mappers/BlockingApiExceptionMapper.java
index 7f23095..aed9c10 100644
--- a/jaxrs/src/main/java/com/ning/billing/jaxrs/mappers/BlockingApiExceptionMapper.java
+++ b/jaxrs/src/main/java/com/ning/billing/jaxrs/mappers/BlockingApiExceptionMapper.java
@@ -37,6 +37,6 @@ public class BlockingApiExceptionMapper extends ExceptionMapperBase implements E
@Override
public Response toResponse(final BlockingApiException exception) {
- return buildBadRequestResponse(exception, uriInfo);
+ return fallback(exception, uriInfo);
}
}
diff --git a/jaxrs/src/main/java/com/ning/billing/jaxrs/mappers/CatalogApiExceptionMapper.java b/jaxrs/src/main/java/com/ning/billing/jaxrs/mappers/CatalogApiExceptionMapper.java
index d5a464d..fe85c6f 100644
--- a/jaxrs/src/main/java/com/ning/billing/jaxrs/mappers/CatalogApiExceptionMapper.java
+++ b/jaxrs/src/main/java/com/ning/billing/jaxrs/mappers/CatalogApiExceptionMapper.java
@@ -37,6 +37,6 @@ public class CatalogApiExceptionMapper extends ExceptionMapperBase implements Ex
@Override
public Response toResponse(final CatalogApiException exception) {
- return buildBadRequestResponse(exception, uriInfo);
+ return fallback(exception, uriInfo);
}
}
diff --git a/jaxrs/src/main/java/com/ning/billing/jaxrs/mappers/EmailApiExceptionMapper.java b/jaxrs/src/main/java/com/ning/billing/jaxrs/mappers/EmailApiExceptionMapper.java
index e619556..ffdf1c9 100644
--- a/jaxrs/src/main/java/com/ning/billing/jaxrs/mappers/EmailApiExceptionMapper.java
+++ b/jaxrs/src/main/java/com/ning/billing/jaxrs/mappers/EmailApiExceptionMapper.java
@@ -37,6 +37,6 @@ public class EmailApiExceptionMapper extends ExceptionMapperBase implements Exce
@Override
public Response toResponse(final EmailApiException exception) {
- return buildInternalErrorResponse(exception, uriInfo);
+ return fallback(exception, uriInfo);
}
}
diff --git a/jaxrs/src/main/java/com/ning/billing/jaxrs/mappers/EntitlementApiExceptionMapper.java b/jaxrs/src/main/java/com/ning/billing/jaxrs/mappers/EntitlementApiExceptionMapper.java
index 5dcfa3b..c4506cb 100644
--- a/jaxrs/src/main/java/com/ning/billing/jaxrs/mappers/EntitlementApiExceptionMapper.java
+++ b/jaxrs/src/main/java/com/ning/billing/jaxrs/mappers/EntitlementApiExceptionMapper.java
@@ -45,7 +45,7 @@ public class EntitlementApiExceptionMapper extends ExceptionMapperBase implement
} else if (exception.getCode() == ErrorCode.SUB_INVALID_SUBSCRIPTION_ID.getCode()) {
return buildNotFoundResponse(exception, uriInfo);
} else {
- return buildBadRequestResponse(exception, uriInfo);
+ return fallback(exception, uriInfo);
}
}
}
diff --git a/jaxrs/src/main/java/com/ning/billing/jaxrs/mappers/EntityPersistenceExceptionMapper.java b/jaxrs/src/main/java/com/ning/billing/jaxrs/mappers/EntityPersistenceExceptionMapper.java
index d7c6194..f9301b7 100644
--- a/jaxrs/src/main/java/com/ning/billing/jaxrs/mappers/EntityPersistenceExceptionMapper.java
+++ b/jaxrs/src/main/java/com/ning/billing/jaxrs/mappers/EntityPersistenceExceptionMapper.java
@@ -37,6 +37,6 @@ public class EntityPersistenceExceptionMapper extends ExceptionMapperBase implem
@Override
public Response toResponse(final EntityPersistenceException exception) {
- return buildInternalErrorResponse(exception, uriInfo);
+ return fallback(exception, uriInfo);
}
}
diff --git a/jaxrs/src/main/java/com/ning/billing/jaxrs/mappers/ExceptionMapperBase.java b/jaxrs/src/main/java/com/ning/billing/jaxrs/mappers/ExceptionMapperBase.java
index 2c82fa6..c070c79 100644
--- a/jaxrs/src/main/java/com/ning/billing/jaxrs/mappers/ExceptionMapperBase.java
+++ b/jaxrs/src/main/java/com/ning/billing/jaxrs/mappers/ExceptionMapperBase.java
@@ -24,7 +24,22 @@ import javax.ws.rs.core.UriInfo;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import com.ning.billing.BillingExceptionBase;
+import com.ning.billing.account.api.AccountApiException;
+import com.ning.billing.catalog.api.CatalogApiException;
+import com.ning.billing.entitlement.api.BlockingApiException;
+import com.ning.billing.entitlement.api.EntitlementApiException;
+import com.ning.billing.entitlement.api.SubscriptionApiException;
+import com.ning.billing.entity.EntityPersistenceException;
+import com.ning.billing.invoice.api.InvoiceApiException;
import com.ning.billing.jaxrs.json.BillingExceptionJson;
+import com.ning.billing.overdue.OverdueApiException;
+import com.ning.billing.payment.api.PaymentApiException;
+import com.ning.billing.subscription.api.SubscriptionBillingApiException;
+import com.ning.billing.subscription.api.timeline.SubscriptionBaseRepairException;
+import com.ning.billing.util.api.TagApiException;
+import com.ning.billing.util.api.TagDefinitionApiException;
+import com.ning.billing.util.email.EmailApiException;
import com.ning.billing.util.jackson.ObjectMapper;
import com.fasterxml.jackson.core.JsonProcessingException;
@@ -34,6 +49,67 @@ public abstract class ExceptionMapperBase {
private static final Logger log = LoggerFactory.getLogger(ExceptionMapperBase.class);
private static final ObjectMapper mapper = new ObjectMapper();
+ protected Response fallback(final Exception exception, final UriInfo uriInfo) {
+ if (exception.getCause() == null) {
+ return buildBadRequestResponse(exception, uriInfo);
+ } else {
+ return doFallback(exception, uriInfo);
+ }
+ }
+
+ private Response doFallback(final Exception exception, final UriInfo uriInfo) {
+ if (exception.getCause() == null || !(exception.getCause() instanceof BillingExceptionBase)) {
+ return buildBadRequestResponse(exception, uriInfo);
+ }
+
+ final BillingExceptionBase cause = (BillingExceptionBase) exception.getCause();
+ if (cause instanceof AccountApiException) {
+ final AccountApiExceptionMapper mapper = new AccountApiExceptionMapper(uriInfo);
+ return mapper.toResponse((AccountApiException) cause);
+ } else if (cause instanceof BlockingApiException) {
+ final BlockingApiExceptionMapper mapper = new BlockingApiExceptionMapper(uriInfo);
+ return mapper.toResponse((BlockingApiException) cause);
+ } else if (cause instanceof CatalogApiException) {
+ final CatalogApiExceptionMapper mapper = new CatalogApiExceptionMapper(uriInfo);
+ return mapper.toResponse((CatalogApiException) cause);
+ } else if (cause instanceof EmailApiException) {
+ final EmailApiExceptionMapper mapper = new EmailApiExceptionMapper(uriInfo);
+ return mapper.toResponse((EmailApiException) cause);
+ } else if (cause instanceof EntitlementApiException) {
+ final EntitlementApiExceptionMapper mapper = new EntitlementApiExceptionMapper(uriInfo);
+ return mapper.toResponse((EntitlementApiException) cause);
+ } else if (cause instanceof EntityPersistenceException) {
+ final EntityPersistenceExceptionMapper mapper = new EntityPersistenceExceptionMapper(uriInfo);
+ return mapper.toResponse((EntityPersistenceException) cause);
+ } else if (cause instanceof InvoiceApiException) {
+ final InvoiceApiExceptionMapper mapper = new InvoiceApiExceptionMapper(uriInfo);
+ return mapper.toResponse((InvoiceApiException) cause);
+ } else if (cause instanceof OverdueApiException) {
+ final OverdueApiExceptionMapper mapper = new OverdueApiExceptionMapper(uriInfo);
+ return mapper.toResponse((OverdueApiException) cause);
+ } else if (cause instanceof PaymentApiException) {
+ final PaymentApiExceptionMapper mapper = new PaymentApiExceptionMapper(uriInfo);
+ return mapper.toResponse((PaymentApiException) cause);
+ } else if (cause instanceof SubscriptionApiException) {
+ final SubscriptionApiExceptionMapper mapper = new SubscriptionApiExceptionMapper(uriInfo);
+ return mapper.toResponse((SubscriptionApiException) cause);
+ } else if (cause instanceof SubscriptionBillingApiException) {
+ final SubscriptionBillingApiExceptionMapper mapper = new SubscriptionBillingApiExceptionMapper(uriInfo);
+ return mapper.toResponse((SubscriptionBillingApiException) cause);
+ } else if (cause instanceof SubscriptionBaseRepairException) {
+ final SubscriptionRepairExceptionMapper mapper = new SubscriptionRepairExceptionMapper(uriInfo);
+ return mapper.toResponse((SubscriptionBaseRepairException) cause);
+ } else if (cause instanceof TagApiException) {
+ final TagApiExceptionMapper mapper = new TagApiExceptionMapper(uriInfo);
+ return mapper.toResponse((TagApiException) cause);
+ } else if (cause instanceof TagDefinitionApiException) {
+ final TagDefinitionApiExceptionMapper mapper = new TagDefinitionApiExceptionMapper(uriInfo);
+ return mapper.toResponse((TagDefinitionApiException) cause);
+ } else {
+ return buildBadRequestResponse(cause, uriInfo);
+ }
+ }
+
protected Response buildConflictingRequestResponse(final Exception e, final UriInfo uriInfo) {
// Log the full stacktrace
log.warn("Conflicting request", e);
diff --git a/jaxrs/src/main/java/com/ning/billing/jaxrs/mappers/InvoiceApiExceptionMapper.java b/jaxrs/src/main/java/com/ning/billing/jaxrs/mappers/InvoiceApiExceptionMapper.java
index 6dc12e4..8f19f44 100644
--- a/jaxrs/src/main/java/com/ning/billing/jaxrs/mappers/InvoiceApiExceptionMapper.java
+++ b/jaxrs/src/main/java/com/ning/billing/jaxrs/mappers/InvoiceApiExceptionMapper.java
@@ -69,7 +69,7 @@ public class InvoiceApiExceptionMapper extends ExceptionMapperBase implements Ex
} else if (exception.getCode() == ErrorCode.EXTERNAL_CHARGE_AMOUNT_INVALID.getCode()) {
return buildBadRequestResponse(exception, uriInfo);
} else {
- return buildBadRequestResponse(exception, uriInfo);
+ return fallback(exception, uriInfo);
}
}
}
diff --git a/jaxrs/src/main/java/com/ning/billing/jaxrs/mappers/OverdueApiExceptionMapper.java b/jaxrs/src/main/java/com/ning/billing/jaxrs/mappers/OverdueApiExceptionMapper.java
index 13b5e44..061f0f0 100644
--- a/jaxrs/src/main/java/com/ning/billing/jaxrs/mappers/OverdueApiExceptionMapper.java
+++ b/jaxrs/src/main/java/com/ning/billing/jaxrs/mappers/OverdueApiExceptionMapper.java
@@ -37,6 +37,6 @@ public class OverdueApiExceptionMapper extends ExceptionMapperBase implements Ex
@Override
public Response toResponse(final OverdueApiException exception) {
- return buildBadRequestResponse(exception, uriInfo);
+ return fallback(exception, uriInfo);
}
}
diff --git a/jaxrs/src/main/java/com/ning/billing/jaxrs/mappers/PaymentApiExceptionMapper.java b/jaxrs/src/main/java/com/ning/billing/jaxrs/mappers/PaymentApiExceptionMapper.java
index f4678e2..e1bafc0 100644
--- a/jaxrs/src/main/java/com/ning/billing/jaxrs/mappers/PaymentApiExceptionMapper.java
+++ b/jaxrs/src/main/java/com/ning/billing/jaxrs/mappers/PaymentApiExceptionMapper.java
@@ -99,7 +99,7 @@ public class PaymentApiExceptionMapper extends ExceptionMapperBase implements Ex
} else if (exception.getCode() == ErrorCode.PAYMENT_UPD_PAYMENT_PROVIDER_ACCOUNT.getCode()) {
return buildInternalErrorResponse(exception, uriInfo);
} else {
- return buildBadRequestResponse(exception, uriInfo);
+ return fallback(exception, uriInfo);
}
}
}
diff --git a/jaxrs/src/main/java/com/ning/billing/jaxrs/mappers/SubscriptionApiExceptionMapper.java b/jaxrs/src/main/java/com/ning/billing/jaxrs/mappers/SubscriptionApiExceptionMapper.java
index cdcc153..36a9bd0 100644
--- a/jaxrs/src/main/java/com/ning/billing/jaxrs/mappers/SubscriptionApiExceptionMapper.java
+++ b/jaxrs/src/main/java/com/ning/billing/jaxrs/mappers/SubscriptionApiExceptionMapper.java
@@ -25,7 +25,6 @@ import javax.ws.rs.ext.Provider;
import com.ning.billing.ErrorCode;
import com.ning.billing.entitlement.api.SubscriptionApiException;
-import com.ning.billing.subscription.api.user.SubscriptionBaseApiException;
@Singleton
@Provider
@@ -81,10 +80,10 @@ public class SubscriptionApiExceptionMapper extends ExceptionMapperBase implemen
return buildNotFoundResponse(exception, uriInfo);
} else if (exception.getCode() == ErrorCode.SUB_RECREATE_BAD_STATE.getCode()) {
return buildInternalErrorResponse(exception, uriInfo);
- } else if (exception.getCode() == ErrorCode.SUB_UNCANCEL_BAD_STATE.getCode()) {
+ } else if (exception.getCode() == ErrorCode.SUB_UNCANCEL_BAD_STATE.getCode()) {
return buildInternalErrorResponse(exception, uriInfo);
} else {
- return buildBadRequestResponse(exception, uriInfo);
+ return fallback(exception, uriInfo);
}
}
}
diff --git a/jaxrs/src/main/java/com/ning/billing/jaxrs/mappers/SubscriptionBillingApiExceptionMapper.java b/jaxrs/src/main/java/com/ning/billing/jaxrs/mappers/SubscriptionBillingApiExceptionMapper.java
index 2b38fe8..b48434e 100644
--- a/jaxrs/src/main/java/com/ning/billing/jaxrs/mappers/SubscriptionBillingApiExceptionMapper.java
+++ b/jaxrs/src/main/java/com/ning/billing/jaxrs/mappers/SubscriptionBillingApiExceptionMapper.java
@@ -37,6 +37,6 @@ public class SubscriptionBillingApiExceptionMapper extends ExceptionMapperBase i
@Override
public Response toResponse(final SubscriptionBillingApiException exception) {
- return buildBadRequestResponse(exception, uriInfo);
+ return fallback(exception, uriInfo);
}
}
diff --git a/jaxrs/src/main/java/com/ning/billing/jaxrs/mappers/SubscriptionRepairExceptionMapper.java b/jaxrs/src/main/java/com/ning/billing/jaxrs/mappers/SubscriptionRepairExceptionMapper.java
index 0947a82..f00f0c0 100644
--- a/jaxrs/src/main/java/com/ning/billing/jaxrs/mappers/SubscriptionRepairExceptionMapper.java
+++ b/jaxrs/src/main/java/com/ning/billing/jaxrs/mappers/SubscriptionRepairExceptionMapper.java
@@ -69,7 +69,7 @@ public class SubscriptionRepairExceptionMapper extends ExceptionMapperBase imple
} else if (exception.getCode() == ErrorCode.SUB_REPAIR_VIEW_CHANGED.getCode()) {
return buildBadRequestResponse(exception, uriInfo);
} else {
- return buildBadRequestResponse(exception, uriInfo);
+ return fallback(exception, uriInfo);
}
}
}
diff --git a/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/SubscriptionResource.java b/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/SubscriptionResource.java
index 5ca3936..f464200 100644
--- a/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/SubscriptionResource.java
+++ b/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/SubscriptionResource.java
@@ -61,6 +61,7 @@ import com.ning.billing.events.InvoiceCreationInternalEvent;
import com.ning.billing.events.NullInvoiceInternalEvent;
import com.ning.billing.events.PaymentErrorInternalEvent;
import com.ning.billing.events.PaymentInfoInternalEvent;
+import com.ning.billing.events.PaymentPluginErrorInternalEvent;
import com.ning.billing.jaxrs.json.CustomFieldJson;
import com.ning.billing.jaxrs.json.SubscriptionJson;
import com.ning.billing.jaxrs.util.Context;
@@ -341,6 +342,12 @@ public class SubscriptionResource extends JaxRsResourceBase {
log.info(String.format("Got event PaymentError token = %s ", event.getUserToken()));
notifyForCompletion();
}
+
+ @Override
+ public void onPaymentPluginError(final PaymentPluginErrorInternalEvent event) {
+ log.info(String.format("Got event PaymentPluginError token = %s ", event.getUserToken()));
+ notifyForCompletion();
+ }
}
private interface EntitlementCallCompletionCallback<T> {
diff --git a/jaxrs/src/test/java/com/ning/billing/jaxrs/json/TestRefundJson.java b/jaxrs/src/test/java/com/ning/billing/jaxrs/json/TestRefundJson.java
index 544c23d..6e4e71a 100644
--- a/jaxrs/src/test/java/com/ning/billing/jaxrs/json/TestRefundJson.java
+++ b/jaxrs/src/test/java/com/ning/billing/jaxrs/json/TestRefundJson.java
@@ -27,6 +27,7 @@ import org.testng.annotations.Test;
import com.ning.billing.catalog.api.Currency;
import com.ning.billing.jaxrs.JaxrsTestSuiteNoDB;
+import com.ning.billing.payment.api.RefundStatus;
import com.google.common.collect.ImmutableList;
@@ -43,9 +44,10 @@ public class TestRefundJson extends JaxrsTestSuiteNoDB {
final boolean isAdjusted = true;
final DateTime requestedDate = clock.getUTCNow();
final DateTime effectiveDate = clock.getUTCNow();
+ final RefundStatus status = RefundStatus.COMPLETED;
final List<InvoiceItemJson> adjustments = ImmutableList.<InvoiceItemJson>of(createInvoiceItemJson());
final List<AuditLogJson> auditLogs = createAuditLogsJson(clock.getUTCNow());
- final RefundJson refundJson = new RefundJson(refundId, paymentId, amount, currency, isAdjusted, requestedDate,
+ final RefundJson refundJson = new RefundJson(refundId, paymentId, amount, currency, status.toString(), isAdjusted, requestedDate,
effectiveDate, adjustments, auditLogs);
Assert.assertEquals(refundJson.getRefundId(), refundId);
Assert.assertEquals(refundJson.getPaymentId(), paymentId);
junction/pom.xml 12(+11 -1)
diff --git a/junction/pom.xml b/junction/pom.xml
index 71d8d07..e187bd6 100644
--- a/junction/pom.xml
+++ b/junction/pom.xml
@@ -19,7 +19,7 @@
<parent>
<artifactId>killbill</artifactId>
<groupId>com.ning.billing</groupId>
- <version>0.8.3-SNAPSHOT</version>
+ <version>0.8.6-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>killbill-junction</artifactId>
@@ -50,6 +50,11 @@
<scope>test</scope>
</dependency>
<dependency>
+ <groupId>com.jayway.awaitility</groupId>
+ <artifactId>awaitility</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
<groupId>com.ning.billing</groupId>
<artifactId>killbill-api</artifactId>
</dependency>
@@ -81,6 +86,11 @@
</dependency>
<dependency>
<groupId>com.ning.billing</groupId>
+ <artifactId>killbill-subscription</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>com.ning.billing</groupId>
<artifactId>killbill-util</artifactId>
</dependency>
<dependency>
diff --git a/junction/src/main/java/com/ning/billing/junction/plumbing/billing/BlockingCalculator.java b/junction/src/main/java/com/ning/billing/junction/plumbing/billing/BlockingCalculator.java
index e2cca24..bc98975 100644
--- a/junction/src/main/java/com/ning/billing/junction/plumbing/billing/BlockingCalculator.java
+++ b/junction/src/main/java/com/ning/billing/junction/plumbing/billing/BlockingCalculator.java
@@ -25,22 +25,23 @@ import java.util.TreeSet;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicLong;
+import javax.annotation.Nullable;
+
import org.joda.time.DateTime;
import org.joda.time.DateTimeZone;
import com.ning.billing.account.api.Account;
+import com.ning.billing.callcontext.InternalTenantContext;
import com.ning.billing.catalog.api.BillingPeriod;
import com.ning.billing.catalog.api.Currency;
import com.ning.billing.catalog.api.Plan;
import com.ning.billing.catalog.api.PlanPhase;
-import com.ning.billing.entitlement.api.BlockingStateType;
-import com.ning.billing.subscription.api.SubscriptionBaseTransitionType;
-import com.ning.billing.subscription.api.SubscriptionBase;
import com.ning.billing.entitlement.api.BlockingState;
-import com.ning.billing.callcontext.InternalTenantContext;
import com.ning.billing.junction.BillingEvent;
import com.ning.billing.junction.BillingModeType;
import com.ning.billing.junction.BlockingInternalApi;
+import com.ning.billing.subscription.api.SubscriptionBase;
+import com.ning.billing.subscription.api.SubscriptionBaseTransitionType;
import com.google.common.annotations.VisibleForTesting;
import com.google.inject.Inject;
@@ -52,8 +53,9 @@ public class BlockingCalculator {
private final BlockingInternalApi blockingApi;
protected static class DisabledDuration {
+
private final DateTime start;
- private final DateTime end;
+ private DateTime end;
public DisabledDuration(final DateTime start, final DateTime end) {
this.start = start;
@@ -68,6 +70,9 @@ public class BlockingCalculator {
return end;
}
+ public void setEnd(final DateTime end) {
+ this.end = end;
+ }
}
@Inject
@@ -92,11 +97,9 @@ public class BlockingCalculator {
final SortedSet<BillingEvent> billingEventsToAdd = new TreeSet<BillingEvent>();
final SortedSet<BillingEvent> billingEventsToRemove = new TreeSet<BillingEvent>();
+ final List<BlockingState> blockingEvents = blockingApi.getBlockingAllForAccount(context);
+ final List<DisabledDuration> blockingDurations = createBlockingDurations(blockingEvents);
for (final UUID bundleId : bundleMap.keySet()) {
- final List<BlockingState> blockingEvents = blockingApi.getBlockingAll(bundleId, BlockingStateType.SUBSCRIPTION_BUNDLE, context);
- blockingEvents.addAll(blockingApi.getBlockingAll(account.getId(), BlockingStateType.ACCOUNT, context));
- final List<DisabledDuration> blockingDurations = createBlockingDurations(blockingEvents);
-
for (final SubscriptionBase subscription : bundleMap.get(bundleId)) {
billingEventsToAdd.addAll(createNewEvents(blockingDurations, billingEvents, account, subscription));
billingEventsToRemove.addAll(eventsToRemove(blockingDurations, billingEvents, subscription));
@@ -252,31 +255,56 @@ public class BlockingCalculator {
return result;
}
-
// In ascending order
- protected List<DisabledDuration> createBlockingDurations(final List<BlockingState> overdueBundleEvents) {
+ protected List<DisabledDuration> createBlockingDurations(final Iterable<BlockingState> overdueBundleEvents) {
final List<DisabledDuration> result = new ArrayList<BlockingCalculator.DisabledDuration>();
// Earliest blocking event
BlockingState first = null;
+ int blockedNesting = 0;
+ BlockingState lastOne = null;
for (final BlockingState e : overdueBundleEvents) {
- if (e.isBlockBilling() && first == null) {
+ lastOne = e;
+ if (e.isBlockBilling() && blockedNesting == 0) {
// First blocking event of contiguous series of blocking events
first = e;
- } else if (first != null && !e.isBlockBilling()) {
- // End of the interval
- result.add(new DisabledDuration(first.getEffectiveDate(), e.getEffectiveDate()));
- first = null;
+ blockedNesting++;
+ } else if (e.isBlockBilling() && blockedNesting > 0) {
+ // Nest blocking states
+ blockedNesting++;
+ } else if (!e.isBlockBilling() && blockedNesting > 0) {
+ blockedNesting--;
+ if (blockedNesting == 0) {
+ // End of the interval
+ addDisabledDuration(result, first, e);
+ first = null;
+ }
}
}
if (first != null) { // found a transition to disabled with no terminating event
- result.add(new DisabledDuration(first.getEffectiveDate(), null));
+ addDisabledDuration(result, first, lastOne.isBlockBilling() ? null : lastOne);
}
return result;
}
+ private void addDisabledDuration(final List<DisabledDuration> result, final BlockingState firstBlocking, @Nullable final BlockingState firstNonBlocking) {
+ final DisabledDuration lastOne;
+ if (!result.isEmpty()) {
+ lastOne = result.get(result.size() - 1);
+ } else {
+ lastOne = null;
+ }
+
+ final DateTime endDate = firstNonBlocking == null ? null : firstNonBlocking.getEffectiveDate();
+ if (lastOne != null && lastOne.getEnd().compareTo(firstBlocking.getEffectiveDate()) == 0) {
+ lastOne.setEnd(endDate);
+ } else {
+ result.add(new DisabledDuration(firstBlocking.getEffectiveDate(), endDate));
+ }
+ }
+
@VisibleForTesting
static AtomicLong getGlobalTotalOrder() {
return globaltotalOrder;
diff --git a/junction/src/main/java/com/ning/billing/junction/plumbing/billing/DefaultBillingEventSet.java b/junction/src/main/java/com/ning/billing/junction/plumbing/billing/DefaultBillingEventSet.java
index 03df1e7..c55aaa3 100644
--- a/junction/src/main/java/com/ning/billing/junction/plumbing/billing/DefaultBillingEventSet.java
+++ b/junction/src/main/java/com/ning/billing/junction/plumbing/billing/DefaultBillingEventSet.java
@@ -55,10 +55,6 @@ public class DefaultBillingEventSet extends TreeSet<BillingEvent> implements Sor
this.subscriptionIdsWithAutoInvoiceOff = subscriptionIdsWithAutoInvoiceOff;
}
- public boolean isLast(final BillingEvent event) {
- return last() == event;
- }
-
@Override
public String toString() {
return "DefaultBillingEventSet [accountAutoInvoiceOff=" + accountAutoInvoiceOff
diff --git a/junction/src/main/java/com/ning/billing/junction/plumbing/billing/DefaultInternalBillingApi.java b/junction/src/main/java/com/ning/billing/junction/plumbing/billing/DefaultInternalBillingApi.java
index 83ef135..defc439 100644
--- a/junction/src/main/java/com/ning/billing/junction/plumbing/billing/DefaultInternalBillingApi.java
+++ b/junction/src/main/java/com/ning/billing/junction/plumbing/billing/DefaultInternalBillingApi.java
@@ -28,18 +28,18 @@ import org.slf4j.LoggerFactory;
import com.ning.billing.ObjectType;
import com.ning.billing.account.api.Account;
import com.ning.billing.account.api.AccountApiException;
+import com.ning.billing.account.api.AccountInternalApi;
import com.ning.billing.account.api.MutableAccountData;
+import com.ning.billing.callcontext.InternalCallContext;
import com.ning.billing.catalog.api.CatalogApiException;
import com.ning.billing.catalog.api.CatalogService;
-import com.ning.billing.subscription.api.SubscriptionBase;
-import com.ning.billing.subscription.api.user.SubscriptionBaseBundle;
-import com.ning.billing.callcontext.InternalCallContext;
import com.ning.billing.events.EffectiveSubscriptionInternalEvent;
-import com.ning.billing.account.api.AccountInternalApi;
-import com.ning.billing.subscription.api.SubscriptionBaseInternalApi;
import com.ning.billing.junction.BillingEvent;
import com.ning.billing.junction.BillingEventSet;
import com.ning.billing.junction.BillingInternalApi;
+import com.ning.billing.subscription.api.SubscriptionBase;
+import com.ning.billing.subscription.api.SubscriptionBaseInternalApi;
+import com.ning.billing.subscription.api.user.SubscriptionBaseBundle;
import com.ning.billing.tag.TagInternalApi;
import com.ning.billing.util.tag.ControlTagType;
import com.ning.billing.util.tag.Tag;
diff --git a/junction/src/test/java/com/ning/billing/junction/glue/TestJunctionModule.java b/junction/src/test/java/com/ning/billing/junction/glue/TestJunctionModule.java
index 0d4307d..b74b247 100644
--- a/junction/src/test/java/com/ning/billing/junction/glue/TestJunctionModule.java
+++ b/junction/src/test/java/com/ning/billing/junction/glue/TestJunctionModule.java
@@ -18,19 +18,16 @@ package com.ning.billing.junction.glue;
import org.skife.config.ConfigSource;
-import com.ning.billing.catalog.MockCatalogModule;
import com.ning.billing.entitlement.api.svcs.DefaultInternalBlockingApi;
import com.ning.billing.entitlement.block.BlockingChecker;
import com.ning.billing.entitlement.block.MockBlockingChecker;
import com.ning.billing.entitlement.dao.BlockingStateDao;
import com.ning.billing.entitlement.dao.MockBlockingStateDao;
-import com.ning.billing.mock.glue.MockAccountModule;
+import com.ning.billing.junction.BlockingInternalApi;
import com.ning.billing.mock.glue.MockEntitlementModule;
-import com.ning.billing.mock.glue.MockSubscriptionModule;
import com.ning.billing.util.glue.CacheModule;
import com.ning.billing.util.glue.CallContextModule;
import com.ning.billing.util.glue.MetricsModule;
-import com.ning.billing.junction.BlockingInternalApi;
public class TestJunctionModule extends DefaultJunctionModule {
@@ -45,10 +42,6 @@ public class TestJunctionModule extends DefaultJunctionModule {
install(new MetricsModule());
install(new CacheModule(configSource));
install(new CallContextModule());
- install(new MockAccountModule());
- install(new MockCatalogModule());
- install(new MockSubscriptionModule());
- install(new MockEntitlementModuleForJunction());
}
public class MockEntitlementModuleForJunction extends MockEntitlementModule {
@@ -67,6 +60,5 @@ public class TestJunctionModule extends DefaultJunctionModule {
public void installBlockingChecker() {
bind(BlockingChecker.class).to(MockBlockingChecker.class).asEagerSingleton();
}
-
}
}
diff --git a/junction/src/test/java/com/ning/billing/junction/glue/TestJunctionModuleNoDB.java b/junction/src/test/java/com/ning/billing/junction/glue/TestJunctionModuleNoDB.java
index faf6939..081d6aa 100644
--- a/junction/src/test/java/com/ning/billing/junction/glue/TestJunctionModuleNoDB.java
+++ b/junction/src/test/java/com/ning/billing/junction/glue/TestJunctionModuleNoDB.java
@@ -20,7 +20,10 @@ import org.skife.config.ConfigSource;
import org.skife.config.ConfigurationObjectFactory;
import com.ning.billing.GuicyKillbillTestNoDBModule;
+import com.ning.billing.catalog.MockCatalogModule;
+import com.ning.billing.mock.glue.MockAccountModule;
import com.ning.billing.mock.glue.MockNonEntityDaoModule;
+import com.ning.billing.mock.glue.MockSubscriptionModule;
import com.ning.billing.mock.glue.MockTagModule;
import com.ning.billing.notificationq.MockNotificationQueueService;
import com.ning.billing.notificationq.api.NotificationQueueConfig;
@@ -42,6 +45,10 @@ public class TestJunctionModuleNoDB extends TestJunctionModule {
install(new GuicyKillbillTestNoDBModule());
install(new MockNonEntityDaoModule());
install(new InMemoryBusModule(configSource));
+ install(new MockAccountModule());
+ install(new MockCatalogModule());
+ install(new MockSubscriptionModule());
+ install(new MockEntitlementModuleForJunction());
install(new MockTagModule());
installNotificationQueue();
}
diff --git a/junction/src/test/java/com/ning/billing/junction/glue/TestJunctionModuleWithEmbeddedDB.java b/junction/src/test/java/com/ning/billing/junction/glue/TestJunctionModuleWithEmbeddedDB.java
index ccac19f..32dff2d 100644
--- a/junction/src/test/java/com/ning/billing/junction/glue/TestJunctionModuleWithEmbeddedDB.java
+++ b/junction/src/test/java/com/ning/billing/junction/glue/TestJunctionModuleWithEmbeddedDB.java
@@ -19,9 +19,17 @@ package com.ning.billing.junction.glue;
import org.skife.config.ConfigSource;
import com.ning.billing.GuicyKillbillTestWithEmbeddedDBModule;
+import com.ning.billing.account.glue.DefaultAccountModule;
+import com.ning.billing.api.TestApiListener;
+import com.ning.billing.api.TestListenerStatus;
+import com.ning.billing.catalog.glue.CatalogModule;
+import com.ning.billing.entitlement.glue.DefaultEntitlementModule;
+import com.ning.billing.junction.JunctionTestListenerStatus;
+import com.ning.billing.subscription.glue.DefaultSubscriptionModule;
import com.ning.billing.util.glue.BusModule;
import com.ning.billing.util.glue.MetricsModule;
import com.ning.billing.util.glue.NonEntityDaoModule;
+import com.ning.billing.util.glue.NotificationQueueModule;
import com.ning.billing.util.glue.TagStoreModule;
public class TestJunctionModuleWithEmbeddedDB extends TestJunctionModule {
@@ -36,8 +44,16 @@ public class TestJunctionModuleWithEmbeddedDB extends TestJunctionModule {
install(new GuicyKillbillTestWithEmbeddedDBModule());
install(new NonEntityDaoModule());
+ install(new CatalogModule(configSource));
+ install(new DefaultAccountModule(configSource));
+ install(new DefaultEntitlementModule(configSource));
+ install(new NotificationQueueModule(configSource));
+ install(new DefaultSubscriptionModule(configSource));
install(new BusModule(configSource));
install(new MetricsModule());
install(new TagStoreModule());
+
+ bind(TestListenerStatus.class).to(JunctionTestListenerStatus.class).asEagerSingleton();
+ bind(TestApiListener.class).asEagerSingleton();
}
}
diff --git a/junction/src/test/java/com/ning/billing/junction/JunctionTestListenerStatus.java b/junction/src/test/java/com/ning/billing/junction/JunctionTestListenerStatus.java
new file mode 100644
index 0000000..a54976d
--- /dev/null
+++ b/junction/src/test/java/com/ning/billing/junction/JunctionTestListenerStatus.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2010-2013 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.ning.billing.junction;
+
+import javax.inject.Inject;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.testng.Assert;
+
+import com.ning.billing.api.TestListenerStatus;
+
+public class JunctionTestListenerStatus implements TestListenerStatus {
+
+ private final Logger log = LoggerFactory.getLogger(JunctionTestListenerStatus.class);
+
+ private boolean isListenerFailed;
+ private String listenerFailedMsg;
+
+ @Inject
+ public JunctionTestListenerStatus() {
+ isListenerFailed = false;
+ }
+
+ @Override
+ public void failed(final String msg) {
+ this.isListenerFailed = true;
+ this.listenerFailedMsg = msg;
+ }
+
+ @Override
+ public void resetTestListenerStatus() {
+ this.isListenerFailed = false;
+ this.listenerFailedMsg = null;
+ }
+
+ public void assertListenerStatus() {
+ if (isListenerFailed) {
+ log.error(listenerFailedMsg);
+ Assert.fail(listenerFailedMsg);
+ }
+ }
+}
\ No newline at end of file
diff --git a/junction/src/test/java/com/ning/billing/junction/JunctionTestSuiteWithEmbeddedDB.java b/junction/src/test/java/com/ning/billing/junction/JunctionTestSuiteWithEmbeddedDB.java
index 1c989e2..20750d0 100644
--- a/junction/src/test/java/com/ning/billing/junction/JunctionTestSuiteWithEmbeddedDB.java
+++ b/junction/src/test/java/com/ning/billing/junction/JunctionTestSuiteWithEmbeddedDB.java
@@ -16,34 +16,63 @@
package com.ning.billing.junction;
+import java.net.URL;
+import java.util.UUID;
+
+import org.joda.time.DateTime;
+import org.joda.time.DateTimeZone;
+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.ning.billing.GuicyKillbillTestSuiteWithEmbeddedDB;
+import com.ning.billing.account.api.AccountData;
+import com.ning.billing.account.api.AccountUserApi;
+import com.ning.billing.api.TestApiListener;
+import com.ning.billing.api.TestListenerStatus;
import com.ning.billing.bus.api.PersistentBus;
+import com.ning.billing.catalog.DefaultCatalogService;
+import com.ning.billing.catalog.api.Catalog;
import com.ning.billing.catalog.api.CatalogService;
+import com.ning.billing.catalog.api.Currency;
+import com.ning.billing.clock.ClockMock;
+import com.ning.billing.entitlement.DefaultEntitlementService;
+import com.ning.billing.entitlement.EntitlementService;
+import com.ning.billing.entitlement.api.EntitlementApi;
import com.ning.billing.junction.glue.TestJunctionModuleWithEmbeddedDB;
-import com.ning.billing.junction.plumbing.billing.BlockingCalculator;
-import com.ning.billing.account.api.AccountInternalApi;
+import com.ning.billing.mock.MockAccountBuilder;
import com.ning.billing.subscription.api.SubscriptionBaseInternalApi;
-import com.ning.billing.tag.TagInternalApi;
-import com.ning.billing.util.tag.dao.TagDao;
+import com.ning.billing.subscription.api.SubscriptionBaseService;
+import com.ning.billing.subscription.engine.core.DefaultSubscriptionBaseService;
+import com.ning.billing.util.callcontext.InternalCallContextFactory;
+import com.ning.billing.util.svcsapi.bus.BusService;
import com.google.inject.Guice;
import com.google.inject.Inject;
import com.google.inject.Injector;
+import com.google.inject.Stage;
+
+import static org.testng.Assert.assertNotNull;
+import static org.testng.Assert.assertTrue;
public abstract class JunctionTestSuiteWithEmbeddedDB extends GuicyKillbillTestSuiteWithEmbeddedDB {
+ protected static final Logger log = LoggerFactory.getLogger(JunctionTestSuiteWithEmbeddedDB.class);
+
+ // Be generous...
+ protected static final Long DELAY = 20000L;
+
@Inject
- protected AccountInternalApi accountInternalApi;
+ protected AccountUserApi accountApi;
@Inject
- protected BillingInternalApi billingInternalApi;
+ protected BlockingInternalApi blockingInternalApi;
@Inject
- protected BlockingCalculator blockingCalculator;
+ protected EntitlementApi entitlementApi;
@Inject
- protected BlockingInternalApi blockingInternalApi;
+ protected BillingInternalApi billingInternalApi;
@Inject
protected CatalogService catalogService;
@Inject
@@ -51,24 +80,145 @@ public abstract class JunctionTestSuiteWithEmbeddedDB extends GuicyKillbillTestS
@Inject
protected PersistentBus bus;
@Inject
- protected TagDao tagDao;
+ protected TestApiListener testListener;
+ @Inject
+ protected TestListenerStatus testListenerStatus;
+ @Inject
+ protected BusService busService;
@Inject
- protected TagInternalApi tagInternalApi;
+ protected SubscriptionBaseService subscriptionBaseService;
+ @Inject
+ protected EntitlementService entitlementService;
+ @Inject
+ protected InternalCallContextFactory internalCallContextFactory;
+
+ protected Catalog catalog;
+
+ private void loadSystemPropertiesFromClasspath(final String resource) {
+ final URL url = JunctionTestSuiteWithEmbeddedDB.class.getResource(resource);
+ Assert.assertNotNull(url);
+
+ configSource.merge(url);
+ }
@BeforeClass(groups = "slow")
protected void beforeClass() throws Exception {
- final Injector injector = Guice.createInjector(new TestJunctionModuleWithEmbeddedDB(configSource));
+ loadSystemPropertiesFromClasspath("/junction.properties");
+ final Injector injector = Guice.createInjector(Stage.PRODUCTION, new TestJunctionModuleWithEmbeddedDB(configSource));
injector.injectMembers(this);
}
@BeforeMethod(groups = "slow")
public void beforeMethod() throws Exception {
super.beforeMethod();
- bus.start();
+ startTestFamework();
+ this.catalog = initCatalog(catalogService);
+
+ // Make sure we start with a clean state
+ assertListenerStatus();
}
@AfterMethod(groups = "slow")
- public void afterMethod() {
- bus.stop();
+ public void afterMethod() throws Exception {
+ // Make sure we finish in a clean state
+ assertListenerStatus();
+
+ stopTestFramework();
+ }
+
+ private Catalog initCatalog(final CatalogService catalogService) throws Exception {
+ ((DefaultCatalogService) catalogService).loadCatalog();
+ final Catalog catalog = catalogService.getFullCatalog();
+ assertNotNull(catalog);
+ return catalog;
+ }
+
+ private void startTestFamework() throws Exception {
+ log.debug("STARTING TEST FRAMEWORK");
+
+ resetTestListener(testListener, testListenerStatus);
+
+ resetClockToStartOfTest(clock);
+
+ startBusAndRegisterListener(busService, testListener);
+
+ restartSubscriptionService(subscriptionBaseService);
+ restartEntitlementService(entitlementService);
+
+ log.debug("STARTED TEST FRAMEWORK");
+ }
+
+ private void stopTestFramework() throws Exception {
+ log.debug("STOPPING TEST FRAMEWORK");
+ stopBusAndUnregisterListener(busService, testListener);
+ stopSubscriptionService(subscriptionBaseService);
+ stopEntitlementService(entitlementService);
+ log.debug("STOPPED TEST FRAMEWORK");
+ }
+
+ private void resetTestListener(final TestApiListener testListener, final TestListenerStatus testListenerStatus) {
+ // RESET LIST OF EXPECTED EVENTS
+ if (testListener != null) {
+ testListener.reset();
+ testListenerStatus.resetTestListenerStatus();
+ }
+ }
+
+ private void resetClockToStartOfTest(final ClockMock clock) {
+ clock.resetDeltaFromReality();
+
+ // Date at which all tests start-- we create the date object here after the system properties which set the JVM in UTC have been set.
+ final DateTime testStartDate = new DateTime(2012, 5, 7, 0, 3, 42, 0);
+ clock.setDeltaFromReality(testStartDate.getMillis() - clock.getUTCNow().getMillis());
+ }
+
+ private void startBusAndRegisterListener(final BusService busService, final TestApiListener testListener) throws Exception {
+ busService.getBus().start();
+ busService.getBus().register(testListener);
+ }
+
+ private void restartSubscriptionService(final SubscriptionBaseService subscriptionBaseService) {
+ // START NOTIFICATION QUEUE FOR SUBSCRIPTION
+ ((DefaultSubscriptionBaseService) subscriptionBaseService).initialize();
+ ((DefaultSubscriptionBaseService) subscriptionBaseService).start();
+ }
+
+ private void restartEntitlementService(final EntitlementService entitlementService) {
+ // START NOTIFICATION QUEUE FOR ENTITLEMENT
+ ((DefaultEntitlementService) entitlementService).initialize();
+ ((DefaultEntitlementService) entitlementService).start();
+ }
+
+ private void stopBusAndUnregisterListener(final BusService busService, final TestApiListener testListener) throws Exception {
+ busService.getBus().unregister(testListener);
+ busService.getBus().stop();
+ }
+
+ private void stopSubscriptionService(final SubscriptionBaseService subscriptionBaseService) throws Exception {
+ ((DefaultSubscriptionBaseService) subscriptionBaseService).stop();
+ }
+
+ private void stopEntitlementService(final EntitlementService entitlementService) throws Exception {
+ ((DefaultEntitlementService) entitlementService).stop();
+ }
+
+ protected AccountData getAccountData(final int billingDay) {
+ return new MockAccountBuilder().name(UUID.randomUUID().toString().substring(1, 8))
+ .firstNameLength(6)
+ .email(UUID.randomUUID().toString().substring(1, 8))
+ .phone(UUID.randomUUID().toString().substring(1, 8))
+ .migrated(false)
+ .isNotifiedForInvoices(false)
+ .externalKey(UUID.randomUUID().toString().substring(1, 8))
+ .billingCycleDayLocal(billingDay)
+ .currency(Currency.USD)
+ .paymentMethodId(UUID.randomUUID())
+ .timeZone(DateTimeZone.UTC)
+ .build();
+ }
+
+ protected void assertListenerStatus() {
+ assertTrue(testListener.isCompleted(DELAY));
+ ((JunctionTestListenerStatus) testListenerStatus).assertListenerStatus();
}
}
diff --git a/junction/src/test/java/com/ning/billing/junction/plumbing/billing/TestBillingApi.java b/junction/src/test/java/com/ning/billing/junction/plumbing/billing/TestBillingApi.java
index cd4ac59..c2354e9 100644
--- a/junction/src/test/java/com/ning/billing/junction/plumbing/billing/TestBillingApi.java
+++ b/junction/src/test/java/com/ning/billing/junction/plumbing/billing/TestBillingApi.java
@@ -181,11 +181,9 @@ public class TestBillingApi extends JunctionTestSuiteNoDB {
final Account account = createAccount(32);
- final List<BlockingState> blockingStates = new ArrayList<BlockingState>();
- blockingStates.add(new DefaultBlockingState(bunId, BlockingStateType.SUBSCRIPTION_BUNDLE, DISABLED_BUNDLE, "test", true, true, true, now.plusDays(1)));
- blockingStates.add(new DefaultBlockingState(bunId, BlockingStateType.SUBSCRIPTION_BUNDLE, CLEAR_BUNDLE, "test", false, false, false, now.plusDays(2)));
+ blockingStateDao.setBlockingState(new DefaultBlockingState(bunId, BlockingStateType.SUBSCRIPTION_BUNDLE, DISABLED_BUNDLE, "test", true, true, true, now.plusDays(1)), clock, internalCallContext);
+ blockingStateDao.setBlockingState(new DefaultBlockingState(bunId, BlockingStateType.SUBSCRIPTION_BUNDLE, CLEAR_BUNDLE, "test", false, false, false, now.plusDays(2)), clock, internalCallContext);
- ((MockBlockingStateDao) blockingStateDao).setBlockingStates(bunId, blockingStates);
final SortedSet<BillingEvent> events = billingInternalApi.getBillingEventsForAccountAndUpdateAccountBCD(account.getId(), internalCallContext);
Assert.assertEquals(events.size(), 3);
diff --git a/junction/src/test/java/com/ning/billing/junction/plumbing/billing/TestBlockingCalculator.java b/junction/src/test/java/com/ning/billing/junction/plumbing/billing/TestBlockingCalculator.java
index 409ce5f..71a5b22 100644
--- a/junction/src/test/java/com/ning/billing/junction/plumbing/billing/TestBlockingCalculator.java
+++ b/junction/src/test/java/com/ning/billing/junction/plumbing/billing/TestBlockingCalculator.java
@@ -16,6 +16,22 @@
package com.ning.billing.junction.plumbing.billing;
+import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.Hashtable;
+import java.util.Iterator;
+import java.util.List;
+import java.util.SortedSet;
+import java.util.TreeSet;
+import java.util.UUID;
+
+import org.joda.time.DateTime;
+import org.joda.time.DateTimeZone;
+import org.joda.time.LocalDate;
+import org.mockito.Mockito;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
import com.ning.billing.account.api.Account;
import com.ning.billing.catalog.MockPlan;
import com.ning.billing.catalog.MockPlanPhase;
@@ -23,31 +39,16 @@ import com.ning.billing.catalog.api.BillingPeriod;
import com.ning.billing.catalog.api.Currency;
import com.ning.billing.catalog.api.Plan;
import com.ning.billing.catalog.api.PlanPhase;
+import com.ning.billing.entitlement.api.BlockingState;
import com.ning.billing.entitlement.api.BlockingStateType;
import com.ning.billing.entitlement.dao.MockBlockingStateDao;
-import com.ning.billing.junction.JunctionTestSuiteNoDB;
-import com.ning.billing.entitlement.api.BlockingState;
-import com.ning.billing.junction.plumbing.billing.BlockingCalculator.DisabledDuration;
-import com.ning.billing.subscription.api.SubscriptionBaseTransitionType;
-import com.ning.billing.subscription.api.SubscriptionBase;
import com.ning.billing.junction.BillingEvent;
import com.ning.billing.junction.BillingModeType;
import com.ning.billing.junction.DefaultBlockingState;
-import org.joda.time.DateTime;
-import org.joda.time.DateTimeZone;
-import org.joda.time.LocalDate;
-import org.mockito.Mockito;
-import org.testng.annotations.BeforeMethod;
-import org.testng.annotations.Test;
-
-import java.math.BigDecimal;
-import java.util.ArrayList;
-import java.util.Hashtable;
-import java.util.Iterator;
-import java.util.List;
-import java.util.SortedSet;
-import java.util.TreeSet;
-import java.util.UUID;
+import com.ning.billing.junction.JunctionTestSuiteNoDB;
+import com.ning.billing.junction.plumbing.billing.BlockingCalculator.DisabledDuration;
+import com.ning.billing.subscription.api.SubscriptionBase;
+import com.ning.billing.subscription.api.SubscriptionBaseTransitionType;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertNotNull;
@@ -114,9 +115,9 @@ public class TestBlockingCalculator extends JunctionTestSuiteNoDB {
final List<BlockingState> blockingStates = new ArrayList<BlockingState>();
blockingStates.add(new DefaultBlockingState(bundleId1, BlockingStateType.SUBSCRIPTION_BUNDLE, DISABLED_BUNDLE, "test", true, true, true, now));
- blockingStates.add(new DefaultBlockingState(bundleId1, BlockingStateType.SUBSCRIPTION_BUNDLE, CLEAR_BUNDLE, "test", false, false, false, now.plusDays(2)));
+ blockingStates.add(new DefaultBlockingState(bundleId1, BlockingStateType.SUBSCRIPTION_BUNDLE, CLEAR_BUNDLE, "test", false, false, false, now.plusDays(2)));
- setBlockingStates(bundleId1, blockingStates);
+ setBlockingStates(blockingStates);
blockingCalculator.insertBlockingEvents(billingEvents, internalCallContext);
@@ -516,7 +517,6 @@ public class TestBlockingCalculator extends JunctionTestSuiteNoDB {
assertNotNull(minus5andAHalf);
assertEquals(minus5andAHalf.getEffectiveDate(), now.minusDays(6));
-
}
protected BillingEvent createRealEvent(final DateTime effectiveDate, final SubscriptionBase subscription) {
@@ -538,9 +538,9 @@ public class TestBlockingCalculator extends JunctionTestSuiteNoDB {
final DateTimeZone tz = DateTimeZone.UTC;
return new DefaultBillingEvent(account, subscription, effectiveDate, plan, planPhase,
- fixedPrice, recurringPrice, currency,
- billingPeriod, billCycleDay, billingModeType,
- description, totalOrdering, type, tz);
+ fixedPrice, recurringPrice, currency,
+ billingPeriod, billCycleDay, billingModeType,
+ description, totalOrdering, type, tz);
}
@Test(groups = "fast")
@@ -610,7 +610,7 @@ public class TestBlockingCalculator extends JunctionTestSuiteNoDB {
public MockBillingEvent() {
super(account, subscription1, clock.getUTCNow(), null, null, BigDecimal.ZERO, BigDecimal.TEN, Currency.USD, BillingPeriod.ANNUAL,
- 4, BillingModeType.IN_ADVANCE, "", 3L, SubscriptionBaseTransitionType.CREATE, DateTimeZone.UTC);
+ 4, BillingModeType.IN_ADVANCE, "", 3L, SubscriptionBaseTransitionType.CREATE, DateTimeZone.UTC);
}
}
@@ -642,9 +642,10 @@ public class TestBlockingCalculator extends JunctionTestSuiteNoDB {
public void testCreateDisablePairs() {
List<BlockingState> blockingEvents;
final UUID ovdId = UUID.randomUUID();
+ final UUID ovdId2 = UUID.randomUUID();
final DateTime now = clock.getUTCNow();
- //simple events open clear -> disabled
+ // Simple events open clear -> disabled
blockingEvents = new ArrayList<BlockingState>();
blockingEvents.add(new DefaultBlockingState(ovdId, BlockingStateType.SUBSCRIPTION_BUNDLE, CLEAR_BUNDLE, "test", false, false, false, now));
blockingEvents.add(new DefaultBlockingState(ovdId, BlockingStateType.SUBSCRIPTION_BUNDLE, DISABLED_BUNDLE, "test", true, true, true, now.plusDays(1)));
@@ -655,7 +656,7 @@ public class TestBlockingCalculator extends JunctionTestSuiteNoDB {
assertEquals(pairs.get(0).getStart(), now.plusDays(1));
assertNull(pairs.get(0).getEnd());
- //simple events closed clear -> disabled
+ // Simple events closed clear -> disabled
blockingEvents = new ArrayList<BlockingState>();
blockingEvents.add(new DefaultBlockingState(ovdId, BlockingStateType.SUBSCRIPTION_BUNDLE, CLEAR_BUNDLE, "test", false, false, false, now));
blockingEvents.add(new DefaultBlockingState(ovdId, BlockingStateType.SUBSCRIPTION_BUNDLE, DISABLED_BUNDLE, "test", true, true, true, now.plusDays(1)));
@@ -668,7 +669,7 @@ public class TestBlockingCalculator extends JunctionTestSuiteNoDB {
assertNotNull(pairs.get(0).getEnd());
assertEquals(pairs.get(0).getEnd(), now.plusDays(2));
- //simple BUNDLE events closed clear -> disabled
+ // Simple BUNDLE events closed clear -> disabled
blockingEvents = new ArrayList<BlockingState>();
blockingEvents.add(new DefaultBlockingState(ovdId, BlockingStateType.SUBSCRIPTION_BUNDLE, CLEAR_BUNDLE, "test", false, false, false, now));
blockingEvents.add(new DefaultBlockingState(ovdId, BlockingStateType.SUBSCRIPTION_BUNDLE, DISABLED_BUNDLE, "test", true, true, true, now.plusDays(1)));
@@ -681,7 +682,7 @@ public class TestBlockingCalculator extends JunctionTestSuiteNoDB {
assertNotNull(pairs.get(0).getEnd());
assertEquals(pairs.get(0).getEnd(), now.plusDays(2));
- //two or more disableds in a row
+ // Two or more disabled in a row
blockingEvents = new ArrayList<BlockingState>();
blockingEvents.add(new DefaultBlockingState(ovdId, BlockingStateType.SUBSCRIPTION_BUNDLE, CLEAR_BUNDLE, "test", false, false, false, now));
blockingEvents.add(new DefaultBlockingState(ovdId, BlockingStateType.SUBSCRIPTION_BUNDLE, DISABLED_BUNDLE, "test", true, true, true, now.plusDays(1)));
@@ -708,6 +709,30 @@ public class TestBlockingCalculator extends JunctionTestSuiteNoDB {
assertEquals(pairs.get(0).getStart(), now.plusDays(1));
assertNotNull(pairs.get(0).getEnd());
assertEquals(pairs.get(0).getEnd(), now.plusDays(4));
+
+ // Verify ordering at the same effective date doesn't matter. This is to work around nondeterministic ordering
+ // behavior in ProxyBlockingStateDao#BLOCKING_STATE_ORDERING_WITH_TIES_UNHANDLED. See also TestDefaultInternalBillingApi.
+ blockingEvents = new ArrayList<BlockingState>();
+ blockingEvents.add(new DefaultBlockingState(ovdId, BlockingStateType.SUBSCRIPTION_BUNDLE, DISABLED_BUNDLE, "test", true, true, true, now.plusDays(1)));
+ blockingEvents.add(new DefaultBlockingState(ovdId, BlockingStateType.SUBSCRIPTION_BUNDLE, CLEAR_BUNDLE, "test", false, false, false, now.plusDays(2)));
+ blockingEvents.add(new DefaultBlockingState(ovdId2, BlockingStateType.SUBSCRIPTION_BUNDLE, DISABLED_BUNDLE, "test", true, true, true, now.plusDays(2)));
+ blockingEvents.add(new DefaultBlockingState(ovdId2, BlockingStateType.SUBSCRIPTION_BUNDLE, CLEAR_BUNDLE, "test", false, false, false, now.plusDays(3)));
+
+ pairs = blockingCalculator.createBlockingDurations(blockingEvents);
+ assertEquals(pairs.size(), 1);
+ assertEquals(pairs.get(0).getStart(), now.plusDays(1));
+ assertEquals(pairs.get(0).getEnd(), now.plusDays(3));
+
+ blockingEvents = new ArrayList<BlockingState>();
+ blockingEvents.add(new DefaultBlockingState(ovdId, BlockingStateType.SUBSCRIPTION_BUNDLE, DISABLED_BUNDLE, "test", true, true, true, now.plusDays(1)));
+ blockingEvents.add(new DefaultBlockingState(ovdId2, BlockingStateType.SUBSCRIPTION_BUNDLE, DISABLED_BUNDLE, "test", true, true, true, now.plusDays(2)));
+ blockingEvents.add(new DefaultBlockingState(ovdId, BlockingStateType.SUBSCRIPTION_BUNDLE, CLEAR_BUNDLE, "test", false, false, false, now.plusDays(2)));
+ blockingEvents.add(new DefaultBlockingState(ovdId2, BlockingStateType.SUBSCRIPTION_BUNDLE, CLEAR_BUNDLE, "test", false, false, false, now.plusDays(3)));
+
+ pairs = blockingCalculator.createBlockingDurations(blockingEvents);
+ assertEquals(pairs.size(), 1);
+ assertEquals(pairs.get(0).getStart(), now.plusDays(1));
+ assertEquals(pairs.get(0).getEnd(), now.plusDays(3));
}
@Test(groups = "fast")
@@ -728,7 +753,7 @@ public class TestBlockingCalculator extends JunctionTestSuiteNoDB {
blockingEvents.add(new DefaultBlockingState(ovdId, BlockingStateType.SUBSCRIPTION_BUNDLE, DISABLED_BUNDLE, "test", true, true, true, new LocalDate(2012, 7, 25).toDateTimeAtStartOfDay(DateTimeZone.UTC)));
blockingEvents.add(new DefaultBlockingState(ovdId, BlockingStateType.SUBSCRIPTION_BUNDLE, CLEAR_BUNDLE, "test", false, false, false, new LocalDate(2012, 7, 25).toDateTimeAtStartOfDay(DateTimeZone.UTC)));
- setBlockingStates(bundleId1, blockingEvents);
+ setBlockingStates(blockingEvents);
blockingCalculator.insertBlockingEvents(billingEvents, internalCallContext);
@@ -746,7 +771,9 @@ public class TestBlockingCalculator extends JunctionTestSuiteNoDB {
assertEquals(events.get(4).getTransitionType(), SubscriptionBaseTransitionType.CHANGE);
}
- private void setBlockingStates(final UUID blockedId, final List<BlockingState> blockingStates) {
- ((MockBlockingStateDao) blockingStateDao).setBlockingStates(blockedId, blockingStates);
+ private void setBlockingStates(final List<BlockingState> blockingStates) {
+ for (final BlockingState blockingState : blockingStates) {
+ blockingStateDao.setBlockingState(blockingState, clock, internalCallContext);
+ }
}
}
diff --git a/junction/src/test/java/com/ning/billing/junction/plumbing/billing/TestDefaultInternalBillingApi.java b/junction/src/test/java/com/ning/billing/junction/plumbing/billing/TestDefaultInternalBillingApi.java
new file mode 100644
index 0000000..e99269a
--- /dev/null
+++ b/junction/src/test/java/com/ning/billing/junction/plumbing/billing/TestDefaultInternalBillingApi.java
@@ -0,0 +1,269 @@
+/*
+ * Copyright 2010-2013 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.ning.billing.junction.plumbing.billing;
+
+import java.util.List;
+
+import org.joda.time.DateTime;
+import org.joda.time.LocalDate;
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+import com.ning.billing.account.api.Account;
+import com.ning.billing.api.TestApiListener.NextEvent;
+import com.ning.billing.callcontext.InternalCallContext;
+import com.ning.billing.catalog.api.BillingPeriod;
+import com.ning.billing.catalog.api.PlanPhaseSpecifier;
+import com.ning.billing.catalog.api.PriceListSet;
+import com.ning.billing.catalog.api.ProductCategory;
+import com.ning.billing.entitlement.EntitlementService;
+import com.ning.billing.entitlement.api.BlockingStateType;
+import com.ning.billing.entitlement.api.DefaultEntitlementApi;
+import com.ning.billing.entitlement.api.Entitlement;
+import com.ning.billing.junction.BillingEvent;
+import com.ning.billing.junction.DefaultBlockingState;
+import com.ning.billing.junction.JunctionTestSuiteWithEmbeddedDB;
+import com.ning.billing.subscription.api.SubscriptionBase;
+import com.ning.billing.subscription.api.SubscriptionBaseTransitionType;
+
+import com.google.common.collect.ImmutableList;
+
+public class TestDefaultInternalBillingApi extends JunctionTestSuiteWithEmbeddedDB {
+
+ // See https://github.com/killbill/killbill/issues/123
+ //
+ // Pierre, why do we have invocationCount > 0 here?
+ //
+ // This test will exercise ProxyBlockingStateDao#BLOCKING_STATE_ORDERING_WITH_TIES_UNHANDLED - unfortunately, for some reason,
+ // the ordering doesn't seem deterministic. In some scenarii,
+ // BlockingState(idA, effectiveDate1, BLOCK), BlockingState(idA, effectiveDate2, CLEAR), BlockingState(idB, effectiveDate2, BLOCK), BlockingState(idB, effectiveDate3, CLEAR)
+ // is ordered
+ // BlockingState(idA, effectiveDate1, BLOCK), BlockingState(idB, effectiveDate2, BLOCK), BlockingState(idA, effectiveDate2, CLEAR), BlockingState(idB, effectiveDate3, CLEAR)
+ // The code BlockingCalculator#createBlockingDurations has been updated to support it, but we still want to make sure it actually works in both scenarii
+ // (invocationCount = 10 will trigger both usecases in my testing).
+ @Test(groups = "slow", description = "Check blocking states with same effective date are correctly handled", invocationCount = 10)
+ public void testBlockingStatesWithSameEffectiveDate() throws Exception {
+ final LocalDate initialDate = new LocalDate(2013, 8, 7);
+ clock.setDay(initialDate);
+
+ final Account account = accountApi.createAccount(getAccountData(7), callContext);
+ final InternalCallContext internalCallContext = internalCallContextFactory.createInternalCallContext(account.getId(), callContext);
+
+ testListener.pushExpectedEvent(NextEvent.CREATE);
+ final PlanPhaseSpecifier spec = new PlanPhaseSpecifier("Shotgun", ProductCategory.BASE, BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, null);
+ final Entitlement entitlement = entitlementApi.createBaseEntitlement(account.getId(), spec, account.getExternalKey(), initialDate, callContext);
+ final SubscriptionBase subscription = subscriptionInternalApi.getSubscriptionFromId(entitlement.getId(), internalCallContext);
+ assertListenerStatus();
+
+ final DateTime block1Date = clock.getUTCNow();
+ testListener.pushExpectedEvents(NextEvent.BLOCK, NextEvent.BLOCK);
+ final DefaultBlockingState state1 = new DefaultBlockingState(account.getId(),
+ BlockingStateType.ACCOUNT,
+ DefaultEntitlementApi.ENT_STATE_BLOCKED,
+ EntitlementService.ENTITLEMENT_SERVICE_NAME,
+ true,
+ true,
+ true,
+ block1Date);
+ blockingInternalApi.setBlockingState(state1, internalCallContext);
+ // Same date, we'll order by record id asc
+ final DefaultBlockingState state2 = new DefaultBlockingState(account.getId(),
+ BlockingStateType.ACCOUNT,
+ DefaultEntitlementApi.ENT_STATE_CLEAR,
+ EntitlementService.ENTITLEMENT_SERVICE_NAME,
+ false,
+ false,
+ false,
+ block1Date);
+ blockingInternalApi.setBlockingState(state2, internalCallContext);
+ assertListenerStatus();
+
+ clock.addDays(5);
+
+ final DateTime block2Date = clock.getUTCNow();
+ testListener.pushExpectedEvents(NextEvent.BLOCK, NextEvent.BLOCK);
+ final DefaultBlockingState state3 = new DefaultBlockingState(entitlement.getBundleId(),
+ BlockingStateType.SUBSCRIPTION_BUNDLE,
+ DefaultEntitlementApi.ENT_STATE_BLOCKED,
+ EntitlementService.ENTITLEMENT_SERVICE_NAME,
+ true,
+ true,
+ true,
+ block2Date);
+ blockingInternalApi.setBlockingState(state3, internalCallContext);
+ // Same date, we'll order by record id asc
+ final DefaultBlockingState state4 = new DefaultBlockingState(entitlement.getBundleId(),
+ BlockingStateType.SUBSCRIPTION_BUNDLE,
+ DefaultEntitlementApi.ENT_STATE_CLEAR,
+ EntitlementService.ENTITLEMENT_SERVICE_NAME,
+ false,
+ false,
+ false,
+ block2Date);
+ blockingInternalApi.setBlockingState(state4, internalCallContext);
+ assertListenerStatus();
+
+ final DateTime block3Date = block2Date.plusDays(3);
+
+ // Pass the phase
+ testListener.pushExpectedEvent(NextEvent.PHASE);
+ clock.addDays(50);
+ assertListenerStatus();
+
+ final DateTime block4Date = clock.getUTCNow();
+ final DateTime block5Date = block4Date.plusDays(3);
+ // Only one event on the bus (for state5)
+ testListener.pushExpectedEvents(NextEvent.BLOCK);
+ // Insert the clear state first, to make sure the order in which we insert blocking states doesn't matter
+ // Since we are already in an ENT_STATE_CLEAR state for service ENTITLEMENT_SERVICE_NAME, we need to use a different
+ // state name to simulate this behavior (otherwise, by design, this event won't be created)
+ final DefaultBlockingState state6 = new DefaultBlockingState(entitlement.getBundleId(),
+ BlockingStateType.SUBSCRIPTION_BUNDLE,
+ DefaultEntitlementApi.ENT_STATE_CLEAR + "-something",
+ EntitlementService.ENTITLEMENT_SERVICE_NAME,
+ false,
+ false,
+ false,
+ block5Date);
+ blockingInternalApi.setBlockingState(state6, internalCallContext);
+ final DefaultBlockingState state5 = new DefaultBlockingState(entitlement.getBundleId(),
+ BlockingStateType.SUBSCRIPTION_BUNDLE,
+ DefaultEntitlementApi.ENT_STATE_BLOCKED + "-something",
+ EntitlementService.ENTITLEMENT_SERVICE_NAME,
+ true,
+ true,
+ true,
+ block4Date);
+ blockingInternalApi.setBlockingState(state5, internalCallContext);
+ assertListenerStatus();
+
+ // Now, add back blocking states at an earlier date, for a different blockable id, to make sure the effective
+ // date ordering is correctly respected when computing blocking durations
+ testListener.pushExpectedEvents(NextEvent.BLOCK, NextEvent.BLOCK);
+ final DefaultBlockingState state7 = new DefaultBlockingState(account.getId(),
+ BlockingStateType.ACCOUNT,
+ DefaultEntitlementApi.ENT_STATE_BLOCKED + "-something2",
+ EntitlementService.ENTITLEMENT_SERVICE_NAME,
+ true,
+ true,
+ true,
+ block3Date);
+ blockingInternalApi.setBlockingState(state7, internalCallContext);
+ final DefaultBlockingState state8 = new DefaultBlockingState(account.getId(),
+ BlockingStateType.ACCOUNT,
+ DefaultEntitlementApi.ENT_STATE_CLEAR + "-something2",
+ EntitlementService.ENTITLEMENT_SERVICE_NAME,
+ false,
+ false,
+ false,
+ block4Date);
+ blockingInternalApi.setBlockingState(state8, internalCallContext);
+ assertListenerStatus();
+
+ // Advance for state6 to be active
+ testListener.pushExpectedEvents(NextEvent.BLOCK);
+ clock.addDays(5);
+ assertListenerStatus();
+
+ // Expected blocking durations:
+ // * 2013-08-07 to 2013-08-07 (block1Date)
+ // * 2013-08-12 to 2013-08-12 (block2Date)
+ // * 2013-08-15 to 2013-10-04 [2013-08-15 to 2013-10-01 (block3Date -> block4Date) and 2013-10-01 to 2013-10-04 (block4Date -> block5Date)]
+ final List<BillingEvent> events = ImmutableList.<BillingEvent>copyOf(billingInternalApi.getBillingEventsForAccountAndUpdateAccountBCD(account.getId(), internalCallContext));
+ Assert.assertEquals(events.size(), 7);
+ Assert.assertEquals(events.get(0).getTransitionType(), SubscriptionBaseTransitionType.CREATE);
+ Assert.assertEquals(events.get(0).getEffectiveDate(), subscription.getStartDate());
+ Assert.assertEquals(events.get(1).getTransitionType(), SubscriptionBaseTransitionType.START_BILLING_DISABLED);
+ Assert.assertEquals(events.get(1).getEffectiveDate(), block1Date);
+ Assert.assertEquals(events.get(2).getTransitionType(), SubscriptionBaseTransitionType.END_BILLING_DISABLED);
+ Assert.assertEquals(events.get(2).getEffectiveDate(), block1Date);
+ Assert.assertEquals(events.get(3).getTransitionType(), SubscriptionBaseTransitionType.START_BILLING_DISABLED);
+ Assert.assertEquals(events.get(3).getEffectiveDate(), block2Date);
+ Assert.assertEquals(events.get(4).getTransitionType(), SubscriptionBaseTransitionType.END_BILLING_DISABLED);
+ Assert.assertEquals(events.get(4).getEffectiveDate(), block2Date);
+ Assert.assertEquals(events.get(5).getTransitionType(), SubscriptionBaseTransitionType.START_BILLING_DISABLED);
+ Assert.assertEquals(events.get(5).getEffectiveDate(), block3Date);
+ Assert.assertEquals(events.get(6).getTransitionType(), SubscriptionBaseTransitionType.END_BILLING_DISABLED);
+ Assert.assertEquals(events.get(6).getEffectiveDate(), block5Date);
+ }
+
+ // See https://github.com/killbill/killbill/commit/92042843e38a67f75495b207385e4c1f9ca60990#commitcomment-4749967
+ @Test(groups = "slow", description = "Check unblock then block states with same effective date are correctly handled", invocationCount = 10)
+ public void testUnblockThenBlockBlockingStatesWithSameEffectiveDate() throws Exception {
+ final LocalDate initialDate = new LocalDate(2013, 8, 7);
+ clock.setDay(initialDate);
+
+ final Account account = accountApi.createAccount(getAccountData(7), callContext);
+ final InternalCallContext internalCallContext = internalCallContextFactory.createInternalCallContext(account.getId(), callContext);
+
+ testListener.pushExpectedEvent(NextEvent.CREATE);
+ final PlanPhaseSpecifier spec = new PlanPhaseSpecifier("Shotgun", ProductCategory.BASE, BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, null);
+ final Entitlement entitlement = entitlementApi.createBaseEntitlement(account.getId(), spec, account.getExternalKey(), initialDate, callContext);
+ final SubscriptionBase subscription = subscriptionInternalApi.getSubscriptionFromId(entitlement.getId(), internalCallContext);
+ assertListenerStatus();
+
+ final DateTime block1Date = clock.getUTCNow();
+ testListener.pushExpectedEvents(NextEvent.BLOCK);
+ final DefaultBlockingState state1 = new DefaultBlockingState(account.getId(),
+ BlockingStateType.ACCOUNT,
+ DefaultEntitlementApi.ENT_STATE_BLOCKED,
+ EntitlementService.ENTITLEMENT_SERVICE_NAME,
+ true,
+ true,
+ true,
+ block1Date);
+ blockingInternalApi.setBlockingState(state1, internalCallContext);
+
+ clock.addDays(1);
+
+ final DateTime block2Date = clock.getUTCNow();
+ testListener.pushExpectedEvents(NextEvent.BLOCK, NextEvent.BLOCK);
+ final DefaultBlockingState state2 = new DefaultBlockingState(account.getId(),
+ BlockingStateType.ACCOUNT,
+ DefaultEntitlementApi.ENT_STATE_CLEAR,
+ EntitlementService.ENTITLEMENT_SERVICE_NAME,
+ false,
+ false,
+ false,
+ block2Date);
+ blockingInternalApi.setBlockingState(state2, internalCallContext);
+ // Same date
+ final DefaultBlockingState state3 = new DefaultBlockingState(account.getId(),
+ BlockingStateType.ACCOUNT,
+ DefaultEntitlementApi.ENT_STATE_BLOCKED,
+ EntitlementService.ENTITLEMENT_SERVICE_NAME,
+ true,
+ true,
+ true,
+ block2Date);
+ blockingInternalApi.setBlockingState(state3, internalCallContext);
+ assertListenerStatus();
+
+ // Nothing should happen
+ clock.addDays(3);
+ assertListenerStatus();
+
+ // Expected blocking duration:
+ // * 2013-08-07 to now [2013-08-07 to 2013-08-08 then 2013-08-08 to now]
+ final List<BillingEvent> events = ImmutableList.<BillingEvent>copyOf(billingInternalApi.getBillingEventsForAccountAndUpdateAccountBCD(account.getId(), internalCallContext));
+ Assert.assertEquals(events.size(), 2);
+ Assert.assertEquals(events.get(0).getTransitionType(), SubscriptionBaseTransitionType.CREATE);
+ Assert.assertEquals(events.get(0).getEffectiveDate(), subscription.getStartDate());
+ Assert.assertEquals(events.get(1).getTransitionType(), SubscriptionBaseTransitionType.START_BILLING_DISABLED);
+ Assert.assertEquals(events.get(1).getEffectiveDate(), block1Date);
+ }
+}
junction/src/test/resources/catalog.xml 828(+828 -0)
diff --git a/junction/src/test/resources/catalog.xml b/junction/src/test/resources/catalog.xml
new file mode 100644
index 0000000..973afd0
--- /dev/null
+++ b/junction/src/test/resources/catalog.xml
@@ -0,0 +1,828 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- ~ Copyright 2010-2011 Ning, Inc. ~ ~ Ning licenses this file to you
+ under the Apache License, version 2.0 ~ (the "License"); you may not use
+ this file except in compliance with the ~ License. You may obtain a copy
+ of the License at: ~ ~ http://www.apache.org/licenses/LICENSE-2.0 ~ ~ Unless
+ required by applicable law or agreed to in writing, software ~ distributed
+ under the License is distributed on an "AS IS" BASIS, WITHOUT ~ WARRANTIES
+ OR CONDITIONS OF ANY KIND, either express or implied. See the ~ License for
+ the specific language governing permissions and limitations ~ under the License. -->
+
+<!-- Use cases covered so far: Tiered Product (Pistol/Shotgun/Assault-Rifle)
+ Multiple changeEvent plan policies Multiple PlanAlignment (see below, trial
+ add-on alignments and rescue discount package) Product transition rules Add
+ on (Scopes, Hoster) Multi-pack addon (Extra-Ammo) Addon Trial aligned to
+ base plan (holster-monthly-regular) Addon Trial aligned to creation (holster-monthly-special)
+ Rescue discount package (assault-rifle-annual-rescue) Plan phase with a reccurring
+ and a one off (refurbish-maintenance) Phan with more than 2 phase (gunclub
+ discount plans) Use Cases to do: Tiered Add On Riskfree period -->
+<catalog xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:noNamespaceSchemaLocation="CatalogSchema.xsd ">
+
+ <effectiveDate>2011-01-01T00:00:00+00:00</effectiveDate>
+ <catalogName>Firearms</catalogName>
+
+ <currencies>
+ <currency>USD</currency>
+ <currency>EUR</currency>
+ <currency>GBP</currency>
+ </currencies>
+
+ <products>
+ <product name="Pistol">
+ <category>BASE</category>
+ </product>
+ <product name="Shotgun">
+ <category>BASE</category>
+ <available>
+ <addonProduct>Telescopic-Scope</addonProduct>
+ <addonProduct>Laser-Scope</addonProduct>
+ </available>
+ </product>
+ <product name="Assault-Rifle">
+ <category>BASE</category>
+ <included>
+ <addonProduct>Telescopic-Scope</addonProduct>
+ </included>
+ <available>
+ <addonProduct>Laser-Scope</addonProduct>
+ </available>
+ </product>
+ <product name="Telescopic-Scope">
+ <category>ADD_ON</category>
+ </product>
+ <product name="Laser-Scope">
+ <category>ADD_ON</category>
+ </product>
+ <product name="Holster">
+ <category>ADD_ON</category>
+ </product>
+ <product name="Extra-Ammo">
+ <category>ADD_ON</category>
+ </product>
+ <product name="Refurbish-Maintenance">
+ <category>ADD_ON</category>
+ </product>
+ </products>
+
+ <rules>
+ <changePolicy>
+ <changePolicyCase>
+ <phaseType>TRIAL</phaseType>
+ <policy>IMMEDIATE</policy>
+ </changePolicyCase>
+ <changePolicyCase>
+ <toPriceList>rescue</toPriceList>
+ <policy>END_OF_TERM</policy>
+ </changePolicyCase>
+ <changePolicyCase>
+ <toProduct>Assault-Rifle</toProduct>
+ <policy>IMMEDIATE</policy>
+ </changePolicyCase>
+ <changePolicyCase>
+ <fromProduct>Pistol</fromProduct>
+ <toProduct>Shotgun</toProduct>
+ <policy>IMMEDIATE</policy>
+ </changePolicyCase>
+ <changePolicyCase>
+ <fromBillingPeriod>MONTHLY</fromBillingPeriod>
+ <toBillingPeriod>ANNUAL</toBillingPeriod>
+ <policy>IMMEDIATE</policy>
+ </changePolicyCase>
+ <changePolicyCase>
+ <fromBillingPeriod>ANNUAL</fromBillingPeriod>
+ <toBillingPeriod>MONTHLY</toBillingPeriod>
+ <policy>END_OF_TERM</policy>
+ </changePolicyCase>
+ <changePolicyCase>
+ <policy>END_OF_TERM</policy>
+ </changePolicyCase>
+ </changePolicy>
+ <changeAlignment>
+ <changeAlignmentCase>
+ <toPriceList>rescue</toPriceList>
+ <alignment>CHANGE_OF_PLAN</alignment>
+ </changeAlignmentCase>
+ <changeAlignmentCase>
+ <fromPriceList>rescue</fromPriceList>
+ <toPriceList>rescue</toPriceList>
+ <alignment>CHANGE_OF_PRICELIST</alignment>
+ </changeAlignmentCase>
+ <changeAlignmentCase>
+ <alignment>START_OF_SUBSCRIPTION</alignment>
+ </changeAlignmentCase>
+ </changeAlignment>
+ <cancelPolicy>
+ <cancelPolicyCase>
+ <phaseType>TRIAL</phaseType>
+ <policy>IMMEDIATE</policy>
+ </cancelPolicyCase>
+ <cancelPolicyCase>
+ <policy>END_OF_TERM</policy>
+ </cancelPolicyCase>
+ </cancelPolicy>
+ <createAlignment>
+ <createAlignmentCase>
+ <product>Laser-Scope</product>
+ <alignment>START_OF_SUBSCRIPTION</alignment>
+ </createAlignmentCase>
+ <createAlignmentCase>
+ <alignment>START_OF_BUNDLE</alignment>
+ </createAlignmentCase>
+ </createAlignment>
+ <billingAlignment>
+ <billingAlignmentCase>
+ <productCategory>ADD_ON</productCategory>
+ <alignment>BUNDLE</alignment>
+ </billingAlignmentCase>
+ <billingAlignmentCase>
+ <billingPeriod>ANNUAL</billingPeriod>
+ <alignment>SUBSCRIPTION</alignment>
+ </billingAlignmentCase>
+ <billingAlignmentCase>
+ <alignment>ACCOUNT</alignment>
+ </billingAlignmentCase>
+ </billingAlignment>
+ <priceList>
+ <priceListCase>
+ <fromPriceList>rescue</fromPriceList>
+ <toPriceList>DEFAULT</toPriceList>
+ </priceListCase>
+ </priceList>
+ </rules>
+
+ <plans>
+ <plan name="pistol-monthly">
+ <product>Pistol</product>
+ <initialPhases>
+ <phase type="TRIAL">
+ <duration>
+ <unit>DAYS</unit>
+ <number>30</number>
+ </duration>
+ <billingPeriod>NO_BILLING_PERIOD</billingPeriod>
+ <fixedPrice> <!-- empty price implies $0 -->
+ </fixedPrice>
+ </phase>
+ </initialPhases>
+ <finalPhase type="EVERGREEN">
+ <duration>
+ <unit>UNLIMITED</unit>
+ </duration>
+ <billingPeriod>MONTHLY</billingPeriod>
+ <recurringPrice>
+ <price>
+ <currency>GBP</currency>
+ <value>29.95</value>
+ </price>
+ <price>
+ <currency>EUR</currency>
+ <value>29.95</value>
+ </price>
+ <price>
+ <currency>USD</currency>
+ <value>29.95</value>
+ </price>
+ </recurringPrice>
+ </finalPhase>
+ </plan>
+ <plan name="shotgun-monthly">
+ <product>Shotgun</product>
+ <initialPhases>
+ <phase type="TRIAL">
+ <duration>
+ <unit>DAYS</unit>
+ <number>30</number>
+ </duration>
+ <billingPeriod>NO_BILLING_PERIOD</billingPeriod>
+ <fixedPrice></fixedPrice>
+ <!-- no price implies $0 -->
+ </phase>
+ </initialPhases>
+ <finalPhase type="EVERGREEN">
+ <duration>
+ <unit>UNLIMITED</unit>
+ <number>-1</number>
+ </duration>
+ <billingPeriod>MONTHLY</billingPeriod>
+ <recurringPrice>
+ <price>
+ <currency>USD</currency>
+ <value>249.95</value>
+ </price>
+ <price>
+ <currency>EUR</currency>
+ <value>149.95</value>
+ </price>
+ <price>
+ <currency>GBP</currency>
+ <value>169.95</value>
+ </price>
+ </recurringPrice>
+ </finalPhase>
+ </plan>
+ <plan name="assault-rifle-monthly">
+ <product>Assault-Rifle</product>
+ <initialPhases>
+ <phase type="TRIAL">
+ <duration>
+ <unit>DAYS</unit>
+ <number>30</number>
+ </duration>
+ <billingPeriod>NO_BILLING_PERIOD</billingPeriod>
+ <fixedPrice>
+ </fixedPrice>
+ </phase>
+ </initialPhases>
+ <finalPhase type="EVERGREEN">
+ <duration>
+ <unit>UNLIMITED</unit>
+ </duration>
+ <billingPeriod>MONTHLY</billingPeriod>
+ <recurringPrice>
+ <price>
+ <currency>USD</currency>
+ <value>599.95</value>
+ </price>
+ <price>
+ <currency>EUR</currency>
+ <value>349.95</value>
+ </price>
+ <price>
+ <currency>GBP</currency>
+ <value>399.95</value>
+ </price>
+ </recurringPrice>
+ </finalPhase>
+ </plan>
+ <plan name="pistol-annual">
+ <product>Pistol</product>
+ <initialPhases>
+ <phase type="TRIAL">
+ <duration>
+ <unit>DAYS</unit>
+ <number>30</number>
+ </duration>
+ <billingPeriod>NO_BILLING_PERIOD</billingPeriod>
+ <fixedPrice>
+ </fixedPrice>
+ </phase>
+ </initialPhases>
+ <finalPhase type="EVERGREEN">
+ <duration>
+ <unit>UNLIMITED</unit>
+ </duration>
+ <billingPeriod>ANNUAL</billingPeriod>
+ <recurringPrice>
+ <price>
+ <currency>USD</currency>
+ <value>199.95</value>
+ </price>
+ <price>
+ <currency>EUR</currency>
+ <value>199.95</value>
+ </price>
+ <price>
+ <currency>GBP</currency>
+ <value>199.95</value>
+ </price>
+ </recurringPrice>
+ </finalPhase>
+ </plan>
+ <plan name="shotgun-annual">
+ <product>Shotgun</product>
+ <initialPhases>
+ <phase type="TRIAL">
+ <duration>
+ <unit>DAYS</unit>
+ <number>30</number>
+ </duration>
+ <billingPeriod>NO_BILLING_PERIOD</billingPeriod>
+ <fixedPrice>
+ </fixedPrice>
+ </phase>
+ </initialPhases>
+ <finalPhase type="EVERGREEN">
+ <duration>
+ <unit>UNLIMITED</unit>
+ </duration>
+ <billingPeriod>ANNUAL</billingPeriod>
+ <recurringPrice>
+ <price>
+ <currency>USD</currency>
+ <value>2399.95</value>
+ </price>
+ <price>
+ <currency>EUR</currency>
+ <value>1499.95</value>
+ </price>
+ <price>
+ <currency>GBP</currency>
+ <value>1699.95</value>
+ </price>
+ </recurringPrice>
+ </finalPhase>
+ </plan>
+ <plan name="assault-rifle-annual">
+ <product>Assault-Rifle</product>
+ <initialPhases>
+ <phase type="TRIAL">
+ <duration>
+ <unit>DAYS</unit>
+ <number>30</number>
+ </duration>
+ <billingPeriod>NO_BILLING_PERIOD</billingPeriod>
+ <fixedPrice>
+ </fixedPrice>
+ </phase>
+ </initialPhases>
+ <finalPhase type="EVERGREEN">
+ <duration>
+ <unit>UNLIMITED</unit>
+ </duration>
+ <billingPeriod>ANNUAL</billingPeriod>
+ <recurringPrice>
+ <price>
+ <currency>USD</currency>
+ <value>5999.95</value>
+ </price>
+ <price>
+ <currency>EUR</currency>
+ <value>3499.95</value>
+ </price>
+ <price>
+ <currency>GBP</currency>
+ <value>3999.95</value>
+ </price>
+ </recurringPrice>
+ </finalPhase>
+ </plan>
+ <plan name="pistol-annual-gunclub-discount">
+ <product>Pistol</product>
+ <initialPhases>
+ <phase type="TRIAL">
+ <duration>
+ <unit>DAYS</unit>
+ <number>30</number>
+ </duration>
+ <billingPeriod>NO_BILLING_PERIOD</billingPeriod>
+ <fixedPrice>
+ </fixedPrice>
+ </phase>
+ <phase type="DISCOUNT">
+ <duration>
+ <unit>MONTHS</unit>
+ <number>6</number>
+ </duration>
+ <billingPeriod>MONTHLY</billingPeriod>
+ <recurringPrice>
+ <price>
+ <currency>USD</currency>
+ <value>9.95</value>
+ </price>
+ <price>
+ <currency>EUR</currency>
+ <value>9.95</value>
+ </price>
+ <price>
+ <currency>GBP</currency>
+ <value>9.95</value>
+ </price>
+ </recurringPrice>
+ </phase>
+ </initialPhases>
+ <finalPhase type="EVERGREEN">
+ <duration>
+ <unit>UNLIMITED</unit>
+ </duration>
+ <billingPeriod>ANNUAL</billingPeriod>
+ <recurringPrice>
+ <price>
+ <currency>USD</currency>
+ <value>199.95</value>
+ </price>
+ <price>
+ <currency>EUR</currency>
+ <value>199.95</value>
+ </price>
+ <price>
+ <currency>GBP</currency>
+ <value>199.95</value>
+ </price>
+ </recurringPrice>
+ </finalPhase>
+ </plan>
+ <plan name="shotgun-annual-gunclub-discount">
+ <product>Shotgun</product>
+ <initialPhases>
+ <phase type="TRIAL">
+ <duration>
+ <unit>DAYS</unit>
+ <number>30</number>
+ </duration>
+ <billingPeriod>NO_BILLING_PERIOD</billingPeriod>
+ <fixedPrice>
+ </fixedPrice>
+ </phase>
+ <phase type="DISCOUNT">
+ <duration>
+ <unit>MONTHS</unit>
+ <number>6</number>
+ </duration>
+ <billingPeriod>MONTHLY</billingPeriod>
+ <recurringPrice>
+ <price>
+ <currency>USD</currency>
+ <value>19.95</value>
+ </price>
+ <price>
+ <currency>EUR</currency>
+ <value>49.95</value>
+ </price>
+ <price>
+ <currency>GBP</currency>
+ <value>69.95</value>
+ </price>
+ </recurringPrice>
+ </phase>
+ </initialPhases>
+ <finalPhase type="EVERGREEN">
+ <duration>
+ <unit>UNLIMITED</unit>
+ </duration>
+ <billingPeriod>ANNUAL</billingPeriod>
+ <recurringPrice>
+ <price>
+ <currency>USD</currency>
+ <value>2399.95</value>
+ </price>
+ <price>
+ <currency>EUR</currency>
+ <value>1499.95</value>
+ </price>
+ <price>
+ <currency>GBP</currency>
+ <value>1699.95</value>
+ </price>
+ </recurringPrice>
+ </finalPhase>
+ </plan>
+ <plan name="assault-rifle-annual-gunclub-discount">
+ <product>Assault-Rifle</product>
+ <initialPhases>
+ <phase type="TRIAL">
+ <duration>
+ <unit>DAYS</unit>
+ <number>30</number>
+ </duration>
+ <billingPeriod>NO_BILLING_PERIOD</billingPeriod>
+ <fixedPrice>
+ </fixedPrice>
+ </phase>
+ <phase type="DISCOUNT">
+ <duration>
+ <unit>MONTHS</unit>
+ <number>6</number>
+ </duration>
+ <billingPeriod>MONTHLY</billingPeriod>
+ <recurringPrice>
+ <price>
+ <currency>USD</currency>
+ <value>99.95</value>
+ </price>
+ <price>
+ <currency>EUR</currency>
+ <value>99.95</value>
+ </price>
+ <price>
+ <currency>GBP</currency>
+ <value>99.95</value>
+ </price>
+ </recurringPrice>
+ </phase>
+ </initialPhases>
+ <finalPhase type="EVERGREEN">
+ <duration>
+ <unit>UNLIMITED</unit>
+ </duration>
+ <billingPeriod>ANNUAL</billingPeriod>
+ <recurringPrice>
+ <price>
+ <currency>USD</currency>
+ <value>5999.95</value>
+ </price>
+ <price>
+ <currency>EUR</currency>
+ <value>3499.95</value>
+ </price>
+ <price>
+ <currency>GBP</currency>
+ <value>3999.95</value>
+ </price>
+ </recurringPrice>
+ </finalPhase>
+ </plan>
+ <plan name="laser-scope-monthly">
+ <product>Laser-Scope</product>
+ <initialPhases>
+ <phase type="DISCOUNT">
+ <duration>
+ <unit>MONTHS</unit>
+ <number>1</number>
+ </duration>
+ <billingPeriod>MONTHLY</billingPeriod>
+ <recurringPrice>
+ <price>
+ <currency>USD</currency>
+ <value>999.95</value>
+ </price>
+ <price>
+ <currency>EUR</currency>
+ <value>499.95</value>
+ </price>
+ <price>
+ <currency>GBP</currency>
+ <value>999.95</value>
+ </price>
+ </recurringPrice>
+ </phase>
+ </initialPhases>
+ <finalPhase type="EVERGREEN">
+ <duration>
+ <unit>UNLIMITED</unit>
+ </duration>
+ <billingPeriod>MONTHLY</billingPeriod>
+ <recurringPrice>
+ <price>
+ <currency>USD</currency>
+ <value>1999.95</value>
+ </price>
+ <price>
+ <currency>EUR</currency>
+ <value>1499.95</value>
+ </price>
+ <price>
+ <currency>GBP</currency>
+ <value>1999.95</value>
+ </price>
+ </recurringPrice>
+ </finalPhase>
+ </plan>
+ <plan name="telescopic-scope-monthly">
+ <product>Telescopic-Scope</product>
+ <initialPhases>
+ <phase type="DISCOUNT">
+ <duration>
+ <unit>MONTHS</unit>
+ <number>1</number>
+ </duration>
+ <billingPeriod>MONTHLY</billingPeriod>
+ <recurringPrice>
+ <price>
+ <currency>USD</currency>
+ <value>399.95</value>
+ </price>
+ <price>
+ <currency>EUR</currency>
+ <value>299.95</value>
+ </price>
+ <price>
+ <currency>GBP</currency>
+ <value>399.95</value>
+ </price>
+ </recurringPrice>
+ </phase>
+ </initialPhases>
+ <finalPhase type="EVERGREEN">
+ <duration>
+ <unit>UNLIMITED</unit>
+ </duration>
+ <billingPeriod>MONTHLY</billingPeriod>
+ <recurringPrice>
+ <price>
+ <currency>USD</currency>
+ <value>999.95</value>
+ </price>
+ <price>
+ <currency>EUR</currency>
+ <value>499.95</value>
+ </price>
+ <price>
+ <currency>GBP</currency>
+ <value>999.95</value>
+ </price>
+ </recurringPrice>
+ </finalPhase>
+ </plan>
+ <plan name="extra-ammo-monthly">
+ <product>Extra-Ammo</product>
+ <finalPhase type="EVERGREEN">
+ <duration>
+ <unit>UNLIMITED</unit>
+ </duration>
+ <billingPeriod>MONTHLY</billingPeriod>
+ <recurringPrice>
+ <price>
+ <currency>USD</currency>
+ <value>999.95</value>
+ </price>
+ <price>
+ <currency>EUR</currency>
+ <value>499.95</value>
+ </price>
+ <price>
+ <currency>GBP</currency>
+ <value>999.95</value>
+ </price>
+ </recurringPrice>
+ </finalPhase>
+ <plansAllowedInBundle>-1</plansAllowedInBundle> <!-- arbitrary number of these (multipack) -->
+ </plan>
+ <plan name="holster-monthly-regular">
+ <product>Holster</product>
+ <initialPhases>
+ <phase type="TRIAL">
+ <duration>
+ <unit>DAYS</unit>
+ <number>30</number>
+ </duration>
+ <billingPeriod>NO_BILLING_PERIOD</billingPeriod>
+ <fixedPrice>
+ </fixedPrice>
+ </phase>
+ </initialPhases>
+ <finalPhase type="EVERGREEN">
+ <duration>
+ <unit>UNLIMITED</unit>
+ </duration>
+ <billingPeriod>ANNUAL</billingPeriod>
+ <recurringPrice>
+ <price>
+ <currency>USD</currency>
+ <value>199.95</value>
+ </price>
+ <price>
+ <currency>EUR</currency>
+ <value>199.95</value>
+ </price>
+ <price>
+ <currency>GBP</currency>
+ <value>199.95</value>
+ </price>
+ </recurringPrice>
+ </finalPhase>
+ </plan>
+ <plan name="holster-monthly-special">
+ <product>Holster</product>
+ <initialPhases>
+ <phase type="TRIAL">
+ <duration>
+ <unit>DAYS</unit>
+ <number>30</number>
+ </duration>
+ <billingPeriod>NO_BILLING_PERIOD</billingPeriod>
+ <fixedPrice>
+ </fixedPrice>
+ </phase>
+ </initialPhases>
+ <finalPhase type="EVERGREEN">
+ <duration>
+ <unit>UNLIMITED</unit>
+ </duration>
+ <billingPeriod>ANNUAL</billingPeriod>
+ <recurringPrice>
+ <price>
+ <currency>USD</currency>
+ <value>199.95</value>
+ </price>
+ <price>
+ <currency>EUR</currency>
+ <value>199.95</value>
+ </price>
+ <price>
+ <currency>GBP</currency>
+ <value>199.95</value>
+ </price>
+ </recurringPrice>
+ </finalPhase>
+ </plan>
+ <plan name="assault-rifle-annual-rescue">
+ <product>Assault-Rifle</product>
+ <initialPhases>
+ <phase type="DISCOUNT">
+ <duration>
+ <unit>YEARS</unit>
+ <number>1</number>
+ </duration>
+ <billingPeriod>ANNUAL</billingPeriod>
+ <recurringPrice>
+ <price>
+ <currency>USD</currency>
+ <value>5999.95</value>
+ </price>
+ <price>
+ <currency>EUR</currency>
+ <value>3499.95</value>
+ </price>
+ <price>
+ <currency>GBP</currency>
+ <value>3999.95</value>
+ </price>
+ </recurringPrice>
+ </phase>
+ </initialPhases>
+ <finalPhase type="EVERGREEN">
+ <duration>
+ <unit>UNLIMITED</unit>
+ </duration>
+ <billingPeriod>ANNUAL</billingPeriod>
+ <recurringPrice>
+ <price>
+ <currency>USD</currency>
+ <value>5999.95</value>
+ </price>
+ <price>
+ <currency>EUR</currency>
+ <value>3499.95</value>
+ </price>
+ <price>
+ <currency>GBP</currency>
+ <value>3999.95</value>
+ </price>
+ </recurringPrice>
+ </finalPhase>
+ </plan>
+ <plan name="refurbish-maintenance">
+ <product>Refurbish-Maintenance</product>
+ <finalPhase type="FIXEDTERM">
+ <duration>
+ <unit>MONTHS</unit>
+ <number>12</number>
+ </duration>
+ <billingPeriod>MONTHLY</billingPeriod>
+ <recurringPrice>
+ <price>
+ <currency>USD</currency>
+ <value>199.95</value>
+ </price>
+ <price>
+ <currency>EUR</currency>
+ <value>199.95</value>
+ </price>
+ <price>
+ <currency>GBP</currency>
+ <value>199.95</value>
+ </price>
+ </recurringPrice>
+ <fixedPrice>
+ <price>
+ <currency>USD</currency>
+ <value>599.95</value>
+ </price>
+ <price>
+ <currency>EUR</currency>
+ <value>599.95</value>
+ </price>
+ <price>
+ <currency>GBP</currency>
+ <value>599.95</value>
+ </price>
+ </fixedPrice>
+ </finalPhase>
+ </plan>
+ </plans>
+ <priceLists>
+ <defaultPriceList name="DEFAULT">
+ <plans>
+ <plan>pistol-monthly</plan>
+ <plan>shotgun-monthly</plan>
+ <plan>assault-rifle-monthly</plan>
+ <plan>pistol-annual</plan>
+ <plan>shotgun-annual</plan>
+ <plan>assault-rifle-annual</plan>
+ <plan>laser-scope-monthly</plan>
+ <plan>telescopic-scope-monthly</plan>
+ <plan>extra-ammo-monthly</plan>
+ <plan>holster-monthly-regular</plan>
+ <plan>refurbish-maintenance</plan>
+ </plans>
+ </defaultPriceList>
+ <childPriceList name="gunclubDiscount">
+ <plans>
+ <plan>pistol-monthly</plan>
+ <plan>shotgun-monthly</plan>
+ <plan>assault-rifle-monthly</plan>
+ <plan>pistol-annual-gunclub-discount</plan>
+ <plan>shotgun-annual-gunclub-discount</plan>
+ <plan>assault-rifle-annual-gunclub-discount</plan>
+ <plan>holster-monthly-special</plan>
+ </plans>
+ </childPriceList>
+ <childPriceList name="rescue">
+ <plans>
+ <plan>assault-rifle-annual-rescue</plan>
+ </plans>
+ </childPriceList>
+ </priceLists>
+
+</catalog>
NEWS 16(+16 -0)
diff --git a/NEWS b/NEWS
index c116256..3a050f2 100644
--- a/NEWS
+++ b/NEWS
@@ -1,3 +1,19 @@
+0.8.5
+ https://github.com/killbill/killbill/issues/134
+ https://github.com/killbill/killbill/issues/135
+ https://github.com/killbill/killbill/issues/136
+ https://github.com/killbill/killbill/issues/140
+
+0.8.4
+ Subscription base speedups
+ Fix regression in bundle timeline API
+ Fix ClassCastException in Beatrix
+ https://github.com/killbill/killbill/issues/123
+ https://github.com/killbill/killbill/issues/132
+
+0.8.3
+ Entitlement speedups
+
0.8.2
Lots of subscription & entitlement bugfixes
https://github.com/killbill/killbill/issues/121
osgi/pom.xml 2(+1 -1)
diff --git a/osgi/pom.xml b/osgi/pom.xml
index 8dec480..10b9dd3 100644
--- a/osgi/pom.xml
+++ b/osgi/pom.xml
@@ -19,7 +19,7 @@
<parent>
<artifactId>killbill</artifactId>
<groupId>com.ning.billing</groupId>
- <version>0.8.3-SNAPSHOT</version>
+ <version>0.8.6-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>killbill-osgi</artifactId>
osgi-bundles/bundles/jruby/pom.xml 2(+1 -1)
diff --git a/osgi-bundles/bundles/jruby/pom.xml b/osgi-bundles/bundles/jruby/pom.xml
index 31b2830..fd3ade3 100644
--- a/osgi-bundles/bundles/jruby/pom.xml
+++ b/osgi-bundles/bundles/jruby/pom.xml
@@ -20,7 +20,7 @@
<parent>
<groupId>com.ning.billing</groupId>
<artifactId>killbill-osgi-bundles</artifactId>
- <version>0.8.3-SNAPSHOT</version>
+ <version>0.8.6-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>killbill-osgi-bundles-jruby</artifactId>
osgi-bundles/bundles/logger/pom.xml 2(+1 -1)
diff --git a/osgi-bundles/bundles/logger/pom.xml b/osgi-bundles/bundles/logger/pom.xml
index 906e35b..f5fc4b1 100644
--- a/osgi-bundles/bundles/logger/pom.xml
+++ b/osgi-bundles/bundles/logger/pom.xml
@@ -19,7 +19,7 @@
<parent>
<artifactId>killbill-osgi-bundles</artifactId>
<groupId>com.ning.billing</groupId>
- <version>0.8.3-SNAPSHOT</version>
+ <version>0.8.6-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>killbill-osgi-bundles-logger</artifactId>
osgi-bundles/bundles/pom.xml 2(+1 -1)
diff --git a/osgi-bundles/bundles/pom.xml b/osgi-bundles/bundles/pom.xml
index 4f8c8cc..f582871 100644
--- a/osgi-bundles/bundles/pom.xml
+++ b/osgi-bundles/bundles/pom.xml
@@ -19,7 +19,7 @@
<parent>
<artifactId>killbill-osgi-all-bundles</artifactId>
<groupId>com.ning.billing</groupId>
- <version>0.8.3-SNAPSHOT</version>
+ <version>0.8.6-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>killbill-osgi-bundles</artifactId>
diff --git a/osgi-bundles/bundles/webconsolebranding/pom.xml b/osgi-bundles/bundles/webconsolebranding/pom.xml
index b5133e7..8f4d304 100644
--- a/osgi-bundles/bundles/webconsolebranding/pom.xml
+++ b/osgi-bundles/bundles/webconsolebranding/pom.xml
@@ -19,7 +19,7 @@
<parent>
<artifactId>killbill-osgi-bundles</artifactId>
<groupId>com.ning.billing</groupId>
- <version>0.8.3-SNAPSHOT</version>
+ <version>0.8.6-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>killbill-osgi-bundles-webconsolebranding</artifactId>
osgi-bundles/defaultbundles/pom.xml 2(+1 -1)
diff --git a/osgi-bundles/defaultbundles/pom.xml b/osgi-bundles/defaultbundles/pom.xml
index a72ee5f..2447276 100644
--- a/osgi-bundles/defaultbundles/pom.xml
+++ b/osgi-bundles/defaultbundles/pom.xml
@@ -19,7 +19,7 @@
<parent>
<artifactId>killbill-osgi-all-bundles</artifactId>
<groupId>com.ning.billing</groupId>
- <version>0.8.3-SNAPSHOT</version>
+ <version>0.8.6-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>killbill-osgi-bundles-defaultbundles</artifactId>
osgi-bundles/libs/killbill/pom.xml 2(+1 -1)
diff --git a/osgi-bundles/libs/killbill/pom.xml b/osgi-bundles/libs/killbill/pom.xml
index 267a987..1d327f7 100644
--- a/osgi-bundles/libs/killbill/pom.xml
+++ b/osgi-bundles/libs/killbill/pom.xml
@@ -19,7 +19,7 @@
<parent>
<artifactId>killbill-osgi-lib-bundles</artifactId>
<groupId>com.ning.billing</groupId>
- <version>0.8.3-SNAPSHOT</version>
+ <version>0.8.6-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>killbill-osgi-bundles-lib-killbill</artifactId>
osgi-bundles/libs/pom.xml 2(+1 -1)
diff --git a/osgi-bundles/libs/pom.xml b/osgi-bundles/libs/pom.xml
index 1bb6ae2..655a061 100644
--- a/osgi-bundles/libs/pom.xml
+++ b/osgi-bundles/libs/pom.xml
@@ -19,7 +19,7 @@
<parent>
<artifactId>killbill-osgi-all-bundles</artifactId>
<groupId>com.ning.billing</groupId>
- <version>0.8.3-SNAPSHOT</version>
+ <version>0.8.6-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>killbill-osgi-lib-bundles</artifactId>
osgi-bundles/libs/slf4j-osgi/pom.xml 2(+1 -1)
diff --git a/osgi-bundles/libs/slf4j-osgi/pom.xml b/osgi-bundles/libs/slf4j-osgi/pom.xml
index 010af7f..22a896f 100644
--- a/osgi-bundles/libs/slf4j-osgi/pom.xml
+++ b/osgi-bundles/libs/slf4j-osgi/pom.xml
@@ -19,7 +19,7 @@
<parent>
<artifactId>killbill-osgi-lib-bundles</artifactId>
<groupId>com.ning.billing</groupId>
- <version>0.8.3-SNAPSHOT</version>
+ <version>0.8.6-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>killbill-osgi-bundles-lib-slf4j-osgi</artifactId>
osgi-bundles/pom.xml 2(+1 -1)
diff --git a/osgi-bundles/pom.xml b/osgi-bundles/pom.xml
index a3a5e6b..c8d28b6 100644
--- a/osgi-bundles/pom.xml
+++ b/osgi-bundles/pom.xml
@@ -19,7 +19,7 @@
<parent>
<artifactId>killbill</artifactId>
<groupId>com.ning.billing</groupId>
- <version>0.8.3-SNAPSHOT</version>
+ <version>0.8.6-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>killbill-osgi-all-bundles</artifactId>
osgi-bundles/tests/beatrix/pom.xml 2(+1 -1)
diff --git a/osgi-bundles/tests/beatrix/pom.xml b/osgi-bundles/tests/beatrix/pom.xml
index cf066e8..a4e9c33 100644
--- a/osgi-bundles/tests/beatrix/pom.xml
+++ b/osgi-bundles/tests/beatrix/pom.xml
@@ -19,7 +19,7 @@
<parent>
<artifactId>killbill-osgi-test-bundles</artifactId>
<groupId>com.ning.billing</groupId>
- <version>0.8.3-SNAPSHOT</version>
+ <version>0.8.6-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>killbill-osgi-bundles-test-beatrix</artifactId>
osgi-bundles/tests/payment/pom.xml 2(+1 -1)
diff --git a/osgi-bundles/tests/payment/pom.xml b/osgi-bundles/tests/payment/pom.xml
index 157f015..0a17689 100644
--- a/osgi-bundles/tests/payment/pom.xml
+++ b/osgi-bundles/tests/payment/pom.xml
@@ -19,7 +19,7 @@
<parent>
<artifactId>killbill-osgi-test-bundles</artifactId>
<groupId>com.ning.billing</groupId>
- <version>0.8.3-SNAPSHOT</version>
+ <version>0.8.6-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>killbill-osgi-bundles-test-payment</artifactId>
osgi-bundles/tests/pom.xml 2(+1 -1)
diff --git a/osgi-bundles/tests/pom.xml b/osgi-bundles/tests/pom.xml
index d629b01..629cbc8 100644
--- a/osgi-bundles/tests/pom.xml
+++ b/osgi-bundles/tests/pom.xml
@@ -19,7 +19,7 @@
<parent>
<artifactId>killbill-osgi-all-bundles</artifactId>
<groupId>com.ning.billing</groupId>
- <version>0.8.3-SNAPSHOT</version>
+ <version>0.8.6-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>killbill-osgi-test-bundles</artifactId>
overdue/pom.xml 2(+1 -1)
diff --git a/overdue/pom.xml b/overdue/pom.xml
index 00f74f3..7a324dd 100644
--- a/overdue/pom.xml
+++ b/overdue/pom.xml
@@ -19,7 +19,7 @@
<parent>
<artifactId>killbill</artifactId>
<groupId>com.ning.billing</groupId>
- <version>0.8.3-SNAPSHOT</version>
+ <version>0.8.6-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>killbill-overdue</artifactId>
diff --git a/overdue/src/test/java/com/ning/billing/overdue/glue/ApplicatorMockJunctionModule.java b/overdue/src/test/java/com/ning/billing/overdue/glue/ApplicatorMockJunctionModule.java
index 12625ab..1d4bf63 100644
--- a/overdue/src/test/java/com/ning/billing/overdue/glue/ApplicatorMockJunctionModule.java
+++ b/overdue/src/test/java/com/ning/billing/overdue/glue/ApplicatorMockJunctionModule.java
@@ -54,7 +54,7 @@ public class ApplicatorMockJunctionModule extends AbstractModule {
}
@Override
- public List<BlockingState> getBlockingAll(final UUID blockableId, final BlockingStateType blockingStateType, final InternalTenantContext context) {
+ public List<BlockingState> getBlockingAllForAccount(final InternalTenantContext context) {
throw new UnsupportedOperationException();
}
payment/pom.xml 2(+1 -1)
diff --git a/payment/pom.xml b/payment/pom.xml
index 3823ba6..36e140f 100644
--- a/payment/pom.xml
+++ b/payment/pom.xml
@@ -19,7 +19,7 @@
<parent>
<artifactId>killbill</artifactId>
<groupId>com.ning.billing</groupId>
- <version>0.8.3-SNAPSHOT</version>
+ <version>0.8.6-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>killbill-payment</artifactId>
diff --git a/payment/src/main/java/com/ning/billing/payment/api/DefaultPayment.java b/payment/src/main/java/com/ning/billing/payment/api/DefaultPayment.java
index 37bde5f..b47865b 100644
--- a/payment/src/main/java/com/ning/billing/payment/api/DefaultPayment.java
+++ b/payment/src/main/java/com/ning/billing/payment/api/DefaultPayment.java
@@ -27,12 +27,11 @@ import javax.annotation.Nullable;
import org.joda.time.DateTime;
import com.ning.billing.catalog.api.Currency;
+import com.ning.billing.entity.EntityBase;
import com.ning.billing.payment.dao.PaymentAttemptModelDao;
import com.ning.billing.payment.dao.PaymentModelDao;
import com.ning.billing.payment.dao.RefundModelDao;
-import com.ning.billing.payment.dao.RefundModelDao.RefundStatus;
import com.ning.billing.payment.plugin.api.PaymentInfoPlugin;
-import com.ning.billing.entity.EntityBase;
import com.google.common.base.Function;
import com.google.common.collect.Collections2;
@@ -159,7 +158,7 @@ public class DefaultPayment extends EntityBase implements Payment {
BigDecimal result = amount;
for (RefundModelDao cur : refunds) {
- if (cur.getRefundStatus() != RefundStatus.CREATED) {
+ if (cur.getRefundStatus() == RefundStatus.COMPLETED) {
result = result.subtract(cur.getAmount());
}
}
diff --git a/payment/src/main/java/com/ning/billing/payment/api/DefaultPaymentPluginErrorEvent.java b/payment/src/main/java/com/ning/billing/payment/api/DefaultPaymentPluginErrorEvent.java
index db409b7..29372a2 100644
--- a/payment/src/main/java/com/ning/billing/payment/api/DefaultPaymentPluginErrorEvent.java
+++ b/payment/src/main/java/com/ning/billing/payment/api/DefaultPaymentPluginErrorEvent.java
@@ -65,7 +65,7 @@ public class DefaultPaymentPluginErrorEvent extends BusEventBase implements Paym
@JsonIgnore
@Override
public BusInternalEventType getBusEventType() {
- return BusInternalEventType.PAYMENT_ERROR;
+ return BusInternalEventType.PAYMENT_PLUGIN_ERROR;
}
@Override
diff --git a/payment/src/main/java/com/ning/billing/payment/api/DefaultRefund.java b/payment/src/main/java/com/ning/billing/payment/api/DefaultRefund.java
index 54f99e0..9349504 100644
--- a/payment/src/main/java/com/ning/billing/payment/api/DefaultRefund.java
+++ b/payment/src/main/java/com/ning/billing/payment/api/DefaultRefund.java
@@ -24,8 +24,8 @@ import javax.annotation.Nullable;
import org.joda.time.DateTime;
import com.ning.billing.catalog.api.Currency;
-import com.ning.billing.payment.plugin.api.RefundInfoPlugin;
import com.ning.billing.entity.EntityBase;
+import com.ning.billing.payment.plugin.api.RefundInfoPlugin;
public class DefaultRefund extends EntityBase implements Refund {
@@ -34,16 +34,18 @@ public class DefaultRefund extends EntityBase implements Refund {
private final Currency currency;
private final boolean isAdjusted;
private final DateTime effectiveDate;
+ private final RefundStatus refundStatus;
public DefaultRefund(final UUID id, @Nullable final DateTime createdDate, @Nullable final DateTime updatedDate,
final UUID paymentId, final BigDecimal amount,
- final Currency currency, final boolean isAdjusted, final DateTime effectiveDate) {
+ final Currency currency, final boolean isAdjusted, final DateTime effectiveDate, final RefundStatus refundStatus) {
super(id, createdDate, updatedDate);
this.paymentId = paymentId;
this.amount = amount;
this.currency = currency;
this.isAdjusted = isAdjusted;
this.effectiveDate = effectiveDate;
+ this.refundStatus = refundStatus;
}
@Override
@@ -72,6 +74,11 @@ public class DefaultRefund extends EntityBase implements Refund {
}
@Override
+ public RefundStatus getRefundStatus() {
+ return refundStatus;
+ }
+
+ @Override
public RefundInfoPlugin getPluginDetail() {
// TODO not implemented
return null;
@@ -85,6 +92,7 @@ public class DefaultRefund extends EntityBase implements Refund {
sb.append(", amount=").append(amount);
sb.append(", currency=").append(currency);
sb.append(", isAdjusted=").append(isAdjusted);
+ sb.append(", status=").append(refundStatus);
sb.append(", effectiveDate=").append(effectiveDate);
sb.append('}');
return sb.toString();
@@ -113,6 +121,9 @@ public class DefaultRefund extends EntityBase implements Refund {
if (currency != that.currency) {
return false;
}
+ if (refundStatus != that.refundStatus) {
+ return false;
+ }
if (paymentId != null ? !paymentId.equals(that.paymentId) : that.paymentId != null) {
return false;
}
@@ -125,6 +136,7 @@ public class DefaultRefund extends EntityBase implements Refund {
int result = paymentId != null ? paymentId.hashCode() : 0;
result = 31 * result + (amount != null ? amount.hashCode() : 0);
result = 31 * result + (currency != null ? currency.hashCode() : 0);
+ result = 31 * result + (refundStatus != null ? refundStatus.hashCode() : 0);
result = 31 * result + (isAdjusted ? 1 : 0);
result = 31 * result + (effectiveDate != null ? effectiveDate.hashCode() : 0);
return result;
diff --git a/payment/src/main/java/com/ning/billing/payment/core/RefundProcessor.java b/payment/src/main/java/com/ning/billing/payment/core/RefundProcessor.java
index 7eab0e9..a3fe237 100644
--- a/payment/src/main/java/com/ning/billing/payment/core/RefundProcessor.java
+++ b/payment/src/main/java/com/ning/billing/payment/core/RefundProcessor.java
@@ -43,10 +43,10 @@ import com.ning.billing.osgi.api.OSGIServiceRegistration;
import com.ning.billing.payment.api.DefaultRefund;
import com.ning.billing.payment.api.PaymentApiException;
import com.ning.billing.payment.api.Refund;
+import com.ning.billing.payment.api.RefundStatus;
import com.ning.billing.payment.dao.PaymentDao;
import com.ning.billing.payment.dao.PaymentModelDao;
import com.ning.billing.payment.dao.RefundModelDao;
-import com.ning.billing.payment.dao.RefundModelDao.RefundStatus;
import com.ning.billing.payment.plugin.api.PaymentPluginApi;
import com.ning.billing.payment.plugin.api.PaymentPluginApiException;
import com.ning.billing.payment.plugin.api.RefundInfoPlugin;
@@ -136,7 +136,7 @@ public class RefundProcessor extends ProcessorBase {
return new DefaultRefund(refundInfo.getId(), refundInfo.getCreatedDate(), refundInfo.getUpdatedDate(),
paymentId, refundInfo.getAmount(), account.getCurrency(),
- isAdjusted, refundInfo.getCreatedDate());
+ isAdjusted, refundInfo.getCreatedDate(), RefundStatus.COMPLETED);
} else {
paymentDao.updateRefundStatus(refundInfo.getId(), RefundStatus.PLUGIN_ERRORED, refundAmount, account.getCurrency(), context);
throw new PaymentPluginApiException("Refund error for RefundInfo: " + refundInfo.toString(),
@@ -208,7 +208,7 @@ public class RefundProcessor extends ProcessorBase {
}
return new DefaultRefund(result.getId(), result.getCreatedDate(), result.getUpdatedDate(),
result.getPaymentId(), result.getAmount(), result.getCurrency(),
- result.isAdjusted(), result.getCreatedDate());
+ result.isAdjusted(), result.getCreatedDate(), result.getRefundStatus());
}
public List<Refund> getAccountRefunds(final Account account, final InternalTenantContext context)
@@ -237,7 +237,7 @@ public class RefundProcessor extends ProcessorBase {
public Refund apply(final RefundModelDao cur) {
return new DefaultRefund(cur.getId(), cur.getCreatedDate(), cur.getUpdatedDate(),
cur.getPaymentId(), cur.getAmount(), cur.getCurrency(),
- cur.isAdjusted(), cur.getCreatedDate());
+ cur.isAdjusted(), cur.getCreatedDate(), cur.getRefundStatus());
}
}));
}
diff --git a/payment/src/main/java/com/ning/billing/payment/dao/DefaultPaymentDao.java b/payment/src/main/java/com/ning/billing/payment/dao/DefaultPaymentDao.java
index ca5509a..ccc0e17 100644
--- a/payment/src/main/java/com/ning/billing/payment/dao/DefaultPaymentDao.java
+++ b/payment/src/main/java/com/ning/billing/payment/dao/DefaultPaymentDao.java
@@ -32,7 +32,7 @@ import com.ning.billing.catalog.api.Currency;
import com.ning.billing.clock.Clock;
import com.ning.billing.entity.EntityPersistenceException;
import com.ning.billing.payment.api.PaymentStatus;
-import com.ning.billing.payment.dao.RefundModelDao.RefundStatus;
+import com.ning.billing.payment.api.RefundStatus;
import com.ning.billing.util.cache.CacheControllerDispatcher;
import com.ning.billing.util.dao.NonEntityDao;
import com.ning.billing.util.entity.DefaultPagination;
diff --git a/payment/src/main/java/com/ning/billing/payment/dao/PaymentAttemptModelDao.java b/payment/src/main/java/com/ning/billing/payment/dao/PaymentAttemptModelDao.java
index 058288c..1565e9b 100644
--- a/payment/src/main/java/com/ning/billing/payment/dao/PaymentAttemptModelDao.java
+++ b/payment/src/main/java/com/ning/billing/payment/dao/PaymentAttemptModelDao.java
@@ -119,6 +119,46 @@ public class PaymentAttemptModelDao extends EntityBase implements EntityModelDao
return requestedCurrency;
}
+ public void setAccountId(final UUID accountId) {
+ this.accountId = accountId;
+ }
+
+ public void setInvoiceId(final UUID invoiceId) {
+ this.invoiceId = invoiceId;
+ }
+
+ public void setPaymentId(final UUID paymentId) {
+ this.paymentId = paymentId;
+ }
+
+ public void setPaymentMethodId(final UUID paymentMethodId) {
+ this.paymentMethodId = paymentMethodId;
+ }
+
+ public void setProcessingStatus(final PaymentStatus processingStatus) {
+ this.processingStatus = processingStatus;
+ }
+
+ public void setEffectiveDate(final DateTime effectiveDate) {
+ this.effectiveDate = effectiveDate;
+ }
+
+ public void setGatewayErrorCode(final String gatewayErrorCode) {
+ this.gatewayErrorCode = gatewayErrorCode;
+ }
+
+ public void setGatewayErrorMsg(final String gatewayErrorMsg) {
+ this.gatewayErrorMsg = gatewayErrorMsg;
+ }
+
+ public void setRequestedAmount(final BigDecimal requestedAmount) {
+ this.requestedAmount = requestedAmount;
+ }
+
+ public void setRequestedCurrency(final Currency requestedCurrency) {
+ this.requestedCurrency = requestedCurrency;
+ }
+
@Override
public String toString() {
final StringBuilder sb = new StringBuilder();
diff --git a/payment/src/main/java/com/ning/billing/payment/dao/PaymentDao.java b/payment/src/main/java/com/ning/billing/payment/dao/PaymentDao.java
index 575e6ca..2c2e76e 100644
--- a/payment/src/main/java/com/ning/billing/payment/dao/PaymentDao.java
+++ b/payment/src/main/java/com/ning/billing/payment/dao/PaymentDao.java
@@ -20,13 +20,11 @@ import java.math.BigDecimal;
import java.util.List;
import java.util.UUID;
-import org.joda.time.DateTime;
-
-import com.ning.billing.catalog.api.Currency;
-import com.ning.billing.payment.api.PaymentStatus;
-import com.ning.billing.payment.dao.RefundModelDao.RefundStatus;
import com.ning.billing.callcontext.InternalCallContext;
import com.ning.billing.callcontext.InternalTenantContext;
+import com.ning.billing.catalog.api.Currency;
+import com.ning.billing.payment.api.PaymentStatus;
+import com.ning.billing.payment.api.RefundStatus;
import com.ning.billing.util.entity.Pagination;
public interface PaymentDao {
diff --git a/payment/src/main/java/com/ning/billing/payment/dao/PaymentModelDao.java b/payment/src/main/java/com/ning/billing/payment/dao/PaymentModelDao.java
index f7fa1e9..9b8a4f9 100644
--- a/payment/src/main/java/com/ning/billing/payment/dao/PaymentModelDao.java
+++ b/payment/src/main/java/com/ning/billing/payment/dao/PaymentModelDao.java
@@ -131,6 +131,54 @@ public class PaymentModelDao extends EntityBase implements EntityModelDao<Paymen
return extSecondPaymentRefId;
}
+ public void setAccountId(final UUID accountId) {
+ this.accountId = accountId;
+ }
+
+ public void setInvoiceId(final UUID invoiceId) {
+ this.invoiceId = invoiceId;
+ }
+
+ public void setPaymentMethodId(final UUID paymentMethodId) {
+ this.paymentMethodId = paymentMethodId;
+ }
+
+ public void setAmount(final BigDecimal amount) {
+ this.amount = amount;
+ }
+
+ public void setCurrency(final Currency currency) {
+ this.currency = currency;
+ }
+
+ public void setProcessedAmount(final BigDecimal processedAmount) {
+ this.processedAmount = processedAmount;
+ }
+
+ public void setProcessedCurrency(final Currency processedCurrency) {
+ this.processedCurrency = processedCurrency;
+ }
+
+ public void setEffectiveDate(final DateTime effectiveDate) {
+ this.effectiveDate = effectiveDate;
+ }
+
+ public void setPaymentNumber(final Integer paymentNumber) {
+ this.paymentNumber = paymentNumber;
+ }
+
+ public void setPaymentStatus(final PaymentStatus paymentStatus) {
+ this.paymentStatus = paymentStatus;
+ }
+
+ public void setExtFirstPaymentRefId(final String extFirstPaymentRefId) {
+ this.extFirstPaymentRefId = extFirstPaymentRefId;
+ }
+
+ public void setExtSecondPaymentRefId(final String extSecondPaymentRefId) {
+ this.extSecondPaymentRefId = extSecondPaymentRefId;
+ }
+
@Override
public String toString() {
final StringBuilder sb = new StringBuilder();
diff --git a/payment/src/main/java/com/ning/billing/payment/dao/RefundModelDao.java b/payment/src/main/java/com/ning/billing/payment/dao/RefundModelDao.java
index eb4d052..176fe0c 100644
--- a/payment/src/main/java/com/ning/billing/payment/dao/RefundModelDao.java
+++ b/payment/src/main/java/com/ning/billing/payment/dao/RefundModelDao.java
@@ -24,9 +24,10 @@ import javax.annotation.Nullable;
import org.joda.time.DateTime;
import com.ning.billing.catalog.api.Currency;
+import com.ning.billing.entity.EntityBase;
import com.ning.billing.payment.api.Refund;
+import com.ning.billing.payment.api.RefundStatus;
import com.ning.billing.util.dao.TableName;
-import com.ning.billing.entity.EntityBase;
import com.ning.billing.util.entity.dao.EntityModelDao;
public class RefundModelDao extends EntityBase implements EntityModelDao<Refund> {
@@ -100,11 +101,36 @@ public class RefundModelDao extends EntityBase implements EntityModelDao<Refund>
return isAdjusted;
}
- public enum RefundStatus {
- CREATED,
- PLUGIN_COMPLETED,
- COMPLETED,
- PLUGIN_ERRORED
+ public void setAccountId(final UUID accountId) {
+ this.accountId = accountId;
+ }
+
+ public void setPaymentId(final UUID paymentId) {
+ this.paymentId = paymentId;
+ }
+
+ public void setAmount(final BigDecimal amount) {
+ this.amount = amount;
+ }
+
+ public void setCurrency(final Currency currency) {
+ this.currency = currency;
+ }
+
+ public void setProcessedAmount(final BigDecimal processedAmount) {
+ this.processedAmount = processedAmount;
+ }
+
+ public void setProcessedCurrency(final Currency processedCurrency) {
+ this.processedCurrency = processedCurrency;
+ }
+
+ public void setIsAdjusted(final boolean isAdjusted) {
+ this.isAdjusted = isAdjusted;
+ }
+
+ public void setRefundStatus(final RefundStatus refundStatus) {
+ this.refundStatus = refundStatus;
}
@Override
diff --git a/payment/src/main/resources/com/ning/billing/payment/dao/PaymentAttemptSqlDao.sql.stg b/payment/src/main/resources/com/ning/billing/payment/dao/PaymentAttemptSqlDao.sql.stg
index 15c73bb..17692e2 100644
--- a/payment/src/main/resources/com/ning/billing/payment/dao/PaymentAttemptSqlDao.sql.stg
+++ b/payment/src/main/resources/com/ning/billing/payment/dao/PaymentAttemptSqlDao.sql.stg
@@ -56,7 +56,7 @@ where pa.payment_id = :paymentId
and p.id = :paymentId
<AND_CHECK_TENANT("pa.")>
<AND_CHECK_TENANT("p.")>
-order by effective_date asc
+<defaultOrderBy("pa.")>
;
>>
diff --git a/payment/src/main/resources/com/ning/billing/payment/dao/PaymentSqlDao.sql.stg b/payment/src/main/resources/com/ning/billing/payment/dao/PaymentSqlDao.sql.stg
index 6ad9b50..871d641 100644
--- a/payment/src/main/resources/com/ning/billing/payment/dao/PaymentSqlDao.sql.stg
+++ b/payment/src/main/resources/com/ning/billing/payment/dao/PaymentSqlDao.sql.stg
@@ -5,6 +5,10 @@ extraTableFieldsWithComma(prefix) ::= <<
, <prefix>record_id as payment_number
>>
+defaultOrderBy(prefix) ::= <<
+order by <prefix>effective_date ASC, <recordIdField(prefix)> ASC
+>>
+
tableFields(prefix) ::= <<
<prefix>account_id
, <prefix>invoice_id
@@ -48,6 +52,7 @@ select <allTableFields()>
from payments
where account_id = :accountId
<AND_CHECK_TENANT()>
+<defaultOrderBy()>
;
>>
@@ -57,6 +62,7 @@ select <allTableFields()>
from payments
where invoice_id = :invoiceId
<AND_CHECK_TENANT()>
+<defaultOrderBy()>
;
>>
diff --git a/payment/src/main/resources/com/ning/billing/payment/dao/RefundSqlDao.sql.stg b/payment/src/main/resources/com/ning/billing/payment/dao/RefundSqlDao.sql.stg
index aa8daa6..4efb97d 100644
--- a/payment/src/main/resources/com/ning/billing/payment/dao/RefundSqlDao.sql.stg
+++ b/payment/src/main/resources/com/ning/billing/payment/dao/RefundSqlDao.sql.stg
@@ -49,6 +49,7 @@ select <allTableFields()>
from <tableName()>
where payment_id = :paymentId
<AND_CHECK_TENANT()>
+<defaultOrderBy()>
;
>>
@@ -57,5 +58,6 @@ select <allTableFields()>
from <tableName()>
where account_id = :accountId
<AND_CHECK_TENANT()>
+<defaultOrderBy()>
;
>>
diff --git a/payment/src/test/java/com/ning/billing/payment/dao/MockPaymentDao.java b/payment/src/test/java/com/ning/billing/payment/dao/MockPaymentDao.java
index fc75d31..2a245c6 100644
--- a/payment/src/test/java/com/ning/billing/payment/dao/MockPaymentDao.java
+++ b/payment/src/test/java/com/ning/billing/payment/dao/MockPaymentDao.java
@@ -30,7 +30,7 @@ import com.ning.billing.callcontext.InternalCallContext;
import com.ning.billing.callcontext.InternalTenantContext;
import com.ning.billing.catalog.api.Currency;
import com.ning.billing.payment.api.PaymentStatus;
-import com.ning.billing.payment.dao.RefundModelDao.RefundStatus;
+import com.ning.billing.payment.api.RefundStatus;
import com.ning.billing.util.entity.Pagination;
import com.google.common.collect.ImmutableList;
@@ -186,6 +186,7 @@ public class MockPaymentDao implements PaymentDao {
return null;
}
+
@Override
public void updateRefundStatus(final UUID refundId, final RefundStatus status, final BigDecimal processedAmount, final Currency processedCurrency, final InternalCallContext context) {
return;
diff --git a/payment/src/test/java/com/ning/billing/payment/dao/TestPaymentDao.java b/payment/src/test/java/com/ning/billing/payment/dao/TestPaymentDao.java
index 70033b8..49717e9 100644
--- a/payment/src/test/java/com/ning/billing/payment/dao/TestPaymentDao.java
+++ b/payment/src/test/java/com/ning/billing/payment/dao/TestPaymentDao.java
@@ -27,13 +27,12 @@ import org.testng.annotations.Test;
import com.ning.billing.catalog.api.Currency;
import com.ning.billing.payment.PaymentTestSuiteWithEmbeddedDB;
import com.ning.billing.payment.api.PaymentStatus;
-import com.ning.billing.payment.dao.RefundModelDao.RefundStatus;
+import com.ning.billing.payment.api.RefundStatus;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertFalse;
import static org.testng.Assert.assertNotNull;
import static org.testng.Assert.assertNull;
-import static org.testng.Assert.assertTrue;
import static org.testng.Assert.fail;
public class TestPaymentDao extends PaymentTestSuiteWithEmbeddedDB {
@@ -205,7 +204,6 @@ public class TestPaymentDao extends PaymentTestSuiteWithEmbeddedDB {
assertEquals(lastPayment.getEffectiveDate().compareTo(effectiveDate), 0);
assertEquals(lastPayment.getPaymentStatus(), PaymentStatus.UNKNOWN);
-
clock.addDays(3);
final DateTime newEffectiveDate = clock.getUTCNow();
final UUID newPaymentMethodId = UUID.randomUUID();
pom.xml 4(+2 -2)
diff --git a/pom.xml b/pom.xml
index d69541b..858d7fd 100644
--- a/pom.xml
+++ b/pom.xml
@@ -19,10 +19,10 @@
<parent>
<artifactId>killbill-oss-parent</artifactId>
<groupId>com.ning.billing</groupId>
- <version>0.5.6</version>
+ <version>0.5.8</version>
</parent>
<artifactId>killbill</artifactId>
- <version>0.8.3-SNAPSHOT</version>
+ <version>0.8.6-SNAPSHOT</version>
<packaging>pom</packaging>
<name>killbill</name>
<description>Library for managing recurring subscriptions and the associated billing</description>
server/pom.xml 2(+1 -1)
diff --git a/server/pom.xml b/server/pom.xml
index 4d079a1..12e0b5a 100644
--- a/server/pom.xml
+++ b/server/pom.xml
@@ -19,7 +19,7 @@
<parent>
<artifactId>killbill</artifactId>
<groupId>com.ning.billing</groupId>
- <version>0.8.3-SNAPSHOT</version>
+ <version>0.8.6-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>killbill-server</artifactId>
diff --git a/server/src/test/java/com/ning/billing/jaxrs/KillbillClient.java b/server/src/test/java/com/ning/billing/jaxrs/KillbillClient.java
index baecbc6..5126a79 100644
--- a/server/src/test/java/com/ning/billing/jaxrs/KillbillClient.java
+++ b/server/src/test/java/com/ning/billing/jaxrs/KillbillClient.java
@@ -59,6 +59,7 @@ import com.ning.billing.jaxrs.json.RefundJson;
import com.ning.billing.jaxrs.json.SubscriptionJson;
import com.ning.billing.jaxrs.json.TenantJson;
import com.ning.billing.jaxrs.resources.JaxrsResource;
+import com.ning.billing.payment.api.RefundStatus;
import com.ning.billing.util.api.AuditLevel;
import com.ning.http.client.AsyncCompletionHandler;
import com.ning.http.client.AsyncHttpClient;
@@ -370,13 +371,11 @@ public abstract class KillbillClient extends GuicyKillbillTestSuiteWithEmbeddedD
assertEquals(fifthResponse.getStatusCode(), javax.ws.rs.core.Response.Status.OK.getStatusCode());
}
-
-
protected SubscriptionJson createEntitlement(final String accountId, final String bundleExternalKey, final String productName, final String productCategory, final String billingPeriod, final boolean waitCompletion) throws Exception {
- final SubscriptionJson input = new SubscriptionJson(accountId, null, null, bundleExternalKey, null ,productName, productCategory,
- billingPeriod, PriceListSet.DEFAULT_PRICELIST_NAME, null, null, null, null, null,
- null, null, null);
+ final SubscriptionJson input = new SubscriptionJson(accountId, null, null, bundleExternalKey, null, productName, productCategory,
+ billingPeriod, PriceListSet.DEFAULT_PRICELIST_NAME, null, null, null, null, null,
+ null, null, null);
String baseJson = mapper.writeValueAsString(input);
final Map<String, String> queryParams = waitCompletion ? getQueryParamsForCallCompletion("5") : DEFAULT_EMPTY_QUERY;
@@ -542,25 +541,25 @@ public abstract class KillbillClient extends GuicyKillbillTestSuiteWithEmbeddedD
}
final InvoiceItemJson adjustment = new InvoiceItemJson(invoiceItemId, null, null, accountId, null, null, null, null,
- null, null, null, null, amount, currency, null);
+ null, null, null, null, amount, currency, null);
final String adjustmentJson = mapper.writeValueAsString(adjustment);
final Response response = doPost(uri, adjustmentJson, queryParams, DEFAULT_HTTP_TIMEOUT_SEC);
Assert.assertEquals(response.getStatusCode(), Status.CREATED.getStatusCode());
}
protected InvoiceJson createExternalCharge(final String accountId, final BigDecimal amount, @Nullable final String bundleId,
- @Nullable final Currency currency, @Nullable final DateTime requestedDate, final Boolean autoPay) throws Exception {
+ @Nullable final Currency currency, @Nullable final DateTime requestedDate, final Boolean autoPay) throws Exception {
return doCreateExternalCharge(accountId, null, bundleId, amount, currency, requestedDate, autoPay, JaxrsResource.CHARGES_PATH);
}
protected InvoiceJson createExternalChargeForInvoice(final String accountId, final String invoiceId, @Nullable final String bundleId, final BigDecimal amount,
- @Nullable final Currency currency, @Nullable final DateTime requestedDate, final Boolean autoPay) throws Exception {
+ @Nullable final Currency currency, @Nullable final DateTime requestedDate, final Boolean autoPay) throws Exception {
final String uri = JaxrsResource.INVOICES_PATH + "/" + invoiceId + "/" + JaxrsResource.CHARGES;
return doCreateExternalCharge(accountId, invoiceId, bundleId, amount, currency, requestedDate, autoPay, uri);
}
private InvoiceJson doCreateExternalCharge(final String accountId, @Nullable final String invoiceId, @Nullable final String bundleId, @Nullable final BigDecimal amount,
- @Nullable final Currency currency, final DateTime requestedDate, final Boolean autoPay, final String uri) throws IOException {
+ @Nullable final Currency currency, final DateTime requestedDate, final Boolean autoPay, final String uri) throws IOException {
final Map<String, String> queryParams = new HashMap<String, String>();
if (requestedDate != null) {
queryParams.put(JaxrsResource.QUERY_REQUESTED_DT, requestedDate.toDateTimeISO().toString());
@@ -570,7 +569,7 @@ public abstract class KillbillClient extends GuicyKillbillTestSuiteWithEmbeddedD
}
final InvoiceItemJson externalCharge = new InvoiceItemJson(null, invoiceId, null, accountId, bundleId, null, null, null,
- null, null, null, null, amount, currency, null);
+ null, null, null, null, amount, currency, null);
final String externalChargeJson = mapper.writeValueAsString(externalCharge);
final Response response = doPost(uri, externalChargeJson, queryParams, DEFAULT_HTTP_TIMEOUT_SEC);
Assert.assertEquals(response.getStatusCode(), Status.CREATED.getStatusCode());
@@ -691,7 +690,7 @@ public abstract class KillbillClient extends GuicyKillbillTestSuiteWithEmbeddedD
protected void payAllInvoices(final AccountJson accountJson, final Boolean externalPayment) throws IOException {
final PaymentJson payment = new PaymentJson(null, null, accountJson.getAccountId(), null, null, null, null,
- null, null, 0, null, null, null, null, null, null, null, null);
+ null, null, 0, null, null, null, null, null, null, null, null);
final String postJson = mapper.writeValueAsString(payment);
final String uri = JaxrsResource.ACCOUNTS_PATH + "/" + accountJson.getAccountId() + "/" + JaxrsResource.PAYMENTS;
@@ -700,7 +699,7 @@ public abstract class KillbillClient extends GuicyKillbillTestSuiteWithEmbeddedD
protected List<PaymentJson> createInstaPayment(final AccountJson accountJson, final InvoiceJson invoice) throws IOException {
final PaymentJson payment = new PaymentJson(invoice.getAmount(), BigDecimal.ZERO, accountJson.getAccountId(),
- invoice.getInvoiceId(), null, null, null, null, null, 0, null, null, null, null, null, null, null, null);
+ invoice.getInvoiceId(), null, null, null, null, null, 0, null, null, null, null, null, null, null, null);
final String postJson = mapper.writeValueAsString(payment);
final String uri = JaxrsResource.INVOICES_PATH + "/" + invoice.getInvoiceId() + "/" + JaxrsResource.PAYMENTS;
@@ -711,8 +710,8 @@ public abstract class KillbillClient extends GuicyKillbillTestSuiteWithEmbeddedD
protected List<PaymentJson> createExternalPayment(final AccountJson accountJson, final String invoiceId, final BigDecimal paidAmount) throws IOException {
final PaymentJson payment = new PaymentJson(paidAmount, BigDecimal.ZERO, accountJson.getAccountId(),
- invoiceId, null, null, null, null, null, 0,
- null, null, null, null, null, null, null, null);
+ invoiceId, null, null, null, null, null, 0,
+ null, null, null, null, null, null, null, null);
final String postJson = mapper.writeValueAsString(payment);
final String paymentURI = JaxrsResource.INVOICES_PATH + "/" + invoiceId + "/" + JaxrsResource.PAYMENTS;
@@ -791,9 +790,9 @@ public abstract class KillbillClient extends GuicyKillbillTestSuiteWithEmbeddedD
final List<InvoiceItemJson> adjustments = new ArrayList<InvoiceItemJson>();
for (final String itemId : itemAdjustments.keySet()) {
adjustments.add(new InvoiceItemJson(itemId, null, null, null, null, null, null, null, null, null, null, null,
- itemAdjustments.get(itemId), null, null));
+ itemAdjustments.get(itemId), null, null));
}
- final RefundJson refundJson = new RefundJson(null, paymentId, amount, DEFAULT_CURRENCY, adjusted, null, null, adjustments, null);
+ final RefundJson refundJson = new RefundJson(null, paymentId, amount, DEFAULT_CURRENCY, RefundStatus.COMPLETED.toString(), adjusted, null, null, adjustments, null);
final String baseJson = mapper.writeValueAsString(refundJson);
final Response response = doPost(uri, baseJson, DEFAULT_EMPTY_QUERY, DEFAULT_HTTP_TIMEOUT_SEC);
assertEquals(response.getStatusCode(), Status.CREATED.getStatusCode());
@@ -867,7 +866,6 @@ public abstract class KillbillClient extends GuicyKillbillTestSuiteWithEmbeddedD
// OVERDUE
//
-
protected OverdueStateJson getOverdueStateForAccount(final String accountId) throws Exception {
final String overdueURI = JaxrsResource.ACCOUNTS_PATH + "/" + accountId + "/" + OVERDUE;
final Response overdueResponse = doGet(overdueURI, DEFAULT_EMPTY_QUERY, DEFAULT_HTTP_TIMEOUT_SEC);
diff --git a/server/src/test/java/com/ning/billing/jaxrs/TestBundle.java b/server/src/test/java/com/ning/billing/jaxrs/TestBundle.java
index 97201a2..b99874d 100644
--- a/server/src/test/java/com/ning/billing/jaxrs/TestBundle.java
+++ b/server/src/test/java/com/ning/billing/jaxrs/TestBundle.java
@@ -95,7 +95,7 @@ public class TestBundle extends TestJaxrsBase {
queryParams.put(JaxrsResource.QUERY_EXTERNAL_KEY, "56566");
uri = JaxrsResource.ACCOUNTS_PATH + "/" + accountJson.getAccountId() + "/" + JaxrsResource.BUNDLES;
response = doGet(uri, queryParams, DEFAULT_HTTP_TIMEOUT_SEC);
- Assert.assertEquals(response.getStatusCode(), Status.NOT_FOUND.getStatusCode());
+ Assert.assertEquals(response.getStatusCode(), Status.OK.getStatusCode());
uri = JaxrsResource.ACCOUNTS_PATH + "/" + accountJson.getAccountId() + "/" + JaxrsResource.BUNDLES;
response = doGet(uri, DEFAULT_EMPTY_QUERY, DEFAULT_HTTP_TIMEOUT_SEC);
diff --git a/server/src/test/java/com/ning/billing/jaxrs/TestPayment.java b/server/src/test/java/com/ning/billing/jaxrs/TestPayment.java
index 01200d6..321903c 100644
--- a/server/src/test/java/com/ning/billing/jaxrs/TestPayment.java
+++ b/server/src/test/java/com/ning/billing/jaxrs/TestPayment.java
@@ -30,6 +30,7 @@ import com.ning.billing.jaxrs.json.InvoiceJson;
import com.ning.billing.jaxrs.json.PaymentJson;
import com.ning.billing.jaxrs.json.PaymentMethodJson;
import com.ning.billing.jaxrs.json.RefundJson;
+import com.ning.billing.payment.api.RefundStatus;
public class TestPayment extends TestJaxrsBase {
@@ -177,6 +178,7 @@ public class TestPayment extends TestJaxrsBase {
Assert.assertEquals(refundJsonCheck.getPaymentId(), paymentJson.getPaymentId());
Assert.assertEquals(refundJsonCheck.getAmount().setScale(2, RoundingMode.HALF_UP), refundAmount.setScale(2, RoundingMode.HALF_UP));
Assert.assertEquals(refundJsonCheck.getCurrency(), DEFAULT_CURRENCY);
+ Assert.assertEquals(refundJsonCheck.getStatus(), RefundStatus.COMPLETED.toString());
Assert.assertEquals(refundJsonCheck.getEffectiveDate().getYear(), clock.getUTCNow().getYear());
Assert.assertEquals(refundJsonCheck.getEffectiveDate().getMonthOfYear(), clock.getUTCNow().getMonthOfYear());
Assert.assertEquals(refundJsonCheck.getEffectiveDate().getDayOfMonth(), clock.getUTCNow().getDayOfMonth());
subscription/pom.xml 2(+1 -1)
diff --git a/subscription/pom.xml b/subscription/pom.xml
index 8b8f285..856419f 100644
--- a/subscription/pom.xml
+++ b/subscription/pom.xml
@@ -19,7 +19,7 @@
<parent>
<artifactId>killbill</artifactId>
<groupId>com.ning.billing</groupId>
- <version>0.8.3-SNAPSHOT</version>
+ <version>0.8.6-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>killbill-subscription</artifactId>
diff --git a/subscription/src/main/java/com/ning/billing/subscription/engine/dao/DefaultSubscriptionDao.java b/subscription/src/main/java/com/ning/billing/subscription/engine/dao/DefaultSubscriptionDao.java
index 7aec7aa..21edeeb 100644
--- a/subscription/src/main/java/com/ning/billing/subscription/engine/dao/DefaultSubscriptionDao.java
+++ b/subscription/src/main/java/com/ning/billing/subscription/engine/dao/DefaultSubscriptionDao.java
@@ -39,10 +39,20 @@ import org.slf4j.LoggerFactory;
import com.ning.billing.ErrorCode;
import com.ning.billing.bus.api.PersistentBus;
import com.ning.billing.bus.api.PersistentBus.EventBusException;
+import com.ning.billing.callcontext.InternalCallContext;
+import com.ning.billing.callcontext.InternalTenantContext;
import com.ning.billing.catalog.api.CatalogService;
import com.ning.billing.catalog.api.Plan;
import com.ning.billing.catalog.api.ProductCategory;
import com.ning.billing.clock.Clock;
+import com.ning.billing.entity.EntityPersistenceException;
+import com.ning.billing.events.EffectiveSubscriptionInternalEvent;
+import com.ning.billing.events.RepairSubscriptionInternalEvent;
+import com.ning.billing.notificationq.api.NotificationEvent;
+import com.ning.billing.notificationq.api.NotificationQueue;
+import com.ning.billing.notificationq.api.NotificationQueueService;
+import com.ning.billing.notificationq.api.NotificationQueueService.NoSuchNotificationQueue;
+import com.ning.billing.subscription.api.SubscriptionBase;
import com.ning.billing.subscription.api.migration.AccountMigrationData;
import com.ning.billing.subscription.api.migration.AccountMigrationData.BundleMigrationData;
import com.ning.billing.subscription.api.migration.AccountMigrationData.SubscriptionMigrationData;
@@ -54,13 +64,13 @@ import com.ning.billing.subscription.api.user.DefaultRequestedSubscriptionEvent;
import com.ning.billing.subscription.api.user.DefaultSubscriptionBase;
import com.ning.billing.subscription.api.user.DefaultSubscriptionBaseBundle;
import com.ning.billing.subscription.api.user.SubscriptionBaseBundle;
-import com.ning.billing.subscription.api.user.SubscriptionBuilder;
import com.ning.billing.subscription.api.user.SubscriptionBaseTransitionData;
+import com.ning.billing.subscription.api.user.SubscriptionBuilder;
import com.ning.billing.subscription.engine.addon.AddonUtils;
import com.ning.billing.subscription.engine.core.DefaultSubscriptionBaseService;
import com.ning.billing.subscription.engine.core.SubscriptionNotificationKey;
-import com.ning.billing.subscription.engine.dao.model.SubscriptionEventModelDao;
import com.ning.billing.subscription.engine.dao.model.SubscriptionBundleModelDao;
+import com.ning.billing.subscription.engine.dao.model.SubscriptionEventModelDao;
import com.ning.billing.subscription.engine.dao.model.SubscriptionModelDao;
import com.ning.billing.subscription.events.SubscriptionBaseEvent;
import com.ning.billing.subscription.events.SubscriptionBaseEvent.EventType;
@@ -72,27 +82,20 @@ import com.ning.billing.subscription.events.user.ApiEventChange;
import com.ning.billing.subscription.events.user.ApiEventMigrateBilling;
import com.ning.billing.subscription.events.user.ApiEventType;
import com.ning.billing.subscription.exceptions.SubscriptionBaseError;
-import com.ning.billing.notificationq.api.NotificationEvent;
-import com.ning.billing.notificationq.api.NotificationQueue;
-import com.ning.billing.notificationq.api.NotificationQueueService;
-import com.ning.billing.notificationq.api.NotificationQueueService.NoSuchNotificationQueue;
-import com.ning.billing.subscription.api.SubscriptionBase;
import com.ning.billing.util.cache.CacheControllerDispatcher;
-import com.ning.billing.callcontext.InternalCallContext;
-import com.ning.billing.callcontext.InternalTenantContext;
import com.ning.billing.util.dao.NonEntityDao;
-import com.ning.billing.entity.EntityPersistenceException;
import com.ning.billing.util.entity.dao.EntitySqlDao;
import com.ning.billing.util.entity.dao.EntitySqlDaoTransactionWrapper;
import com.ning.billing.util.entity.dao.EntitySqlDaoTransactionalJdbiWrapper;
import com.ning.billing.util.entity.dao.EntitySqlDaoWrapperFactory;
-import com.ning.billing.events.EffectiveSubscriptionInternalEvent;
-import com.ning.billing.events.RepairSubscriptionInternalEvent;
import com.google.common.base.Function;
import com.google.common.base.Predicate;
+import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.Collections2;
-
+import com.google.common.collect.HashMultimap;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Multimap;
public class DefaultSubscriptionDao implements SubscriptionDao {
@@ -236,10 +239,9 @@ public class DefaultSubscriptionDao implements SubscriptionDao {
return buildSubscription(shellSubscription, context);
}
-
@Override
public List<SubscriptionBase> getSubscriptions(final UUID bundleId, final InternalTenantContext context) {
- return buildBundleSubscriptions(bundleId, getSubscriptionFromBundleId(bundleId, context), context);
+ return buildBundleSubscriptions(getSubscriptionFromBundleId(bundleId, context), null, context);
}
private List<SubscriptionBase> getSubscriptionFromBundleId(final UUID bundleId, final InternalTenantContext context) {
@@ -257,13 +259,37 @@ public class DefaultSubscriptionDao implements SubscriptionDao {
});
}
+
@Override
public Map<UUID, List<SubscriptionBase>> getSubscriptionsForAccount(final InternalTenantContext context) {
final Map<UUID, List<SubscriptionBase>> subscriptionsFromAccountId = getSubscriptionsFromAccountId(context);
+ final List<SubscriptionBaseEvent> eventsForAccount = getEventsForAccountId(context);
+
final Map<UUID, List<SubscriptionBase>> result = new HashMap<UUID, List<SubscriptionBase>>();
for (final UUID bundleId : subscriptionsFromAccountId.keySet()) {
- result.put(bundleId, buildBundleSubscriptions(bundleId, subscriptionsFromAccountId.get(bundleId), context));
+
+ final List<SubscriptionBase> subscriptionsForBundle = subscriptionsFromAccountId.get(bundleId);
+ final Collection<UUID> subscriptionIdsForBundle = Collections2.transform(subscriptionsForBundle, new Function<SubscriptionBase, UUID>() {
+ @Override
+ public UUID apply(final SubscriptionBase input) {
+ return input.getId();
+ }
+ });
+ final Multimap<UUID, SubscriptionBaseEvent> eventsForSubscriptions = ArrayListMultimap.create();
+
+ for (final SubscriptionBase cur : subscriptionsForBundle) {
+ final Collection<SubscriptionBaseEvent> events= Collections2.filter(eventsForAccount, new Predicate<SubscriptionBaseEvent>() {
+ @Override
+ public boolean apply(final SubscriptionBaseEvent input) {
+ return input.getSubscriptionId().equals(cur.getId());
+
+ }
+ });
+ eventsForSubscriptions.putAll(cur.getId(), ImmutableList.copyOf(events));
+ }
+
+ result.put(bundleId, buildBundleSubscriptions(subscriptionsForBundle, eventsForSubscriptions,context));
}
return result;
}
@@ -292,23 +318,6 @@ public class DefaultSubscriptionDao implements SubscriptionDao {
return result;
}
- /*
- @Override
- public List<SubscriptionBase> getSubscriptionsForAccountAndKey(final UUID accountId,
- final String bundleKey, final InternalTenantContext callcontext) {
- return transactionalSqlDao.execute(new EntitySqlDaoTransactionWrapper<List<SubscriptionBase>>() {
- @Override
- public List<SubscriptionBase> inTransaction(final EntitySqlDaoWrapperFactory<EntitySqlDao> entitySqlDaoWrapperFactory) throws Exception {
- final SubscriptionBundleModelDao bundleModel = entitySqlDaoWrapperFactory.become(BundleSqlDao.class).getBundlesFromAccountAndKey(accountId.toString(), bundleKey, callcontext);
- if (bundleModel == null) {
- return Collections.emptyList();
- }
- return getSubscriptions(bundleModel.getId(), callcontext);
- }
- });
- }
- */
-
@Override
public void updateChargedThroughDate(final DefaultSubscriptionBase subscription, final InternalCallContext context) {
final Date ctd = (subscription.getChargedThroughDate() != null) ? subscription.getChargedThroughDate().toDate() : null;
@@ -365,23 +374,12 @@ public class DefaultSubscriptionDao implements SubscriptionDao {
@Override
public List<SubscriptionBaseEvent> inTransaction(final EntitySqlDaoWrapperFactory<EntitySqlDao> entitySqlDaoWrapperFactory) throws Exception {
final List<SubscriptionEventModelDao> models = entitySqlDaoWrapperFactory.become(SubscriptionEventSqlDao.class).getEventsForSubscription(subscriptionId.toString(), context);
- // Remove UNCANCEL events early on as they are not representative of a state transition but are just markers
- final Collection<SubscriptionEventModelDao> filteredModels = Collections2.filter(models, new Predicate<SubscriptionEventModelDao>() {
- @Override
- public boolean apply(@Nullable final SubscriptionEventModelDao input) {
- return input.getUserType() != ApiEventType.UNCANCEL;
- }
- });
- return new ArrayList<SubscriptionBaseEvent>(Collections2.transform(filteredModels, new Function<SubscriptionEventModelDao, SubscriptionBaseEvent>() {
- @Override
- public SubscriptionBaseEvent apply(@Nullable final SubscriptionEventModelDao input) {
- return SubscriptionEventModelDao.toSubscriptionEvent(input);
- }
- }));
+ return filterSubscriptionBaseEvents(models);
}
});
}
+
@Override
public Map<UUID, List<SubscriptionBaseEvent>> getEventsForBundle(final UUID bundleId, final InternalTenantContext context) {
return transactionalSqlDao.execute(new EntitySqlDaoTransactionWrapper<Map<UUID, List<SubscriptionBaseEvent>>>() {
@@ -455,7 +453,6 @@ public class DefaultSubscriptionDao implements SubscriptionDao {
});
}
-
@Override
public void recreateSubscription(final DefaultSubscriptionBase subscription, final List<SubscriptionBaseEvent> recreateEvents, final InternalCallContext context) {
transactionalSqlDao.execute(new EntitySqlDaoTransactionWrapper<Void>() {
@@ -494,7 +491,6 @@ public class DefaultSubscriptionDao implements SubscriptionDao {
});
}
-
@Override
public void cancelSubscription(final DefaultSubscriptionBase subscription, final SubscriptionBaseEvent cancelEvent, final InternalCallContext context, final int seqId) {
transactionalSqlDao.execute(new EntitySqlDaoTransactionWrapper<Void>() {
@@ -557,9 +553,9 @@ public class DefaultSubscriptionDao implements SubscriptionDao {
final UUID subscriptionId = subscription.getId();
final List<SubscriptionBaseEvent> changeEventsTweakedWithMigrateBilling = reinsertFutureMigrateBillingEventOnChangeFromTransaction(subscriptionId,
- changeEvents,
- entitySqlDaoWrapperFactory,
- context);
+ changeEvents,
+ entitySqlDaoWrapperFactory,
+ context);
cancelFutureEventsFromTransaction(subscriptionId, entitySqlDaoWrapperFactory, context);
@@ -669,6 +665,31 @@ public class DefaultSubscriptionDao implements SubscriptionDao {
return changeEvents;
}
+ private List<SubscriptionBaseEvent> filterSubscriptionBaseEvents(final List<SubscriptionEventModelDao> models) {
+ final Collection<SubscriptionEventModelDao> filteredModels = Collections2.filter(models, new Predicate<SubscriptionEventModelDao>() {
+ @Override
+ public boolean apply(@Nullable final SubscriptionEventModelDao input) {
+ return input.getUserType() != ApiEventType.UNCANCEL;
+ }
+ });
+ return new ArrayList<SubscriptionBaseEvent>(Collections2.transform(filteredModels, new Function<SubscriptionEventModelDao, SubscriptionBaseEvent>() {
+ @Override
+ public SubscriptionBaseEvent apply(@Nullable final SubscriptionEventModelDao input) {
+ return SubscriptionEventModelDao.toSubscriptionEvent(input);
+ }
+ }));
+ }
+
+ private List<SubscriptionBaseEvent> getEventsForAccountId(final InternalTenantContext context) {
+ return transactionalSqlDao.execute(new EntitySqlDaoTransactionWrapper<List<SubscriptionBaseEvent>>() {
+ @Override
+ public List<SubscriptionBaseEvent> inTransaction(final EntitySqlDaoWrapperFactory<EntitySqlDao> entitySqlDaoWrapperFactory) throws Exception {
+ final List<SubscriptionEventModelDao> models = entitySqlDaoWrapperFactory.become(SubscriptionEventSqlDao.class).getByAccountRecordId(context);
+ return filterSubscriptionBaseEvents(models);
+ }
+ });
+ }
+
private void cancelSubscriptionFromTransaction(final DefaultSubscriptionBase subscription, final SubscriptionBaseEvent cancelEvent, final EntitySqlDaoWrapperFactory<EntitySqlDao> entitySqlDaoWrapperFactory, final InternalCallContext context, final int seqId)
throws EntityPersistenceException {
final UUID subscriptionId = subscription.getId();
@@ -701,7 +722,7 @@ public class DefaultSubscriptionDao implements SubscriptionDao {
}
private SubscriptionEventModelDao findFutureEventFromTransaction(final UUID subscriptionId, final EntitySqlDaoWrapperFactory<EntitySqlDao> dao, final EventType type,
- @Nullable final ApiEventType apiType, final InternalCallContext context) {
+ @Nullable final ApiEventType apiType, final InternalCallContext context) {
SubscriptionEventModelDao futureEvent = null;
final Date now = clock.getUTCNow().toDate();
@@ -711,7 +732,7 @@ public class DefaultSubscriptionDao implements SubscriptionDao {
(apiType == null || apiType == cur.getUserType())) {
if (futureEvent != null) {
throw new SubscriptionBaseError(String.format("Found multiple future events for type %s for subscriptions %s",
- type, subscriptionId.toString()));
+ type, subscriptionId.toString()));
}
futureEvent = cur;
// To check that there is only one such event
@@ -746,7 +767,7 @@ public class DefaultSubscriptionDao implements SubscriptionDao {
bundleInput.add(input);
}
- final List<SubscriptionBase> reloadedSubscriptions = buildBundleSubscriptions(input.getBundleId(), bundleInput, context);
+ final List<SubscriptionBase> reloadedSubscriptions = buildBundleSubscriptions(bundleInput, null, context);
for (final SubscriptionBase cur : reloadedSubscriptions) {
if (cur.getId().equals(input.getId())) {
return cur;
@@ -756,7 +777,7 @@ public class DefaultSubscriptionDao implements SubscriptionDao {
throw new SubscriptionBaseError("Unexpected code path in buildSubscription");
}
- private List<SubscriptionBase> buildBundleSubscriptions(final UUID bundleId, final List<SubscriptionBase> input, final InternalTenantContext context) {
+ private List<SubscriptionBase> buildBundleSubscriptions(final List<SubscriptionBase> input, @Nullable final Multimap<UUID, SubscriptionBaseEvent> eventsForSubscription, final InternalTenantContext context) {
if (input == null || input.size() == 0) {
return Collections.emptyList();
}
@@ -778,7 +799,12 @@ public class DefaultSubscriptionDao implements SubscriptionDao {
SubscriptionBaseEvent futureBaseEvent = null;
final List<SubscriptionBase> result = new ArrayList<SubscriptionBase>(input.size());
for (final SubscriptionBase cur : input) {
- final List<SubscriptionBaseEvent> events = getEventsForSubscription(cur.getId(), context);
+
+
+ final List<SubscriptionBaseEvent> events = eventsForSubscription != null ?
+ (List<SubscriptionBaseEvent>) eventsForSubscription.get(cur.getId()) :
+ getEventsForSubscription(cur.getId(), context);
+
SubscriptionBase reloaded = createSubscriptionForInternalUse(cur, events);
switch (cur.getCategory()) {
@@ -806,15 +832,15 @@ public class DefaultSubscriptionDao implements SubscriptionDao {
if (createCancelEvent && reloaded.getFutureEndDate() == null) {
final DateTime now = clock.getUTCNow();
final SubscriptionBaseEvent addOnCancelEvent = new ApiEventCancel(new ApiEventBuilder()
- .setSubscriptionId(reloaded.getId())
- .setActiveVersion(((DefaultSubscriptionBase) reloaded).getActiveVersion())
- .setProcessedDate(now)
- .setEffectiveDate(futureBaseEvent.getEffectiveDate())
- .setRequestedDate(now)
- .setCreatedDate(futureBaseEvent.getCreatedDate())
- // This event is only there to indicate the ADD_ON is future canceled, but it is not there
- // on disk until the base plan cancellation becomes effective
- .setFromDisk(false));
+ .setSubscriptionId(reloaded.getId())
+ .setActiveVersion(((DefaultSubscriptionBase) reloaded).getActiveVersion())
+ .setProcessedDate(now)
+ .setEffectiveDate(futureBaseEvent.getEffectiveDate())
+ .setRequestedDate(now)
+ .setCreatedDate(futureBaseEvent.getCreatedDate())
+ // This event is only there to indicate the ADD_ON is future canceled, but it is not there
+ // on disk until the base plan cancellation becomes effective
+ .setFromDisk(false));
events.add(addOnCancelEvent);
// Finally reload subscription with full set of events
@@ -873,7 +899,7 @@ public class DefaultSubscriptionDao implements SubscriptionDao {
try {
// Note: we don't send a requested change event here, but a repair event
final RepairSubscriptionInternalEvent busEvent = new DefaultRepairSubscriptionEvent(accountId, bundleId, clock.getUTCNow(),
- context.getAccountRecordId(), context.getTenantRecordId(), context.getUserToken());
+ context.getAccountRecordId(), context.getTenantRecordId(), context.getUserToken());
eventBus.postFromTransaction(busEvent, entitySqlDaoWrapperFactory.getSqlDao());
} catch (EventBusException e) {
log.warn("Failed to post repair subscription event for bundle " + bundleId, e);
@@ -922,7 +948,6 @@ public class DefaultSubscriptionDao implements SubscriptionDao {
return null;
}
-
//
// Either records a notfication or sends a bus event is operation is immediate
//
@@ -953,7 +978,6 @@ public class DefaultSubscriptionDao implements SubscriptionDao {
context.getUserToken(),
context.getAccountRecordId(), context.getTenantRecordId());
-
eventBus.postFromTransaction(busEvent, entitySqlDaoWrapperFactory.getSqlDao());
} catch (EventBusException e) {
log.warn("Failed to post effective event for subscription " + subscription.getId(), e);
diff --git a/subscription/src/main/java/com/ning/billing/subscription/engine/dao/model/SubscriptionBundleModelDao.java b/subscription/src/main/java/com/ning/billing/subscription/engine/dao/model/SubscriptionBundleModelDao.java
index 4cbabcc..9af84a0 100644
--- a/subscription/src/main/java/com/ning/billing/subscription/engine/dao/model/SubscriptionBundleModelDao.java
+++ b/subscription/src/main/java/com/ning/billing/subscription/engine/dao/model/SubscriptionBundleModelDao.java
@@ -64,6 +64,22 @@ public class SubscriptionBundleModelDao extends EntityBase implements EntityMode
return originalCreatedDate;
}
+ public void setExternalKey(final String externalKey) {
+ this.externalKey = externalKey;
+ }
+
+ public void setAccountId(final UUID accountId) {
+ this.accountId = accountId;
+ }
+
+ public void setLastSysUpdateDate(final DateTime lastSysUpdateDate) {
+ this.lastSysUpdateDate = lastSysUpdateDate;
+ }
+
+ public void setOriginalCreatedDate(final DateTime originalCreatedDate) {
+ this.originalCreatedDate = originalCreatedDate;
+ }
+
public static SubscriptionBaseBundle toSubscriptionbundle(final SubscriptionBundleModelDao src) {
if (src == null) {
return null;
diff --git a/subscription/src/main/java/com/ning/billing/subscription/engine/dao/model/SubscriptionEventModelDao.java b/subscription/src/main/java/com/ning/billing/subscription/engine/dao/model/SubscriptionEventModelDao.java
index b4d94e4..8a53635 100644
--- a/subscription/src/main/java/com/ning/billing/subscription/engine/dao/model/SubscriptionEventModelDao.java
+++ b/subscription/src/main/java/com/ning/billing/subscription/engine/dao/model/SubscriptionEventModelDao.java
@@ -142,6 +142,50 @@ public class SubscriptionEventModelDao extends EntityBase implements EntityModel
return isActive;
}
+ public void setTotalOrdering(final long totalOrdering) {
+ this.totalOrdering = totalOrdering;
+ }
+
+ public void setEventType(final EventType eventType) {
+ this.eventType = eventType;
+ }
+
+ public void setUserType(final ApiEventType userType) {
+ this.userType = userType;
+ }
+
+ public void setRequestedDate(final DateTime requestedDate) {
+ this.requestedDate = requestedDate;
+ }
+
+ public void setEffectiveDate(final DateTime effectiveDate) {
+ this.effectiveDate = effectiveDate;
+ }
+
+ public void setSubscriptionId(final UUID subscriptionId) {
+ this.subscriptionId = subscriptionId;
+ }
+
+ public void setPlanName(final String planName) {
+ this.planName = planName;
+ }
+
+ public void setPhaseName(final String phaseName) {
+ this.phaseName = phaseName;
+ }
+
+ public void setPriceListName(final String priceListName) {
+ this.priceListName = priceListName;
+ }
+
+ public void setCurrentVersion(final long currentVersion) {
+ this.currentVersion = currentVersion;
+ }
+
+ public void setIsActive(final boolean isActive) {
+ this.isActive = isActive;
+ }
+
public static SubscriptionBaseEvent toSubscriptionEvent(final SubscriptionEventModelDao src) {
if (src == null) {
diff --git a/subscription/src/main/java/com/ning/billing/subscription/engine/dao/model/SubscriptionModelDao.java b/subscription/src/main/java/com/ning/billing/subscription/engine/dao/model/SubscriptionModelDao.java
index f650cb5..bd181c6 100644
--- a/subscription/src/main/java/com/ning/billing/subscription/engine/dao/model/SubscriptionModelDao.java
+++ b/subscription/src/main/java/com/ning/billing/subscription/engine/dao/model/SubscriptionModelDao.java
@@ -85,6 +85,34 @@ public class SubscriptionModelDao extends EntityBase implements EntityModelDao<S
return paidThroughDate;
}
+ public void setBundleId(final UUID bundleId) {
+ this.bundleId = bundleId;
+ }
+
+ public void setCategory(final ProductCategory category) {
+ this.category = category;
+ }
+
+ public void setStartDate(final DateTime startDate) {
+ this.startDate = startDate;
+ }
+
+ public void setBundleStartDate(final DateTime bundleStartDate) {
+ this.bundleStartDate = bundleStartDate;
+ }
+
+ public void setActiveVersion(final long activeVersion) {
+ this.activeVersion = activeVersion;
+ }
+
+ public void setChargedThroughDate(final DateTime chargedThroughDate) {
+ this.chargedThroughDate = chargedThroughDate;
+ }
+
+ public void setPaidThroughDate(final DateTime paidThroughDate) {
+ this.paidThroughDate = paidThroughDate;
+ }
+
public static SubscriptionBase toSubscription(final SubscriptionModelDao src) {
if (src == null) {
return null;
diff --git a/subscription/src/main/java/com/ning/billing/subscription/glue/DefaultSubscriptionModule.java b/subscription/src/main/java/com/ning/billing/subscription/glue/DefaultSubscriptionModule.java
index 0c35d1c..953a2ea 100644
--- a/subscription/src/main/java/com/ning/billing/subscription/glue/DefaultSubscriptionModule.java
+++ b/subscription/src/main/java/com/ning/billing/subscription/glue/DefaultSubscriptionModule.java
@@ -23,6 +23,7 @@ import com.ning.billing.glue.SubscriptionModule;
import com.ning.billing.subscription.alignment.MigrationPlanAligner;
import com.ning.billing.subscription.alignment.PlanAligner;
import com.ning.billing.subscription.api.SubscriptionBaseApiService;
+import com.ning.billing.subscription.api.SubscriptionBaseInternalApi;
import com.ning.billing.subscription.api.SubscriptionBaseService;
import com.ning.billing.subscription.api.migration.DefaultSubscriptionBaseMigrationApi;
import com.ning.billing.subscription.api.migration.SubscriptionBaseMigrationApi;
@@ -40,7 +41,6 @@ import com.ning.billing.subscription.engine.dao.DefaultSubscriptionDao;
import com.ning.billing.subscription.engine.dao.RepairSubscriptionDao;
import com.ning.billing.subscription.engine.dao.SubscriptionDao;
import com.ning.billing.util.config.SubscriptionConfig;
-import com.ning.billing.subscription.api.SubscriptionBaseInternalApi;
import com.google.inject.AbstractModule;
import com.google.inject.name.Names;
@@ -68,7 +68,6 @@ public class DefaultSubscriptionModule extends AbstractModule implements Subscri
}
protected void installSubscriptionCore() {
-
bind(SubscriptionBaseApiService.class).annotatedWith(Names.named(REPAIR_NAMED)).to(RepairSubscriptionApiService.class).asEagerSingleton();
bind(SubscriptionBaseApiService.class).to(DefaultSubscriptionBaseApiService.class).asEagerSingleton();
@@ -106,7 +105,6 @@ public class DefaultSubscriptionModule extends AbstractModule implements Subscri
bind(SubscriptionBaseMigrationApi.class).to(DefaultSubscriptionBaseMigrationApi.class).asEagerSingleton();
}
-
@Override
public void installSubscriptionInternalApi() {
bind(SubscriptionBaseInternalApi.class).to(DefaultSubscriptionInternalApi.class).asEagerSingleton();
diff --git a/subscription/src/main/resources/com/ning/billing/subscription/engine/dao/BundleSqlDao.sql.stg b/subscription/src/main/resources/com/ning/billing/subscription/engine/dao/BundleSqlDao.sql.stg
index 390af6f..b026267 100644
--- a/subscription/src/main/resources/com/ning/billing/subscription/engine/dao/BundleSqlDao.sql.stg
+++ b/subscription/src/main/resources/com/ning/billing/subscription/engine/dao/BundleSqlDao.sql.stg
@@ -42,7 +42,7 @@ from bundles
where
external_key = :externalKey
<AND_CHECK_TENANT()>
-order by record_id asc
+<defaultOrderBy()>
;
>>
@@ -53,7 +53,7 @@ where
external_key = :externalKey
and account_id = :accountId
<AND_CHECK_TENANT()>
-order by record_id asc
+<defaultOrderBy()>
;
>>
@@ -63,6 +63,6 @@ from bundles
where
account_id = :accountId
<AND_CHECK_TENANT()>
-order by record_id asc
+<defaultOrderBy()>
;
>>
diff --git a/subscription/src/main/resources/com/ning/billing/subscription/engine/dao/SubscriptionEventSqlDao.sql.stg b/subscription/src/main/resources/com/ning/billing/subscription/engine/dao/SubscriptionEventSqlDao.sql.stg
index 3008da9..4550610 100644
--- a/subscription/src/main/resources/com/ning/billing/subscription/engine/dao/SubscriptionEventSqlDao.sql.stg
+++ b/subscription/src/main/resources/com/ning/billing/subscription/engine/dao/SubscriptionEventSqlDao.sql.stg
@@ -8,6 +8,11 @@ extraTableFieldsWithComma(prefix) ::= <<
, <prefix>record_id as total_ordering
>>
+defaultOrderBy(prefix) ::= <<
+order by <prefix>effective_date ASC, <recordIdField(prefix)> ASC
+>>
+
+
tableFields(prefix) ::= <<
<prefix> event_type
, <prefix> user_type
@@ -90,9 +95,7 @@ subscription_id = :subscriptionId
and is_active = 1
and effective_date > :now
<AND_CHECK_TENANT()>
-order by
-effective_date asc
-, record_id asc
+<defaultOrderBy()>
;
>>
@@ -104,9 +107,7 @@ where
subscription_id = :subscriptionId
and is_active = 1
<AND_CHECK_TENANT()>
-order by
-effective_date asc
-, record_id asc
+<defaultOrderBy()>
;
>>
diff --git a/subscription/src/main/resources/com/ning/billing/subscription/engine/dao/SubscriptionSqlDao.sql.stg b/subscription/src/main/resources/com/ning/billing/subscription/engine/dao/SubscriptionSqlDao.sql.stg
index 378afef..ad122a2 100644
--- a/subscription/src/main/resources/com/ning/billing/subscription/engine/dao/SubscriptionSqlDao.sql.stg
+++ b/subscription/src/main/resources/com/ning/billing/subscription/engine/dao/SubscriptionSqlDao.sql.stg
@@ -37,6 +37,7 @@ select
from <tableName()>
where bundle_id = :bundleId
<AND_CHECK_TENANT()>
+<defaultOrderBy()>
;
>>
tenant/pom.xml 2(+1 -1)
diff --git a/tenant/pom.xml b/tenant/pom.xml
index afc66bf..082ca08 100644
--- a/tenant/pom.xml
+++ b/tenant/pom.xml
@@ -19,7 +19,7 @@
<parent>
<artifactId>killbill</artifactId>
<groupId>com.ning.billing</groupId>
- <version>0.8.3-SNAPSHOT</version>
+ <version>0.8.6-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>killbill-tenant</artifactId>
diff --git a/tenant/src/main/java/com/ning/billing/tenant/dao/TenantKVModelDao.java b/tenant/src/main/java/com/ning/billing/tenant/dao/TenantKVModelDao.java
index cebbd8f..0e281c0 100644
--- a/tenant/src/main/java/com/ning/billing/tenant/dao/TenantKVModelDao.java
+++ b/tenant/src/main/java/com/ning/billing/tenant/dao/TenantKVModelDao.java
@@ -53,6 +53,18 @@ public class TenantKVModelDao extends EntityBase implements EntityModelDao<Tenan
return isActive;
}
+ public void setTenantKey(final String tenantKey) {
+ this.tenantKey = tenantKey;
+ }
+
+ public void setTenantValue(final String tenantValue) {
+ this.tenantValue = tenantValue;
+ }
+
+ public void setIsActive(final Boolean isActive) {
+ this.isActive = isActive;
+ }
+
@Override
public String toString() {
final StringBuilder sb = new StringBuilder();
diff --git a/tenant/src/main/java/com/ning/billing/tenant/dao/TenantModelDao.java b/tenant/src/main/java/com/ning/billing/tenant/dao/TenantModelDao.java
index c310649..e6bc707 100644
--- a/tenant/src/main/java/com/ning/billing/tenant/dao/TenantModelDao.java
+++ b/tenant/src/main/java/com/ning/billing/tenant/dao/TenantModelDao.java
@@ -64,6 +64,22 @@ public class TenantModelDao extends EntityBase implements EntityModelDao<Tenant>
return apiSalt;
}
+ public void setExternalKey(final String externalKey) {
+ this.externalKey = externalKey;
+ }
+
+ public void setApiKey(final String apiKey) {
+ this.apiKey = apiKey;
+ }
+
+ public void setApiSecret(final String apiSecret) {
+ this.apiSecret = apiSecret;
+ }
+
+ public void setApiSalt(final String apiSalt) {
+ this.apiSalt = apiSalt;
+ }
+
@Override
public String toString() {
final StringBuilder sb = new StringBuilder();
usage/pom.xml 2(+1 -1)
diff --git a/usage/pom.xml b/usage/pom.xml
index f2d5cec..1824d27 100644
--- a/usage/pom.xml
+++ b/usage/pom.xml
@@ -19,7 +19,7 @@
<parent>
<artifactId>killbill</artifactId>
<groupId>com.ning.billing</groupId>
- <version>0.8.3-SNAPSHOT</version>
+ <version>0.8.6-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>killbill-usage</artifactId>
diff --git a/usage/src/main/java/com/ning/billing/usage/dao/RolledUpUsageModelDao.java b/usage/src/main/java/com/ning/billing/usage/dao/RolledUpUsageModelDao.java
index 34a45f3..2ef4ea3 100644
--- a/usage/src/main/java/com/ning/billing/usage/dao/RolledUpUsageModelDao.java
+++ b/usage/src/main/java/com/ning/billing/usage/dao/RolledUpUsageModelDao.java
@@ -66,6 +66,30 @@ public class RolledUpUsageModelDao {
return amount;
}
+ public void setId(final UUID id) {
+ this.id = id;
+ }
+
+ public void setSubscriptionId(final UUID subscriptionId) {
+ this.subscriptionId = subscriptionId;
+ }
+
+ public void setUnitType(final String unitType) {
+ this.unitType = unitType;
+ }
+
+ public void setStartTime(final DateTime startTime) {
+ this.startTime = startTime;
+ }
+
+ public void setEndTime(final DateTime endTime) {
+ this.endTime = endTime;
+ }
+
+ public void setAmount(final BigDecimal amount) {
+ this.amount = amount;
+ }
+
@Override
public String toString() {
final StringBuilder sb = new StringBuilder();
util/pom.xml 2(+1 -1)
diff --git a/util/pom.xml b/util/pom.xml
index 01c5fba..61bf29f 100644
--- a/util/pom.xml
+++ b/util/pom.xml
@@ -12,7 +12,7 @@
<parent>
<artifactId>killbill</artifactId>
<groupId>com.ning.billing</groupId>
- <version>0.8.3-SNAPSHOT</version>
+ <version>0.8.6-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>killbill-util</artifactId>
diff --git a/util/src/main/java/com/ning/billing/util/customfield/dao/CustomFieldModelDao.java b/util/src/main/java/com/ning/billing/util/customfield/dao/CustomFieldModelDao.java
index fdeab59..e3d4b1e 100644
--- a/util/src/main/java/com/ning/billing/util/customfield/dao/CustomFieldModelDao.java
+++ b/util/src/main/java/com/ning/billing/util/customfield/dao/CustomFieldModelDao.java
@@ -65,6 +65,22 @@ public class CustomFieldModelDao extends EntityBase implements EntityModelDao<Cu
return objectType;
}
+ public void setFieldName(final String fieldName) {
+ this.fieldName = fieldName;
+ }
+
+ public void setFieldValue(final String fieldValue) {
+ this.fieldValue = fieldValue;
+ }
+
+ public void setObjectId(final UUID objectId) {
+ this.objectId = objectId;
+ }
+
+ public void setObjectType(final ObjectType objectType) {
+ this.objectType = objectType;
+ }
+
@Override
public String toString() {
final StringBuilder sb = new StringBuilder();
diff --git a/util/src/main/java/com/ning/billing/util/customfield/ShouldntHappenException.java b/util/src/main/java/com/ning/billing/util/customfield/ShouldntHappenException.java
new file mode 100644
index 0000000..5d4cc2d
--- /dev/null
+++ b/util/src/main/java/com/ning/billing/util/customfield/ShouldntHappenException.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright 2010-2013 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF 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.customfield;
+
+public class ShouldntHappenException extends RuntimeException {
+
+ public ShouldntHappenException(final String message) {
+ super(message);
+ }
+}
diff --git a/util/src/main/java/com/ning/billing/util/dao/EntityHistoryModelDao.java b/util/src/main/java/com/ning/billing/util/dao/EntityHistoryModelDao.java
index 791f89d..28ce9fa 100644
--- a/util/src/main/java/com/ning/billing/util/dao/EntityHistoryModelDao.java
+++ b/util/src/main/java/com/ning/billing/util/dao/EntityHistoryModelDao.java
@@ -27,9 +27,9 @@ import com.ning.billing.util.entity.dao.EntityModelDao;
public class EntityHistoryModelDao<M extends EntityModelDao<E>, E extends Entity> extends EntityBase {
- private final Long targetRecordId;
- private final M entity;
- private final ChangeType changeType;
+ private Long targetRecordId;
+ private M entity;
+ private ChangeType changeType;
public EntityHistoryModelDao(final UUID id, final M src, final Long targetRecordId, final ChangeType type, final DateTime createdDate) {
super(id, createdDate, createdDate);
@@ -53,4 +53,16 @@ public class EntityHistoryModelDao<M extends EntityModelDao<E>, E extends Entity
public Long getTargetRecordId() {
return targetRecordId;
}
+
+ public void setTargetRecordId(final Long targetRecordId) {
+ this.targetRecordId = targetRecordId;
+ }
+
+ public void setEntity(final M entity) {
+ this.entity = entity;
+ }
+
+ public void setChangeType(final ChangeType changeType) {
+ this.changeType = changeType;
+ }
}
diff --git a/util/src/main/java/com/ning/billing/util/tag/dao/TagDefinitionModelDao.java b/util/src/main/java/com/ning/billing/util/tag/dao/TagDefinitionModelDao.java
index d8db1b0..5de4cfd 100644
--- a/util/src/main/java/com/ning/billing/util/tag/dao/TagDefinitionModelDao.java
+++ b/util/src/main/java/com/ning/billing/util/tag/dao/TagDefinitionModelDao.java
@@ -66,6 +66,18 @@ public class TagDefinitionModelDao extends EntityBase implements EntityModelDao<
return isActive;
}
+ public void setName(final String name) {
+ this.name = name;
+ }
+
+ public void setDescription(final String description) {
+ this.description = description;
+ }
+
+ public void setIsActive(final Boolean isActive) {
+ this.isActive = isActive;
+ }
+
@Override
public String toString() {
final StringBuilder sb = new StringBuilder();
diff --git a/util/src/main/java/com/ning/billing/util/tag/dao/TagModelDao.java b/util/src/main/java/com/ning/billing/util/tag/dao/TagModelDao.java
index bb9b609..5cfe1ea 100644
--- a/util/src/main/java/com/ning/billing/util/tag/dao/TagModelDao.java
+++ b/util/src/main/java/com/ning/billing/util/tag/dao/TagModelDao.java
@@ -69,6 +69,22 @@ public class TagModelDao extends EntityBase implements EntityModelDao<Tag> {
return isActive;
}
+ public void setTagDefinitionId(final UUID tagDefinitionId) {
+ this.tagDefinitionId = tagDefinitionId;
+ }
+
+ public void setObjectId(final UUID objectId) {
+ this.objectId = objectId;
+ }
+
+ public void setObjectType(final ObjectType objectType) {
+ this.objectType = objectType;
+ }
+
+ public void setIsActive(final Boolean isActive) {
+ this.isActive = isActive;
+ }
+
@Override
public String toString() {
final StringBuilder sb = new StringBuilder();
diff --git a/util/src/main/java/com/ning/billing/util/timezone/DateAndTimeZoneContext.java b/util/src/main/java/com/ning/billing/util/timezone/DateAndTimeZoneContext.java
index b9e60b1..c96a9f1 100644
--- a/util/src/main/java/com/ning/billing/util/timezone/DateAndTimeZoneContext.java
+++ b/util/src/main/java/com/ning/billing/util/timezone/DateAndTimeZoneContext.java
@@ -18,6 +18,7 @@ package com.ning.billing.util.timezone;
import org.joda.time.DateTime;
import org.joda.time.DateTimeZone;
+import org.joda.time.Days;
import org.joda.time.LocalDate;
import org.joda.time.LocalTime;
@@ -31,6 +32,7 @@ import com.ning.billing.clock.Clock;
public final class DateAndTimeZoneContext {
private final LocalTime referenceTime;
+ private final int offsetFromUtc;
private final DateTimeZone accountTimeZone;
private final Clock clock;
@@ -38,6 +40,13 @@ public final class DateAndTimeZoneContext {
this.clock = clock;
this.referenceTime = effectiveDateTime != null ? effectiveDateTime.toLocalTime() : null;
this.accountTimeZone = accountTimeZone;
+ this.offsetFromUtc = computeOffsetFromUtc(effectiveDateTime, accountTimeZone);
+ }
+
+ static int computeOffsetFromUtc(final DateTime effectiveDateTime, final DateTimeZone accountTimeZone) {
+ final LocalDate localDateInAccountTimeZone = new LocalDate(effectiveDateTime, accountTimeZone);
+ final LocalDate localDateInUTC = new LocalDate(effectiveDateTime, DateTimeZone.UTC);
+ return Days.daysBetween(localDateInUTC, localDateInAccountTimeZone).getDays();
}
public LocalDate computeTargetDate(final DateTime targetDateTime) {
@@ -50,9 +59,9 @@ public final class DateAndTimeZoneContext {
// Since we create the targetDate for next invoice using the date from the notificationQ, we need to make sure
// that this datetime once transformed into a LocalDate points to the correct day.
//
- // e.g If accountTimeZone is -8 and we want to invoice on the 16, with a toDateTimeAtCurrentTime = 00:00:23,
- // we will generate a datetime that is 16T08:00:23 => LocalDate in that timeZone stays on the 16.
- //
+ // All we need to do is figure is the transformation from DateTime (point in time) to LocalDate (date in account time zone)
+ // changed the day; if so, when we recompute a UTC date from LocalDate (date in account time zone), we can simply chose a reference
+ // time and apply the offset backward to end up on the right day
//
// We use clock.getUTCNow() to get the offset with account timezone but that may not be correct
// when we transition from standard time and daylight saving time. We could end up with a result
@@ -60,9 +69,7 @@ public final class DateAndTimeZoneContext {
// We will fix that by re-inserting ourselves in the notificationQ if we detect that there is no invoice
// and yet the subscription is recurring and not cancelled.
//
- final int utcOffest = accountTimeZone.getOffset(clock.getUTCNow());
- final int localToUTCOffest = -1 * utcOffest;
- return invoiceItemEndDate.toDateTime(referenceTime, DateTimeZone.UTC).plusMillis(localToUTCOffest);
+ return invoiceItemEndDate.toDateTime(referenceTime, DateTimeZone.UTC).plusDays(-offsetFromUtc);
}
public DateTime computeUTCDateTimeFromNow() {
diff --git a/util/src/main/java/com/ning/billing/util/userrequest/CompletionUserRequestBase.java b/util/src/main/java/com/ning/billing/util/userrequest/CompletionUserRequestBase.java
index 84a5e3a..7d6c11c 100644
--- a/util/src/main/java/com/ning/billing/util/userrequest/CompletionUserRequestBase.java
+++ b/util/src/main/java/com/ning/billing/util/userrequest/CompletionUserRequestBase.java
@@ -29,6 +29,7 @@ import com.ning.billing.events.InvoiceCreationInternalEvent;
import com.ning.billing.events.NullInvoiceInternalEvent;
import com.ning.billing.events.PaymentErrorInternalEvent;
import com.ning.billing.events.PaymentInfoInternalEvent;
+import com.ning.billing.events.PaymentPluginErrorInternalEvent;
public class CompletionUserRequestBase implements CompletionUserRequest {
@@ -123,6 +124,9 @@ public class CompletionUserRequestBase implements CompletionUserRequest {
case PAYMENT_ERROR:
onPaymentError((PaymentErrorInternalEvent) curEvent);
break;
+ case PAYMENT_PLUGIN_ERROR:
+ onPaymentPluginError((PaymentPluginErrorInternalEvent) curEvent);
+ break;
default:
throw new RuntimeException("Unexpected event type " + curEvent.getBusEventType());
}
@@ -157,4 +161,7 @@ public class CompletionUserRequestBase implements CompletionUserRequest {
public void onPaymentError(final PaymentErrorInternalEvent curEvent) {
}
+ @Override
+ public void onPaymentPluginError(final PaymentPluginErrorInternalEvent curEvent) {
+ }
}
diff --git a/util/src/main/java/com/ning/billing/util/userrequest/CompletionUserRequestWaiter.java b/util/src/main/java/com/ning/billing/util/userrequest/CompletionUserRequestWaiter.java
index 99b34dc..898ad3e 100644
--- a/util/src/main/java/com/ning/billing/util/userrequest/CompletionUserRequestWaiter.java
+++ b/util/src/main/java/com/ning/billing/util/userrequest/CompletionUserRequestWaiter.java
@@ -27,6 +27,7 @@ import com.ning.billing.events.InvoiceCreationInternalEvent;
import com.ning.billing.events.NullInvoiceInternalEvent;
import com.ning.billing.events.PaymentErrorInternalEvent;
import com.ning.billing.events.PaymentInfoInternalEvent;
+import com.ning.billing.events.PaymentPluginErrorInternalEvent;
public interface CompletionUserRequestWaiter {
@@ -45,4 +46,6 @@ public interface CompletionUserRequestWaiter {
public void onPaymentInfo(final PaymentInfoInternalEvent curEvent);
public void onPaymentError(final PaymentErrorInternalEvent curEvent);
+
+ public void onPaymentPluginError(final PaymentPluginErrorInternalEvent curEvent);
}
diff --git a/util/src/main/resources/com/ning/billing/util/customfield/dao/CustomFieldSqlDao.sql.stg b/util/src/main/resources/com/ning/billing/util/customfield/dao/CustomFieldSqlDao.sql.stg
index 2f8b857..1a91d2f 100644
--- a/util/src/main/resources/com/ning/billing/util/customfield/dao/CustomFieldSqlDao.sql.stg
+++ b/util/src/main/resources/com/ning/billing/util/customfield/dao/CustomFieldSqlDao.sql.stg
@@ -35,6 +35,7 @@ where
object_id = :objectId
and object_type = :objectType
<AND_CHECK_TENANT()>
+<defaultOrderBy()>
;
>>
diff --git a/util/src/main/resources/com/ning/billing/util/entity/dao/EntitySqlDao.sql.stg b/util/src/main/resources/com/ning/billing/util/entity/dao/EntitySqlDao.sql.stg
index 297a502..64a66fd 100644
--- a/util/src/main/resources/com/ning/billing/util/entity/dao/EntitySqlDao.sql.stg
+++ b/util/src/main/resources/com/ning/billing/util/entity/dao/EntitySqlDao.sql.stg
@@ -31,6 +31,10 @@ andCheckSoftDeletionWithComma(prefix) ::= ""
/** Add extra fields for SELECT queries **/
extraTableFieldsWithComma(prefix) ::= ""
+defaultOrderBy(prefix) ::= <<
+order by <recordIdField(prefix)> ASC
+>>
+
/****************** To override in each EntitySqlDao template file <end> *****************************/
@@ -143,6 +147,7 @@ select
from <tableName()> t
where <CHECK_TENANT("t.")>
<andCheckSoftDeletionWithComma("t.")>
+<defaultOrderBy("t.")>
;
>>
@@ -194,6 +199,7 @@ from <tableName()> t
where <accountRecordIdField("t.")> \<=\> :accountRecordId
<andCheckSoftDeletionWithComma("t.")>
<AND_CHECK_TENANT("t.")>
+<defaultOrderBy("t.")>
;
>>
@@ -334,7 +340,7 @@ where t.target_record_id = :targetRecordId
and t.table_name = :tableName
<andCheckSoftDeletionWithComma("t.")>
<AND_CHECK_TENANT("t.")>
-order by <recordIdField("t.")> ASC
+<defaultOrderBy("t.")>
;
>>
@@ -352,7 +358,7 @@ join (
) history_record_ids on t.target_record_id = history_record_ids.record_id
where t.table_name = :tableName
<AND_CHECK_TENANT("t.")>
-order by <recordIdField("t.")> ASC
+<defaultOrderBy("t.")>
;
>>
diff --git a/util/src/test/java/com/ning/billing/mock/glue/MockEntitlementModule.java b/util/src/test/java/com/ning/billing/mock/glue/MockEntitlementModule.java
index 407e98f..60f9fe8 100644
--- a/util/src/test/java/com/ning/billing/mock/glue/MockEntitlementModule.java
+++ b/util/src/test/java/com/ning/billing/mock/glue/MockEntitlementModule.java
@@ -18,6 +18,7 @@ package com.ning.billing.mock.glue;
import org.mockito.Mockito;
+import com.ning.billing.entitlement.EntitlementInternalApi;
import com.ning.billing.entitlement.api.EntitlementApi;
import com.ning.billing.entitlement.api.SubscriptionApi;
import com.ning.billing.glue.EntitlementModule;
@@ -29,6 +30,7 @@ public class MockEntitlementModule extends AbstractModule implements Entitlement
private final BlockingInternalApi blockingApi = Mockito.mock(BlockingInternalApi.class);
private final EntitlementApi entitlementApi = Mockito.mock(EntitlementApi.class);
+ private final EntitlementInternalApi entitlementInternalApi = Mockito.mock(EntitlementInternalApi.class);
private final SubscriptionApi subscriptionApi = Mockito.mock(SubscriptionApi.class);
@Override
@@ -54,6 +56,11 @@ public class MockEntitlementModule extends AbstractModule implements Entitlement
}
@Override
+ public void installEntitlementInternalApi() {
+ bind(EntitlementInternalApi.class).toInstance(entitlementInternalApi);
+ }
+
+ @Override
public void installSubscriptionApi() {
bind(SubscriptionApi.class).toInstance(subscriptionApi);
}
@@ -61,5 +68,4 @@ public class MockEntitlementModule extends AbstractModule implements Entitlement
@Override
public void installBlockingChecker() {
}
-
}
diff --git a/util/src/test/java/com/ning/billing/util/customfield/api/TestDefaultCustomFieldUserApi.java b/util/src/test/java/com/ning/billing/util/customfield/api/TestDefaultCustomFieldUserApi.java
index 6fe8609..7d11d99 100644
--- a/util/src/test/java/com/ning/billing/util/customfield/api/TestDefaultCustomFieldUserApi.java
+++ b/util/src/test/java/com/ning/billing/util/customfield/api/TestDefaultCustomFieldUserApi.java
@@ -27,6 +27,7 @@ import org.testng.Assert;
import org.testng.annotations.Test;
import com.ning.billing.ObjectType;
+import com.ning.billing.api.TestApiListener.NextEvent;
import com.ning.billing.util.UtilTestSuiteWithEmbeddedDB;
import com.ning.billing.util.customfield.CustomField;
import com.ning.billing.util.customfield.StringCustomField;
@@ -35,7 +36,6 @@ import com.google.common.collect.ImmutableList;
public class TestDefaultCustomFieldUserApi extends UtilTestSuiteWithEmbeddedDB {
-
@Test(groups = "slow")
public void testSaveCustomFieldWithAccountRecordId() throws Exception {
final UUID accountId = UUID.randomUUID();
@@ -53,7 +53,9 @@ public class TestDefaultCustomFieldUserApi extends UtilTestSuiteWithEmbeddedDB {
});
final CustomField customField = new StringCustomField(UUID.randomUUID().toString().substring(1, 4), UUID.randomUUID().toString().substring(1, 4), ObjectType.ACCOUNT, accountId, callContext.getCreatedDate());
+ eventsListener.pushExpectedEvent(NextEvent.CUSTOM_FIELD);
customFieldUserApi.addCustomFields(ImmutableList.<CustomField>of(customField), callContext);
+ assertListenerStatus();
// Verify the field was saved
final List<CustomField> customFields = customFieldUserApi.getCustomFieldsForObject(accountId, ObjectType.ACCOUNT, callContext);
diff --git a/util/src/test/java/com/ning/billing/util/dao/TestStringTemplateInheritance.java b/util/src/test/java/com/ning/billing/util/dao/TestStringTemplateInheritance.java
index 1306cfe..df148e1 100644
--- a/util/src/test/java/com/ning/billing/util/dao/TestStringTemplateInheritance.java
+++ b/util/src/test/java/com/ning/billing/util/dao/TestStringTemplateInheritance.java
@@ -112,6 +112,7 @@ public class TestStringTemplateInheritance extends UtilTestSuiteNoDB {
", t.tenant_record_id\n" +
"from kombucha t\n" +
"where t.tenant_record_id = :tenantRecordId\n" +
+ "order by t.record_id ASC\n" +
";");
Assert.assertEquals(kombucha.getInstanceOf("get", ImmutableMap.<String, String>of("orderBy", "recordId", "offset", "3", "rowCount", "12")).toString(), "select SQL_CALC_FOUND_ROWS\n" +
" t.record_id\n" +
diff --git a/util/src/test/java/com/ning/billing/util/timezone/TestDateAndTimeZoneContext.java b/util/src/test/java/com/ning/billing/util/timezone/TestDateAndTimeZoneContext.java
new file mode 100644
index 0000000..f73a120
--- /dev/null
+++ b/util/src/test/java/com/ning/billing/util/timezone/TestDateAndTimeZoneContext.java
@@ -0,0 +1,213 @@
+/*
+ * Copyright 2010-2013 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF 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.timezone;
+
+import org.joda.time.DateTime;
+import org.joda.time.DateTimeZone;
+import org.joda.time.LocalDate;
+import org.joda.time.format.DateTimeFormatter;
+import org.joda.time.format.ISODateTimeFormat;
+import org.testng.annotations.Test;
+
+import com.ning.billing.util.UtilTestSuiteNoDB;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertTrue;
+
+//
+// There are two categories of tests, one that test the offset calculation and one that calculates
+// how to get a DateTime from a LocalDate (in account time zone)
+//
+// Tests {1, 2, 3} use an account timezone with a negative offset (-8) and tests {A, B, C} use an account timezone with a positive offset (+8)
+//
+public class TestDateAndTimeZoneContext extends UtilTestSuiteNoDB {
+
+ private final DateTimeFormatter DATE_TIME_FORMATTER = ISODateTimeFormat.dateTimeParser();
+
+ final String effectiveDateTime1 = "2012-01-20T07:30:42.000Z";
+ final String effectiveDateTime2 = "2012-01-20T08:00:00.000Z";
+ final String effectiveDateTime3 = "2012-01-20T08:45:33.000Z";
+
+ final String effectiveDateTimeA = "2012-01-20T16:30:42.000Z";
+ final String effectiveDateTimeB = "2012-01-20T16:00:00.000Z";
+ final String effectiveDateTimeC = "2012-01-20T15:30:42.000Z";
+
+
+ //
+ // Take an negative timezone offset and a reference time that is less than the offset (07:30:42 < 8)
+ // => to expect a negative offset of one day
+ //
+ @Test(groups = "fast")
+ public void testComputeOffset1() {
+
+ final DateTimeZone timeZone = DateTimeZone.forOffsetHours(-8);
+ final DateTime effectiveDateTime = DATE_TIME_FORMATTER.parseDateTime(effectiveDateTime1);
+
+ int offset = DateAndTimeZoneContext.computeOffsetFromUtc(effectiveDateTime, timeZone);
+ assertEquals(offset, -1);
+ }
+
+ //
+ // Take an negative timezone offset and a reference time that is equal than the offset (08:00:00 = 8)
+ // => to expect an offset of 0
+ //
+ @Test(groups = "fast")
+ public void testComputeOffset2() {
+
+ final DateTimeZone timeZone = DateTimeZone.forOffsetHours(-8);
+ final DateTime effectiveDateTime = DATE_TIME_FORMATTER.parseDateTime(effectiveDateTime2);
+
+ int offset = DateAndTimeZoneContext.computeOffsetFromUtc(effectiveDateTime, timeZone);
+ assertEquals(offset, 0);
+ }
+
+ //
+ // Take an negative timezone offset and a reference time that is greater than the offset (08:45:33 > 8)
+ // => to expect an offset of 0
+ //
+ @Test(groups = "fast")
+ public void testComputeOffset3() {
+
+ final DateTimeZone timeZone = DateTimeZone.forOffsetHours(-8);
+ final DateTime effectiveDateTime = DATE_TIME_FORMATTER.parseDateTime(effectiveDateTime3);
+
+ int offset = DateAndTimeZoneContext.computeOffsetFromUtc(effectiveDateTime, timeZone);
+ assertEquals(offset, 0);
+ }
+
+ //
+ // Take an positive timezone offset and a reference time that closer to the end of the day than the timezone (16:30:42 + 8 > 24)
+ // => to expect a positive offset of one day
+ //
+ @Test(groups = "fast")
+ public void testComputeOffsetA() {
+
+ final DateTimeZone timeZone = DateTimeZone.forOffsetHours(8);
+ final DateTime effectiveDateTime = DATE_TIME_FORMATTER.parseDateTime(effectiveDateTimeA);
+
+ int offset = DateAndTimeZoneContext.computeOffsetFromUtc(effectiveDateTime, timeZone);
+ assertEquals(offset, 1);
+ }
+
+ //
+ // Take an positive timezone offset and a reference time that brings us exactly at the end of the day (16:00:00 + 8 = 24)
+ // => to expect an offset of 1
+ //
+ @Test(groups = "fast")
+ public void testComputeOffsetB() {
+
+ final DateTimeZone timeZone = DateTimeZone.forOffsetHours(8);
+ final DateTime effectiveDateTime = DATE_TIME_FORMATTER.parseDateTime(effectiveDateTimeB);
+
+ int offset = DateAndTimeZoneContext.computeOffsetFromUtc(effectiveDateTime, timeZone);
+ assertEquals(offset, 1);
+ }
+
+ //
+ // Take an positive timezone offset and a reference time that further away to the end of the day (15:30:42 + 8 < 24)
+ // => to expect an offset of 0
+ //
+ @Test(groups = "fast")
+ public void testComputeOffsetC() {
+
+ final DateTimeZone timeZone = DateTimeZone.forOffsetHours(8);
+ final DateTime effectiveDateTime = DATE_TIME_FORMATTER.parseDateTime(effectiveDateTimeC);
+
+ int offset = DateAndTimeZoneContext.computeOffsetFromUtc(effectiveDateTime, timeZone);
+ assertEquals(offset, 0);
+ }
+
+ @Test(groups = "fast")
+ public void testComputeUTCDateTimeFromLocalDate1() {
+
+ final DateTime effectiveDateTime = DATE_TIME_FORMATTER.parseDateTime(effectiveDateTime1);
+
+ final DateTimeZone timeZone = DateTimeZone.forOffsetHours(-8);
+ final DateAndTimeZoneContext dateContext = new DateAndTimeZoneContext(effectiveDateTime, timeZone, clock);
+
+ final LocalDate endDate = new LocalDate(2013, 01, 19);
+ final DateTime endDateTimeInUTC = dateContext.computeUTCDateTimeFromLocalDate(endDate);
+ assertTrue(endDateTimeInUTC.compareTo(effectiveDateTime.plusYears(1)) == 0);
+ }
+
+
+ @Test(groups = "fast")
+ public void testComputeUTCDateTimeFromLocalDate2() {
+
+ final DateTime effectiveDateTime = DATE_TIME_FORMATTER.parseDateTime(effectiveDateTime2);
+
+ final DateTimeZone timeZone = DateTimeZone.forOffsetHours(-8);
+ final DateAndTimeZoneContext dateContext = new DateAndTimeZoneContext(effectiveDateTime, timeZone, clock);
+
+ final LocalDate endDate = new LocalDate(2013, 01, 20);
+ final DateTime endDateTimeInUTC = dateContext.computeUTCDateTimeFromLocalDate(endDate);
+ assertTrue(endDateTimeInUTC.compareTo(effectiveDateTime.plusYears(1)) == 0);
+ }
+
+
+ @Test(groups = "fast")
+ public void testComputeUTCDateTimeFromLocalDate3() {
+
+ final DateTime effectiveDateTime = DATE_TIME_FORMATTER.parseDateTime(effectiveDateTime3);
+
+ final DateTimeZone timeZone = DateTimeZone.forOffsetHours(-8);
+ final DateAndTimeZoneContext dateContext = new DateAndTimeZoneContext(effectiveDateTime, timeZone, clock);
+
+ final LocalDate endDate = new LocalDate(2013, 01, 20);
+ final DateTime endDateTimeInUTC = dateContext.computeUTCDateTimeFromLocalDate(endDate);
+ assertTrue(endDateTimeInUTC.compareTo(effectiveDateTime.plusYears(1)) == 0);
+ }
+
+ @Test(groups = "fast")
+ public void testComputeUTCDateTimeFromLocalDateA() {
+
+ final DateTime effectiveDateTime = DATE_TIME_FORMATTER.parseDateTime(effectiveDateTimeA);
+
+ final DateTimeZone timeZone = DateTimeZone.forOffsetHours(8);
+ final DateAndTimeZoneContext dateContext = new DateAndTimeZoneContext(effectiveDateTime, timeZone, clock);
+
+ final LocalDate endDate = new LocalDate(2013, 01, 21);
+ final DateTime endDateTimeInUTC = dateContext.computeUTCDateTimeFromLocalDate(endDate);
+ assertTrue(endDateTimeInUTC.compareTo(effectiveDateTime.plusYears(1)) == 0);
+ }
+
+ @Test(groups = "fast")
+ public void testComputeUTCDateTimeFromLocalDateB() {
+
+ final DateTime effectiveDateTime = DATE_TIME_FORMATTER.parseDateTime(effectiveDateTimeB);
+
+ final DateTimeZone timeZone = DateTimeZone.forOffsetHours(8);
+ final DateAndTimeZoneContext dateContext = new DateAndTimeZoneContext(effectiveDateTime, timeZone, clock);
+
+ final LocalDate endDate = new LocalDate(2013, 01, 21);
+ final DateTime endDateTimeInUTC = dateContext.computeUTCDateTimeFromLocalDate(endDate);
+ assertTrue(endDateTimeInUTC.compareTo(effectiveDateTime.plusYears(1)) == 0);
+ }
+
+ @Test(groups = "fast")
+ public void testComputeUTCDateTimeFromLocalDateC() {
+
+ final DateTime effectiveDateTime = DATE_TIME_FORMATTER.parseDateTime(effectiveDateTimeC);
+
+ final DateTimeZone timeZone = DateTimeZone.forOffsetHours(8);
+ final DateAndTimeZoneContext dateContext = new DateAndTimeZoneContext(effectiveDateTime, timeZone, clock);
+
+ final LocalDate endDate = new LocalDate(2013, 01, 20);
+ final DateTime endDateTimeInUTC = dateContext.computeUTCDateTimeFromLocalDate(endDate);
+ assertTrue(endDateTimeInUTC.compareTo(effectiveDateTime.plusYears(1)) == 0);
+ }
+}