killbill-memoizeit
Changes
pom.xml 2(+1 -1)
Details
diff --git a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/SubscriptionResource.java b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/SubscriptionResource.java
index 2d9aff1..8cefa75 100644
--- a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/SubscriptionResource.java
+++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/SubscriptionResource.java
@@ -104,6 +104,7 @@ import org.slf4j.LoggerFactory;
import com.google.common.base.Preconditions;
import com.google.common.base.Predicate;
+import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
import com.google.inject.Inject;
import io.swagger.annotations.Api;
@@ -268,69 +269,8 @@ public class SubscriptionResource extends JaxRsResourceBase {
@HeaderParam(HDR_COMMENT) final String comment,
@javax.ws.rs.core.Context final HttpServletRequest request,
@javax.ws.rs.core.Context final UriInfo uriInfo) throws EntitlementApiException, AccountApiException, SubscriptionApiException {
-
- Preconditions.checkArgument(Iterables.size(entitlements) > 0, "Subscription list mustn't be null or empty.");
-
- logDeprecationParameterWarningIfNeeded(QUERY_REQUESTED_DT, QUERY_ENTITLEMENT_REQUESTED_DT, QUERY_BILLING_REQUESTED_DT);
-
- final int baseSubscriptionsSize = Iterables.size(Iterables.filter(entitlements, new Predicate<SubscriptionJson>() {
- @Override
- public boolean apply(final SubscriptionJson subscription) {
- return ProductCategory.BASE.toString().equals(subscription.getProductCategory());
- }
- }));
- verifyNumberOfElements(baseSubscriptionsSize, 1, "Only one BASE product is allowed.");
-
- final int addOnSubscriptionsSize = Iterables.size(Iterables.filter(entitlements, new Predicate<SubscriptionJson>() {
- @Override
- public boolean apply(final SubscriptionJson subscription) {
- return ProductCategory.ADD_ON.toString().equals(subscription.getProductCategory());
- }
- }));
- verifyNumberOfElements(addOnSubscriptionsSize, entitlements.size() - 1, "It should be " + (entitlements.size() - 1) + " ADD_ON products.");
-
- final Iterable<PluginProperty> pluginProperties = extractPluginProperties(pluginPropertiesString);
- final CallContext callContext = context.createContext(createdBy, reason, comment, request);
-
- final SubscriptionJson baseEntitlement = Iterables.tryFind(entitlements, new Predicate<SubscriptionJson>() {
- @Override
- public boolean apply(final SubscriptionJson subscription) {
- return ProductCategory.BASE.toString().equalsIgnoreCase(subscription.getProductCategory());
- }
- }).orNull();
-
- verifyNonNull(baseEntitlement.getAccountId(), "SubscriptionJson accountId needs to be set for BASE product.");
-
- final EntitlementCallCompletionCallback<List<Entitlement>> callback = new EntitlementCallCompletionCallback<List<Entitlement>>() {
- @Override
- public List<Entitlement> doOperation(final CallContext ctx) throws InterruptedException, TimeoutException, EntitlementApiException, SubscriptionApiException, AccountApiException {
-
- final Account account = getAccountFromSubscriptionJson(baseEntitlement, callContext);
-
- final List<EntitlementSpecifier> entitlementSpecifierList = buildEntitlementSpecifierList(entitlements, account.getCurrency());
-
- final LocalDate resolvedEntitlementDate = requestedDate != null ? toLocalDate(requestedDate) : toLocalDate(entitlementDate);
- final LocalDate resolvedBillingDate = requestedDate != null ? toLocalDate(requestedDate) : toLocalDate(billingDate);
-
- final UUID bundleId = baseEntitlement.getBundleId() != null ? UUID.fromString(baseEntitlement.getBundleId()) : null;
-
- BaseEntitlementWithAddOnsSpecifier baseEntitlementSpecifierWithAddOns = buildBaseEntitlementWithAddOnsSpecifier(entitlementSpecifierList, resolvedEntitlementDate, resolvedBillingDate, bundleId, baseEntitlement, isMigrated);
- final List<BaseEntitlementWithAddOnsSpecifier> baseEntitlementWithAddOnsSpecifierList = new ArrayList<BaseEntitlementWithAddOnsSpecifier>();
- baseEntitlementWithAddOnsSpecifierList.add(baseEntitlementSpecifierWithAddOns);
-
- return entitlementApi.createBaseEntitlementsWithAddOns(account.getId(), baseEntitlementWithAddOnsSpecifierList, pluginProperties, callContext);
- }
- @Override
- public boolean isImmOperation() {
- return true;
- }
- @Override
- public Response doResponseOk(final List<Entitlement> entitlements) {
- return uriBuilder.buildResponse(uriInfo, BundleResource.class, "getBundle", entitlements.get(0).getBundleId(), request);
- }
- };
- final EntitlementCallCompletion<List<Entitlement>> callCompletionCreation = new EntitlementCallCompletion<List<Entitlement>>();
- return callCompletionCreation.withSynchronization(callback, timeoutSec, callCompletion, callContext);
+ final List<BulkBaseSubscriptionAndAddOnsJson> entitlementsWithAddOns = ImmutableList.of(new BulkBaseSubscriptionAndAddOnsJson(entitlements));
+ return createEntitlementsWithAddOnsInternal(entitlementsWithAddOns, requestedDate, entitlementDate, billingDate, isMigrated, callCompletion, timeoutSec, pluginPropertiesString, createdBy, reason, comment, request, uriInfo, ObjectType.BUNDLE);
}
@TimedResource
@@ -353,6 +293,22 @@ public class SubscriptionResource extends JaxRsResourceBase {
@HeaderParam(HDR_COMMENT) final String comment,
@javax.ws.rs.core.Context final HttpServletRequest request,
@javax.ws.rs.core.Context final UriInfo uriInfo) throws EntitlementApiException, AccountApiException, SubscriptionApiException {
+ return createEntitlementsWithAddOnsInternal(entitlementsWithAddOns, requestedDate, entitlementDate, billingDate, isMigrated, callCompletion, timeoutSec, pluginPropertiesString, createdBy, reason, comment, request, uriInfo, ObjectType.ACCOUNT);
+ }
+
+
+ public Response createEntitlementsWithAddOnsInternal(final List<BulkBaseSubscriptionAndAddOnsJson> entitlementsWithAddOns,
+ final String requestedDate,
+ final String entitlementDate,
+ final String billingDate,
+ final Boolean isMigrated, final Boolean callCompletion,
+ final long timeoutSec,
+ final List<String> pluginPropertiesString,
+ final String createdBy,
+ final String reason,
+ final String comment,
+ final HttpServletRequest request,
+ final UriInfo uriInfo, final ObjectType responseObject) throws EntitlementApiException, AccountApiException, SubscriptionApiException {
Preconditions.checkArgument(Iterables.size(entitlementsWithAddOns) > 0, "Subscription bulk list mustn't be null or empty.");
@@ -367,35 +323,27 @@ public class SubscriptionResource extends JaxRsResourceBase {
for (BulkBaseSubscriptionAndAddOnsJson bulkBaseEntitlementWithAddOns : entitlementsWithAddOns) {
final Iterable<SubscriptionJson> baseEntitlements = Iterables.filter(
bulkBaseEntitlementWithAddOns.getBaseEntitlementAndAddOns(), new Predicate<SubscriptionJson>() {
- @Override
- public boolean apply(final SubscriptionJson subscription) {
- return ProductCategory.BASE.toString().equalsIgnoreCase(subscription.getProductCategory());
- }
- });
+ @Override
+ public boolean apply(final SubscriptionJson subscription) {
+ return ProductCategory.BASE.toString().equalsIgnoreCase(subscription.getProductCategory());
+ }
+ });
Preconditions.checkArgument(Iterables.size(baseEntitlements) > 0, "SubscriptionJson Base Entitlement needs to be provided");
verifyNumberOfElements(Iterables.size(baseEntitlements), 1, "Only one BASE product is allowed per bundle.");
+ final SubscriptionJson baseEntitlement = baseEntitlements.iterator().next();
+
- final Iterable<SubscriptionJson> entitlementsWithBundleSpecified = Iterables.filter(
- bulkBaseEntitlementWithAddOns.getBaseEntitlementAndAddOns(), new Predicate<SubscriptionJson>() {
- @Override
- public boolean apply(final SubscriptionJson subscription) {
- return subscription.getBundleId() != null;
+ final Iterable<SubscriptionJson> addonEntitlements = Iterables.filter(
+ bulkBaseEntitlementWithAddOns.getBaseEntitlementAndAddOns(), new Predicate<SubscriptionJson>() {
+ @Override
+ public boolean apply(final SubscriptionJson subscription) {
+ return ProductCategory.ADD_ON.toString().equalsIgnoreCase(subscription.getProductCategory());
+ }
}
- }
- );
- Preconditions.checkArgument(Iterables.size(entitlementsWithBundleSpecified) == 0, "BundleId must not be specified when creating new bulks");
+ );
- SubscriptionJson baseEntitlement = baseEntitlements.iterator().next();
- final int addOnSubscriptionsSize = Iterables.size(Iterables.filter(bulkBaseEntitlementWithAddOns.getBaseEntitlementAndAddOns(), new Predicate<SubscriptionJson>() {
- @Override
- public boolean apply(final SubscriptionJson subscription) {
- return ProductCategory.ADD_ON.toString().equals(subscription.getProductCategory());
- }
- }));
- verifyNumberOfElements(addOnSubscriptionsSize, bulkBaseEntitlementWithAddOns.getBaseEntitlementAndAddOns().size() - 1, "It should be " + (bulkBaseEntitlementWithAddOns.getBaseEntitlementAndAddOns().size() - 1) + " ADD_ON products.");
-
- final List<EntitlementSpecifier> entitlementSpecifierList = buildEntitlementSpecifierList(bulkBaseEntitlementWithAddOns.getBaseEntitlementAndAddOns(), account.getCurrency());
+ final List<EntitlementSpecifier> entitlementSpecifierList = buildEntitlementSpecifierList(baseEntitlement, addonEntitlements, account.getCurrency());
// create the baseEntitlementSpecifierWithAddOns
final LocalDate resolvedEntitlementDate = requestedDate != null ? toLocalDate(requestedDate) : toLocalDate(entitlementDate);
@@ -416,16 +364,52 @@ public class SubscriptionResource extends JaxRsResourceBase {
}
@Override
public Response doResponseOk(final List<Entitlement> entitlements) {
- return uriBuilder.buildResponse(uriInfo, AccountResource.class, "getAccountBundles", entitlements.get(0).getAccountId(), buildQueryParams(buildBundleIdList(entitlements)), request);
+ if (responseObject == ObjectType.ACCOUNT) {
+ return uriBuilder.buildResponse(uriInfo, AccountResource.class, "getAccountBundles", entitlements.get(0).getAccountId(), buildQueryParams(buildBundleIdList(entitlements)), request);
+ } else if (responseObject == ObjectType.BUNDLE) {
+ return uriBuilder.buildResponse(uriInfo, BundleResource.class, "getBundle", entitlements.get(0).getBundleId(), request);
+ } else {
+ throw new IllegalStateException("Unexpected input responseObject " + responseObject);
+ }
}
};
final EntitlementCallCompletion<List<Entitlement>> callCompletionCreation = new EntitlementCallCompletion<List<Entitlement>>();
return callCompletionCreation.withSynchronization(callback, timeoutSec, callCompletion, callContext);
}
- private List<EntitlementSpecifier> buildEntitlementSpecifierList(final List<SubscriptionJson> entitlements, final Currency currency) {
+
+
+ private List<EntitlementSpecifier> buildEntitlementSpecifierList(final SubscriptionJson baseEntitlement, final Iterable<SubscriptionJson> addonEntitlements, final Currency currency) {
final List<EntitlementSpecifier> entitlementSpecifierList = new ArrayList<EntitlementSpecifier>();
- for (final SubscriptionJson entitlement : entitlements) {
+
+ //
+ // BASE is fully specified we can add it
+ //
+ if (baseEntitlement.getPlanName() != null ||
+ (baseEntitlement.getProductName() != null &&
+ baseEntitlement.getProductCategory() != null &&
+ baseEntitlement.getBillingPeriod() != null &&
+ baseEntitlement.getPriceList() != null)) {
+ final PlanPhaseSpecifier planPhaseSpecifier = baseEntitlement.getPlanName() != null ?
+ new PlanPhaseSpecifier(baseEntitlement.getPlanName(), null) :
+ new PlanPhaseSpecifier(baseEntitlement.getProductName(),
+ BillingPeriod.valueOf(baseEntitlement.getBillingPeriod()), baseEntitlement.getPriceList(), null);
+ final List<PlanPhasePriceOverride> overrides = PhasePriceOverrideJson.toPlanPhasePriceOverrides(baseEntitlement.getPriceOverrides(), planPhaseSpecifier, currency);
+
+ EntitlementSpecifier specifier = new EntitlementSpecifier() {
+ @Override
+ public PlanPhaseSpecifier getPlanPhaseSpecifier() {
+ return planPhaseSpecifier;
+ }
+ @Override
+ public List<PlanPhasePriceOverride> getOverrides() {
+ return overrides;
+ }
+ };
+ entitlementSpecifierList.add(specifier);
+ }
+
+ for (final SubscriptionJson entitlement : addonEntitlements) {
// verifications
verifyNonNullOrEmpty(entitlement, "SubscriptionJson body should be specified for each element");
if (entitlement.getPlanName() == null) {
pom.xml 2(+1 -1)
diff --git a/pom.xml b/pom.xml
index 2a08c7b..bd8df0c 100644
--- a/pom.xml
+++ b/pom.xml
@@ -21,7 +21,7 @@
<parent>
<artifactId>killbill-oss-parent</artifactId>
<groupId>org.kill-bill.billing</groupId>
- <version>0.140.18</version>
+ <version>0.140.19</version>
</parent>
<artifactId>killbill</artifactId>
<version>0.18.6-SNAPSHOT</version>
diff --git a/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestEntitlement.java b/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestEntitlement.java
index 22b57a2..9b64984 100644
--- a/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestEntitlement.java
+++ b/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestEntitlement.java
@@ -48,6 +48,9 @@ import org.killbill.billing.util.api.AuditLevel;
import org.testng.Assert;
import org.testng.annotations.Test;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterables;
+
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertFalse;
import static org.testng.Assert.assertNotNull;
@@ -363,6 +366,8 @@ public class TestEntitlement extends TestJaxrsBase {
assertEquals(invoices.size(), 1);
}
+
+
@Test(groups = "slow", description = "Create a bulk of base entitlement and addOns under the same transaction")
public void testCreateEntitlementsWithAddOns() throws Exception {
final DateTime initialDate = new DateTime(2012, 4, 25, 0, 3, 42, 0);
@@ -411,13 +416,18 @@ public class TestEntitlement extends TestJaxrsBase {
}
@Test(groups = "slow", description = "Create a bulk of base entitlements and addOns under the same transaction",
- expectedExceptions = KillBillClientException.class, expectedExceptionsMessageRegExp = "SubscriptionJson Base Entitlement needs to be provided")
+ expectedExceptions = KillBillClientException.class, expectedExceptionsMessageRegExp = "Missing Base Subscription for bundle 12345")
public void testCreateEntitlementsWithoutBase() throws Exception {
final DateTime initialDate = new DateTime(2012, 4, 25, 0, 3, 42, 0);
clock.setDeltaFromReality(initialDate.getMillis() - clock.getUTCNow().getMillis());
final Account accountJson = createAccountWithDefaultPaymentMethod();
+ final Subscription bp = new Subscription();
+ bp.setAccountId(accountJson.getAccountId());
+ bp.setProductCategory(ProductCategory.BASE);
+ bp.setExternalKey("12345");
+
final Subscription addOn1 = new Subscription();
addOn1.setAccountId(accountJson.getAccountId());
addOn1.setProductName("Telescopic-Scope");
@@ -426,6 +436,7 @@ public class TestEntitlement extends TestJaxrsBase {
addOn1.setPriceList(PriceListSet.DEFAULT_PRICELIST_NAME);
final List<Subscription> subscriptions = new ArrayList<Subscription>();
+ subscriptions.add(bp);
subscriptions.add(addOn1);
final List<BulkBaseSubscriptionAndAddOns> bulkList = new ArrayList<BulkBaseSubscriptionAndAddOns>();
@@ -435,6 +446,58 @@ public class TestEntitlement extends TestJaxrsBase {
killBillClient.createSubscriptionsWithAddOns(bulkList, null, 10, requestOptions);
}
+ @Test(groups = "slow", description = "Create addOns in a bundle where BP subscrsiptions already exist")
+ public void testEntitlementsWithAddOnsAndAlreadyExistingBP() throws Exception {
+ final DateTime initialDate = new DateTime(2012, 4, 25, 0, 3, 42, 0);
+ clock.setDeltaFromReality(initialDate.getMillis() - clock.getUTCNow().getMillis());
+
+ final Account accountJson = createAccountWithDefaultPaymentMethod();
+
+ final Subscription input = new Subscription();
+ input.setAccountId(accountJson.getAccountId());
+ input.setExternalKey("foobarxyz");
+ input.setProductName("Shotgun");
+ input.setProductCategory(ProductCategory.BASE);
+ input.setBillingPeriod(BillingPeriod.MONTHLY);
+ input.setPriceList(PriceListSet.DEFAULT_PRICELIST_NAME);
+ final Subscription subscription = killBillClient.createSubscription(input, null, DEFAULT_WAIT_COMPLETION_TIMEOUT_SEC, requestOptions);
+
+ final Subscription base = new Subscription();
+ base.setAccountId(accountJson.getAccountId());
+ base.setProductCategory(ProductCategory.BASE);
+ base.setExternalKey("foobarxyz");
+
+ final Subscription addOn1 = new Subscription();
+ addOn1.setAccountId(accountJson.getAccountId());
+ addOn1.setProductName("Telescopic-Scope");
+ addOn1.setProductCategory(ProductCategory.ADD_ON);
+ addOn1.setBillingPeriod(BillingPeriod.MONTHLY);
+ addOn1.setPriceList(PriceListSet.DEFAULT_PRICELIST_NAME);
+
+ final Subscription addOn2 = new Subscription();
+ addOn2.setAccountId(accountJson.getAccountId());
+ addOn2.setProductName("Laser-Scope");
+ addOn2.setProductCategory(ProductCategory.ADD_ON);
+ addOn2.setBillingPeriod(BillingPeriod.MONTHLY);
+ addOn2.setPriceList(PriceListSet.DEFAULT_PRICELIST_NAME);
+
+ final List<Subscription> subscriptions = new ArrayList<Subscription>();
+ subscriptions.add(base);
+ subscriptions.add(addOn1);
+ subscriptions.add(addOn2);
+
+ final Iterable<BulkBaseSubscriptionAndAddOns> bulkBaseSubscriptionAndAddOns = ImmutableList.of(new BulkBaseSubscriptionAndAddOns(subscriptions));
+
+ final Bundles bundles = killBillClient.createSubscriptionsWithAddOns(bulkBaseSubscriptionAndAddOns, null, 10, requestOptions);
+ assertNotNull(bundles);
+ assertEquals(bundles.size(), 1);
+ assertEquals(bundles.get(0).getSubscriptions().size(), 3);
+
+ final List<Invoice> invoices = killBillClient.getInvoicesForAccount(accountJson.getAccountId(), true, false, false, AuditLevel.FULL, requestOptions);
+ assertEquals(invoices.size(), 2);
+ }
+
+
@Test(groups = "slow", description = "Can create an entitlement in the future")
public void testCreateEntitlementInTheFuture() throws Exception {
final DateTime initialDate = new DateTime(2012, 4, 25, 0, 3, 42, 0);
diff --git a/subscription/src/main/java/org/killbill/billing/subscription/api/svcs/DefaultSubscriptionInternalApi.java b/subscription/src/main/java/org/killbill/billing/subscription/api/svcs/DefaultSubscriptionInternalApi.java
index 48f6105..5b3b8d5 100644
--- a/subscription/src/main/java/org/killbill/billing/subscription/api/svcs/DefaultSubscriptionInternalApi.java
+++ b/subscription/src/main/java/org/killbill/billing/subscription/api/svcs/DefaultSubscriptionInternalApi.java
@@ -55,7 +55,6 @@ import org.killbill.billing.entitlement.api.Entitlement.EntitlementState;
import org.killbill.billing.entitlement.api.EntitlementAOStatusDryRun;
import org.killbill.billing.entitlement.api.EntitlementAOStatusDryRun.DryRunChangeReason;
import org.killbill.billing.entitlement.api.EntitlementSpecifier;
-import org.killbill.billing.entitlement.api.Subscription;
import org.killbill.billing.events.EffectiveSubscriptionInternalEvent;
import org.killbill.billing.invoice.api.DryRunArguments;
import org.killbill.billing.subscription.api.SubscriptionApiBase;
@@ -66,9 +65,7 @@ import org.killbill.billing.subscription.api.SubscriptionBaseTransitionType;
import org.killbill.billing.subscription.api.SubscriptionBaseWithAddOns;
import org.killbill.billing.subscription.api.user.DefaultEffectiveSubscriptionEvent;
import org.killbill.billing.subscription.api.user.DefaultSubscriptionBase;
-import org.killbill.billing.subscription.api.user.DefaultSubscriptionBaseApiService;
import org.killbill.billing.subscription.api.user.DefaultSubscriptionBaseBundle;
-import org.killbill.billing.subscription.api.user.DefaultSubscriptionBaseWithAddOns;
import org.killbill.billing.subscription.api.user.DefaultSubscriptionStatusDryRun;
import org.killbill.billing.subscription.api.user.SubscriptionAndAddOnsSpecifier;
import org.killbill.billing.subscription.api.user.SubscriptionBaseApiException;
@@ -260,7 +257,6 @@ public class DefaultSubscriptionInternalApi extends SubscriptionApiBase implemen
return subscriptions;
}
-
private boolean isBaseSpecified(final Catalog catalog, final BaseEntitlementWithAddOnsSpecifier entitlementWithAddOnsSpecifier, final DateTime effectiveDate) throws SubscriptionBaseApiException {
// We expect input to be correctly ordered where BP is first
@@ -271,8 +267,27 @@ public class DefaultSubscriptionInternalApi extends SubscriptionApiBase implemen
final EntitlementSpecifier firstPlanSpecifier = iterator.next();
try {
+ // Look for BASE plan in the first entry
final Plan plan = catalog.createOrFindPlan(firstPlanSpecifier.getPlanPhaseSpecifier(), null, effectiveDate);
- return plan.getProduct().getCategory() == ProductCategory.BASE;
+ final boolean isBaseSpecified = plan.getProduct().getCategory() == ProductCategory.BASE;
+
+ // If there is no BASE as a first entry, validate it is not in the subsequent entries
+ if (!isBaseSpecified) {
+ if (Iterables.any(entitlementWithAddOnsSpecifier.getEntitlementSpecifier(), new Predicate<EntitlementSpecifier>() {
+ @Override
+ public boolean apply(final EntitlementSpecifier input) {
+ try {
+ final Plan inputPlan = catalog.createOrFindPlan(input.getPlanPhaseSpecifier(), null, effectiveDate);
+ return inputPlan.getProduct().getCategory() == ProductCategory.BASE;
+ } catch (CatalogApiException e) {
+ throw new IllegalArgumentException(String.format("createBaseSubscriptionsWithAddOns plan='%s' does not exist", input.getPlanPhaseSpecifier().getPlanName()));
+ }
+ }
+ })) {
+ throw new IllegalArgumentException("createBaseSubscriptionsWithAddOns BASE plan should be first in the list of specifier");
+ }
+ }
+ return isBaseSpecified;
} catch (final CatalogApiException e) {
throw new SubscriptionBaseApiException(e);
}