killbill-uncached
Changes
beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestCatalogRetireElements.java 3(+2 -1)
beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestInvoiceNotifications.java 7(+4 -3)
beatrix/src/test/resources/catalogs/default/catalogTest.xml 1465(+1465 -0)
pom.xml 2(+1 -1)
subscription/src/main/java/org/killbill/billing/subscription/api/user/DefaultSubscriptionBase.java 15(+13 -2)
Details
diff --git a/beatrix/src/test/java/org/killbill/billing/beatrix/BeatrixTestSuiteWithEmbeddedDB.java b/beatrix/src/test/java/org/killbill/billing/beatrix/BeatrixTestSuiteWithEmbeddedDB.java
index 29350d9..82bf4fe 100644
--- a/beatrix/src/test/java/org/killbill/billing/beatrix/BeatrixTestSuiteWithEmbeddedDB.java
+++ b/beatrix/src/test/java/org/killbill/billing/beatrix/BeatrixTestSuiteWithEmbeddedDB.java
@@ -21,10 +21,24 @@ package org.killbill.billing.beatrix;
import org.killbill.billing.GuicyKillbillTestSuiteWithEmbeddedDB;
import org.killbill.billing.platform.api.KillbillConfigSource;
+import com.google.common.collect.ImmutableMap;
+
public abstract class BeatrixTestSuiteWithEmbeddedDB extends GuicyKillbillTestSuiteWithEmbeddedDB {
+ final static protected ImmutableMap DEFAULT_BEATRIX_PROPERTIES = ImmutableMap.builder()
+ .put("org.killbill.catalog.uri", "catalogs/default/catalogTest.xml")
+ .put("org.killbill.invoice.maxDailyNumberOfItemsSafetyBound", "30")
+ .put("org.killbill.payment.retry.days", "8,8,8,8,8,8,8,8")
+ .put("org.killbill.osgi.bundle.install.dir", "/var/tmp/beatrix-bundles")
+ // The default value is 50, i.e. wait 50 x 100ms = 5s to get the lock. This isn't always enough and can lead to random tests failures
+ // in the listener status: after moving the clock, if there are two notifications triggering an invoice run, we typically expect
+ // both an INVOICE and a NULL_INVOICE event. If the invoice generation takes too long, the NULL_INVOICE event is never generated
+ // (LockFailedException): the test itself doesn't fail (the correct invoice is generated), but assertListenerStatus() would.
+ .put("org.killbill.invoice.globalLock.retries", 150)
+ .build();
+
@Override
protected KillbillConfigSource getConfigSource() {
- return getConfigSource("/beatrix.properties");
+ return getConfigSource(null, DEFAULT_BEATRIX_PROPERTIES);
}
}
diff --git a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestCatalogRetireElements.java b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestCatalogRetireElements.java
index 965a5fb..149b1c1 100644
--- a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestCatalogRetireElements.java
+++ b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestCatalogRetireElements.java
@@ -40,6 +40,7 @@ import org.killbill.billing.platform.api.KillbillConfigSource;
import org.testng.annotations.Test;
import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertNotNull;
@@ -50,7 +51,7 @@ public class TestCatalogRetireElements extends TestIntegrationBase {
@Override
protected KillbillConfigSource getConfigSource() {
- return super.getConfigSource("/beatrixCatalogRetireElements.properties");
+ return super.getConfigSource(null, ImmutableMap.of("org.killbill.catalog.uri", "catalogs/testCatalogRetireElements"));
}
@Test(groups = "slow")
diff --git a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegrationBase.java b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegrationBase.java
index 822cffe..44c383e 100644
--- a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegrationBase.java
+++ b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegrationBase.java
@@ -311,13 +311,9 @@ public class TestIntegrationBase extends BeatrixTestSuiteWithEmbeddedDB implemen
return;
}
+
final InvoiceConfig defaultInvoiceConfig = new ConfigurationObjectFactory(skifeConfigSource).build(InvoiceConfig.class);
invoiceConfig = new ConfigurableInvoiceConfig(defaultInvoiceConfig);
- // The default value is 50, i.e. wait 50 x 100ms = 5s to get the lock. This isn't always enough and can lead to random tests failures
- // in the listener status: after moving the clock, if there are two notifications triggering an invoice run, we typically expect
- // both an INVOICE and a NULL_INVOICE event. If the invoice generation takes too long, the NULL_INVOICE event is never generated
- // (LockFailedException): the test itself doesn't fail (the correct invoice is generated), but assertListenerStatus() would.
- invoiceConfig.setMaxGlobalLockRetries(150);
final Injector g = Guice.createInjector(Stage.PRODUCTION, new BeatrixIntegrationModule(configSource, invoiceConfig));
g.injectMembers(this);
}
@@ -1009,13 +1005,10 @@ public class TestIntegrationBase extends BeatrixTestSuiteWithEmbeddedDB implemen
static class ConfigurableInvoiceConfig implements InvoiceConfig {
private final InvoiceConfig defaultInvoiceConfig;
-
- private int maxGlobalLockRetries;
private boolean isInvoicingSystemEnabled;
public ConfigurableInvoiceConfig(final InvoiceConfig defaultInvoiceConfig) {
this.defaultInvoiceConfig = defaultInvoiceConfig;
- maxGlobalLockRetries = defaultInvoiceConfig.getMaxGlobalLockRetries();
isInvoicingSystemEnabled = defaultInvoiceConfig.isInvoicingSystemEnabled();
}
@@ -1071,7 +1064,7 @@ public class TestIntegrationBase extends BeatrixTestSuiteWithEmbeddedDB implemen
@Override
public int getMaxGlobalLockRetries() {
- return maxGlobalLockRetries;
+ return defaultInvoiceConfig.getMaxGlobalLockRetries();
}
@Override
@@ -1119,10 +1112,6 @@ public class TestIntegrationBase extends BeatrixTestSuiteWithEmbeddedDB implemen
return getItemResultBehaviorMode();
}
- public void setMaxGlobalLockRetries(final int maxGlobalLockRetries) {
- this.maxGlobalLockRetries = maxGlobalLockRetries;
- }
-
public void setInvoicingSystemEnabled(final boolean invoicingSystemEnabled) {
isInvoicingSystemEnabled = invoicingSystemEnabled;
}
diff --git a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestInvoiceNotifications.java b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestInvoiceNotifications.java
index cabc048..4bb7b12 100644
--- a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestInvoiceNotifications.java
+++ b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestInvoiceNotifications.java
@@ -39,10 +39,11 @@ public class TestInvoiceNotifications extends TestIntegrationBase {
@Override
protected KillbillConfigSource getConfigSource() {
- ImmutableMap additionalProperties = new ImmutableMap.Builder()
+
+ return getConfigSource(null, new ImmutableMap.Builder()
+ .putAll(DEFAULT_BEATRIX_PROPERTIES)
.put("org.killbill.invoice.dryRunNotificationSchedule", "7d")
- .build();
- return getConfigSource("/beatrix.properties", additionalProperties);
+ .build());
}
@Test(groups = "slow")
diff --git a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestPublicBus.java b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestPublicBus.java
index 2c9aa27..9b5c52d 100644
--- a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestPublicBus.java
+++ b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestPublicBus.java
@@ -66,9 +66,10 @@ public class TestPublicBus extends TestIntegrationBase {
@Override
protected KillbillConfigSource getConfigSource() {
- final ImmutableMap<String, String> additionalProperties = new ImmutableMap.Builder<String, String>().put("org.killbill.billing.util.broadcast.rate", "500ms")
- .build();
- return getConfigSource("/beatrix.properties", additionalProperties);
+ return getConfigSource(null, new ImmutableMap.Builder()
+ .putAll(DEFAULT_BEATRIX_PROPERTIES)
+ .put("org.killbill.billing.util.broadcast.rate", "500ms")
+ .build());
}
@Override
diff --git a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestWithFakeKPMPlugin.java b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestWithFakeKPMPlugin.java
index f3892bf..44476e3 100644
--- a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestWithFakeKPMPlugin.java
+++ b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestWithFakeKPMPlugin.java
@@ -95,10 +95,10 @@ public class TestWithFakeKPMPlugin extends TestIntegrationBase {
@Override
protected KillbillConfigSource getConfigSource() {
- ImmutableMap additionalProperties = new ImmutableMap.Builder()
+ return getConfigSource(null, new ImmutableMap.Builder()
+ .putAll(DEFAULT_BEATRIX_PROPERTIES)
.put("org.killbill.billing.util.broadcast.rate", "100ms")
- .build();
- return getConfigSource("/beatrix.properties", additionalProperties);
+ .build());
}
public class FakeKPMPlugin {
diff --git a/beatrix/src/test/resources/beatrixCatalogRetireElements.properties b/beatrix/src/test/resources/beatrixCatalogRetireElements.properties
index 65c1483..889acf2 100644
--- a/beatrix/src/test/resources/beatrixCatalogRetireElements.properties
+++ b/beatrix/src/test/resources/beatrixCatalogRetireElements.properties
@@ -15,6 +15,6 @@
# under the License.
#
-org.killbill.catalog.uri=retiredCatalogs
+org.killbill.catalog.uri=catalogs/testCatalogRetireElements
org.killbill.payment.retry.days=8,8,8,8,8,8,8,8
org.killbill.osgi.bundle.install.dir=/var/tmp/beatrix-bundles
beatrix/src/test/resources/catalogs/default/catalogTest.xml 1465(+1465 -0)
diff --git a/beatrix/src/test/resources/catalogs/default/catalogTest.xml b/beatrix/src/test/resources/catalogs/default/catalogTest.xml
new file mode 100644
index 0000000..77a6048
--- /dev/null
+++ b/beatrix/src/test/resources/catalogs/default/catalogTest.xml
@@ -0,0 +1,1465 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!--
+ ~ Copyright 2010-2013 Ning, Inc.
+ ~ Copyright 2014-2018 Groupon, Inc
+ ~ Copyright 2014-2018 The Billing Project, LLC
+ ~
+ ~ The Billing Project licenses this file to you under the Apache License, version 2.0
+ ~ (the "License"); you may not use this file except in compliance with the
+ ~ License. You may obtain a copy of the License at:
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ ~ 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>
+
+ <recurringBillingMode>IN_ADVANCE</recurringBillingMode>
+
+ <currencies>
+ <currency>USD</currency>
+ <currency>EUR</currency>
+ <currency>GBP</currency>
+ </currencies>
+
+ <units>
+ <unit name="bullets"/>
+ <unit name="stones"/>
+ </units>
+
+ <products>
+ <product name="Knife">
+ <category>STANDALONE</category>
+ </product>
+ <product name="Blowdart">
+ <category>BASE</category>
+ </product>
+ <product name="Pistol">
+ <category>BASE</category>
+ <available>
+ <addonProduct>Cleaning</addonProduct>
+ <addonProduct>Bullets</addonProduct>
+ <addonProduct>Refurbish-Maintenance</addonProduct>
+ </available>
+ </product>
+ <product name="Shotgun">
+ <category>BASE</category>
+ <included>
+ <addonProduct>Cleaning</addonProduct>
+ </included>
+ <available>
+ <addonProduct>Telescopic-Scope</addonProduct>
+ <addonProduct>Laser-Scope</addonProduct>
+ <addonProduct>Holster</addonProduct>
+ <addonProduct>Bullets</addonProduct>
+ </available>
+ </product>
+ <product name="Assault-Rifle">
+ <category>BASE</category>
+ <included>
+ <addonProduct>Telescopic-Scope</addonProduct>
+ <addonProduct>Cleaning</addonProduct>
+ </included>
+ <available>
+ <addonProduct>Laser-Scope</addonProduct>
+ <addonProduct>Bullets</addonProduct>
+ </available>
+ </product>
+ <product name="Trebuchet">
+ <category>BASE</category>
+ </product>
+ <product name="Cleaning">
+ <category>ADD_ON</category>
+ </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>
+ <product name="Bullets">
+ <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>
+ <fromPriceList>notrial</fromPriceList>
+ <toPriceList>trial</toPriceList>
+ <alignment>CHANGE_OF_PLAN</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="knife-monthly-notrial">
+ <product>Knife</product>
+ <finalPhase type="EVERGREEN">
+ <duration>
+ <unit>UNLIMITED</unit>
+ </duration>
+ <recurring>
+ <billingPeriod>MONTHLY</billingPeriod>
+ <recurringPrice>
+ <price>
+ <currency>USD</currency>
+ <value>29.95</value>
+ </price>
+ <price>
+ <currency>EUR</currency>
+ <value>29.95</value>
+ </price>
+ <price>
+ <currency>GBP</currency>
+ <value>29.95</value>
+ </price>
+ </recurringPrice>
+ </recurring>
+ </finalPhase>
+ </plan>
+ <plan name="blowdart-monthly-notrial">
+ <product>Blowdart</product>
+ <finalPhase type="EVERGREEN">
+ <duration>
+ <unit>UNLIMITED</unit>
+ </duration>
+ <recurring>
+ <billingPeriod>MONTHLY</billingPeriod>
+ <recurringPrice>
+ <price>
+ <currency>USD</currency>
+ <value>29.95</value>
+ </price>
+ <price>
+ <currency>EUR</currency>
+ <value>29.95</value>
+ </price>
+ <price>
+ <currency>GBP</currency>
+ <value>29.95</value>
+ </price>
+ </recurringPrice>
+ </recurring>
+ </finalPhase>
+ </plan>
+ <plan name="pistol-monthly-notrial">
+ <product>Pistol</product>
+ <finalPhase type="EVERGREEN">
+ <duration>
+ <unit>UNLIMITED</unit>
+ </duration>
+ <recurring>
+ <billingPeriod>MONTHLY</billingPeriod>
+ <recurringPrice>
+ <price>
+ <currency>USD</currency>
+ <value>19.95</value>
+ </price>
+ <price>
+ <currency>EUR</currency>
+ <value>19.95</value>
+ </price>
+ <price>
+ <currency>GBP</currency>
+ <value>19.95</value>
+ </price>
+ </recurringPrice>
+ </recurring>
+ </finalPhase>
+ </plan>
+ <plan name="blowdart-monthly">
+ <product>Blowdart</product>
+ <initialPhases>
+ <phase type="TRIAL">
+ <duration>
+ <unit>DAYS</unit>
+ <number>30</number>
+ </duration>
+ <fixed>
+ <fixedPrice>
+ </fixedPrice>
+ </fixed>
+ </phase>
+ <phase type="DISCOUNT">
+ <duration>
+ <unit>MONTHS</unit>
+ <number>6</number>
+ </duration>
+ <recurring>
+ <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>
+ </recurring>
+ </phase>
+ </initialPhases>
+ <finalPhase type="EVERGREEN">
+ <duration>
+ <unit>UNLIMITED</unit>
+ </duration>
+ <recurring>
+ <billingPeriod>MONTHLY</billingPeriod>
+ <recurringPrice>
+ <price>
+ <currency>USD</currency>
+ <value>29.95</value>
+ </price>
+ <price>
+ <currency>EUR</currency>
+ <value>29.95</value>
+ </price>
+ <price>
+ <currency>GBP</currency>
+ <value>29.95</value>
+ </price>
+ </recurringPrice>
+ </recurring>
+ </finalPhase>
+ </plan>
+ <plan name="blowdart-monthly-trial">
+ <product>Blowdart</product>
+ <initialPhases>
+ <phase type="TRIAL">
+ <duration>
+ <unit>DAYS</unit>
+ <number>30</number>
+ </duration>
+ <fixed>
+ <fixedPrice>
+ </fixedPrice>
+ </fixed>
+ </phase>
+ </initialPhases>
+ <finalPhase type="EVERGREEN">
+ <duration>
+ <unit>UNLIMITED</unit>
+ </duration>
+ <recurring>
+ <billingPeriod>MONTHLY</billingPeriod>
+ <recurringPrice>
+ <price>
+ <currency>USD</currency>
+ <value>29.95</value>
+ </price>
+ <price>
+ <currency>EUR</currency>
+ <value>29.95</value>
+ </price>
+ <price>
+ <currency>GBP</currency>
+ <value>29.95</value>
+ </price>
+ </recurringPrice>
+ </recurring>
+ </finalPhase>
+ </plan>
+ <plan name="pistol-weekly">
+ <product>Pistol</product>
+ <initialPhases>
+ <phase type="TRIAL">
+ <duration>
+ <unit>DAYS</unit>
+ <number>30</number>
+ </duration>
+ <fixed>
+ <fixedPrice>
+ </fixedPrice>
+ </fixed>
+ </phase>
+ </initialPhases>
+ <finalPhase type="EVERGREEN">
+ <duration>
+ <unit>UNLIMITED</unit>
+ </duration>
+ <recurring>
+ <billingPeriod>WEEKLY</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>
+ </recurring>
+ </finalPhase>
+ </plan>
+ <plan name="pistol-thirty-days">
+ <product>Pistol</product>
+ <initialPhases>
+ <phase type="TRIAL">
+ <duration>
+ <unit>DAYS</unit>
+ <number>30</number>
+ </duration>
+ <fixed>
+ <fixedPrice>
+ </fixedPrice>
+ </fixed>
+ </phase>
+ </initialPhases>
+ <finalPhase type="EVERGREEN">
+ <duration>
+ <unit>UNLIMITED</unit>
+ </duration>
+ <recurring>
+ <billingPeriod>THIRTY_DAYS</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>
+ </recurring>
+ </finalPhase>
+ </plan>
+ <plan name="pistol-monthly">
+ <product>Pistol</product>
+ <initialPhases>
+ <phase type="TRIAL">
+ <duration>
+ <unit>DAYS</unit>
+ <number>30</number>
+ </duration>
+ <fixed>
+ <fixedPrice>
+ </fixedPrice>
+ </fixed>
+ </phase>
+ </initialPhases>
+ <finalPhase type="EVERGREEN">
+ <duration>
+ <unit>UNLIMITED</unit>
+ </duration>
+ <recurring>
+ <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>
+ </recurring>
+ </finalPhase>
+ </plan>
+
+ <plan name="pistol-monthly-fixedterm">
+ <product>Pistol</product>
+ <initialPhases>
+ <phase type="TRIAL">
+ <duration>
+ <unit>DAYS</unit>
+ <number>30</number>
+ </duration>
+ <fixed>
+ <fixedPrice>
+ </fixedPrice>
+ </fixed>
+ </phase>
+ </initialPhases>
+ <finalPhase type="FIXEDTERM">
+ <duration>
+ <unit>MONTHS</unit>
+ <number>12</number>
+ </duration>
+ <recurring>
+ <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>
+ </recurring>
+ </finalPhase>
+ </plan>
+
+ <plan name="shotgun-monthly" prettyName="Shotgun Monthly">
+ <product>Shotgun</product>
+ <initialPhases>
+ <phase type="TRIAL">
+ <duration>
+ <unit>DAYS</unit>
+ <number>30</number>
+ </duration>
+ <fixed>
+ <fixedPrice>
+ </fixedPrice>
+ </fixed>
+ </phase>
+ </initialPhases>
+ <finalPhase type="EVERGREEN">
+ <duration>
+ <unit>UNLIMITED</unit>
+ <number>-1</number>
+ </duration>
+ <recurring>
+ <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>
+ </recurring>
+ </finalPhase>
+ </plan>
+ <plan name="assault-rifle-monthly">
+ <product>Assault-Rifle</product>
+ <initialPhases>
+ <phase type="TRIAL">
+ <duration>
+ <unit>DAYS</unit>
+ <number>30</number>
+ </duration>
+ <fixed>
+ <fixedPrice>
+ </fixedPrice>
+ </fixed>
+ </phase>
+ </initialPhases>
+ <finalPhase type="EVERGREEN">
+ <duration>
+ <unit>UNLIMITED</unit>
+ </duration>
+ <recurring>
+ <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>
+ </recurring>
+ </finalPhase>
+ </plan>
+ <plan name="pistol-quarterly">
+ <product>Pistol</product>
+ <initialPhases>
+ <phase type="TRIAL">
+ <duration>
+ <unit>DAYS</unit>
+ <number>30</number>
+ </duration>
+ <fixed>
+ <fixedPrice>
+ </fixedPrice>
+ </fixed>
+ </phase>
+ </initialPhases>
+ <finalPhase type="EVERGREEN">
+ <duration>
+ <unit>UNLIMITED</unit>
+ </duration>
+ <recurring>
+ <billingPeriod>QUARTERLY</billingPeriod>
+ <recurringPrice>
+ <price>
+ <currency>GBP</currency>
+ <value>69.95</value>
+ </price>
+ <price>
+ <currency>EUR</currency>
+ <value>69.95</value>
+ </price>
+ <price>
+ <currency>USD</currency>
+ <value>69.95</value>
+ </price>
+ </recurringPrice>
+ </recurring>
+ </finalPhase>
+ </plan>
+ <plan name="pistol-annual">
+ <product>Pistol</product>
+ <initialPhases>
+ <phase type="TRIAL">
+ <duration>
+ <unit>DAYS</unit>
+ <number>30</number>
+ </duration>
+ <fixed>
+ <fixedPrice>
+ </fixedPrice>
+ </fixed>
+ </phase>
+ </initialPhases>
+ <finalPhase type="EVERGREEN">
+ <duration>
+ <unit>UNLIMITED</unit>
+ </duration>
+ <recurring>
+ <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>
+ </recurring>
+ </finalPhase>
+ </plan>
+ <plan name="shotgun-annual">
+ <product>Shotgun</product>
+ <initialPhases>
+ <phase type="TRIAL">
+ <duration>
+ <unit>DAYS</unit>
+ <number>30</number>
+ </duration>
+ <fixed>
+ <fixedPrice>
+ </fixedPrice>
+ </fixed>
+ </phase>
+ </initialPhases>
+ <finalPhase type="EVERGREEN">
+ <duration>
+ <unit>UNLIMITED</unit>
+ </duration>
+ <recurring>
+ <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>
+ </recurring>
+ </finalPhase>
+ </plan>
+ <plan name="assault-rifle-annual">
+ <product>Assault-Rifle</product>
+ <initialPhases>
+ <phase type="TRIAL">
+ <duration>
+ <unit>DAYS</unit>
+ <number>30</number>
+ </duration>
+ <fixed>
+ <fixedPrice>
+ </fixedPrice>
+ </fixed>
+ </phase>
+ </initialPhases>
+ <finalPhase type="EVERGREEN">
+ <duration>
+ <unit>UNLIMITED</unit>
+ </duration>
+ <recurring>
+ <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>
+ </recurring>
+ </finalPhase>
+ </plan>
+ <plan name="pistol-annual-gunclub-discount">
+ <product>Pistol</product>
+ <initialPhases>
+ <phase type="TRIAL">
+ <duration>
+ <unit>DAYS</unit>
+ <number>30</number>
+ </duration>
+ <fixed>
+ <fixedPrice>
+ </fixedPrice>
+ </fixed>
+ </phase>
+ <phase type="DISCOUNT">
+ <duration>
+ <unit>MONTHS</unit>
+ <number>6</number>
+ </duration>
+ <recurring>
+ <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>
+ </recurring>
+ </phase>
+ </initialPhases>
+ <finalPhase type="EVERGREEN">
+ <duration>
+ <unit>UNLIMITED</unit>
+ </duration>
+ <recurring>
+ <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>
+ </recurring>
+ </finalPhase>
+ </plan>
+ <plan name="pistol-annual-gunclub-discount-notrial">
+ <product>Pistol</product>
+ <initialPhases>
+ <phase type="DISCOUNT">
+ <duration>
+ <unit>MONTHS</unit>
+ <number>6</number>
+ </duration>
+ <recurring>
+ <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>
+ </recurring>
+ </phase>
+ </initialPhases>
+ <finalPhase type="EVERGREEN">
+ <duration>
+ <unit>UNLIMITED</unit>
+ </duration>
+ <recurring>
+ <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>
+ </recurring>
+ </finalPhase>
+ </plan>
+ <plan name="shotgun-annual-gunclub-discount">
+ <product>Shotgun</product>
+ <initialPhases>
+ <phase type="TRIAL">
+ <duration>
+ <unit>DAYS</unit>
+ <number>30</number>
+ </duration>
+ <fixed>
+ <fixedPrice>
+ </fixedPrice>
+ </fixed>
+ </phase>
+ <phase type="DISCOUNT">
+ <duration>
+ <unit>MONTHS</unit>
+ <number>6</number>
+ </duration>
+ <recurring>
+ <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>
+ </recurring>
+ </phase>
+ </initialPhases>
+ <finalPhase type="EVERGREEN">
+ <duration>
+ <unit>UNLIMITED</unit>
+ </duration>
+ <recurring>
+ <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>
+ </recurring>
+ </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>
+ <fixed>
+ <fixedPrice>
+ </fixedPrice>
+ </fixed>
+ </phase>
+ <phase type="DISCOUNT">
+ <duration>
+ <unit>MONTHS</unit>
+ <number>6</number>
+ </duration>
+ <recurring>
+ <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>
+ </recurring>
+ </phase>
+ </initialPhases>
+ <finalPhase type="EVERGREEN">
+ <duration>
+ <unit>UNLIMITED</unit>
+ </duration>
+ <recurring>
+ <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>
+ </recurring>
+ </finalPhase>
+ </plan>
+ <plan name="laser-scope-monthly">
+ <product>Laser-Scope</product>
+ <initialPhases>
+ <phase type="DISCOUNT">
+ <duration>
+ <unit>MONTHS</unit>
+ <number>1</number>
+ </duration>
+ <recurring>
+ <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>
+ </recurring>
+ </phase>
+ </initialPhases>
+ <finalPhase type="EVERGREEN">
+ <duration>
+ <unit>UNLIMITED</unit>
+ </duration>
+ <recurring>
+ <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>
+ </recurring>
+ </finalPhase>
+ <plansAllowedInBundle>2</plansAllowedInBundle>
+ </plan>
+ <plan name="cleaning-monthly">
+ <product>Cleaning</product>
+ <finalPhase type="EVERGREEN">
+ <duration>
+ <unit>UNLIMITED</unit>
+ </duration>
+ <recurring>
+ <billingPeriod>MONTHLY</billingPeriod>
+ <recurringPrice>
+ <price>
+ <currency>USD</currency>
+ <value>2.95</value>
+ </price>
+ <price>
+ <currency>EUR</currency>
+ <value>1.95</value>
+ </price>
+ <price>
+ <currency>GBP</currency>
+ <value>0.95</value>
+ </price>
+ </recurringPrice>
+ </recurring>
+ </finalPhase>
+ </plan>
+ <plan name="telescopic-scope-monthly">
+ <product>Telescopic-Scope</product>
+ <initialPhases>
+ <phase type="DISCOUNT">
+ <duration>
+ <unit>MONTHS</unit>
+ <number>1</number>
+ </duration>
+ <recurring>
+ <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>
+ </recurring>
+ </phase>
+ </initialPhases>
+ <finalPhase type="EVERGREEN">
+ <duration>
+ <unit>UNLIMITED</unit>
+ </duration>
+ <recurring>
+ <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>
+ </recurring>
+ </finalPhase>
+ </plan>
+ <plan name="extra-ammo-monthly">
+ <product>Extra-Ammo</product>
+ <finalPhase type="EVERGREEN">
+ <duration>
+ <unit>UNLIMITED</unit>
+ </duration>
+ <recurring>
+ <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>
+ </recurring>
+ </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>
+ <fixed>
+ <fixedPrice>
+ </fixedPrice>
+ </fixed>
+ </phase>
+ </initialPhases>
+ <finalPhase type="EVERGREEN">
+ <duration>
+ <unit>UNLIMITED</unit>
+ </duration>
+ <recurring>
+ <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>
+ </recurring>
+ </finalPhase>
+ </plan>
+ <plan name="holster-monthly-special">
+ <product>Holster</product>
+ <initialPhases>
+ <phase type="TRIAL">
+ <duration>
+ <unit>DAYS</unit>
+ <number>30</number>
+ </duration>
+ <fixed>
+ <fixedPrice>
+ </fixedPrice>
+ </fixed>
+ </phase>
+ </initialPhases>
+ <finalPhase type="EVERGREEN">
+ <duration>
+ <unit>UNLIMITED</unit>
+ </duration>
+ <recurring>
+ <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>
+ </recurring>
+ </finalPhase>
+ </plan>
+ <plan name="assault-rifle-annual-rescue">
+ <product>Assault-Rifle</product>
+ <initialPhases>
+ <phase type="DISCOUNT">
+ <duration>
+ <unit>YEARS</unit>
+ <number>1</number>
+ </duration>
+ <recurring>
+ <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>
+ </recurring>
+ </phase>
+ </initialPhases>
+ <finalPhase type="EVERGREEN">
+ <duration>
+ <unit>UNLIMITED</unit>
+ </duration>
+ <recurring>
+ <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>
+ </recurring>
+ </finalPhase>
+ </plan>
+ <plan name="refurbish-maintenance">
+ <product>Refurbish-Maintenance</product>
+ <finalPhase type="FIXEDTERM">
+ <duration>
+ <unit>MONTHS</unit>
+ <number>12</number>
+ </duration>
+ <fixed>
+ <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>
+ </fixed>
+ <recurring>
+ <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>
+ </recurring>
+ </finalPhase>
+ </plan>
+ <plan name="bullets-usage-in-arrear" prettyName="Bullet Monthly Plan">
+ <product>Bullets</product>
+ <finalPhase type="EVERGREEN" prettyName="Bullet Monthly Plan Evergreen">
+ <duration>
+ <unit>UNLIMITED</unit>
+ </duration>
+ <usages>
+ <usage name="bullets-usage-in-arrear-usage" prettyName="Bullet Usage In Arrear" billingMode="IN_ARREAR" usageType="CONSUMABLE">
+ <billingPeriod>MONTHLY</billingPeriod>
+ <tiers>
+ <tier>
+ <blocks>
+ <tieredBlock>
+ <unit>bullets</unit>
+ <size>100</size>
+ <prices>
+ <price>
+ <currency>USD</currency>
+ <value>2.95</value>
+ </price>
+ <price>
+ <currency>EUR</currency>
+ <value>1.95</value>
+ </price>
+ <price>
+ <currency>GBP</currency>
+ <value>0.95</value>
+ </price>
+ </prices>
+ <max>10</max>
+ </tieredBlock>
+ </blocks>
+ </tier>
+ <tier>
+ <blocks>
+ <tieredBlock>
+ <unit>bullets</unit>
+ <size>1000</size>
+ <prices>
+ <price>
+ <currency>USD</currency>
+ <value>5.95</value>
+ </price>
+ <price>
+ <currency>EUR</currency>
+ <value>4.95</value>
+ </price>
+ <price>
+ <currency>GBP</currency>
+ <value>3.95</value>
+ </price>
+ </prices>
+ <max>100</max>
+ </tieredBlock>
+ </blocks>
+ </tier>
+ </tiers>
+ </usage>
+ </usages>
+ </finalPhase>
+ <plansAllowedInBundle>-1</plansAllowedInBundle>
+ <!-- arbitrary number of these (multipack) -->
+ </plan>
+ <plan name="trebuchet-usage-in-arrear" prettyName="Trebuchet Monthly Plan">
+ <product>Trebuchet</product>
+ <finalPhase type="EVERGREEN">
+ <duration>
+ <unit>UNLIMITED</unit>
+ </duration>
+ <usages>
+ <usage name="trebuchet-in-arrear-usage" billingMode="IN_ARREAR" usageType="CAPACITY">
+ <billingPeriod>MONTHLY</billingPeriod>
+ <tiers>
+ <tier>
+ <limits>
+ <limit>
+ <unit>stones</unit>
+ <max>100</max>
+ </limit>
+ </limits>
+ <recurringPrice>
+ <price>
+ <currency>USD</currency>
+ <value>100</value>
+ </price>
+ </recurringPrice>
+ </tier>
+ <tier>
+ <limits>
+ <limit>
+ <unit>stones</unit>
+ <max>-1</max>
+ </limit>
+ </limits>
+ <recurringPrice>
+ <price>
+ <currency>USD</currency>
+ <value>1000</value>
+ </price>
+ </recurringPrice>
+ </tier>
+ </tiers>
+ </usage>
+ </usages>
+ </finalPhase>
+ </plan>
+ </plans>
+ <priceLists>
+ <defaultPriceList name="DEFAULT">
+ <plans>
+ <plan>pistol-weekly</plan>
+ <plan>pistol-thirty-days</plan>
+ <plan>blowdart-monthly</plan>
+ <plan>pistol-monthly</plan>
+ <plan>shotgun-monthly</plan>
+ <plan>assault-rifle-monthly</plan>
+ <plan>pistol-annual</plan>
+ <plan>pistol-quarterly</plan>
+ <plan>shotgun-annual</plan>
+ <plan>assault-rifle-annual</plan>
+ <plan>trebuchet-usage-in-arrear</plan>
+ <plan>laser-scope-monthly</plan>
+ <plan>telescopic-scope-monthly</plan>
+ <plan>cleaning-monthly</plan>
+ <plan>extra-ammo-monthly</plan>
+ <plan>holster-monthly-regular</plan>
+ <plan>refurbish-maintenance</plan>
+ <plan>bullets-usage-in-arrear</plan>
+ <plan>holster-monthly-special</plan>
+ </plans>
+ </defaultPriceList>
+ <childPriceList name="gunclubDiscount">
+ <plans>
+ <plan>pistol-annual-gunclub-discount</plan>
+ <plan>shotgun-annual-gunclub-discount</plan>
+ <plan>assault-rifle-annual-gunclub-discount</plan>
+ </plans>
+ </childPriceList>
+ <childPriceList name="gunclubDiscountNoTrial">
+ <plans>
+ <plan>pistol-annual-gunclub-discount-notrial</plan>
+ </plans>
+ </childPriceList>
+ <childPriceList name="rescue">
+ <plans>
+ <plan>assault-rifle-annual-rescue</plan>
+ </plans>
+ </childPriceList>
+ <childPriceList name="fixedTerm">
+ <plans>
+ <plan>pistol-monthly-fixedterm</plan>
+ </plans>
+ </childPriceList>
+ <childPriceList name="notrial">
+ <plans>
+ <plan>knife-monthly-notrial</plan>
+ <plan>blowdart-monthly-notrial</plan>
+ <plan>pistol-monthly-notrial</plan>
+ </plans>
+ </childPriceList>
+ <childPriceList name="trial">
+ <plans>
+ <plan>blowdart-monthly-trial</plan>
+ </plans>
+ </childPriceList>
+ </priceLists>
+
+</catalog>
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/api/user/DefaultInvoiceUserApi.java b/invoice/src/main/java/org/killbill/billing/invoice/api/user/DefaultInvoiceUserApi.java
index 443c8ff..264066e 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/api/user/DefaultInvoiceUserApi.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/api/user/DefaultInvoiceUserApi.java
@@ -548,7 +548,7 @@ public class DefaultInvoiceUserApi implements InvoiceUserApi {
// Note! The amount is negated here!
inputItem.getAmount().negate(),
accountCurrency,
- inputItem.getDescription());
+ inputItem.getItemDetails());
break;
case TAX:
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/dao/DefaultInvoiceDao.java b/invoice/src/main/java/org/killbill/billing/invoice/dao/DefaultInvoiceDao.java
index 5470980..fd7d11a 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/dao/DefaultInvoiceDao.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/dao/DefaultInvoiceDao.java
@@ -357,8 +357,9 @@ public class DefaultInvoiceDao extends EntityDaoBase<InvoiceModelDao, Invoice, I
// The restriction on the amount is to deal with https://github.com/killbill/killbill/issues/993 - and esnure that duplicate
// items would not be re-written
(invoiceItemModelDao.getAmount().compareTo(existingInvoiceItem.getAmount()) != 0)) {
- checkAgainstExistingInvoiceItemState(existingInvoiceItem, invoiceItemModelDao);
- transInvoiceItemSqlDao.updateItemFields(invoiceItemModelDao.getId().toString(), invoiceItemModelDao.getAmount(), invoiceItemModelDao.getDescription(), invoiceItemModelDao.getItemDetails(), context);
+ if (checkAgainstExistingInvoiceItemState(existingInvoiceItem, invoiceItemModelDao)) {
+ transInvoiceItemSqlDao.updateItemFields(invoiceItemModelDao.getId().toString(), invoiceItemModelDao.getAmount(), invoiceItemModelDao.getDescription(), invoiceItemModelDao.getItemDetails(), context);
+ }
}
}
@@ -1321,48 +1322,6 @@ public class DefaultInvoiceDao extends EntityDaoBase<InvoiceModelDao, Invoice, I
}
private static boolean checkAgainstExistingInvoiceItemState(final InvoiceItemModelDao existingInvoiceItem, final InvoiceItemModelDao inputInvoiceItem) {
- Preconditions.checkState(existingInvoiceItem.getAccountId().equals(inputInvoiceItem.getAccountId()), String.format("Unexpected account ID '%s' for invoice item '%s'",
- inputInvoiceItem.getAccountId(), existingInvoiceItem.getId()));
- if (existingInvoiceItem.getChildAccountId() != null) {
- Preconditions.checkState(existingInvoiceItem.getChildAccountId().equals(inputInvoiceItem.getChildAccountId()), String.format("Unexpected child account ID '%s' for invoice item '%s'",
- inputInvoiceItem.getChildAccountId(), existingInvoiceItem.getId()));
- }
- Preconditions.checkState(existingInvoiceItem.getInvoiceId().equals(inputInvoiceItem.getInvoiceId()), String.format("Unexpected invoice ID '%s' for invoice item '%s'",
- inputInvoiceItem.getInvoiceId(), existingInvoiceItem.getId()));
- if (existingInvoiceItem.getBundleId() != null) {
- Preconditions.checkState(existingInvoiceItem.getBundleId().equals(inputInvoiceItem.getBundleId()), String.format("Unexpected bundle ID '%s' for invoice item '%s'",
- inputInvoiceItem.getBundleId(), existingInvoiceItem.getId()));
- }
- if (existingInvoiceItem.getSubscriptionId() != null) {
- Preconditions.checkState(existingInvoiceItem.getSubscriptionId().equals(inputInvoiceItem.getSubscriptionId()), String.format("Unexpected subscription ID '%s' for invoice item '%s'",
- inputInvoiceItem.getSubscriptionId(), existingInvoiceItem.getId()));
- }
- if (existingInvoiceItem.getPlanName() != null) {
- Preconditions.checkState(existingInvoiceItem.getPlanName().equals(inputInvoiceItem.getPlanName()), String.format("Unexpected plan name '%s' for invoice item '%s'",
- inputInvoiceItem.getPlanName(), existingInvoiceItem.getId()));
- }
- if (existingInvoiceItem.getPhaseName() != null) {
- Preconditions.checkState(existingInvoiceItem.getPhaseName().equals(inputInvoiceItem.getPhaseName()), String.format("Unexpected phase name '%s' for invoice item '%s'",
- inputInvoiceItem.getPhaseName(), existingInvoiceItem.getId()));
- }
- if (existingInvoiceItem.getUsageName() != null) {
- Preconditions.checkState(existingInvoiceItem.getUsageName().equals(inputInvoiceItem.getUsageName()), String.format("Unexpected usage name '%s' for invoice item '%s'",
- inputInvoiceItem.getUsageName(), existingInvoiceItem.getId()));
- }
- if (existingInvoiceItem.getStartDate() != null) {
- Preconditions.checkState(existingInvoiceItem.getStartDate().equals(inputInvoiceItem.getStartDate()), String.format("Unexpected startDate '%s' for invoice item '%s'",
- inputInvoiceItem.getStartDate(), existingInvoiceItem.getId()));
- }
- if (existingInvoiceItem.getEndDate() != null) {
- Preconditions.checkState(existingInvoiceItem.getEndDate().equals(inputInvoiceItem.getEndDate()), String.format("Unexpected endDate '%s' for invoice item '%s'",
- inputInvoiceItem.getEndDate(), existingInvoiceItem.getId()));
- }
-
- Preconditions.checkState(existingInvoiceItem.getCurrency() == inputInvoiceItem.getCurrency(), String.format("Unexpected currency '%s' for invoice item '%s'",
- inputInvoiceItem.getCurrency(), existingInvoiceItem.getId()));
- Preconditions.checkState(existingInvoiceItem.getType() == inputInvoiceItem.getType(), String.format("Unexpected item type '%s' for invoice item '%s'",
- inputInvoiceItem.getType(), existingInvoiceItem.getId()));
-
boolean itemShouldBeUpdated = false;
if (inputInvoiceItem.getAmount() != null) {
itemShouldBeUpdated = existingInvoiceItem.getAmount() == null /* unlikely */|| inputInvoiceItem.getAmount().compareTo(existingInvoiceItem.getAmount()) != 0;
diff --git a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/InvoiceResource.java b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/InvoiceResource.java
index 916dbb9..30b16f9 100644
--- a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/InvoiceResource.java
+++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/InvoiceResource.java
@@ -362,9 +362,9 @@ public class InvoiceResource extends JaxRsResourceBase {
final CallContext callContext = context.createCallContextWithAccountId(accountId, createdBy, reason, comment, request);
final LocalDate inputDate;
if (dryRunSubscriptionSpec != null) {
- if (DryRunType.UPCOMING_INVOICE.name().equals(dryRunSubscriptionSpec.getDryRunType())) {
+ if (DryRunType.UPCOMING_INVOICE.equals(dryRunSubscriptionSpec.getDryRunType())) {
inputDate = null;
- } else if (DryRunType.SUBSCRIPTION_ACTION.name().equals(dryRunSubscriptionSpec.getDryRunType()) && dryRunSubscriptionSpec.getEffectiveDate() != null) {
+ } else if (DryRunType.SUBSCRIPTION_ACTION.equals(dryRunSubscriptionSpec.getDryRunType()) && dryRunSubscriptionSpec.getEffectiveDate() != null) {
inputDate = dryRunSubscriptionSpec.getEffectiveDate();
} else {
inputDate = toLocalDate(targetDate);
@@ -376,18 +376,18 @@ public class InvoiceResource extends JaxRsResourceBase {
// Passing a null or empty body means we are trying to generate an invoice with a (future) targetDate
// On the other hand if body is not null, we are attempting a dryRun subscription operation
if (dryRunSubscriptionSpec != null && dryRunSubscriptionSpec.getDryRunAction() != null) {
- if (SubscriptionEventType.START_BILLING.toString().equals(dryRunSubscriptionSpec.getDryRunAction())) {
+ if (SubscriptionEventType.START_BILLING.equals(dryRunSubscriptionSpec.getDryRunAction())) {
verifyNonNullOrEmpty(dryRunSubscriptionSpec.getProductName(), "DryRun subscription product category should be specified");
verifyNonNullOrEmpty(dryRunSubscriptionSpec.getBillingPeriod(), "DryRun subscription billingPeriod should be specified");
verifyNonNullOrEmpty(dryRunSubscriptionSpec.getProductCategory(), "DryRun subscription product category should be specified");
if (dryRunSubscriptionSpec.getProductCategory().equals(ProductCategory.ADD_ON)) {
verifyNonNullOrEmpty(dryRunSubscriptionSpec.getBundleId(), "DryRun bundle ID should be specified");
}
- } else if (SubscriptionEventType.CHANGE.toString().equals(dryRunSubscriptionSpec.getDryRunAction())) {
+ } else if (SubscriptionEventType.CHANGE.equals(dryRunSubscriptionSpec.getDryRunAction())) {
verifyNonNullOrEmpty(dryRunSubscriptionSpec.getProductName(), "DryRun subscription product category should be specified");
verifyNonNullOrEmpty(dryRunSubscriptionSpec.getBillingPeriod(), "DryRun subscription billingPeriod should be specified");
verifyNonNullOrEmpty(dryRunSubscriptionSpec.getSubscriptionId(), "DryRun subscriptionID should be specified");
- } else if (SubscriptionEventType.STOP_BILLING.toString().equals(dryRunSubscriptionSpec.getDryRunAction())) {
+ } else if (SubscriptionEventType.STOP_BILLING.equals(dryRunSubscriptionSpec.getDryRunAction())) {
verifyNonNullOrEmpty(dryRunSubscriptionSpec.getSubscriptionId(), "DryRun subscriptionID should be specified");
}
}
diff --git a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/PluginResource.java b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/PluginResource.java
index 8a7522c..a9a2d4c 100644
--- a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/PluginResource.java
+++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/PluginResource.java
@@ -35,6 +35,9 @@ import javax.servlet.ServletConfig;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletInputStream;
+import javax.servlet.ServletOutputStream;
+import javax.servlet.ServletRequest;
+import javax.servlet.WriteListener;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
@@ -186,17 +189,40 @@ public class PluginResource extends JaxRsResourceBase {
final HttpServletResponse response, final ServletContext servletContext,
final ServletConfig servletConfig, final UriInfo uriInfo) throws ServletException, IOException {
prepareOSGIRequest(request, servletContext, servletConfig);
- osgiServlet.service(new OSGIServletRequestWrapper(request, inputStream, formData, uriInfo.getQueryParameters()), new OSGIServletResponseWrapper(response));
- if (response.isCommitted()) {
- if (response.getStatus() >= 400) {
- log.warn("{} responded {}", request.getPathInfo(), response.getStatus());
- }
- // Jersey will want to return 204, but the servlet should have done the right thing already
- return null;
- } else {
- return Response.status(response.getStatus()).build();
+ final ServletRequest req = new OSGIServletRequestWrapper(request, inputStream, formData, uriInfo.getQueryParameters());
+
+ // The real ServletOutputStream is a HttpOutput, which we don't want to give to plugins.
+ // Jooby for instance would commit the underlying HTTP channel (via ServletServletResponse#send),
+ // meaning that any further headers (e.g. Profiling) that we would add would not be returned.
+ final ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
+ final OSGIServletResponseWrapper res = new OSGIServletResponseWrapper(response,
+ new ServletOutputStream() {
+ @Override
+ public boolean isReady() {
+ return true;
+ }
+
+ @Override
+ public void setWriteListener(final WriteListener writeListener) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void write(final int b) throws IOException {
+ byteArrayOutputStream.write(b);
+ }
+ });
+
+ osgiServlet.service(req, res);
+
+ if (response.getStatus() >= 400) {
+ log.warn("{} responded {}", request.getPathInfo(), response.getStatus());
}
+
+ return Response.status(response.getStatus())
+ .entity(new String(byteArrayOutputStream.toByteArray()))
+ .build();
}
private InputStream createInputStream(final HttpServletRequest request, final MultivaluedMap<String, String> form) throws IOException {
@@ -348,8 +374,16 @@ public class PluginResource extends JaxRsResourceBase {
private static final class OSGIServletResponseWrapper extends HttpServletResponseWrapper {
- public OSGIServletResponseWrapper(final HttpServletResponse response) {
+ private final ServletOutputStream servletOutputStream;
+
+ public OSGIServletResponseWrapper(final HttpServletResponse response, final ServletOutputStream servletOutputStream) {
super(response);
+ this.servletOutputStream = servletOutputStream;
+ }
+
+ @Override
+ public ServletOutputStream getOutputStream() throws IOException {
+ return servletOutputStream;
}
}
}
diff --git a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/SecurityResource.java b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/SecurityResource.java
index 2699bba..dc77b6c 100644
--- a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/SecurityResource.java
+++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/SecurityResource.java
@@ -155,6 +155,7 @@ public class SecurityResource extends JaxRsResourceBase {
@Produces(APPLICATION_JSON)
@Path("/users/{username:" + ANYTHING_PATTERN + "}/roles")
@ApiOperation(value = "Get roles associated to a user", response = UserRolesJson.class)
+ @ApiResponses(value = {@ApiResponse(code = 404, message = "The user does not exist or has been inactivated")})
public Response getUserRoles(@PathParam("username") final String username,
@javax.ws.rs.core.Context final HttpServletRequest request,
@javax.ws.rs.core.Context final UriInfo uriInfo) throws SecurityApiException {
pom.xml 2(+1 -1)
diff --git a/pom.xml b/pom.xml
index 452a2de..4a29a27 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.141.79-SNAPSHOT</version>
+ <version>0.141.81-SNAPSHOT</version>
</parent>
<artifactId>killbill</artifactId>
<version>0.19.17-SNAPSHOT</version>
diff --git a/subscription/src/main/java/org/killbill/billing/subscription/api/user/DefaultSubscriptionBase.java b/subscription/src/main/java/org/killbill/billing/subscription/api/user/DefaultSubscriptionBase.java
index 62bb358..762437d 100644
--- a/subscription/src/main/java/org/killbill/billing/subscription/api/user/DefaultSubscriptionBase.java
+++ b/subscription/src/main/java/org/killbill/billing/subscription/api/user/DefaultSubscriptionBase.java
@@ -47,6 +47,7 @@ import org.killbill.billing.catalog.api.ProductCategory;
import org.killbill.billing.entitlement.api.Entitlement.EntitlementSourceType;
import org.killbill.billing.entitlement.api.Entitlement.EntitlementState;
import org.killbill.billing.entity.EntityBase;
+import org.killbill.billing.payment.api.TransactionType;
import org.killbill.billing.subscription.api.SubscriptionBase;
import org.killbill.billing.subscription.api.SubscriptionBaseApiService;
import org.killbill.billing.subscription.api.SubscriptionBaseTransitionType;
@@ -580,8 +581,18 @@ public class DefaultSubscriptionBase extends EntityBase implements SubscriptionB
public boolean isPendingChangePlan() {
- final SubscriptionBaseTransition pendingTransition = getPendingTransition();
- return pendingTransition != null && pendingTransition.getTransitionType() == SubscriptionBaseTransitionType.CHANGE;
+
+ final SubscriptionBaseTransitionDataIterator it = new SubscriptionBaseTransitionDataIterator(
+ clock, transitions, Order.ASC_FROM_PAST,
+ Visibility.ALL, TimeLimit.FUTURE_ONLY);
+
+ while (it.hasNext()) {
+ final SubscriptionBaseTransition next = it.next();
+ if (next.getTransitionType() == SubscriptionBaseTransitionType.CHANGE) {
+ return true;
+ }
+ }
+ return false;
}
diff --git a/subscription/src/test/java/org/killbill/billing/subscription/api/user/TestUserApiChangePlan.java b/subscription/src/test/java/org/killbill/billing/subscription/api/user/TestUserApiChangePlan.java
index 31a211e..9b4c902 100644
--- a/subscription/src/test/java/org/killbill/billing/subscription/api/user/TestUserApiChangePlan.java
+++ b/subscription/src/test/java/org/killbill/billing/subscription/api/user/TestUserApiChangePlan.java
@@ -613,4 +613,57 @@ public class TestUserApiChangePlan extends SubscriptionTestSuiteWithEmbeddedDB {
assertEquals(refreshedSubscription.getAllTransitions().get(1).getTransitionType(), SubscriptionBaseTransitionType.PHASE);
}
+
+ @Test(groups = "slow")
+ public void testUndoChangePlanOnPendingSubscription() throws SubscriptionBaseApiException {
+
+
+ final DateTime futureStartDate = clock.getUTCNow().plusDays(7);
+ final DefaultSubscriptionBase subscription = testUtil.createSubscription(bundle, "Shotgun", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, null, internalCallContext.toLocalDate(futureStartDate));
+ assertNotNull(subscription);
+
+ clock.addDays(1);
+
+ // Change plan in the future
+ final DateTime futureChangeDate = futureStartDate.plusDays(5);
+ subscription.changePlanWithDate(new PlanPhaseSpecifier("Pistol", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME), null, futureChangeDate, callContext);
+ assertListenerStatus();
+
+ DefaultSubscriptionBase refreshedSubscription = (DefaultSubscriptionBase) subscriptionInternalApi.getSubscriptionFromId(subscription.getId(), internalCallContext);
+ assertEquals(refreshedSubscription.getAllTransitions().size(), 3);
+ assertEquals(refreshedSubscription.getAllTransitions().get(0).getTransitionType(), SubscriptionBaseTransitionType.CREATE);
+ assertEquals(refreshedSubscription.getAllTransitions().get(1).getTransitionType(), SubscriptionBaseTransitionType.CHANGE);
+ assertEquals(refreshedSubscription.getAllTransitions().get(2).getTransitionType(), SubscriptionBaseTransitionType.PHASE);
+ assertEquals(refreshedSubscription.getAllTransitions().get(2).getNextPlan().getName(), "pistol-monthly");
+
+ clock.addDays(1);
+
+ testListener.pushExpectedEvent(NextEvent.UNDO_CHANGE);
+ subscription.undoChangePlan(callContext);
+ assertListenerStatus();
+
+ testListener.pushExpectedEvent(NextEvent.CREATE);
+ clock.addDays(5);
+ assertListenerStatus();
+
+ // No CHANGE_PLAN
+ clock.addDays(5);
+ assertListenerStatus();
+
+
+ // Verify PHASE event for Shotgun is active
+ testListener.pushExpectedEvent(NextEvent.PHASE);
+ clock.addDays(25);
+ assertListenerStatus();
+
+
+ refreshedSubscription = (DefaultSubscriptionBase) subscriptionInternalApi.getSubscriptionFromId(subscription.getId(), internalCallContext);
+ assertEquals(refreshedSubscription.getAllTransitions().size(), 2);
+ assertEquals(refreshedSubscription.getAllTransitions().get(0).getTransitionType(), SubscriptionBaseTransitionType.CREATE);
+ assertEquals(refreshedSubscription.getAllTransitions().get(1).getTransitionType(), SubscriptionBaseTransitionType.PHASE);
+ assertEquals(refreshedSubscription.getAllTransitions().get(1).getNextPlan().getName(), "shotgun-monthly");
+
+ }
+
+
}
diff --git a/util/src/main/java/org/killbill/billing/util/security/api/DefaultSecurityApi.java b/util/src/main/java/org/killbill/billing/util/security/api/DefaultSecurityApi.java
index 3d30a3c..33b4ed5 100644
--- a/util/src/main/java/org/killbill/billing/util/security/api/DefaultSecurityApi.java
+++ b/util/src/main/java/org/killbill/billing/util/security/api/DefaultSecurityApi.java
@@ -188,7 +188,7 @@ public class DefaultSecurityApi implements SecurityApi {
}
@Override
- public List<String> getUserRoles(final String username, final TenantContext tenantContext) {
+ public List<String> getUserRoles(final String username, final TenantContext tenantContext) throws SecurityApiException {
final List<UserRolesModelDao> permissionsModelDao = userDao.getUserRoles(username);
return ImmutableList.copyOf(Iterables.transform(permissionsModelDao, new Function<UserRolesModelDao, String>() {
@Nullable
diff --git a/util/src/main/java/org/killbill/billing/util/security/shiro/dao/DefaultUserDao.java b/util/src/main/java/org/killbill/billing/util/security/shiro/dao/DefaultUserDao.java
index 8f02fe2..82bd893 100644
--- a/util/src/main/java/org/killbill/billing/util/security/shiro/dao/DefaultUserDao.java
+++ b/util/src/main/java/org/killbill/billing/util/security/shiro/dao/DefaultUserDao.java
@@ -17,11 +17,8 @@
package org.killbill.billing.util.security.shiro.dao;
-import java.util.HashSet;
import java.util.List;
-import java.util.Set;
-import javax.annotation.Nullable;
import javax.inject.Inject;
import org.apache.shiro.crypto.RandomNumberGenerator;
@@ -34,8 +31,6 @@ import org.killbill.billing.security.SecurityApiException;
import org.killbill.billing.util.config.definition.SecurityConfig;
import org.killbill.billing.util.security.shiro.KillbillCredentialsMatcher;
import org.killbill.clock.Clock;
-import org.killbill.commons.jdbi.mapper.LowerToCamelBeanMapperFactory;
-import org.skife.jdbi.v2.DBI;
import org.skife.jdbi.v2.Handle;
import org.skife.jdbi.v2.IDBI;
import org.skife.jdbi.v2.TransactionCallback;
@@ -85,10 +80,17 @@ public class DefaultUserDao implements UserDao {
});
}
- public List<UserRolesModelDao> getUserRoles(final String username) {
- return dbi.inTransaction(new TransactionCallback<List<UserRolesModelDao>>() {
+ @Override
+ public List<UserRolesModelDao> getUserRoles(final String username) throws SecurityApiException {
+ return inTransactionWithExceptionHandling(new TransactionCallback<List<UserRolesModelDao>>() {
@Override
public List<UserRolesModelDao> inTransaction(final Handle handle, final TransactionStatus status) throws Exception {
+ final UsersSqlDao usersSqlDao = handle.attach(UsersSqlDao.class);
+ final UserModelDao userModelDao = usersSqlDao.getByUsername(username);
+ if (userModelDao == null) {
+ throw new SecurityApiException(ErrorCode.SECURITY_INVALID_USER, username);
+ }
+
final UserRolesSqlDao userRolesSqlDao = handle.attach(UserRolesSqlDao.class);
return userRolesSqlDao.getByUsername(username);
}
diff --git a/util/src/main/java/org/killbill/billing/util/security/shiro/dao/UserDao.java b/util/src/main/java/org/killbill/billing/util/security/shiro/dao/UserDao.java
index f0b4427..5e80394 100644
--- a/util/src/main/java/org/killbill/billing/util/security/shiro/dao/UserDao.java
+++ b/util/src/main/java/org/killbill/billing/util/security/shiro/dao/UserDao.java
@@ -25,7 +25,7 @@ public interface UserDao {
public void insertUser(String username, String password, List<String> roles, String createdBy) throws SecurityApiException;
- public List<UserRolesModelDao> getUserRoles(String username);
+ public List<UserRolesModelDao> getUserRoles(String username) throws SecurityApiException;
public void addRoleDefinition(String role, List<String> permissions, String createdBy) throws SecurityApiException;