killbill-aplcache
Changes
invoice/src/main/java/org/killbill/billing/invoice/provider/DefaultNoOpInvoiceProviderPlugin.java 5(+1 -4)
Details
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 c52b981..1f06e65 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
@@ -1014,6 +1014,16 @@ public class TestIntegrationBase extends BeatrixTestSuiteWithEmbeddedDB {
}
@Override
+ public List<String> getInvoicePluginNames() {
+ return defaultInvoiceConfig.getInvoicePluginNames();
+ }
+
+ @Override
+ public List<String> getInvoicePluginNames(final InternalTenantContext tenantContext) {
+ return defaultInvoiceConfig.getInvoicePluginNames();
+ }
+
+ @Override
public boolean isEmailNotificationsEnabled() {
return defaultInvoiceConfig.isEmailNotificationsEnabled();
}
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/api/InvoiceApiHelper.java b/invoice/src/main/java/org/killbill/billing/invoice/api/InvoiceApiHelper.java
index 047f073..5b8cbe7 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/api/InvoiceApiHelper.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/api/InvoiceApiHelper.java
@@ -82,10 +82,11 @@ public class InvoiceApiHelper {
final Iterable<Invoice> invoicesForPlugins = withAccountLock.prepareInvoices();
+ final InternalCallContext internalCallContext = internalCallContextFactory.createInternalCallContext(accountId, context);
final List<InvoiceModelDao> invoiceModelDaos = new LinkedList<InvoiceModelDao>();
for (final Invoice invoiceForPlugin : invoicesForPlugins) {
// Call plugin
- final List<InvoiceItem> additionalInvoiceItems = invoicePluginDispatcher.getAdditionalInvoiceItems(invoiceForPlugin, isDryRun, context);
+ final List<InvoiceItem> additionalInvoiceItems = invoicePluginDispatcher.getAdditionalInvoiceItems(invoiceForPlugin, isDryRun, context, internalCallContext);
invoiceForPlugin.addInvoiceItems(additionalInvoiceItems);
// Transformation to InvoiceModelDao
@@ -98,7 +99,6 @@ public class InvoiceApiHelper {
invoiceModelDaos.add(invoiceModelDao);
}
- final InternalCallContext internalCallContext = internalCallContextFactory.createInternalCallContext(accountId, context);
final List<InvoiceItemModelDao> createdInvoiceItems = dao.createInvoices(invoiceModelDaos, internalCallContext);
return fromInvoiceItemModelDao(createdInvoiceItems);
} catch (final LockFailedException e) {
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/config/MultiTenantInvoiceConfig.java b/invoice/src/main/java/org/killbill/billing/invoice/config/MultiTenantInvoiceConfig.java
index 45de5f6..ba2e1e0 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/config/MultiTenantInvoiceConfig.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/config/MultiTenantInvoiceConfig.java
@@ -17,6 +17,8 @@
package org.killbill.billing.invoice.config;
+import java.util.List;
+
import javax.inject.Inject;
import javax.inject.Named;
@@ -119,6 +121,20 @@ public class MultiTenantInvoiceConfig extends MultiTenantConfigBase implements I
}
@Override
+ public List<String> getInvoicePluginNames() {
+ return staticConfig.getInvoicePluginNames();
+ }
+
+ @Override
+ public List<String> getInvoicePluginNames(final InternalTenantContext tenantContext) {
+ final String result = getStringTenantConfig("getInvoicePluginNames", tenantContext);
+ if (result != null) {
+ return convertToListString(result, "getInvoicePluginNames");
+ }
+ return getInvoicePluginNames();
+ }
+
+ @Override
public boolean isInvoicingSystemEnabled() {
return staticConfig.isInvoicingSystemEnabled();
}
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/InvoiceDispatcher.java b/invoice/src/main/java/org/killbill/billing/invoice/InvoiceDispatcher.java
index d55ebac..86f9c90 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/InvoiceDispatcher.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/InvoiceDispatcher.java
@@ -363,20 +363,20 @@ public class InvoiceDispatcher {
}
private Invoice processAccountWithLockAndInputTargetDate(final UUID accountId, final LocalDate targetDate,
- final BillingEventSet billingEvents, final boolean isDryRun, final InternalCallContext context) throws InvoiceApiException {
+ final BillingEventSet billingEvents, final boolean isDryRun, final InternalCallContext internalCallContext) throws InvoiceApiException {
final ImmutableAccountData account;
try {
- account = accountApi.getImmutableAccountDataById(accountId, context);
+ account = accountApi.getImmutableAccountDataById(accountId, internalCallContext);
} catch (final AccountApiException e) {
log.error("Unable to generate invoice for accountId='{}', a future notification has NOT been recorded", accountId, e);
return null;
}
- final InvoiceWithMetadata invoiceWithMetadata = generateKillBillInvoice(account, targetDate, billingEvents, context);
+ final InvoiceWithMetadata invoiceWithMetadata = generateKillBillInvoice(account, targetDate, billingEvents, internalCallContext);
final DefaultInvoice invoice = invoiceWithMetadata.getInvoice();
// Compute future notifications
- final FutureAccountNotifications futureAccountNotifications = createNextFutureNotificationDate(invoiceWithMetadata, context);
+ final FutureAccountNotifications futureAccountNotifications = createNextFutureNotificationDate(invoiceWithMetadata, internalCallContext);
// If invoice comes back null, there is nothing new to generate, we can bail early
if (invoice == null) {
@@ -386,9 +386,9 @@ public class InvoiceDispatcher {
log.info("Generated null invoice for accountId='{}', targetDate='{}'", accountId, targetDate);
final BusInternalEvent event = new DefaultNullInvoiceEvent(accountId, clock.getUTCToday(),
- context.getAccountRecordId(), context.getTenantRecordId(), context.getUserToken());
+ internalCallContext.getAccountRecordId(), internalCallContext.getTenantRecordId(), internalCallContext.getUserToken());
- commitInvoiceAndSetFutureNotifications(account, null, futureAccountNotifications, context);
+ commitInvoiceAndSetFutureNotifications(account, null, futureAccountNotifications, internalCallContext);
postEvent(event);
}
return null;
@@ -397,7 +397,7 @@ public class InvoiceDispatcher {
boolean success = false;
try {
// Generate missing credit (> 0 for generation and < 0 for use) prior we call the plugin
- final InvoiceItem cbaItemPreInvoicePlugins = computeCBAOnExistingInvoice(invoice, context);
+ final InvoiceItem cbaItemPreInvoicePlugins = computeCBAOnExistingInvoice(invoice, internalCallContext);
DefaultInvoice tmpInvoiceForInvoicePlugins = invoice;
if (cbaItemPreInvoicePlugins != null) {
tmpInvoiceForInvoicePlugins = (DefaultInvoice) tmpInvoiceForInvoicePlugins.clone();
@@ -406,8 +406,8 @@ public class InvoiceDispatcher {
//
// Ask external invoice plugins if additional items (tax, etc) shall be added to the invoice
//
- final CallContext callContext = buildCallContext(context);
- final List<InvoiceItem> additionalInvoiceItemsFromPlugins = invoicePluginDispatcher.getAdditionalInvoiceItems(tmpInvoiceForInvoicePlugins, isDryRun, callContext);
+ final CallContext callContext = buildCallContext(internalCallContext);
+ final List<InvoiceItem> additionalInvoiceItemsFromPlugins = invoicePluginDispatcher.getAdditionalInvoiceItems(tmpInvoiceForInvoicePlugins, isDryRun, callContext, internalCallContext);
if (additionalInvoiceItemsFromPlugins.isEmpty()) {
// PERF: avoid re-computing the CBA if no change was made
if (cbaItemPreInvoicePlugins != null) {
@@ -416,7 +416,7 @@ public class InvoiceDispatcher {
} else {
invoice.addInvoiceItems(additionalInvoiceItemsFromPlugins);
// Use credit after we call the plugin (https://github.com/killbill/killbill/issues/637)
- final InvoiceItem cbaItemPostInvoicePlugins = computeCBAOnExistingInvoice(invoice, context);
+ final InvoiceItem cbaItemPostInvoicePlugins = computeCBAOnExistingInvoice(invoice, internalCallContext);
if (cbaItemPostInvoicePlugins != null) {
invoice.addInvoiceItem(cbaItemPostInvoicePlugins);
}
@@ -437,11 +437,11 @@ public class InvoiceDispatcher {
invoiceModelDao.addInvoiceItems(invoiceItemModelDaos);
// Commit invoice on disk
- commitInvoiceAndSetFutureNotifications(account, invoiceModelDao, futureAccountNotifications, context);
+ commitInvoiceAndSetFutureNotifications(account, invoiceModelDao, futureAccountNotifications, internalCallContext);
success = true;
try {
- setChargedThroughDates(invoice.getInvoiceItems(FixedPriceInvoiceItem.class), invoice.getInvoiceItems(RecurringInvoiceItem.class), context);
+ setChargedThroughDates(invoice.getInvoiceItems(FixedPriceInvoiceItem.class), invoice.getInvoiceItems(RecurringInvoiceItem.class), internalCallContext);
} catch (final SubscriptionBaseApiException e) {
log.error("Failed handling SubscriptionBase change.", e);
return null;
@@ -450,7 +450,7 @@ public class InvoiceDispatcher {
} finally {
// Make sure we always set future notifications in case of errors
if (!isDryRun && !success) {
- commitInvoiceAndSetFutureNotifications(account, null, futureAccountNotifications, context);
+ commitInvoiceAndSetFutureNotifications(account, null, futureAccountNotifications, internalCallContext);
}
}
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/InvoicePluginDispatcher.java b/invoice/src/main/java/org/killbill/billing/invoice/InvoicePluginDispatcher.java
index 82c62f5..2d175e4 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/InvoicePluginDispatcher.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/InvoicePluginDispatcher.java
@@ -21,10 +21,12 @@ import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedList;
import java.util.List;
+import java.util.Set;
import javax.inject.Inject;
import org.killbill.billing.ErrorCode;
+import org.killbill.billing.callcontext.InternalTenantContext;
import org.killbill.billing.invoice.api.Invoice;
import org.killbill.billing.invoice.api.InvoiceApiException;
import org.killbill.billing.invoice.api.InvoiceItem;
@@ -34,9 +36,11 @@ import org.killbill.billing.invoice.plugin.api.InvoicePluginApi;
import org.killbill.billing.osgi.api.OSGIServiceRegistration;
import org.killbill.billing.payment.api.PluginProperty;
import org.killbill.billing.util.callcontext.CallContext;
+import org.killbill.billing.util.config.definition.InvoiceConfig;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.ImmutableList;
public class InvoicePluginDispatcher {
@@ -49,21 +53,25 @@ public class InvoicePluginDispatcher {
InvoiceItemType.TAX);
private final OSGIServiceRegistration<InvoicePluginApi> pluginRegistry;
+ private final InvoiceConfig invoiceConfig;
+
@Inject
- public InvoicePluginDispatcher(final OSGIServiceRegistration<InvoicePluginApi> pluginRegistry) {
+ public InvoicePluginDispatcher(final OSGIServiceRegistration<InvoicePluginApi> pluginRegistry,
+ final InvoiceConfig invoiceConfig) {
this.pluginRegistry = pluginRegistry;
+ this.invoiceConfig = invoiceConfig;
}
//
// If we have multiple plugins there is a question of plugin ordering and also a 'product' questions to decide whether
// subsequent plugins should have access to items added by previous plugins
//
- public List<InvoiceItem> getAdditionalInvoiceItems(final Invoice originalInvoice, final boolean isDryRun, final CallContext callContext) throws InvoiceApiException {
+ public List<InvoiceItem> getAdditionalInvoiceItems(final Invoice originalInvoice, final boolean isDryRun, final CallContext callContext, final InternalTenantContext tenantContext) throws InvoiceApiException {
// We clone the original invoice so plugins don't remove/add items
final Invoice clonedInvoice = (Invoice) ((DefaultInvoice) originalInvoice).clone();
final List<InvoiceItem> additionalInvoiceItems = new LinkedList<InvoiceItem>();
- final List<InvoicePluginApi> invoicePlugins = getInvoicePlugins();
+ final List<InvoicePluginApi> invoicePlugins = getInvoicePlugins(tenantContext);
for (final InvoicePluginApi invoicePlugin : invoicePlugins) {
final List<InvoiceItem> items = invoicePlugin.getAdditionalInvoiceItems(clonedInvoice, isDryRun, ImmutableList.<PluginProperty>of(), callContext);
if (items != null) {
@@ -83,11 +91,34 @@ public class InvoicePluginDispatcher {
}
}
- private List<InvoicePluginApi> getInvoicePlugins() {
+ private List<InvoicePluginApi> getInvoicePlugins(final InternalTenantContext tenantContext) {
+
+
+ final Collection<String> resultingPluginList = getResultingPluginNameList(tenantContext);
+
final List<InvoicePluginApi> invoicePlugins = new ArrayList<InvoicePluginApi>();
- for (final String name : pluginRegistry.getAllServices()) {
- invoicePlugins.add(pluginRegistry.getServiceForName(name));
+ for (final String name : resultingPluginList) {
+ final InvoicePluginApi serviceForName = pluginRegistry.getServiceForName(name);
+ invoicePlugins.add(serviceForName);
}
return invoicePlugins;
}
+
+ @VisibleForTesting
+ final Collection<String> getResultingPluginNameList(final InternalTenantContext tenantContext) {
+ final List<String> configuredPlugins = invoiceConfig.getInvoicePluginNames(tenantContext);
+ final Set<String> registeredPlugins = pluginRegistry.getAllServices();
+ // No configuration, we return undeterministic list of registered plugins
+ if (configuredPlugins == null || configuredPlugins.isEmpty()) {
+ return registeredPlugins;
+ } else {
+ final List<String> result = new ArrayList<String>(configuredPlugins.size());
+ for (final String name : configuredPlugins) {
+ if (pluginRegistry.getServiceForName(name) != null) {
+ result.add(name);
+ }
+ }
+ return result;
+ }
+ }
}
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/provider/DefaultNoOpInvoiceProviderPlugin.java b/invoice/src/main/java/org/killbill/billing/invoice/provider/DefaultNoOpInvoiceProviderPlugin.java
index 6b2883d..503d5e9 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/provider/DefaultNoOpInvoiceProviderPlugin.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/provider/DefaultNoOpInvoiceProviderPlugin.java
@@ -30,11 +30,8 @@ import com.google.inject.Inject;
public class DefaultNoOpInvoiceProviderPlugin implements NoOpInvoicePluginApi {
- private final Clock clock;
-
@Inject
- public DefaultNoOpInvoiceProviderPlugin(final Clock clock) {
- this.clock = clock;
+ public DefaultNoOpInvoiceProviderPlugin() {
}
@Override
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/provider/NoOpInvoiceProviderPluginProvider.java b/invoice/src/main/java/org/killbill/billing/invoice/provider/NoOpInvoiceProviderPluginProvider.java
index 1901ffb..c4444af 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/provider/NoOpInvoiceProviderPluginProvider.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/provider/NoOpInvoiceProviderPluginProvider.java
@@ -45,7 +45,7 @@ public class NoOpInvoiceProviderPluginProvider implements Provider<DefaultNoOpIn
@Override
public DefaultNoOpInvoiceProviderPlugin get() {
- final DefaultNoOpInvoiceProviderPlugin plugin = new DefaultNoOpInvoiceProviderPlugin(clock);
+ final DefaultNoOpInvoiceProviderPlugin plugin = new DefaultNoOpInvoiceProviderPlugin();
final OSGIServiceDescriptor desc = new OSGIServiceDescriptor() {
@Override
public String getPluginSymbolicName() {
diff --git a/invoice/src/test/java/org/killbill/billing/invoice/TestInvoicePluginDispatcher.java b/invoice/src/test/java/org/killbill/billing/invoice/TestInvoicePluginDispatcher.java
new file mode 100644
index 0000000..78d1b74
--- /dev/null
+++ b/invoice/src/test/java/org/killbill/billing/invoice/TestInvoicePluginDispatcher.java
@@ -0,0 +1,139 @@
+/*
+ * Copyright 2014-2017 Groupon, Inc
+ * Copyright 2014-2017 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.
+ */
+
+package org.killbill.billing.invoice;
+
+import java.util.Collection;
+import java.util.Iterator;
+
+import org.killbill.billing.callcontext.InternalCallContext;
+import org.killbill.billing.invoice.plugin.api.InvoicePluginApi;
+import org.killbill.billing.invoice.provider.DefaultNoOpInvoiceProviderPlugin;
+import org.killbill.billing.osgi.api.OSGIServiceDescriptor;
+import org.killbill.billing.osgi.api.OSGIServiceRegistration;
+import org.killbill.billing.platform.api.KillbillConfigSource;
+import org.killbill.billing.tenant.api.TenantInternalApi;
+import org.mockito.Mockito;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import com.google.common.base.Joiner;
+import com.google.common.collect.ImmutableMap;
+import com.google.inject.Inject;
+
+import static org.testng.Assert.assertEquals;
+
+public class TestInvoicePluginDispatcher extends InvoiceTestSuiteNoDB {
+
+ private final String PLUGIN_1 = "plugin1";
+ private final String PLUGIN_2 = "plugin2";
+ private final String PLUGIN_3 = "plugin3";
+
+ @Inject
+ protected InvoicePluginDispatcher invoicePluginDispatcher;
+ @Inject
+ OSGIServiceRegistration<InvoicePluginApi> pluginRegistry;
+
+ @Inject
+ TenantInternalApi tenantInternalApi;
+
+ protected KillbillConfigSource getConfigSource() {
+ return getConfigSource("/resource.properties", ImmutableMap.<String, String>builder()
+ .put("org.killbill.invoice.plugin", Joiner.on(",").join(PLUGIN_1, PLUGIN_2))
+ .build());
+ }
+
+ @Override
+ @BeforeMethod(groups = "fast")
+ public void beforeMethod() {
+ super.beforeMethod();
+ for (final String name : pluginRegistry.getAllServices()) {
+ pluginRegistry.unregisterService(name);
+ }
+ }
+
+ @Test(groups = "fast")
+ public void testWithNoConfig() throws Exception {
+
+ // We Use the per-tenant config and specify a empty list of plugins
+ Mockito.when(tenantInternalApi.getTenantConfig(Mockito.any(InternalCallContext.class))).thenReturn("{\"org.killbill.invoice.plugin\":\"\"}");
+ // We register one plugin
+ registerPlugin(PLUGIN_1);
+
+ final Collection<String> result = invoicePluginDispatcher.getResultingPluginNameList(internalCallContext);
+ // Se expect to seee the list of registered plugins
+ assertEquals(result.size(), 1);
+ final Iterator<String> iterator = result.iterator();
+ assertEquals(iterator.next(), PLUGIN_1);
+ }
+
+ @Test(groups = "fast")
+ public void testWithNoRegistration() throws Exception {
+ // Nothing has been registered, we see nothing
+ final Collection<String> result = invoicePluginDispatcher.getResultingPluginNameList(internalCallContext);
+ assertEquals(result.size(), 0);
+ }
+
+ @Test(groups = "fast")
+ public void testWithCorrectOrder() throws Exception {
+ // 3 plugins registered in correct order but only 2 got specified in config
+ registerPlugin(PLUGIN_1);
+ registerPlugin(PLUGIN_2);
+ registerPlugin(PLUGIN_3);
+
+ final Collection<String> result = invoicePluginDispatcher.getResultingPluginNameList(internalCallContext);
+ assertEquals(result.size(), 2);
+ final Iterator<String> iterator = result.iterator();
+ assertEquals(iterator.next(), PLUGIN_1);
+ assertEquals(iterator.next(), PLUGIN_2);
+ }
+
+ @Test(groups = "fast")
+ public void testWithIncorrectCorrectOrder() throws Exception {
+
+ // 3 plugins registered in *incorrect* order and only 2 got specified in config
+ registerPlugin(PLUGIN_2);
+ registerPlugin(PLUGIN_3);
+ registerPlugin(PLUGIN_1);
+
+ final Collection<String> result = invoicePluginDispatcher.getResultingPluginNameList(internalCallContext);
+ assertEquals(result.size(), 2);
+ final Iterator<String> iterator = result.iterator();
+ assertEquals(iterator.next(), PLUGIN_1);
+ assertEquals(iterator.next(), PLUGIN_2);
+ }
+
+
+ private void registerPlugin(final String plugin) {
+ pluginRegistry.registerService(new OSGIServiceDescriptor() {
+ @Override
+ public String getPluginSymbolicName() {
+ return plugin;
+ }
+
+ @Override
+ public String getPluginName() {
+ return plugin;
+ }
+
+ @Override
+ public String getRegistrationName() {
+ return plugin;
+ }
+ }, new DefaultNoOpInvoiceProviderPlugin());
+ }
+}
\ No newline at end of file
diff --git a/util/src/main/java/org/killbill/billing/util/config/definition/InvoiceConfig.java b/util/src/main/java/org/killbill/billing/util/config/definition/InvoiceConfig.java
index 8170bd6..7ed6ac9 100644
--- a/util/src/main/java/org/killbill/billing/util/config/definition/InvoiceConfig.java
+++ b/util/src/main/java/org/killbill/billing/util/config/definition/InvoiceConfig.java
@@ -17,6 +17,8 @@
package org.killbill.billing.util.config.definition;
+import java.util.List;
+
import org.killbill.billing.callcontext.InternalTenantContext;
import org.skife.config.Config;
import org.skife.config.Default;
@@ -81,6 +83,16 @@ public interface InvoiceConfig extends KillbillConfig {
@Description("Maximum number of times the system will retry to grab global lock (with a 100ms wait each time)")
int getMaxGlobalLockRetries();
+ @Config("org.killbill.invoice.plugin")
+ @Default("")
+ @Description("Default invoice plugin names")
+ List<String> getInvoicePluginNames();
+
+ @Config("org.killbill.invoice.plugin")
+ @Default("")
+ @Description("Default invoice plugin names")
+ List<String> getInvoicePluginNames(@Param("dummy") final InternalTenantContext tenantContext);
+
@Config("org.killbill.invoice.emailNotificationsEnabled")
@Default("false")
@Description("Whether to send email notifications on invoice creation (for configured accounts)")
diff --git a/util/src/main/java/org/killbill/billing/util/config/tenant/MultiTenantConfigBase.java b/util/src/main/java/org/killbill/billing/util/config/tenant/MultiTenantConfigBase.java
index 39562ee..10c9e38 100644
--- a/util/src/main/java/org/killbill/billing/util/config/tenant/MultiTenantConfigBase.java
+++ b/util/src/main/java/org/killbill/billing/util/config/tenant/MultiTenantConfigBase.java
@@ -108,7 +108,11 @@ public abstract class MultiTenantConfigBase {
private List<String> getTokens(final Method method, final String value) {
final Separator separator = method.getAnnotation(Separator.class);
- return ImmutableList.copyOf(value.split(separator == null ? Separator.DEFAULT : separator.value()));
+ if (value == null || value.isEmpty()) {
+ return ImmutableList.of();
+ } else {
+ return ImmutableList.copyOf(value.split(separator == null ? Separator.DEFAULT : separator.value()));
+ }
}
protected Method getConfigStaticMethod(final String methodName) {