killbill-memoizeit
Changes
.gitignore 1(+1 -0)
.idea/compiler.xml 116(+4 -112)
account/pom.xml 2(+1 -1)
account/src/main/java/org/killbill/billing/account/api/svcs/DefaultAccountInternalApi.java 12(+6 -6)
account/src/main/java/org/killbill/billing/account/api/svcs/DefaultImmutableAccountInternalApi.java 16(+8 -8)
account/src/test/java/org/killbill/billing/account/api/user/TestDefaultAccountUserApiWithMocks.java 6(+3 -3)
api/pom.xml 2(+1 -1)
beatrix/pom.xml 2(+1 -1)
catalog/pom.xml 2(+1 -1)
currency/pom.xml 2(+1 -1)
entitlement/pom.xml 2(+1 -1)
entitlement/src/main/java/org/killbill/billing/entitlement/dao/DefaultBlockingStateDao.java 16(+8 -8)
entitlement/src/test/java/org/killbill/billing/entitlement/api/TestDefaultEntitlementApi.java 88(+88 -0)
invoice/pom.xml 2(+1 -1)
invoice/src/main/java/org/killbill/billing/invoice/template/bundles/DefaultResourceBundleFactory.java 2(+1 -1)
invoice/src/test/java/org/killbill/billing/invoice/notification/TestNextBillingDateNotifier.java 22(+12 -10)
jaxrs/pom.xml 6(+1 -5)
junction/pom.xml 2(+1 -1)
NEWS 6(+6 -0)
overdue/pom.xml 2(+1 -1)
overdue/src/test/java/org/killbill/billing/overdue/notification/TestOverdueCheckNotifier.java 25(+11 -14)
payment/pom.xml 6(+1 -5)
payment/src/main/java/org/killbill/billing/payment/caching/EhCacheStateMachineConfigCache.java 17(+9 -8)
payment/src/main/java/org/killbill/billing/payment/caching/SerializableStateMachineConfig.java 80(+80 -0)
payment/src/main/java/org/killbill/billing/payment/core/sm/PaymentAutomatonDAOHelper.java 35(+18 -17)
payment/src/main/java/org/killbill/billing/payment/dao/PaymentAndTransactionModelDao.java 40(+40 -0)
payment/src/test/java/org/killbill/billing/payment/caching/TestStateMachineConfigCache.java 10(+5 -5)
payment/src/test/java/org/killbill/billing/payment/caching/TestStateMachineConfigCacheInvalidationCallback.java 11(+5 -6)
payment/src/test/java/org/killbill/billing/payment/core/sm/MockRetryAuthorizeOperationCallback.java 2(+1 -1)
payment/src/test/java/org/killbill/billing/payment/core/TestPaymentMethodProcessorWithDB.java 49(+47 -2)
pom.xml 2(+1 -1)
profiles/killbill/pom.xml 10(+5 -5)
profiles/killbill/src/main/java/org/killbill/billing/server/log/obfuscators/PatternObfuscator.java 3(+2 -1)
profiles/killbill/src/main/java/org/killbill/billing/server/modules/KillbillJdbcTenantRealmProvider.java 12(+3 -9)
profiles/killbill/src/main/java/org/killbill/billing/server/modules/KillBillShiroWebModule.java 40(+7 -33)
profiles/killbill/src/main/java/org/killbill/billing/server/security/KillbillJdbcTenantRealm.java 69(+66 -3)
profiles/killbill/src/main/java/org/killbill/billing/server/security/KillBillWebSessionManager.java 48(+48 -0)
profiles/killbill/src/test/java/org/killbill/billing/server/log/obfuscators/TestPatternObfuscator.java 8(+8 -0)
profiles/killpay/pom.xml 2(+1 -1)
profiles/pom.xml 2(+1 -1)
subscription/pom.xml 2(+1 -1)
subscription/src/main/java/org/killbill/billing/subscription/api/svcs/DefaultSubscriptionInternalApi.java 70(+58 -12)
subscription/src/main/java/org/killbill/billing/subscription/api/user/DefaultSubscriptionBaseApiService.java 43(+10 -33)
subscription/src/main/java/org/killbill/billing/subscription/engine/dao/DefaultSubscriptionDao.java 33(+16 -17)
subscription/src/test/java/org/killbill/billing/subscription/api/user/TestUserApiError.java 10(+8 -2)
subscription/src/test/java/org/killbill/billing/subscription/SubscriptionTestSuiteNoDB.java 15(+13 -2)
tenant/pom.xml 2(+1 -1)
usage/pom.xml 2(+1 -1)
util/pom.xml 31(+22 -9)
util/src/main/java/org/killbill/billing/util/cache/CacheControllerDispatcherProvider.java 52(+19 -33)
util/src/main/java/org/killbill/billing/util/cache/TenantStateMachineConfigCacheLoader.java 20(+5 -15)
util/src/main/java/org/killbill/billing/util/callcontext/InternalCallContextFactory.java 107(+67 -40)
util/src/main/java/org/killbill/billing/util/entity/dao/EntitySqlDaoTransactionalJdbiWrapper.java 4(+4 -0)
util/src/main/java/org/killbill/billing/util/entity/dao/EntitySqlDaoWrapperInvocationHandler.java 169(+97 -72)
util/src/main/resources/ehcache.xml 259(+40 -219)
util/src/test/java/org/killbill/billing/util/callcontext/TestInternalCallContextFactory.java 15(+13 -2)
util/src/test/java/org/killbill/billing/util/customfield/api/TestDefaultCustomFieldUserApi.java 10(+8 -2)
Details
.gitignore 1(+1 -0)
diff --git a/.gitignore b/.gitignore
index 5609cd9..fba5383 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,5 +1,6 @@
.idea/workspace.xml
.idea/libraries
+.idea/kotlinc.xml
*.ipr
*.iws
*.DS_Store
.idea/compiler.xml 116(+4 -112)
diff --git a/.idea/compiler.xml b/.idea/compiler.xml
index 9b1b3d8..d6c37f8 100644
--- a/.idea/compiler.xml
+++ b/.idea/compiler.xml
@@ -1,8 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="CompilerConfiguration">
- <option name="DEFAULT_COMPILER" value="Javac" />
- <resourceExtensions />
<wildcardResourcePatterns>
<entry name="!?*.java" />
<entry name="!?*.form" />
@@ -14,135 +12,29 @@
<entry name="!?*.clj" />
</wildcardResourcePatterns>
<annotationProcessing>
- <profile default="true" name="Default" enabled="false">
- <processorPath useClasspath="true" />
- </profile>
- <profile default="false" name="Maven default annotation processors profile" enabled="true">
+ <profile name="Maven default annotation processors profile" enabled="true">
<sourceOutputDir name="target/generated-sources/annotations" />
<sourceTestOutputDir name="target/generated-test-sources/test-annotations" />
<outputRelativeToContentRoot value="true" />
- <processorPath useClasspath="true" />
<module name="currency" />
- </profile>
- <profile default="false" name="Annotation profile for killbill-account" enabled="true">
- <sourceOutputDir name="target/generated-sources/annotations" />
- <sourceTestOutputDir name="target/generated-test-sources/test-annotations" />
- <outputRelativeToContentRoot value="true" />
- <processorPath useClasspath="true" />
<module name="killbill-account" />
- </profile>
- <profile default="false" name="Annotation profile for killbill-internal-api" enabled="true">
- <sourceOutputDir name="target/generated-sources/annotations" />
- <sourceTestOutputDir name="target/generated-test-sources/test-annotations" />
- <outputRelativeToContentRoot value="true" />
- <processorPath useClasspath="true" />
- <module name="killbill-internal-api" />
- </profile>
- <profile default="false" name="Annotation profile for killbill-beatrix" enabled="true">
- <sourceOutputDir name="target/generated-sources/annotations" />
- <sourceTestOutputDir name="target/generated-test-sources/test-annotations" />
- <outputRelativeToContentRoot value="true" />
- <processorPath useClasspath="true" />
<module name="killbill-beatrix" />
- </profile>
- <profile default="false" name="Annotation profile for killbill-catalog" enabled="true">
- <sourceOutputDir name="target/generated-sources/annotations" />
- <sourceTestOutputDir name="target/generated-test-sources/test-annotations" />
- <outputRelativeToContentRoot value="true" />
- <processorPath useClasspath="true" />
<module name="killbill-catalog" />
- </profile>
- <profile default="false" name="Annotation profile for killbill-currency" enabled="true">
- <sourceOutputDir name="target/generated-sources/annotations" />
- <sourceTestOutputDir name="target/generated-test-sources/test-annotations" />
- <outputRelativeToContentRoot value="true" />
- <processorPath useClasspath="true" />
<module name="killbill-currency" />
- </profile>
- <profile default="false" name="Annotation profile for killbill-entitlement" enabled="true">
- <sourceOutputDir name="target/generated-sources/annotations" />
- <sourceTestOutputDir name="target/generated-test-sources/test-annotations" />
- <outputRelativeToContentRoot value="true" />
- <processorPath useClasspath="true" />
<module name="killbill-entitlement" />
- </profile>
- <profile default="false" name="Annotation profile for killbill-invoice" enabled="true">
- <sourceOutputDir name="target/generated-sources/annotations" />
- <sourceTestOutputDir name="target/generated-test-sources/test-annotations" />
- <outputRelativeToContentRoot value="true" />
- <processorPath useClasspath="true" />
+ <module name="killbill-internal-api" />
<module name="killbill-invoice" />
- </profile>
- <profile default="false" name="Annotation profile for killbill-jaxrs" enabled="true">
- <sourceOutputDir name="target/generated-sources/annotations" />
- <sourceTestOutputDir name="target/generated-test-sources/test-annotations" />
- <outputRelativeToContentRoot value="true" />
- <processorPath useClasspath="true" />
<module name="killbill-jaxrs" />
- </profile>
- <profile default="false" name="Annotation profile for killbill-junction" enabled="true">
- <sourceOutputDir name="target/generated-sources/annotations" />
- <sourceTestOutputDir name="target/generated-test-sources/test-annotations" />
- <outputRelativeToContentRoot value="true" />
- <processorPath useClasspath="true" />
<module name="killbill-junction" />
- </profile>
- <profile default="false" name="Annotation profile for killbill-overdue" enabled="true">
- <sourceOutputDir name="target/generated-sources/annotations" />
- <sourceTestOutputDir name="target/generated-test-sources/test-annotations" />
- <outputRelativeToContentRoot value="true" />
- <processorPath useClasspath="true" />
<module name="killbill-overdue" />
- </profile>
- <profile default="false" name="Annotation profile for killbill-payment" enabled="true">
- <sourceOutputDir name="target/generated-sources/annotations" />
- <sourceTestOutputDir name="target/generated-test-sources/test-annotations" />
- <outputRelativeToContentRoot value="true" />
- <processorPath useClasspath="true" />
<module name="killbill-payment" />
- </profile>
- <profile default="false" name="Annotation profile for killbill-subscription" enabled="true">
- <sourceOutputDir name="target/generated-sources/annotations" />
- <sourceTestOutputDir name="target/generated-test-sources/test-annotations" />
- <outputRelativeToContentRoot value="true" />
- <processorPath useClasspath="true" />
+ <module name="killbill-profiles-killbill" />
+ <module name="killbill-profiles-killpay" />
<module name="killbill-subscription" />
- </profile>
- <profile default="false" name="Annotation profile for killbill-tenant" enabled="true">
- <sourceOutputDir name="target/generated-sources/annotations" />
- <sourceTestOutputDir name="target/generated-test-sources/test-annotations" />
- <outputRelativeToContentRoot value="true" />
- <processorPath useClasspath="true" />
<module name="killbill-tenant" />
- </profile>
- <profile default="false" name="Annotation profile for killbill-usage" enabled="true">
- <sourceOutputDir name="target/generated-sources/annotations" />
- <sourceTestOutputDir name="target/generated-test-sources/test-annotations" />
- <outputRelativeToContentRoot value="true" />
- <processorPath useClasspath="true" />
<module name="killbill-usage" />
- </profile>
- <profile default="false" name="Annotation profile for killbill-util" enabled="true">
- <sourceOutputDir name="target/generated-sources/annotations" />
- <sourceTestOutputDir name="target/generated-test-sources/test-annotations" />
- <outputRelativeToContentRoot value="true" />
- <processorPath useClasspath="true" />
<module name="killbill-util" />
</profile>
- <profile default="false" name="Annotation profile for killbill-profiles-killbill" enabled="true">
- <sourceOutputDir name="target/generated-sources/annotations" />
- <sourceTestOutputDir name="target/generated-test-sources/test-annotations" />
- <outputRelativeToContentRoot value="true" />
- <processorPath useClasspath="true" />
- <module name="killbill-profiles-killbill" />
- </profile>
- <profile default="false" name="Annotation profile for killbill-profiles-killpay" enabled="true">
- <sourceOutputDir name="target/generated-sources/annotations" />
- <sourceTestOutputDir name="target/generated-test-sources/test-annotations" />
- <outputRelativeToContentRoot value="true" />
- <processorPath useClasspath="true" />
- <module name="killbill-profiles-killpay" />
- </profile>
</annotationProcessing>
<bytecodeTargetLevel>
<module name="currency" target="1.6" />
account/pom.xml 2(+1 -1)
diff --git a/account/pom.xml b/account/pom.xml
index d27448c..0059a35 100644
--- a/account/pom.xml
+++ b/account/pom.xml
@@ -19,7 +19,7 @@
<parent>
<artifactId>killbill</artifactId>
<groupId>org.kill-bill.billing</groupId>
- <version>0.18.5-SNAPSHOT</version>
+ <version>0.18.7-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>killbill-account</artifactId>
diff --git a/account/src/main/java/org/killbill/billing/account/api/DefaultImmutableAccountData.java b/account/src/main/java/org/killbill/billing/account/api/DefaultImmutableAccountData.java
index 3bd4afc..7e24a22 100644
--- a/account/src/main/java/org/killbill/billing/account/api/DefaultImmutableAccountData.java
+++ b/account/src/main/java/org/killbill/billing/account/api/DefaultImmutableAccountData.java
@@ -17,28 +17,41 @@
package org.killbill.billing.account.api;
+import java.io.Externalizable;
+import java.io.IOException;
+import java.io.ObjectInput;
+import java.io.ObjectOutput;
import java.util.UUID;
import org.joda.time.DateTime;
import org.joda.time.DateTimeZone;
+import org.killbill.billing.account.dao.AccountModelDao;
import org.killbill.billing.catalog.api.Currency;
import org.killbill.billing.util.account.AccountDateTimeUtils;
+import org.killbill.billing.util.cache.ExternalizableInput;
+import org.killbill.billing.util.cache.ExternalizableOutput;
+import org.killbill.billing.util.cache.MapperHolder;
-public class DefaultImmutableAccountData implements ImmutableAccountData {
+public class DefaultImmutableAccountData implements ImmutableAccountData, Externalizable {
- private final UUID id;
- private final String externalKey;
- private final Currency currency;
- private final DateTimeZone dateTimeZone;
- private final DateTimeZone fixedOffsetDateTimeZone;
- private final DateTime referenceTime;
+ private static final long serialVersionUID = 8117686452347277415L;
- public DefaultImmutableAccountData(final UUID id, final String externalKey, final Currency currency, final DateTimeZone dateTimeZone, final DateTimeZone fixedOffsetDateTimeZone, final DateTime referenceTime) {
+ private UUID id;
+ private String externalKey;
+ private Currency currency;
+ private DateTimeZone timeZone;
+ private DateTimeZone fixedOffsetTimeZone;
+ private DateTime referenceTime;
+
+ // For deserialization
+ public DefaultImmutableAccountData() {}
+
+ public DefaultImmutableAccountData(final UUID id, final String externalKey, final Currency currency, final DateTimeZone timeZone, final DateTimeZone fixedOffsetTimeZone, final DateTime referenceTime) {
this.id = id;
this.externalKey = externalKey;
this.currency = currency;
- this.dateTimeZone = dateTimeZone;
- this.fixedOffsetDateTimeZone = fixedOffsetDateTimeZone;
+ this.timeZone = timeZone;
+ this.fixedOffsetTimeZone = fixedOffsetTimeZone;
this.referenceTime = referenceTime;
}
@@ -51,6 +64,15 @@ public class DefaultImmutableAccountData implements ImmutableAccountData {
AccountDateTimeUtils.getReferenceDateTime(account));
}
+ public DefaultImmutableAccountData(final AccountModelDao account) {
+ this(account.getId(),
+ account.getExternalKey(),
+ account.getCurrency(),
+ account.getTimeZone(),
+ AccountDateTimeUtils.getFixedOffsetTimeZone(account),
+ AccountDateTimeUtils.getReferenceDateTime(account));
+ }
+
@Override
public UUID getId() {
return id;
@@ -68,7 +90,7 @@ public class DefaultImmutableAccountData implements ImmutableAccountData {
@Override
public DateTimeZone getTimeZone() {
- return dateTimeZone;
+ return timeZone;
}
@Override
@@ -86,7 +108,7 @@ public class DefaultImmutableAccountData implements ImmutableAccountData {
}
public DateTimeZone getFixedOffsetTimeZone() {
- return fixedOffsetDateTimeZone;
+ return fixedOffsetTimeZone;
}
@Override
@@ -100,8 +122,8 @@ public class DefaultImmutableAccountData implements ImmutableAccountData {
sb.append("id=").append(id);
sb.append(", externalKey='").append(externalKey).append('\'');
sb.append(", currency=").append(currency);
- sb.append(", dateTimeZone=").append(dateTimeZone);
- sb.append(", fixedOffsetDateTimeZone=").append(fixedOffsetDateTimeZone);
+ sb.append(", timeZone=").append(timeZone);
+ sb.append(", fixedOffsetTimeZone=").append(fixedOffsetTimeZone);
sb.append(", referenceTime=").append(referenceTime);
sb.append('}');
return sb.toString();
@@ -127,10 +149,10 @@ public class DefaultImmutableAccountData implements ImmutableAccountData {
if (currency != that.currency) {
return false;
}
- if (dateTimeZone != null ? !dateTimeZone.equals(that.dateTimeZone) : that.dateTimeZone != null) {
+ if (timeZone != null ? !timeZone.equals(that.timeZone) : that.timeZone != null) {
return false;
}
- if (fixedOffsetDateTimeZone != null ? !fixedOffsetDateTimeZone.equals(that.fixedOffsetDateTimeZone) : that.fixedOffsetDateTimeZone != null) {
+ if (fixedOffsetTimeZone != null ? !fixedOffsetTimeZone.equals(that.fixedOffsetTimeZone) : that.fixedOffsetTimeZone != null) {
return false;
}
return referenceTime != null ? referenceTime.compareTo(that.referenceTime) == 0 : that.referenceTime == null;
@@ -141,9 +163,19 @@ public class DefaultImmutableAccountData implements ImmutableAccountData {
int result = id != null ? id.hashCode() : 0;
result = 31 * result + (externalKey != null ? externalKey.hashCode() : 0);
result = 31 * result + (currency != null ? currency.hashCode() : 0);
- result = 31 * result + (dateTimeZone != null ? dateTimeZone.hashCode() : 0);
- result = 31 * result + (fixedOffsetDateTimeZone != null ? fixedOffsetDateTimeZone.hashCode() : 0);
+ result = 31 * result + (timeZone != null ? timeZone.hashCode() : 0);
+ result = 31 * result + (fixedOffsetTimeZone != null ? fixedOffsetTimeZone.hashCode() : 0);
result = 31 * result + (referenceTime != null ? referenceTime.hashCode() : 0);
return result;
}
+
+ @Override
+ public void readExternal(final ObjectInput in) throws IOException {
+ MapperHolder.mapper().readerForUpdating(this).readValue(new ExternalizableInput(in));
+ }
+
+ @Override
+ public void writeExternal(final ObjectOutput oo) throws IOException {
+ MapperHolder.mapper().writeValue(new ExternalizableOutput(oo), this);
+ }
}
diff --git a/account/src/main/java/org/killbill/billing/account/api/svcs/DefaultAccountInternalApi.java b/account/src/main/java/org/killbill/billing/account/api/svcs/DefaultAccountInternalApi.java
index c9cc74a..ba32fd5 100644
--- a/account/src/main/java/org/killbill/billing/account/api/svcs/DefaultAccountInternalApi.java
+++ b/account/src/main/java/org/killbill/billing/account/api/svcs/DefaultAccountInternalApi.java
@@ -1,7 +1,7 @@
/*
* Copyright 2010-2013 Ning, Inc.
- * Copyright 2014-2016 Groupon, Inc
- * Copyright 2014-2016 The Billing Project, LLC
+ * 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
@@ -56,7 +56,7 @@ public class DefaultAccountInternalApi extends DefaultAccountApiBase implements
private final ImmutableAccountInternalApi immutableAccountInternalApi;
private final AccountDao accountDao;
- private final CacheController bcdCacheController;
+ private final CacheController<UUID, Integer> bcdCacheController;
@Inject
public DefaultAccountInternalApi(final ImmutableAccountInternalApi immutableAccountInternalApi,
@@ -106,7 +106,7 @@ public class DefaultAccountInternalApi extends DefaultAccountApiBase implements
@Override
public int getBCD(final UUID accountId, final InternalTenantContext context) throws AccountApiException {
final CacheLoaderArgument arg = createBCDCacheLoaderArgument(context);
- final Integer result = (Integer) bcdCacheController.get(accountId, arg);
+ final Integer result = bcdCacheController.get(accountId, arg);
return result != null ? result : DefaultMutableAccountData.DEFAULT_BILLING_CYCLE_DAY_LOCAL;
}
@@ -160,8 +160,8 @@ public class DefaultAccountInternalApi extends DefaultAccountApiBase implements
private CacheLoaderArgument createBCDCacheLoaderArgument(final InternalTenantContext context) {
final AccountBCDCacheLoader.LoaderCallback loaderCallback = new AccountBCDCacheLoader.LoaderCallback() {
@Override
- public Object loadAccountBCD(final UUID accountId, final InternalTenantContext context) {
- Object result = accountDao.getAccountBCD(accountId, context);
+ public Integer loadAccountBCD(final UUID accountId, final InternalTenantContext context) {
+ Integer result = accountDao.getAccountBCD(accountId, context);
if (result != null) {
// If the value is 0, then account BCD was not set so we don't want to create a cache entry
result = result.equals(DefaultMutableAccountData.DEFAULT_BILLING_CYCLE_DAY_LOCAL) ? null : result;
diff --git a/account/src/main/java/org/killbill/billing/account/api/svcs/DefaultImmutableAccountInternalApi.java b/account/src/main/java/org/killbill/billing/account/api/svcs/DefaultImmutableAccountInternalApi.java
index 250d47a..4bdf0c6 100644
--- a/account/src/main/java/org/killbill/billing/account/api/svcs/DefaultImmutableAccountInternalApi.java
+++ b/account/src/main/java/org/killbill/billing/account/api/svcs/DefaultImmutableAccountInternalApi.java
@@ -1,6 +1,6 @@
/*
- * Copyright 2016 Groupon, Inc
- * Copyright 2016 The Billing Project, LLC
+ * Copyright 2016-2017 Groupon, Inc
+ * Copyright 2016-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
@@ -48,8 +48,8 @@ public class DefaultImmutableAccountInternalApi implements ImmutableAccountInter
private final EntitySqlDaoTransactionalJdbiWrapper transactionalSqlDao;
private final NonEntityDao nonEntityDao;
- private final CacheControllerDispatcher cacheControllerDispatcher;
- private final CacheController accountCacheController;
+ private final CacheController<Long, ImmutableAccountData> accountCacheController;
+ private final CacheController<String, Long> recordIdCacheController;
@Inject
public DefaultImmutableAccountInternalApi(final IDBI dbi,
@@ -59,26 +59,26 @@ public class DefaultImmutableAccountInternalApi implements ImmutableAccountInter
// This API will directly issue queries instead of relying on the DAO (introduced to avoid Guice circular dependencies with InternalCallContextFactory)
this.transactionalSqlDao = new EntitySqlDaoTransactionalJdbiWrapper(dbi, clock, cacheControllerDispatcher, nonEntityDao, null);
this.nonEntityDao = nonEntityDao;
- this.cacheControllerDispatcher = cacheControllerDispatcher;
this.accountCacheController = cacheControllerDispatcher.getCacheController(CacheType.ACCOUNT_IMMUTABLE);
+ this.recordIdCacheController = cacheControllerDispatcher.getCacheController(CacheType.RECORD_ID);
}
@Override
public ImmutableAccountData getImmutableAccountDataById(final UUID accountId, final InternalTenantContext context) throws AccountApiException {
- final Long recordId = nonEntityDao.retrieveRecordIdFromObject(accountId, ObjectType.ACCOUNT, cacheControllerDispatcher.getCacheController(CacheType.RECORD_ID));
+ final Long recordId = nonEntityDao.retrieveRecordIdFromObject(accountId, ObjectType.ACCOUNT, recordIdCacheController);
return getImmutableAccountDataByRecordId(recordId, context);
}
@Override
public ImmutableAccountData getImmutableAccountDataByRecordId(final Long recordId, final InternalTenantContext context) throws AccountApiException {
final CacheLoaderArgument arg = createImmutableAccountCacheLoaderArgument(context);
- return (ImmutableAccountData) accountCacheController.get(recordId, arg);
+ return accountCacheController.get(recordId, arg);
}
private CacheLoaderArgument createImmutableAccountCacheLoaderArgument(final InternalTenantContext context) {
final LoaderCallback loaderCallback = new LoaderCallback() {
@Override
- public Object loadAccount(final Long recordId, final InternalTenantContext context) {
+ public ImmutableAccountData loadAccount(final Long recordId, final InternalTenantContext context) {
final Account account = getAccountByRecordIdInternal(recordId, context);
return account != null ? new DefaultImmutableAccountData(account) : null;
}
diff --git a/account/src/main/java/org/killbill/billing/account/api/user/DefaultAccountApiBase.java b/account/src/main/java/org/killbill/billing/account/api/user/DefaultAccountApiBase.java
index 7792ab4..a77c9e7 100644
--- a/account/src/main/java/org/killbill/billing/account/api/user/DefaultAccountApiBase.java
+++ b/account/src/main/java/org/killbill/billing/account/api/user/DefaultAccountApiBase.java
@@ -1,6 +1,6 @@
/*
- * Copyright 2014-2015 Groupon, Inc
- * Copyright 2014-2015 The Billing Project, LLC
+ * 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
@@ -25,6 +25,7 @@ import org.killbill.billing.account.api.Account;
import org.killbill.billing.account.api.AccountApiException;
import org.killbill.billing.account.api.DefaultAccount;
import org.killbill.billing.account.api.DefaultImmutableAccountData;
+import org.killbill.billing.account.api.ImmutableAccountData;
import org.killbill.billing.account.dao.AccountDao;
import org.killbill.billing.account.dao.AccountModelDao;
import org.killbill.billing.callcontext.InternalTenantContext;
@@ -36,8 +37,8 @@ import org.killbill.billing.util.dao.NonEntityDao;
public class DefaultAccountApiBase {
private final AccountDao accountDao;
- private final CacheControllerDispatcher cacheControllerDispatcher;
- private final CacheController accountCacheController;
+ private final CacheController<Long, ImmutableAccountData> accountCacheController;
+ private final CacheController<String, Long> recordIdCacheController;
private final NonEntityDao nonEntityDao;
public DefaultAccountApiBase(final AccountDao accountDao,
@@ -45,17 +46,17 @@ public class DefaultAccountApiBase {
final CacheControllerDispatcher cacheControllerDispatcher) {
this.accountDao = accountDao;
this.nonEntityDao = nonEntityDao;
- this.cacheControllerDispatcher = cacheControllerDispatcher;
this.accountCacheController = cacheControllerDispatcher.getCacheController(CacheType.ACCOUNT_IMMUTABLE);
+ this.recordIdCacheController = cacheControllerDispatcher.getCacheController(CacheType.RECORD_ID);
}
protected Account getAccountById(final UUID accountId, final InternalTenantContext context) throws AccountApiException {
- final Long recordId = nonEntityDao.retrieveRecordIdFromObject(accountId, ObjectType.ACCOUNT, cacheControllerDispatcher.getCacheController(CacheType.RECORD_ID));
+ final Long recordId = nonEntityDao.retrieveRecordIdFromObject(accountId, ObjectType.ACCOUNT, recordIdCacheController);
final Account account = getAccountByRecordIdInternal(recordId, context);
if (account == null) {
throw new AccountApiException(ErrorCode.ACCOUNT_DOES_NOT_EXIST_FOR_ID, accountId);
}
- accountCacheController.putIfAbsent(accountId, new DefaultImmutableAccountData(account));
+ accountCacheController.putIfAbsent(recordId, new DefaultImmutableAccountData(account));
return account;
}
@@ -65,7 +66,8 @@ public class DefaultAccountApiBase {
throw new AccountApiException(ErrorCode.ACCOUNT_DOES_NOT_EXIST_FOR_KEY, key);
}
final Account account = new DefaultAccount(accountModelDao);
- accountCacheController.putIfAbsent(account.getId(), new DefaultImmutableAccountData(account));
+ final Long recordId = nonEntityDao.retrieveRecordIdFromObject(account.getId(), ObjectType.ACCOUNT, recordIdCacheController);
+ accountCacheController.putIfAbsent(recordId, new DefaultImmutableAccountData(account));
return account;
}
diff --git a/account/src/main/java/org/killbill/billing/account/api/user/DefaultAccountUserApi.java b/account/src/main/java/org/killbill/billing/account/api/user/DefaultAccountUserApi.java
index a164bef..c1474e4 100644
--- a/account/src/main/java/org/killbill/billing/account/api/user/DefaultAccountUserApi.java
+++ b/account/src/main/java/org/killbill/billing/account/api/user/DefaultAccountUserApi.java
@@ -1,7 +1,7 @@
/*
* Copyright 2010-2013 Ning, Inc.
- * Copyright 2014-2015 Groupon, Inc
- * Copyright 2014-2015 The Billing Project, LLC
+ * 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
@@ -29,6 +29,8 @@ import org.killbill.billing.account.api.AccountEmail;
import org.killbill.billing.account.api.AccountUserApi;
import org.killbill.billing.account.api.DefaultAccount;
import org.killbill.billing.account.api.DefaultAccountEmail;
+import org.killbill.billing.account.api.ImmutableAccountData;
+import org.killbill.billing.account.api.ImmutableAccountInternalApi;
import org.killbill.billing.account.dao.AccountDao;
import org.killbill.billing.account.dao.AccountEmailModelDao;
import org.killbill.billing.account.dao.AccountModelDao;
@@ -51,15 +53,18 @@ import static org.killbill.billing.util.entity.dao.DefaultPaginationHelper.getEn
public class DefaultAccountUserApi extends DefaultAccountApiBase implements AccountUserApi {
+ private final ImmutableAccountInternalApi immutableAccountInternalApi;
private final InternalCallContextFactory internalCallContextFactory;
private final AccountDao accountDao;
@Inject
- public DefaultAccountUserApi(final AccountDao accountDao,
+ public DefaultAccountUserApi(final ImmutableAccountInternalApi immutableAccountInternalApi,
+ final AccountDao accountDao,
final NonEntityDao nonEntityDao,
final CacheControllerDispatcher cacheControllerDispatcher,
final InternalCallContextFactory internalCallContextFactory) {
super(accountDao, nonEntityDao, cacheControllerDispatcher);
+ this.immutableAccountInternalApi = immutableAccountInternalApi;
this.internalCallContextFactory = internalCallContextFactory;
this.accountDao = accountDao;
}
@@ -89,7 +94,10 @@ public class DefaultAccountUserApi extends DefaultAccountApiBase implements Acco
if (data.getParentAccountId() != null) {
// verify that parent account exists if parentAccountId is not null
- getAccountById(data.getParentAccountId(), internalContext);
+ final ImmutableAccountData immutableAccountData = immutableAccountInternalApi.getImmutableAccountDataById(data.getParentAccountId(), internalContext);
+ if (immutableAccountData == null) {
+ throw new AccountApiException(ErrorCode.ACCOUNT_DOES_NOT_EXIST_FOR_ID, data.getParentAccountId());
+ }
}
final AccountModelDao account = new AccountModelDao(data);
diff --git a/account/src/main/java/org/killbill/billing/account/dao/AccountModelDao.java b/account/src/main/java/org/killbill/billing/account/dao/AccountModelDao.java
index 91fe1ce..b0e392f 100644
--- a/account/src/main/java/org/killbill/billing/account/dao/AccountModelDao.java
+++ b/account/src/main/java/org/killbill/billing/account/dao/AccountModelDao.java
@@ -1,7 +1,7 @@
/*
* Copyright 2010-2013 Ning, Inc.
- * Copyright 2014-2015 Groupon, Inc
- * Copyright 2014-2015 The Billing Project, LLC
+ * 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
@@ -31,12 +31,13 @@ import org.killbill.billing.util.UUIDs;
import org.killbill.billing.util.dao.TableName;
import org.killbill.billing.util.entity.dao.EntityModelDao;
import org.killbill.billing.util.entity.dao.EntityModelDaoBase;
+import org.killbill.billing.util.entity.dao.TimeZoneAwareEntity;
import com.google.common.base.MoreObjects;
import static org.killbill.billing.account.api.DefaultMutableAccountData.DEFAULT_BILLING_CYCLE_DAY_LOCAL;
-public class AccountModelDao extends EntityModelDaoBase implements EntityModelDao<Account> {
+public class AccountModelDao extends EntityModelDaoBase implements TimeZoneAwareEntity, EntityModelDao<Account> {
private String externalKey;
private String email;
@@ -133,6 +134,15 @@ public class AccountModelDao extends EntityModelDaoBase implements EntityModelDa
this(UUIDs.randomUUID(), account);
}
+ @Override
+ public void setRecordId(final Long recordId) {
+ super.setRecordId(recordId);
+ // Invoked by the jDBI mapper when retrieving the record: while there is no account_record_id column,
+ // populate the field manually for EntitySqlDaoWrapperInvocationHandler#populateCaches to populate the
+ // ACCOUNT_RECORD_ID cache
+ setAccountRecordId(recordId);
+ }
+
public String getExternalKey() {
return externalKey;
}
@@ -205,6 +215,7 @@ public class AccountModelDao extends EntityModelDaoBase implements EntityModelDa
this.paymentMethodId = paymentMethodId;
}
+ @Override
public DateTimeZone getTimeZone() {
return timeZone;
}
diff --git a/account/src/main/java/org/killbill/billing/account/dao/AccountSqlDao.java b/account/src/main/java/org/killbill/billing/account/dao/AccountSqlDao.java
index 2eccd3c..77bea77 100644
--- a/account/src/main/java/org/killbill/billing/account/dao/AccountSqlDao.java
+++ b/account/src/main/java/org/killbill/billing/account/dao/AccountSqlDao.java
@@ -1,7 +1,9 @@
/*
* Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014-2017 Groupon, Inc
+ * Copyright 2014-2017 The Billing Project, LLC
*
- * Ning licenses this file to you under the Apache License, version 2.0
+ * 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:
*
@@ -44,7 +46,7 @@ public interface AccountSqlDao extends EntitySqlDao<AccountModelDao, Account> {
@SqlQuery
public Integer getBCD(@Bind("id") String accountId,
- @BindBean final InternalTenantContext context);
+ @BindBean final InternalTenantContext context);
@SqlUpdate
@Audited(ChangeType.UPDATE)
@@ -53,9 +55,9 @@ public interface AccountSqlDao extends EntitySqlDao<AccountModelDao, Account> {
@SqlUpdate
@Audited(ChangeType.UPDATE)
- public void updatePaymentMethod(@Bind("id") String accountId,
- @Bind("paymentMethodId") String paymentMethodId,
- @BindBean final InternalCallContext context);
+ public Object updatePaymentMethod(@Bind("id") String accountId,
+ @Bind("paymentMethodId") String paymentMethodId,
+ @BindBean final InternalCallContext context);
@SqlQuery
List<AccountModelDao> getAccountsByParentId(@Bind("parentAccountId") UUID parentAccountId,
diff --git a/account/src/main/java/org/killbill/billing/account/dao/DefaultAccountDao.java b/account/src/main/java/org/killbill/billing/account/dao/DefaultAccountDao.java
index 6ca2fa3..3054b9e 100644
--- a/account/src/main/java/org/killbill/billing/account/dao/DefaultAccountDao.java
+++ b/account/src/main/java/org/killbill/billing/account/dao/DefaultAccountDao.java
@@ -1,7 +1,7 @@
/*
* Copyright 2010-2013 Ning, Inc.
- * Copyright 2014-2016 Groupon, Inc
- * Copyright 2014-2016 The Billing Project, LLC
+ * 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
@@ -26,6 +26,8 @@ import org.killbill.billing.BillingExceptionBase;
import org.killbill.billing.ErrorCode;
import org.killbill.billing.account.api.Account;
import org.killbill.billing.account.api.AccountApiException;
+import org.killbill.billing.account.api.DefaultImmutableAccountData;
+import org.killbill.billing.account.api.ImmutableAccountData;
import org.killbill.billing.account.api.user.DefaultAccountChangeEvent;
import org.killbill.billing.account.api.user.DefaultAccountCreationEvent;
import org.killbill.billing.account.api.user.DefaultAccountCreationEvent.DefaultAccountData;
@@ -35,6 +37,8 @@ import org.killbill.billing.entity.EntityPersistenceException;
import org.killbill.billing.events.AccountChangeInternalEvent;
import org.killbill.billing.events.AccountCreationInternalEvent;
import org.killbill.billing.util.audit.ChangeType;
+import org.killbill.billing.util.cache.Cachable.CacheType;
+import org.killbill.billing.util.cache.CacheController;
import org.killbill.billing.util.cache.CacheControllerDispatcher;
import org.killbill.billing.util.callcontext.InternalCallContextFactory;
import org.killbill.billing.util.dao.NonEntityDao;
@@ -60,6 +64,7 @@ public class DefaultAccountDao extends EntityDaoBase<AccountModelDao, Account, A
private static final Logger log = LoggerFactory.getLogger(DefaultAccountDao.class);
+ private final CacheController<Long, ImmutableAccountData> accountImmutableCacheController;
private final PersistentBus eventBus;
private final InternalCallContextFactory internalCallContextFactory;
private final Clock clock;
@@ -68,12 +73,22 @@ public class DefaultAccountDao extends EntityDaoBase<AccountModelDao, Account, A
public DefaultAccountDao(final IDBI dbi, final PersistentBus eventBus, final Clock clock, final CacheControllerDispatcher cacheControllerDispatcher,
final InternalCallContextFactory internalCallContextFactory, final NonEntityDao nonEntityDao) {
super(new EntitySqlDaoTransactionalJdbiWrapper(dbi, clock, cacheControllerDispatcher, nonEntityDao, internalCallContextFactory), AccountSqlDao.class);
+ this.accountImmutableCacheController = cacheControllerDispatcher.getCacheController(CacheType.ACCOUNT_IMMUTABLE);
this.eventBus = eventBus;
this.internalCallContextFactory = internalCallContextFactory;
this.clock = clock;
}
@Override
+ public void create(final AccountModelDao entity, final InternalCallContext context) throws AccountApiException {
+ final AccountModelDao refreshedEntity = transactionalSqlDao.execute(getCreateEntitySqlDaoTransactionWrapper(entity, context));
+ // Populate the caches only after the transaction has been committed, in case of rollbacks
+ transactionalSqlDao.populateCaches(refreshedEntity);
+ // Eagerly populate the account-immutable cache as well
+ accountImmutableCacheController.putIfAbsent(refreshedEntity.getRecordId(), new DefaultImmutableAccountData(refreshedEntity));
+ }
+
+ @Override
protected AccountApiException generateAlreadyExistsException(final AccountModelDao account, final InternalCallContext context) {
return new AccountApiException(ErrorCode.ACCOUNT_ALREADY_EXISTS, account.getExternalKey());
}
@@ -89,9 +104,9 @@ public class DefaultAccountDao extends EntityDaoBase<AccountModelDao, Account, A
return;
}
- final Long recordId = entitySqlDaoWrapperFactory.become(AccountSqlDao.class).getRecordId(savedAccount.getId().toString(), context);
+ final Long recordId = savedAccount.getRecordId();
// We need to re-hydrate the callcontext with the account record id
- final InternalCallContext rehydratedContext = internalCallContextFactory.createInternalCallContext(recordId, context);
+ final InternalCallContext rehydratedContext = internalCallContextFactory.createInternalCallContext(savedAccount, recordId, context);
final AccountCreationInternalEvent creationEvent = new DefaultAccountCreationEvent(new DefaultAccountData(savedAccount), savedAccount.getId(),
rehydratedContext.getAccountRecordId(), rehydratedContext.getTenantRecordId(), rehydratedContext.getUserToken());
try {
@@ -215,9 +230,8 @@ public class DefaultAccountDao extends EntityDaoBase<AccountModelDao, Account, A
}
final String thePaymentMethodId = paymentMethodId != null ? paymentMethodId.toString() : null;
- transactional.updatePaymentMethod(accountId.toString(), thePaymentMethodId, context);
+ final AccountModelDao account = (AccountModelDao) transactional.updatePaymentMethod(accountId.toString(), thePaymentMethodId, context);
- final AccountModelDao account = transactional.getById(accountId.toString(), context);
final AccountChangeInternalEvent changeEvent = new DefaultAccountChangeEvent(accountId, currentAccount, account,
context.getAccountRecordId(),
context.getTenantRecordId(),
@@ -245,7 +259,7 @@ public class DefaultAccountDao extends EntityDaoBase<AccountModelDao, Account, A
throw new AccountApiException(ErrorCode.ACCOUNT_EMAIL_ALREADY_EXISTS, email.getId());
}
- transactional.create(email, context);
+ createAndRefresh(transactional, email, context);
return null;
}
});
diff --git a/account/src/test/java/org/killbill/billing/account/AccountTestSuiteNoDB.java b/account/src/test/java/org/killbill/billing/account/AccountTestSuiteNoDB.java
index 807e5e1..447b18d 100644
--- a/account/src/test/java/org/killbill/billing/account/AccountTestSuiteNoDB.java
+++ b/account/src/test/java/org/killbill/billing/account/AccountTestSuiteNoDB.java
@@ -1,7 +1,9 @@
/*
* Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014-2017 Groupon, Inc
+ * Copyright 2014-2017 The Billing Project, LLC
*
- * Ning licenses this file to you under the Apache License, version 2.0
+ * 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:
*
@@ -16,6 +18,7 @@
package org.killbill.billing.account;
+import org.killbill.billing.account.api.ImmutableAccountInternalApi;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.BeforeMethod;
@@ -43,6 +46,8 @@ public abstract class AccountTestSuiteNoDB extends GuicyKillbillTestSuiteNoDB {
@Inject
protected AccountDao accountDao;
@Inject
+ protected ImmutableAccountInternalApi immutableAccountInternalApi;
+ @Inject
protected AccountUserApi accountUserApi;
@Inject
protected AuditDao auditDao;
diff --git a/account/src/test/java/org/killbill/billing/account/api/user/TestDefaultAccountUserApi.java b/account/src/test/java/org/killbill/billing/account/api/user/TestDefaultAccountUserApi.java
index 9fe5b8c..b5dbac0 100644
--- a/account/src/test/java/org/killbill/billing/account/api/user/TestDefaultAccountUserApi.java
+++ b/account/src/test/java/org/killbill/billing/account/api/user/TestDefaultAccountUserApi.java
@@ -350,12 +350,10 @@ public class TestDefaultAccountUserApi extends AccountTestSuiteWithEmbeddedDB {
@Test(groups = "slow", description = "Test Account create Child with a non existing Parent",
expectedExceptions = AccountApiException.class, expectedExceptionsMessageRegExp = "Account does not exist for id .*")
public void testCreateChildAccountWithInvalidParent() throws Exception {
-
final AccountModelDao childAccountModel = createTestAccount();
childAccountModel.setParentAccountId(UUID.randomUUID());
final AccountData childAccountData = new DefaultAccount(childAccountModel);
final Account childAccount = accountUserApi.createAccount(childAccountData, callContext);
-
}
@Test(groups = "slow", description = "Test un- and re-parenting")
diff --git a/account/src/test/java/org/killbill/billing/account/api/user/TestDefaultAccountUserApiWithMocks.java b/account/src/test/java/org/killbill/billing/account/api/user/TestDefaultAccountUserApiWithMocks.java
index 793d689..44e370c 100644
--- a/account/src/test/java/org/killbill/billing/account/api/user/TestDefaultAccountUserApiWithMocks.java
+++ b/account/src/test/java/org/killbill/billing/account/api/user/TestDefaultAccountUserApiWithMocks.java
@@ -1,7 +1,7 @@
/*
* Copyright 2010-2013 Ning, Inc.
- * Copyright 2014-2016 Groupon, Inc
- * Copyright 2014-2016 The Billing Project, LLC
+ * 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
@@ -53,7 +53,7 @@ public class TestDefaultAccountUserApiWithMocks extends AccountTestSuiteNoDB {
@BeforeMethod(groups = "fast")
public void setUp() throws Exception {
accountDao = new MockAccountDao(Mockito.mock(PersistentBus.class), clock);
- accountUserApi = new DefaultAccountUserApi(accountDao, nonEntityDao, controllerDispatcher, internalFactory);
+ accountUserApi = new DefaultAccountUserApi(immutableAccountInternalApi, accountDao, nonEntityDao, controllerDispatcher, internalFactory);
}
@Test(groups = "fast", description = "Test Account create API")
diff --git a/account/src/test/java/org/killbill/billing/account/dao/TestAccountDao.java b/account/src/test/java/org/killbill/billing/account/dao/TestAccountDao.java
index 237b8f5..e8ea8f0 100644
--- a/account/src/test/java/org/killbill/billing/account/dao/TestAccountDao.java
+++ b/account/src/test/java/org/killbill/billing/account/dao/TestAccountDao.java
@@ -1,7 +1,7 @@
/*
* Copyright 2010-2013 Ning, Inc.
- * Copyright 2014-2015 Groupon, Inc
- * Copyright 2014-2015 The Billing Project, LLC
+ * 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
@@ -26,6 +26,7 @@ import org.joda.time.DateTimeZone;
import org.killbill.billing.ErrorCode;
import org.killbill.billing.ObjectType;
import org.killbill.billing.account.AccountTestSuiteWithEmbeddedDB;
+import org.killbill.billing.account.api.Account;
import org.killbill.billing.account.api.AccountApiException;
import org.killbill.billing.account.api.AccountData;
import org.killbill.billing.account.api.AccountEmail;
@@ -42,6 +43,7 @@ import org.killbill.billing.util.audit.AuditLog;
import org.killbill.billing.util.audit.ChangeType;
import org.killbill.billing.util.audit.DefaultAccountAuditLogs;
import org.killbill.billing.util.customfield.dao.CustomFieldModelDao;
+import org.killbill.billing.util.dao.EntityHistoryModelDao;
import org.killbill.billing.util.dao.TableName;
import org.killbill.billing.util.entity.Pagination;
import org.killbill.billing.util.tag.DescriptiveTag;
@@ -211,6 +213,18 @@ public class TestAccountDao extends AccountTestSuiteWithEmbeddedDB {
public void testUpdate() throws Exception {
final AccountModelDao account = createTestAccount();
accountDao.create(account, internalCallContext);
+ final AccountModelDao createdAccount = accountDao.getAccountByKey(account.getExternalKey(), internalCallContext);
+
+ final List<EntityHistoryModelDao<AccountModelDao, Account>> history1 = getAccountHistory(createdAccount.getRecordId());
+ Assert.assertEquals(history1.size(), 1);
+ Assert.assertEquals(history1.get(0).getChangeType(), ChangeType.INSERT);
+ Assert.assertEquals(history1.get(0).getEntity().getAccountRecordId(), createdAccount.getRecordId());
+ Assert.assertEquals(history1.get(0).getEntity().getTenantRecordId(), createdAccount.getTenantRecordId());
+ Assert.assertEquals(history1.get(0).getEntity().getExternalKey(), createdAccount.getExternalKey());
+ Assert.assertEquals(history1.get(0).getEntity().getMigrated(), createdAccount.getMigrated());
+ Assert.assertEquals(history1.get(0).getEntity().getIsNotifiedForInvoices(), createdAccount.getIsNotifiedForInvoices());
+ Assert.assertEquals(history1.get(0).getEntity().getTimeZone(), createdAccount.getTimeZone());
+ Assert.assertEquals(history1.get(0).getEntity().getLocale(), createdAccount.getLocale());
final AccountData accountData = new MockAccountBuilder(new DefaultAccount(account)).migrated(false)
.isNotifiedForInvoices(false)
@@ -222,6 +236,40 @@ public class TestAccountDao extends AccountTestSuiteWithEmbeddedDB {
final AccountModelDao retrievedAccount = accountDao.getAccountByKey(account.getExternalKey(), internalCallContext);
checkAccountsEqual(retrievedAccount, updatedAccount);
+
+ final List<EntityHistoryModelDao<AccountModelDao, Account>> history2 = getAccountHistory(createdAccount.getRecordId());
+ Assert.assertEquals(history2.size(), 2);
+ Assert.assertEquals(history2.get(0).getChangeType(), ChangeType.INSERT);
+ Assert.assertEquals(history2.get(1).getChangeType(), ChangeType.UPDATE);
+ Assert.assertEquals(history2.get(1).getEntity().getAccountRecordId(), retrievedAccount.getRecordId());
+ Assert.assertEquals(history2.get(1).getEntity().getTenantRecordId(), retrievedAccount.getTenantRecordId());
+ Assert.assertEquals(history2.get(1).getEntity().getExternalKey(), retrievedAccount.getExternalKey());
+ Assert.assertEquals(history2.get(1).getEntity().getMigrated(), retrievedAccount.getMigrated());
+ Assert.assertEquals(history2.get(1).getEntity().getIsNotifiedForInvoices(), retrievedAccount.getIsNotifiedForInvoices());
+ Assert.assertEquals(history2.get(1).getEntity().getTimeZone(), retrievedAccount.getTimeZone());
+ Assert.assertEquals(history2.get(1).getEntity().getLocale(), retrievedAccount.getLocale());
+
+ final AccountData accountData2 = new MockAccountBuilder(new DefaultAccount(updatedAccount)).isNotifiedForInvoices(true)
+ .locale("en_US")
+ .build();
+ final AccountModelDao updatedAccount2 = new AccountModelDao(account.getId(), accountData2);
+ accountDao.update(updatedAccount2, internalCallContext);
+
+ final AccountModelDao retrievedAccount2 = accountDao.getAccountByKey(account.getExternalKey(), internalCallContext);
+ checkAccountsEqual(retrievedAccount2, updatedAccount2);
+
+ final List<EntityHistoryModelDao<AccountModelDao, Account>> history3 = getAccountHistory(createdAccount.getRecordId());
+ Assert.assertEquals(history3.size(), 3);
+ Assert.assertEquals(history3.get(0).getChangeType(), ChangeType.INSERT);
+ Assert.assertEquals(history3.get(1).getChangeType(), ChangeType.UPDATE);
+ Assert.assertEquals(history3.get(2).getChangeType(), ChangeType.UPDATE);
+ Assert.assertEquals(history3.get(2).getEntity().getAccountRecordId(), retrievedAccount2.getRecordId());
+ Assert.assertEquals(history3.get(2).getEntity().getTenantRecordId(), retrievedAccount2.getTenantRecordId());
+ Assert.assertEquals(history3.get(2).getEntity().getExternalKey(), retrievedAccount2.getExternalKey());
+ Assert.assertEquals(history3.get(2).getEntity().getMigrated(), retrievedAccount2.getMigrated());
+ Assert.assertEquals(history3.get(2).getEntity().getIsNotifiedForInvoices(), retrievedAccount2.getIsNotifiedForInvoices());
+ Assert.assertEquals(history3.get(2).getEntity().getTimeZone(), retrievedAccount2.getTimeZone());
+ Assert.assertEquals(history3.get(2).getEntity().getLocale(), retrievedAccount2.getLocale());
}
@Test(groups = "slow", description = "Test Account DAO: payment method update")
@@ -351,4 +399,10 @@ public class TestAccountDao extends AccountTestSuiteWithEmbeddedDB {
Assert.assertEquals(auditLogsForAccountEmail2.size(), 1);
Assert.assertEquals(auditLogsForAccountEmail2.get(0).getChangeType(), ChangeType.INSERT);
}
+
+ private List<EntityHistoryModelDao<AccountModelDao, Account>> getAccountHistory(final Long accountRecordId) {
+ // See https://github.com/killbill/killbill/issues/335
+ final AccountSqlDao accountSqlDao = dbi.onDemand(AccountSqlDao.class);
+ return accountSqlDao.getHistoryForTargetRecordId(accountRecordId, internalCallContext);
+ }
}
api/pom.xml 2(+1 -1)
diff --git a/api/pom.xml b/api/pom.xml
index c8dbd0c..9c71a87 100644
--- a/api/pom.xml
+++ b/api/pom.xml
@@ -19,7 +19,7 @@
<parent>
<artifactId>killbill</artifactId>
<groupId>org.kill-bill.billing</groupId>
- <version>0.18.5-SNAPSHOT</version>
+ <version>0.18.7-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>killbill-internal-api</artifactId>
diff --git a/api/src/main/java/org/killbill/billing/entity/EntityBase.java b/api/src/main/java/org/killbill/billing/entity/EntityBase.java
index 0c02112..465debe 100644
--- a/api/src/main/java/org/killbill/billing/entity/EntityBase.java
+++ b/api/src/main/java/org/killbill/billing/entity/EntityBase.java
@@ -1,7 +1,9 @@
/*
- * Copyright 2010-2011 Ning, Inc.
+ * Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014-2017 Groupon, Inc
+ * Copyright 2014-2017 The Billing Project, LLC
*
- * Ning licenses this file to you under the Apache License, version 2.0
+ * 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:
*
@@ -19,10 +21,10 @@ package org.killbill.billing.entity;
import java.util.UUID;
import org.joda.time.DateTime;
-
import org.killbill.billing.util.UUIDs;
import org.killbill.billing.util.entity.Entity;
+// Note: not suitable for Serializable Entity classes (e.g. DefaultTenant)
public abstract class EntityBase implements Entity {
protected UUID id;
@@ -45,7 +47,7 @@ public abstract class EntityBase implements Entity {
this.updatedDate = updatedDate;
}
- public EntityBase(final EntityBase target) {
+ public EntityBase(final Entity target) {
this.id = target.getId();
this.createdDate = target.getCreatedDate();
this.updatedDate = target.getUpdatedDate();
beatrix/pom.xml 2(+1 -1)
diff --git a/beatrix/pom.xml b/beatrix/pom.xml
index 3b45ccc..cfdbaaa 100644
--- a/beatrix/pom.xml
+++ b/beatrix/pom.xml
@@ -19,7 +19,7 @@
<parent>
<artifactId>killbill</artifactId>
<groupId>org.kill-bill.billing</groupId>
- <version>0.18.5-SNAPSHOT</version>
+ <version>0.18.7-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>killbill-beatrix</artifactId>
diff --git a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestSubscription.java b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestSubscription.java
index 54788f8..3d4ea4a 100644
--- a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestSubscription.java
+++ b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestSubscription.java
@@ -270,13 +270,20 @@ public class TestSubscription extends TestIntegrationBase {
invoiceChecker.checkInvoice(invoices.get(0).getId(), callContext, toBeChecked);
}
- @Test(groups = "slow", expectedExceptions = EntitlementApiException.class, expectedExceptionsMessageRegExp = "Missing Base Subscription.")
+ @Test(groups = "slow")
public void testCreateMultipleSubscriptionsWithoutBase() throws Exception {
final LocalDate initialDate = new LocalDate(2015, 10, 1);
clock.setDay(initialDate);
final Account account = createAccountWithNonOsgiPaymentMethod(getAccountData(1));
+ final String externalKeyB = "baseExternalKeyBBB";
+
+ final DefaultEntitlement bpEntitlement = createBaseEntitlementAndCheckForCompletion(account.getId(), externalKeyB, "Shotgun", ProductCategory.BASE, BillingPeriod.ANNUAL, NextEvent.CREATE, NextEvent.BLOCK, NextEvent.INVOICE);
+ assertNotNull(bpEntitlement);
+ assertEquals(invoiceUserApi.getInvoicesByAccount(account.getId(), false, callContext).size(), 1);
+
+
final PlanPhaseSpecifier baseSpec = new PlanPhaseSpecifier("Shotgun", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, null);
final PlanPhaseSpecifier addOnSpec1 = new PlanPhaseSpecifier("Telescopic-Scope", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, null);
final PlanPhaseSpecifier addOnSpec2 = new PlanPhaseSpecifier("Laser-Scope", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, null);
@@ -291,7 +298,6 @@ public class TestSubscription extends TestIntegrationBase {
specifierListA.add(addOnEntitlementSpecifier1);
specifierListA.add(addOnEntitlementSpecifier2);
- final String externalKeyB = "baseExternalKeyBBB";
final List<EntitlementSpecifier> specifierListB = new ArrayList<EntitlementSpecifier>();
specifierListB.add(addOnEntitlementSpecifier1);
specifierListB.add(addOnEntitlementSpecifier2);
@@ -302,7 +308,22 @@ public class TestSubscription extends TestIntegrationBase {
entitlementWithAddOnsSpecifierList.add(cartSpecifierA);
entitlementWithAddOnsSpecifierList.add(cartSpecifierB);
- entitlementApi.createBaseEntitlementsWithAddOns(account.getId(), entitlementWithAddOnsSpecifierList, ImmutableList.<PluginProperty>of(), callContext);
+ busHandler.pushExpectedEvents(NextEvent.CREATE, NextEvent.BLOCK,
+ NextEvent.CREATE, NextEvent.BLOCK,
+ NextEvent.CREATE, NextEvent.BLOCK,
+ NextEvent.CREATE, NextEvent.BLOCK,
+ NextEvent.CREATE, NextEvent.BLOCK,
+ NextEvent.NULL_INVOICE, NextEvent.NULL_INVOICE,
+ NextEvent.NULL_INVOICE, NextEvent.NULL_INVOICE,
+ NextEvent.INVOICE,
+ NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT
+ );
+
+ final List<Entitlement> entitlements = entitlementApi.createBaseEntitlementsWithAddOns(account.getId(), entitlementWithAddOnsSpecifierList, ImmutableList.<PluginProperty>of(), callContext);
+ assertListenerStatus();
+
+ Assert.assertEquals(entitlements.size(), 5);
+
}
@Test(groups = "slow", expectedExceptions = EntitlementApiException.class,
diff --git a/beatrix/src/test/java/org/killbill/billing/beatrix/util/SubscriptionChecker.java b/beatrix/src/test/java/org/killbill/billing/beatrix/util/SubscriptionChecker.java
index 1b0c6c9..d46e1d5 100644
--- a/beatrix/src/test/java/org/killbill/billing/beatrix/util/SubscriptionChecker.java
+++ b/beatrix/src/test/java/org/killbill/billing/beatrix/util/SubscriptionChecker.java
@@ -1,7 +1,9 @@
/*
* Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014-2017 Groupon, Inc
+ * Copyright 2014-2017 The Billing Project, LLC
*
- * Ning licenses this file to you under the Apache License, version 2.0
+ * 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:
*
@@ -22,6 +24,7 @@ import java.util.UUID;
import javax.inject.Inject;
import org.killbill.billing.util.cache.Cachable.CacheType;
+import org.killbill.billing.util.cache.CacheController;
import org.killbill.billing.util.cache.CacheControllerDispatcher;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -46,14 +49,14 @@ public class SubscriptionChecker {
private final SubscriptionBaseInternalApi subscriptionApi;
private final AuditChecker auditChecker;
private final NonEntityDao nonEntityDao;
- private final CacheControllerDispatcher cacheControllerDispatcher;
+ private final CacheController<String, UUID> objectIdCacheController;
@Inject
public SubscriptionChecker(final SubscriptionBaseInternalApi subscriptionApi, final AuditChecker auditChecker, final NonEntityDao nonEntityDao, final CacheControllerDispatcher cacheControllerDispatcher) {
this.subscriptionApi = subscriptionApi;
this.auditChecker = auditChecker;
this.nonEntityDao = nonEntityDao;
- this.cacheControllerDispatcher = cacheControllerDispatcher;
+ objectIdCacheController = cacheControllerDispatcher.getCacheController(CacheType.OBJECT_ID);
}
public SubscriptionBaseBundle checkBundleNoAudits(final UUID bundleId, final UUID expectedAccountId, final String expectedKey, final InternalTenantContext context) throws SubscriptionBaseApiException {
@@ -65,7 +68,7 @@ public class SubscriptionChecker {
}
public SubscriptionBase checkSubscriptionCreated(final UUID subscriptionId, final InternalCallContext context) throws SubscriptionBaseApiException {
- final UUID tenantId = nonEntityDao.retrieveIdFromObject(context.getTenantRecordId(), ObjectType.TENANT, cacheControllerDispatcher.getCacheController(CacheType.OBJECT_ID));
+ final UUID tenantId = nonEntityDao.retrieveIdFromObject(context.getTenantRecordId(), ObjectType.TENANT, objectIdCacheController);
final CallContext callContext = context.toCallContext(tenantId);
final SubscriptionBase subscription = subscriptionApi.getSubscriptionFromId(subscriptionId, context);
catalog/pom.xml 2(+1 -1)
diff --git a/catalog/pom.xml b/catalog/pom.xml
index 77babaa..663881d 100644
--- a/catalog/pom.xml
+++ b/catalog/pom.xml
@@ -19,7 +19,7 @@
<parent>
<artifactId>killbill</artifactId>
<groupId>org.kill-bill.billing</groupId>
- <version>0.18.5-SNAPSHOT</version>
+ <version>0.18.7-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>killbill-catalog</artifactId>
diff --git a/catalog/src/main/java/org/killbill/billing/catalog/caching/EhCacheCatalogCache.java b/catalog/src/main/java/org/killbill/billing/catalog/caching/EhCacheCatalogCache.java
index 6a9c625..4d33096 100644
--- a/catalog/src/main/java/org/killbill/billing/catalog/caching/EhCacheCatalogCache.java
+++ b/catalog/src/main/java/org/killbill/billing/catalog/caching/EhCacheCatalogCache.java
@@ -1,6 +1,6 @@
/*
- * Copyright 2014-2015 Groupon, Inc
- * Copyright 2014-2015 The Billing Project, LLC
+ * 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
@@ -28,6 +28,7 @@ import org.killbill.billing.callcontext.InternalTenantContext;
import org.killbill.billing.catalog.StandaloneCatalog;
import org.killbill.billing.catalog.StandaloneCatalogWithPriceOverride;
import org.killbill.billing.catalog.VersionedCatalog;
+import org.killbill.billing.catalog.api.Catalog;
import org.killbill.billing.catalog.api.CatalogApiException;
import org.killbill.billing.catalog.io.VersionedCatalogLoader;
import org.killbill.billing.catalog.override.PriceOverride;
@@ -53,7 +54,7 @@ public class EhCacheCatalogCache implements CatalogCache {
private final Logger logger = LoggerFactory.getLogger(EhCacheCatalogCache.class);
- private final CacheController cacheController;
+ private final CacheController<Long, Catalog> cacheController;
private final VersionedCatalogLoader loader;
private final CacheLoaderArgument cacheLoaderArgumentWithTemplateFiltering;
private final CacheLoaderArgument cacheLoaderArgument;
@@ -97,7 +98,7 @@ public class EhCacheCatalogCache implements CatalogCache {
return pluginVersionedCatalog;
}
- if (tenantContext.getTenantRecordId() == InternalCallContextFactory.INTERNAL_TENANT_RECORD_ID) {
+ if (InternalCallContextFactory.INTERNAL_TENANT_RECORD_ID.equals(tenantContext.getTenantRecordId())) {
return useDefaultCatalog ? defaultCatalog : null;
}
// The cache loader might choke on some bad xml -- unlikely since we check its validity prior storing it,
@@ -113,7 +114,7 @@ public class EhCacheCatalogCache implements CatalogCache {
final StandaloneCatalogWithPriceOverride curWithOverride = new StandaloneCatalogWithPriceOverride(cur, priceOverride, tenantContext.getTenantRecordId(), internalCallContextFactory);
tenantCatalog.add(curWithOverride);
}
- cacheController.add(tenantContext.getTenantRecordId(), tenantCatalog);
+ cacheController.putIfAbsent(tenantContext.getTenantRecordId(), tenantCatalog);
}
return tenantCatalog;
} catch (final IllegalStateException e) {
@@ -123,7 +124,7 @@ public class EhCacheCatalogCache implements CatalogCache {
@Override
public void clearCatalog(final InternalTenantContext tenantContext) {
- if (tenantContext.getTenantRecordId() != InternalCallContextFactory.INTERNAL_TENANT_RECORD_ID) {
+ if (!InternalCallContextFactory.INTERNAL_TENANT_RECORD_ID.equals(tenantContext.getTenantRecordId())) {
cacheController.remove(tenantContext.getTenantRecordId());
}
}
@@ -174,7 +175,7 @@ public class EhCacheCatalogCache implements CatalogCache {
private CacheLoaderArgument initializeCacheLoaderArgument(final boolean filterTemplateCatalog) {
final LoaderCallback loaderCallback = new LoaderCallback() {
@Override
- public Object loadCatalog(final List<String> catalogXMLs, final Long tenantRecordId) throws CatalogApiException {
+ public Catalog loadCatalog(final List<String> catalogXMLs, final Long tenantRecordId) throws CatalogApiException {
return loader.load(catalogXMLs, filterTemplateCatalog, tenantRecordId);
}
};
diff --git a/catalog/src/main/java/org/killbill/billing/catalog/caching/EhCacheOverriddenPlanCache.java b/catalog/src/main/java/org/killbill/billing/catalog/caching/EhCacheOverriddenPlanCache.java
index 897d725..66e6a7d 100644
--- a/catalog/src/main/java/org/killbill/billing/catalog/caching/EhCacheOverriddenPlanCache.java
+++ b/catalog/src/main/java/org/killbill/billing/catalog/caching/EhCacheOverriddenPlanCache.java
@@ -1,6 +1,6 @@
/*
- * Copyright 2014-2015 Groupon, Inc
- * Copyright 2014-2015 The Billing Project, LLC
+ * 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
@@ -61,7 +61,7 @@ import com.google.common.collect.Iterables;
public class EhCacheOverriddenPlanCache implements OverriddenPlanCache {
- private final CacheController cacheController;
+ private final CacheController<String, Plan> cacheController;
private final LoaderCallback loaderCallback;
private final CatalogOverrideDao overrideDao;
@@ -71,7 +71,7 @@ public class EhCacheOverriddenPlanCache implements OverriddenPlanCache {
this.cacheController = cacheControllerDispatcher.getCacheController(CacheType.OVERRIDDEN_PLAN);
this.loaderCallback = new LoaderCallback() {
@Override
- public Object loadPlan(final String planName, final StaticCatalog catalog, final InternalTenantContext context) throws CatalogApiException {
+ public Plan loadPlan(final String planName, final StaticCatalog catalog, final InternalTenantContext context) throws CatalogApiException {
return loadOverriddenPlan(planName, catalog, context);
}
};
diff --git a/catalog/src/main/java/org/killbill/billing/catalog/DefaultBlock.java b/catalog/src/main/java/org/killbill/billing/catalog/DefaultBlock.java
index e4c347b..8e62c6c 100644
--- a/catalog/src/main/java/org/killbill/billing/catalog/DefaultBlock.java
+++ b/catalog/src/main/java/org/killbill/billing/catalog/DefaultBlock.java
@@ -82,7 +82,7 @@ public class DefaultBlock extends ValidatingConfig<StandaloneCatalog> implements
@Override
public Double getMinTopUpCredit() throws CatalogApiException {
- if (minTopUpCredit != CatalogSafetyInitializer.DEFAULT_NON_REQUIRED_DOUBLE_FIELD_VALUE && type != BlockType.TOP_UP) {
+ if (!CatalogSafetyInitializer.DEFAULT_NON_REQUIRED_DOUBLE_FIELD_VALUE.equals(minTopUpCredit) && type != BlockType.TOP_UP) {
throw new CatalogApiException(ErrorCode.CAT_NOT_TOP_UP_BLOCK, phase.getName());
}
return minTopUpCredit;
@@ -95,7 +95,7 @@ public class DefaultBlock extends ValidatingConfig<StandaloneCatalog> implements
throw new IllegalStateException("type should have been automatically been initialized with VANILLA ");
}
- if (type == BlockType.TOP_UP && minTopUpCredit == CatalogSafetyInitializer.DEFAULT_NON_REQUIRED_DOUBLE_FIELD_VALUE) {
+ if (type == BlockType.TOP_UP && CatalogSafetyInitializer.DEFAULT_NON_REQUIRED_DOUBLE_FIELD_VALUE.equals(minTopUpCredit)) {
errors.add(new ValidationError(String.format("TOP_UP block needs to define minTopUpCredit for phase %s",
phase.getName()), catalog.getCatalogURI(), DefaultUsage.class, ""));
}
diff --git a/catalog/src/main/java/org/killbill/billing/catalog/DefaultDuration.java b/catalog/src/main/java/org/killbill/billing/catalog/DefaultDuration.java
index f993a03..7af3daa 100644
--- a/catalog/src/main/java/org/killbill/billing/catalog/DefaultDuration.java
+++ b/catalog/src/main/java/org/killbill/billing/catalog/DefaultDuration.java
@@ -113,10 +113,10 @@ public class DefaultDuration extends ValidatingConfig<StandaloneCatalog> impleme
}
//Validation: TimeUnit UNLIMITED if number == -1
- if ((unit == TimeUnit.UNLIMITED && number != CatalogSafetyInitializer.DEFAULT_NON_REQUIRED_INTEGER_FIELD_VALUE)) {
+ if ((unit == TimeUnit.UNLIMITED && !CatalogSafetyInitializer.DEFAULT_NON_REQUIRED_INTEGER_FIELD_VALUE.equals(number))) {
errors.add(new ValidationError("Duration can only have 'UNLIMITED' unit if the number is omitted",
catalog.getCatalogURI(), DefaultDuration.class, ""));
- } else if ((unit != TimeUnit.UNLIMITED) && number == CatalogSafetyInitializer.DEFAULT_NON_REQUIRED_INTEGER_FIELD_VALUE) {
+ } else if ((unit != TimeUnit.UNLIMITED) && CatalogSafetyInitializer.DEFAULT_NON_REQUIRED_INTEGER_FIELD_VALUE.equals(number)) {
errors.add(new ValidationError("Finite Duration must have a well defined length",
catalog.getCatalogURI(), DefaultDuration.class, ""));
}
diff --git a/catalog/src/main/java/org/killbill/billing/catalog/DefaultLimit.java b/catalog/src/main/java/org/killbill/billing/catalog/DefaultLimit.java
index 10b11af..084b644 100644
--- a/catalog/src/main/java/org/killbill/billing/catalog/DefaultLimit.java
+++ b/catalog/src/main/java/org/killbill/billing/catalog/DefaultLimit.java
@@ -69,8 +69,8 @@ public class DefaultLimit extends ValidatingConfig<StandaloneCatalog> implements
@Override
public ValidationErrors validate(StandaloneCatalog root, ValidationErrors errors) {
- if (max != CatalogSafetyInitializer.DEFAULT_NON_REQUIRED_DOUBLE_FIELD_VALUE &&
- min != CatalogSafetyInitializer.DEFAULT_NON_REQUIRED_DOUBLE_FIELD_VALUE &&
+ if (!CatalogSafetyInitializer.DEFAULT_NON_REQUIRED_DOUBLE_FIELD_VALUE.equals(max) &&
+ !CatalogSafetyInitializer.DEFAULT_NON_REQUIRED_DOUBLE_FIELD_VALUE.equals(min) &&
max.doubleValue() < min.doubleValue()) {
errors.add(new ValidationError("max must be greater than min", root.getCatalogURI(), Limit.class, ""));
}
@@ -86,12 +86,12 @@ public class DefaultLimit extends ValidatingConfig<StandaloneCatalog> implements
@Override
public boolean compliesWith(double value) {
- if (max != CatalogSafetyInitializer.DEFAULT_NON_REQUIRED_DOUBLE_FIELD_VALUE) {
+ if (!CatalogSafetyInitializer.DEFAULT_NON_REQUIRED_DOUBLE_FIELD_VALUE.equals(max)) {
if (value > max.doubleValue()) {
return false;
}
}
- if (min != CatalogSafetyInitializer.DEFAULT_NON_REQUIRED_DOUBLE_FIELD_VALUE) {
+ if (!CatalogSafetyInitializer.DEFAULT_NON_REQUIRED_DOUBLE_FIELD_VALUE.equals(min)) {
if (value < min.doubleValue()) {
return false;
}
diff --git a/catalog/src/main/java/org/killbill/billing/catalog/DefaultPlan.java b/catalog/src/main/java/org/killbill/billing/catalog/DefaultPlan.java
index 0d1757f..f9aa7e4 100644
--- a/catalog/src/main/java/org/killbill/billing/catalog/DefaultPlan.java
+++ b/catalog/src/main/java/org/killbill/billing/catalog/DefaultPlan.java
@@ -1,7 +1,7 @@
/*
* Copyright 2010-2013 Ning, Inc.
- * Copyright 2014-2016 Groupon, Inc
- * Copyright 2014-2016 The Billing Project, LLC
+ * 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
@@ -18,6 +18,10 @@
package org.killbill.billing.catalog;
+import java.io.Externalizable;
+import java.io.IOException;
+import java.io.ObjectInput;
+import java.io.ObjectOutput;
import java.net.URI;
import java.util.ArrayList;
import java.util.Arrays;
@@ -46,12 +50,17 @@ import org.killbill.billing.catalog.api.PriceList;
import org.killbill.billing.catalog.api.Product;
import org.killbill.billing.catalog.api.Recurring;
import org.killbill.billing.catalog.api.TimeUnit;
+import org.killbill.billing.util.cache.ExternalizableInput;
+import org.killbill.billing.util.cache.ExternalizableOutput;
+import org.killbill.billing.util.cache.MapperHolder;
import org.killbill.xmlloader.ValidatingConfig;
import org.killbill.xmlloader.ValidationError;
import org.killbill.xmlloader.ValidationErrors;
@XmlAccessorType(XmlAccessType.NONE)
-public class DefaultPlan extends ValidatingConfig<StandaloneCatalog> implements Plan {
+public class DefaultPlan extends ValidatingConfig<StandaloneCatalog> implements Plan, Externalizable {
+
+ private static final long serialVersionUID = -4159932819592790086L;
@XmlAttribute(required = true)
@XmlID
@@ -80,6 +89,7 @@ public class DefaultPlan extends ValidatingConfig<StandaloneCatalog> implements
private String priceListName;
+ // For deserialization
public DefaultPlan() {
initialPhases = new DefaultPlanPhase[0];
}
@@ -102,31 +112,61 @@ public class DefaultPlan extends ValidatingConfig<StandaloneCatalog> implements
return effectiveDateForExistingSubscriptions;
}
+ public void setEffectiveDateForExistingSubscriptions(
+ final Date effectiveDateForExistingSubscriptions) {
+ this.effectiveDateForExistingSubscriptions = effectiveDateForExistingSubscriptions;
+ }
+
@Override
public DefaultPlanPhase[] getInitialPhases() {
return initialPhases;
}
+ public DefaultPlan setInitialPhases(final DefaultPlanPhase[] phases) {
+ this.initialPhases = phases;
+ return this;
+ }
+
@Override
public Product getProduct() {
return product;
}
+ public DefaultPlan setProduct(final Product product) {
+ this.product = (DefaultProduct) product;
+ return this;
+ }
+
@Override
public String getPriceListName() {
return priceListName;
}
+ public DefaultPlan setPriceListName(final String priceListName) {
+ this.priceListName = priceListName;
+ return this;
+ }
+
@Override
public String getName() {
return name;
}
+ public DefaultPlan setName(final String name) {
+ this.name = name;
+ return this;
+ }
+
@Override
public DefaultPlanPhase getFinalPhase() {
return finalPhase;
}
+ public DefaultPlan setFinalPhase(final DefaultPlanPhase finalPhase) {
+ this.finalPhase = finalPhase;
+ return this;
+ }
+
@Override
public PlanPhase[] getAllPhases() {
final int length = initialPhases.length + 1;
@@ -162,9 +202,11 @@ public class DefaultPlan extends ValidatingConfig<StandaloneCatalog> implements
return plansAllowedInBundle;
}
- /* (non-Javadoc)
- * @see org.killbill.billing.catalog.IPlan#getPhaseIterator()
- */
+ public DefaultPlan setPlansAllowedInBundle(final Integer plansAllowedInBundle) {
+ this.plansAllowedInBundle = plansAllowedInBundle;
+ return this;
+ }
+
@Override
public Iterator<PlanPhase> getInitialPhaseIterator() {
final Collection<PlanPhase> list = new ArrayList<PlanPhase>();
@@ -185,7 +227,7 @@ public class DefaultPlan extends ValidatingConfig<StandaloneCatalog> implements
p.setPlan(this);
p.initialize(catalog, sourceURI);
}
- this.priceListName = this.priceListName != null ? this.priceListName : findPriceListForPlan(catalog);
+ this.priceListName = this.priceListName != null ? this.priceListName : findPriceListForPlan(catalog);
}
@Override
@@ -202,7 +244,7 @@ public class DefaultPlan extends ValidatingConfig<StandaloneCatalog> implements
errors.add(new ValidationError(String.format("Invalid product for plan '%s'", name), catalog.getCatalogURI(), DefaultPlan.class, ""));
}
- for (DefaultPlanPhase cur : initialPhases) {
+ for (final DefaultPlanPhase cur : initialPhases) {
cur.validate(catalog, errors);
if (cur.getPhaseType() == PhaseType.EVERGREEN || cur.getPhaseType() == PhaseType.FIXEDTERM) {
errors.add(new ValidationError(String.format("Initial Phase %s of plan %s cannot be of type %s",
@@ -225,41 +267,6 @@ public class DefaultPlan extends ValidatingConfig<StandaloneCatalog> implements
return errors;
}
- public void setEffectiveDateForExistingSubscriptions(
- final Date effectiveDateForExistingSubscriptions) {
- this.effectiveDateForExistingSubscriptions = effectiveDateForExistingSubscriptions;
- }
-
- public DefaultPlan setName(final String name) {
- this.name = name;
- return this;
- }
-
- public DefaultPlan setFinalPhase(final DefaultPlanPhase finalPhase) {
- this.finalPhase = finalPhase;
- return this;
- }
-
- public DefaultPlan setProduct(final Product product) {
- this.product = (DefaultProduct) product;
- return this;
- }
-
- public DefaultPlan setPriceListName(final String priceListName) {
- this.priceListName = priceListName;
- return this;
- }
-
- public DefaultPlan setInitialPhases(final DefaultPlanPhase[] phases) {
- this.initialPhases = phases;
- return this;
- }
-
- public DefaultPlan setPlansAllowedInBundle(final Integer plansAllowedInBundle) {
- this.plansAllowedInBundle = plansAllowedInBundle;
- return this;
- }
-
@Override
public DateTime dateOfFirstRecurringNonZeroCharge(final DateTime subscriptionStartDate, final PhaseType initialPhaseType) {
DateTime result = subscriptionStartDate;
@@ -345,4 +352,14 @@ public class DefaultPlan extends ValidatingConfig<StandaloneCatalog> implements
}
throw new IllegalStateException("Cannot extract pricelist for plan " + name);
}
+
+ @Override
+ public void readExternal(final ObjectInput in) throws IOException {
+ MapperHolder.mapper().readerForUpdating(this).readValue(new ExternalizableInput(in));
+ }
+
+ @Override
+ public void writeExternal(final ObjectOutput oo) throws IOException {
+ MapperHolder.mapper().writeValue(new ExternalizableOutput(oo), this);
+ }
}
diff --git a/catalog/src/main/java/org/killbill/billing/catalog/VersionedCatalog.java b/catalog/src/main/java/org/killbill/billing/catalog/VersionedCatalog.java
index 8d31aff..e52d6a8 100644
--- a/catalog/src/main/java/org/killbill/billing/catalog/VersionedCatalog.java
+++ b/catalog/src/main/java/org/killbill/billing/catalog/VersionedCatalog.java
@@ -1,7 +1,7 @@
/*
* Copyright 2010-2013 Ning, Inc.
- * Copyright 2014-2016 Groupon, Inc
- * Copyright 2014-2016 The Billing Project, LLC
+ * 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
@@ -18,6 +18,10 @@
package org.killbill.billing.catalog;
+import java.io.Externalizable;
+import java.io.IOException;
+import java.io.ObjectInput;
+import java.io.ObjectOutput;
import java.net.URI;
import java.util.ArrayList;
import java.util.Collection;
@@ -58,6 +62,9 @@ import org.killbill.billing.catalog.api.PriceListSet;
import org.killbill.billing.catalog.api.Product;
import org.killbill.billing.catalog.api.StaticCatalog;
import org.killbill.billing.catalog.api.Unit;
+import org.killbill.billing.util.cache.ExternalizableInput;
+import org.killbill.billing.util.cache.ExternalizableOutput;
+import org.killbill.billing.util.cache.MapperHolder;
import org.killbill.clock.Clock;
import org.killbill.xmlloader.ValidatingConfig;
import org.killbill.xmlloader.ValidationError;
@@ -65,7 +72,9 @@ import org.killbill.xmlloader.ValidationErrors;
@XmlRootElement(name = "catalogs")
@XmlAccessorType(XmlAccessType.NONE)
-public class VersionedCatalog extends ValidatingConfig<VersionedCatalog> implements Catalog, StaticCatalog {
+public class VersionedCatalog extends ValidatingConfig<VersionedCatalog> implements Catalog, StaticCatalog, Externalizable {
+
+ private static final long serialVersionUID = 3181874902672322725L;
private final Clock clock;
@@ -553,4 +562,14 @@ public class VersionedCatalog extends ValidatingConfig<VersionedCatalog> impleme
public boolean compliesWithLimits(final String phaseName, final String unit, final double value) throws CatalogApiException {
return versionForDate(clock.getUTCNow()).compliesWithLimits(phaseName, unit, value);
}
+
+ @Override
+ public void readExternal(final ObjectInput in) throws IOException {
+ MapperHolder.mapper().readerForUpdating(this).readValue(new ExternalizableInput(in));
+ }
+
+ @Override
+ public void writeExternal(final ObjectOutput oo) throws IOException {
+ MapperHolder.mapper().writeValue(new ExternalizableOutput(oo), this);
+ }
}
currency/pom.xml 2(+1 -1)
diff --git a/currency/pom.xml b/currency/pom.xml
index 26b69c6..6592d18 100644
--- a/currency/pom.xml
+++ b/currency/pom.xml
@@ -19,7 +19,7 @@
<parent>
<artifactId>killbill</artifactId>
<groupId>org.kill-bill.billing</groupId>
- <version>0.18.5-SNAPSHOT</version>
+ <version>0.18.7-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>killbill-currency</artifactId>
entitlement/pom.xml 2(+1 -1)
diff --git a/entitlement/pom.xml b/entitlement/pom.xml
index 2c34d9c..7c236e7 100644
--- a/entitlement/pom.xml
+++ b/entitlement/pom.xml
@@ -19,7 +19,7 @@
<parent>
<artifactId>killbill</artifactId>
<groupId>org.kill-bill.billing</groupId>
- <version>0.18.5-SNAPSHOT</version>
+ <version>0.18.7-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>killbill-entitlement</artifactId>
diff --git a/entitlement/src/main/java/org/killbill/billing/entitlement/api/DefaultEntitlementApi.java b/entitlement/src/main/java/org/killbill/billing/entitlement/api/DefaultEntitlementApi.java
index 7ea0b3b..82e0c05 100644
--- a/entitlement/src/main/java/org/killbill/billing/entitlement/api/DefaultEntitlementApi.java
+++ b/entitlement/src/main/java/org/killbill/billing/entitlement/api/DefaultEntitlementApi.java
@@ -155,9 +155,6 @@ public class DefaultEntitlementApi extends DefaultEntitlementApiBase implements
public Entitlement doCall(final EntitlementApi entitlementApi, final EntitlementContext updatedPluginContext) throws EntitlementApiException {
final InternalCallContext contextWithValidAccountRecordId = internalCallContextFactory.createInternalCallContext(accountId, callContext);
try {
- if (entitlementUtils.getFirstActiveSubscriptionIdForKeyOrNull(externalKey, contextWithValidAccountRecordId) != null) {
- throw new EntitlementApiException(new SubscriptionBaseApiException(ErrorCode.SUB_CREATE_ACTIVE_BUNDLE_KEY_EXISTS, externalKey));
- }
final SubscriptionBaseBundle bundle = subscriptionBaseInternalApi.createBundleForAccount(accountId, externalKey, contextWithValidAccountRecordId);
@@ -222,12 +219,6 @@ public class DefaultEntitlementApi extends DefaultEntitlementApiBase implements
final InternalCallContext contextWithValidAccountRecordId = internalCallContextFactory.createInternalCallContext(accountId, callContext);
try {
- // First verify bundleKey
- for (final BaseEntitlementWithAddOnsSpecifier baseEntitlementWithAddOnsSpecifier : baseEntitlementWithAddOnsSpecifiers) {
- if (entitlementUtils.getFirstActiveSubscriptionIdForKeyOrNull(baseEntitlementWithAddOnsSpecifier.getExternalKey(), contextWithValidAccountRecordId) != null) {
- throw new EntitlementApiException(new SubscriptionBaseApiException(ErrorCode.SUB_CREATE_ACTIVE_BUNDLE_KEY_EXISTS, baseEntitlementWithAddOnsSpecifier.getExternalKey()));
- }
- }
final List<SubscriptionBaseWithAddOns> subscriptionsWithAddOns = subscriptionBaseInternalApi.createBaseSubscriptionsWithAddOns(accountId, baseEntitlementWithAddOnsSpecifiers, contextWithValidAccountRecordId);
final Map<BlockingState, UUID> blockingStateMap = new HashMap<BlockingState, UUID>();
diff --git a/entitlement/src/main/java/org/killbill/billing/entitlement/dao/DefaultBlockingStateDao.java b/entitlement/src/main/java/org/killbill/billing/entitlement/dao/DefaultBlockingStateDao.java
index db2577e..29c193c 100644
--- a/entitlement/src/main/java/org/killbill/billing/entitlement/dao/DefaultBlockingStateDao.java
+++ b/entitlement/src/main/java/org/killbill/billing/entitlement/dao/DefaultBlockingStateDao.java
@@ -1,7 +1,7 @@
/*
* Copyright 2010-2013 Ning, Inc.
- * Copyright 2014-2016 Groupon, Inc
- * Copyright 2014-2016 The Billing Project, LLC
+ * 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
@@ -36,7 +36,6 @@ import org.killbill.billing.ObjectType;
import org.killbill.billing.callcontext.InternalCallContext;
import org.killbill.billing.callcontext.InternalTenantContext;
import org.killbill.billing.entitlement.DefaultEntitlementService;
-import org.killbill.billing.entitlement.EntitlementService;
import org.killbill.billing.entitlement.api.BlockingApiException;
import org.killbill.billing.entitlement.api.BlockingState;
import org.killbill.billing.entitlement.api.BlockingStateType;
@@ -46,6 +45,7 @@ import org.killbill.billing.entitlement.block.BlockingChecker.BlockingAggregator
import org.killbill.billing.entitlement.block.StatelessBlockingChecker;
import org.killbill.billing.entitlement.engine.core.BlockingTransitionNotificationKey;
import org.killbill.billing.util.cache.Cachable.CacheType;
+import org.killbill.billing.util.cache.CacheController;
import org.killbill.billing.util.cache.CacheControllerDispatcher;
import org.killbill.billing.util.callcontext.InternalCallContextFactory;
import org.killbill.billing.util.dao.NonEntityDao;
@@ -107,7 +107,7 @@ public class DefaultBlockingStateDao extends EntityDaoBase<BlockingStateModelDao
private final Clock clock;
private final NotificationQueueService notificationQueueService;
private final PersistentBus eventBus;
- private final CacheControllerDispatcher cacheControllerDispatcher;
+ private final CacheController<String, UUID> objectIdCacheController;
private final NonEntityDao nonEntityDao;
private final StatelessBlockingChecker statelessBlockingChecker = new StatelessBlockingChecker();
@@ -118,7 +118,7 @@ public class DefaultBlockingStateDao extends EntityDaoBase<BlockingStateModelDao
this.clock = clock;
this.notificationQueueService = notificationQueueService;
this.eventBus = eventBus;
- this.cacheControllerDispatcher = cacheControllerDispatcher;
+ this.objectIdCacheController = cacheControllerDispatcher.getCacheController(CacheType.OBJECT_ID);
this.nonEntityDao = nonEntityDao;
}
@@ -230,7 +230,7 @@ public class DefaultBlockingStateDao extends EntityDaoBase<BlockingStateModelDao
boolean inserted = false;
// Create the state, if needed
if (!blockingStatesToRemove.contains(newBlockingStateModelDao.getId())) {
- sqlDao.create(newBlockingStateModelDao, context);
+ createAndRefresh(sqlDao, newBlockingStateModelDao, context);
inserted = true;
}
@@ -260,12 +260,12 @@ public class DefaultBlockingStateDao extends EntityDaoBase<BlockingStateModelDao
final List<BlockingState> bundleBlockingStates;
final List<BlockingState> subscriptionBlockingStates;
if (type == BlockingStateType.SUBSCRIPTION) {
- final UUID accountId = nonEntityDao.retrieveIdFromObjectInTransaction(context.getAccountRecordId(), ObjectType.ACCOUNT, cacheControllerDispatcher.getCacheController(CacheType.OBJECT_ID), handle);
+ final UUID accountId = nonEntityDao.retrieveIdFromObjectInTransaction(context.getAccountRecordId(), ObjectType.ACCOUNT, objectIdCacheController, handle);
accountBlockingStates = getBlockingState(sqlDao, accountId, BlockingStateType.ACCOUNT, upToDate, context);
bundleBlockingStates = getBlockingState(sqlDao, bundleId, BlockingStateType.SUBSCRIPTION_BUNDLE, upToDate, context);
subscriptionBlockingStates = getBlockingState(sqlDao, blockableId, BlockingStateType.SUBSCRIPTION, upToDate, context);
} else if (type == BlockingStateType.SUBSCRIPTION_BUNDLE) {
- final UUID accountId = nonEntityDao.retrieveIdFromObjectInTransaction(context.getAccountRecordId(), ObjectType.ACCOUNT, cacheControllerDispatcher.getCacheController(CacheType.OBJECT_ID), handle);
+ final UUID accountId = nonEntityDao.retrieveIdFromObjectInTransaction(context.getAccountRecordId(), ObjectType.ACCOUNT, objectIdCacheController, handle);
accountBlockingStates = getBlockingState(sqlDao, accountId, BlockingStateType.ACCOUNT, upToDate, context);
bundleBlockingStates = getBlockingState(sqlDao, blockableId, BlockingStateType.SUBSCRIPTION_BUNDLE, upToDate, context);
subscriptionBlockingStates = ImmutableList.<BlockingState>of();
diff --git a/entitlement/src/test/java/org/killbill/billing/entitlement/api/TestDefaultEntitlementApi.java b/entitlement/src/test/java/org/killbill/billing/entitlement/api/TestDefaultEntitlementApi.java
index 0d5f109..5eb96de 100644
--- a/entitlement/src/test/java/org/killbill/billing/entitlement/api/TestDefaultEntitlementApi.java
+++ b/entitlement/src/test/java/org/killbill/billing/entitlement/api/TestDefaultEntitlementApi.java
@@ -724,4 +724,92 @@ public class TestDefaultEntitlementApi extends EntitlementTestSuiteWithEmbeddedD
assertEquals(entitlement.getEffectiveStartDate(), initialDate);
}
+
+ @Test(groups = "slow")
+ public void testCreateBaseSubscriptionsWithAddOns() throws AccountApiException, EntitlementApiException, SubscriptionApiException {
+ final LocalDate initialDate = new LocalDate(2013, 8, 7);
+ clock.setDay(initialDate);
+
+ final Account account = createAccount(getAccountData(7));
+
+ final String bundleKey2 = "bundleKey2";
+ final PlanPhaseSpecifier spec = new PlanPhaseSpecifier("Shotgun", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, null);
+ testListener.pushExpectedEvents(NextEvent.BLOCK, NextEvent.CREATE);
+ entitlementApi.createBaseEntitlement(account.getId(), spec, bundleKey2, null, null, null, false, ImmutableList.<PluginProperty>of(), callContext);
+ assertListenerStatus();
+
+
+ // First bundle of EntitlementSpecifier will specify all new subscription
+ final String bundleKey1 = "bundleKey1";
+ final EntitlementSpecifier spec11 = new DefaultEntitlementSpecifier(new PlanPhaseSpecifier("Shotgun", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, null), null);
+ final EntitlementSpecifier spec12 = new DefaultEntitlementSpecifier(new PlanPhaseSpecifier("Telescopic-Scope", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, null), null);
+ final List<EntitlementSpecifier> specs1 = ImmutableList.of(spec11, spec12);
+ final BaseEntitlementWithAddOnsSpecifier baseEntitlementWithAddOnsSpecifier1 = new DefaultBaseEntitlementWithAddOnsSpecifier(null, bundleKey1, specs1, null, null, false);
+
+
+ // Second bundle of EntitlementSpecifier will specify the previously created 'existingEntitlement'
+ final EntitlementSpecifier spec22 = new DefaultEntitlementSpecifier(new PlanPhaseSpecifier("Telescopic-Scope", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, null), null);
+ final List<EntitlementSpecifier> specs2 = ImmutableList.of(spec22);
+ final BaseEntitlementWithAddOnsSpecifier baseEntitlementWithAddOnsSpecifier2 = new DefaultBaseEntitlementWithAddOnsSpecifier(null, bundleKey2, specs2, null, null, false);
+
+ final Iterable<BaseEntitlementWithAddOnsSpecifier> baseEntitlementWithAddOnsSpecifiers = ImmutableList.of(baseEntitlementWithAddOnsSpecifier1, baseEntitlementWithAddOnsSpecifier2);
+
+
+ // We expect 3 {BLOCK, CREATE} events for the 3 subscriptions created,.
+ testListener.pushExpectedEvents(NextEvent.BLOCK, NextEvent.CREATE, NextEvent.BLOCK, NextEvent.CREATE, NextEvent.BLOCK, NextEvent.CREATE);
+ final List<Entitlement> entitlements = entitlementApi.createBaseEntitlementsWithAddOns(account.getId(), baseEntitlementWithAddOnsSpecifiers, ImmutableList.<PluginProperty>of(), callContext);
+ assertListenerStatus();
+
+ // Retun only the created subscriptions
+ Assert.assertEquals(entitlements.size(), 3);
+
+ final List<Entitlement> entitlementsForBundle1 = entitlementApi.getAllEntitlementsForAccountIdAndExternalKey(account.getId(), bundleKey1, callContext);
+ Assert.assertEquals(entitlementsForBundle1.size(), 2);
+
+ // And yet we do have both the BASE and ADD_ON for bundleKey2
+ final List<Entitlement> entitlementsForBundle2 = entitlementApi.getAllEntitlementsForAccountIdAndExternalKey(account.getId(), bundleKey2, callContext);
+ Assert.assertEquals(entitlementsForBundle2.size(), 2);
+
+ }
+
+
+ @Test(groups = "slow", expectedExceptions = EntitlementApiException.class)
+ public void testCreateBaseSubscriptionsWithAddOnsMissingBase() throws AccountApiException, EntitlementApiException, SubscriptionApiException {
+ final LocalDate initialDate = new LocalDate(2013, 8, 7);
+ clock.setDay(initialDate);
+
+ final Account account = createAccount(getAccountData(7));
+
+ final String bundleKey2 = "bundleKey2";
+
+ final EntitlementSpecifier spec22 = new DefaultEntitlementSpecifier(new PlanPhaseSpecifier("Telescopic-Scope", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, null), null);
+ final List<EntitlementSpecifier> specs2 = ImmutableList.of(spec22);
+ final BaseEntitlementWithAddOnsSpecifier baseEntitlementWithAddOnsSpecifier2 = new DefaultBaseEntitlementWithAddOnsSpecifier(null, bundleKey2, specs2, null, null, false);
+
+ final Iterable<BaseEntitlementWithAddOnsSpecifier> baseEntitlementWithAddOnsSpecifiers = ImmutableList.of(baseEntitlementWithAddOnsSpecifier2);
+
+ entitlementApi.createBaseEntitlementsWithAddOns(account.getId(), baseEntitlementWithAddOnsSpecifiers, ImmutableList.<PluginProperty>of(), callContext);
+ }
+
+ @Test(groups = "slow")
+ public void testCreateBaseSubscriptionsWithAddOnsBadOrdering() throws AccountApiException, EntitlementApiException, SubscriptionApiException {
+ final LocalDate initialDate = new LocalDate(2013, 8, 7);
+ clock.setDay(initialDate);
+
+ final Account account = createAccount(getAccountData(7));
+
+ final String bundleKey1 = "bundleKey1";
+ final EntitlementSpecifier spec11 = new DefaultEntitlementSpecifier(new PlanPhaseSpecifier("Telescopic-Scope", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, null), null);
+ final EntitlementSpecifier spec12 = new DefaultEntitlementSpecifier(new PlanPhaseSpecifier("Shotgun", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, null), null);
+ final List<EntitlementSpecifier> specs1 = ImmutableList.of(spec11, spec12);
+ final BaseEntitlementWithAddOnsSpecifier baseEntitlementWithAddOnsSpecifier1 = new DefaultBaseEntitlementWithAddOnsSpecifier(null, bundleKey1, specs1, null, null, false);
+
+ final Iterable<BaseEntitlementWithAddOnsSpecifier> baseEntitlementWithAddOnsSpecifiers = ImmutableList.of(baseEntitlementWithAddOnsSpecifier1);
+
+
+ testListener.pushExpectedEvents(NextEvent.BLOCK, NextEvent.CREATE, NextEvent.BLOCK, NextEvent.CREATE);
+ entitlementApi.createBaseEntitlementsWithAddOns(account.getId(), baseEntitlementWithAddOnsSpecifiers, ImmutableList.<PluginProperty>of(), callContext);
+ assertListenerStatus();
+
+ }
}
invoice/pom.xml 2(+1 -1)
diff --git a/invoice/pom.xml b/invoice/pom.xml
index 74f81c4..dd27586 100644
--- a/invoice/pom.xml
+++ b/invoice/pom.xml
@@ -19,7 +19,7 @@
<parent>
<artifactId>killbill</artifactId>
<groupId>org.kill-bill.billing</groupId>
- <version>0.18.5-SNAPSHOT</version>
+ <version>0.18.7-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>killbill-invoice</artifactId>
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 4976f49..0afa55c 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
@@ -59,6 +59,7 @@ import org.killbill.billing.invoice.notification.ParentInvoiceCommitmentPoster;
import org.killbill.billing.tag.TagInternalApi;
import org.killbill.billing.util.UUIDs;
import org.killbill.billing.util.cache.Cachable.CacheType;
+import org.killbill.billing.util.cache.CacheController;
import org.killbill.billing.util.cache.CacheControllerDispatcher;
import org.killbill.billing.util.callcontext.InternalCallContextFactory;
import org.killbill.billing.util.config.definition.InvoiceConfig;
@@ -115,7 +116,7 @@ public class DefaultInvoiceDao extends EntityDaoBase<InvoiceModelDao, Invoice, I
private final CBADao cbaDao;
private final InvoiceConfig invoiceConfig;
private final Clock clock;
- private final CacheControllerDispatcher cacheControllerDispatcher;
+ private final CacheController<String, UUID> objectIdCacheController;
private final NonEntityDao nonEntityDao;
private final ParentInvoiceCommitmentPoster parentInvoiceCommitmentPoster;
private final TagInternalApi tagInternalApi;
@@ -142,7 +143,7 @@ public class DefaultInvoiceDao extends EntityDaoBase<InvoiceModelDao, Invoice, I
this.invoiceDaoHelper = invoiceDaoHelper;
this.cbaDao = cbaDao;
this.clock = clock;
- this.cacheControllerDispatcher = cacheControllerDispatcher;
+ this.objectIdCacheController = cacheControllerDispatcher.getCacheController(CacheType.OBJECT_ID);
this.nonEntityDao = nonEntityDao;
this.parentInvoiceCommitmentPoster = parentInvoiceCommitmentPoster;
}
@@ -325,7 +326,7 @@ public class DefaultInvoiceDao extends EntityDaoBase<InvoiceModelDao, Invoice, I
// We only want to insert that invoice if there are real invoiceItems associated to it -- if not, this is just
// a shell invoice and we only need to insert the invoiceItems -- for the already existing invoices
if (isRealInvoice) {
- invoiceSqlDao.create(invoiceModelDao, context);
+ createAndRefresh(invoiceSqlDao, invoiceModelDao, context);
createdInvoiceIds.add(invoiceModelDao.getId());
}
}
@@ -333,8 +334,7 @@ public class DefaultInvoiceDao extends EntityDaoBase<InvoiceModelDao, Invoice, I
// Create the invoice items if needed (note: they may not necessarily belong to that invoice)
for (final InvoiceItemModelDao invoiceItemModelDao : invoiceModelDao.getInvoiceItems()) {
if (transInvoiceItemSqlDao.getById(invoiceItemModelDao.getId().toString(), context) == null) {
- createInvoiceItemFromTransaction(transInvoiceItemSqlDao, invoiceItemModelDao, context);
- createdInvoiceItems.add(transInvoiceItemSqlDao.getById(invoiceItemModelDao.getId().toString(), context));
+ createdInvoiceItems.add(createInvoiceItemFromTransaction(transInvoiceItemSqlDao, invoiceItemModelDao, context));
adjustedInvoiceIds.add(invoiceItemModelDao.getInvoiceId());
}
@@ -550,14 +550,13 @@ public class DefaultInvoiceDao extends EntityDaoBase<InvoiceModelDao, Invoice, I
payment.getInvoiceId(), paymentId,
context.getCreatedDate(), requestedPositiveAmount.negate(),
payment.getCurrency(), payment.getProcessedCurrency(), transactionExternalKey, payment.getId(), true);
- transactional.create(refund, context);
+ createAndRefresh(transactional, refund, context);
// Retrieve invoice after the Refund
final InvoiceModelDao invoice = transInvoiceDao.getById(payment.getInvoiceId().toString(), context);
Preconditions.checkState(invoice != null, "Invoice shouldn't be null for payment " + payment.getId());
invoiceDaoHelper.populateChildren(invoice, invoicesTags, entitySqlDaoWrapperFactory, context);
- final BigDecimal invoiceBalanceAfterRefund = InvoiceModelDaoHelper.getBalance(invoice);
final InvoiceItemSqlDao transInvoiceItemDao = entitySqlDaoWrapperFactory.become(InvoiceItemSqlDao.class);
// At this point, we created the refund which made the invoice balance positive and applied any existing
@@ -630,7 +629,7 @@ public class DefaultInvoiceDao extends EntityDaoBase<InvoiceModelDao, Invoice, I
payment.getInvoiceId(), payment.getPaymentId(), context.getCreatedDate(),
requestedChargedBackAmount.negate(), payment.getCurrency(), payment.getProcessedCurrency(),
chargebackTransactionExternalKey, payment.getId(), true);
- transactional.create(chargeBack, context);
+ createAndRefresh(transactional, chargeBack, context);
// Notify the bus since the balance of the invoice changed
final UUID accountId = transactional.getAccountIdFromInvoicePaymentId(chargeBack.getId().toString(), context);
@@ -816,7 +815,7 @@ public class DefaultInvoiceDao extends EntityDaoBase<InvoiceModelDao, Invoice, I
}).orNull();
if (existingAttempt == null) {
- transactional.create(invoicePayment, context);
+ createAndRefresh(transactional, invoicePayment, context);
} else {
transactional.updateAttempt(existingAttempt.getRecordId(),
invoicePayment.getPaymentId().toString(),
@@ -832,7 +831,7 @@ public class DefaultInvoiceDao extends EntityDaoBase<InvoiceModelDao, Invoice, I
}
if (completion) {
- final UUID accountId = nonEntityDao.retrieveIdFromObjectInTransaction(context.getAccountRecordId(), ObjectType.ACCOUNT, cacheControllerDispatcher.getCacheController(CacheType.OBJECT_ID), entitySqlDaoWrapperFactory.getHandle());
+ final UUID accountId = nonEntityDao.retrieveIdFromObjectInTransaction(context.getAccountRecordId(), ObjectType.ACCOUNT, objectIdCacheController, entitySqlDaoWrapperFactory.getHandle());
notifyBusOfInvoicePayment(entitySqlDaoWrapperFactory, invoicePayment, accountId, context.getUserToken(), context);
}
return null;
@@ -1032,7 +1031,7 @@ public class DefaultInvoiceDao extends EntityDaoBase<InvoiceModelDao, Invoice, I
}
}
- private void createInvoiceItemFromTransaction(final InvoiceItemSqlDao invoiceItemSqlDao, final InvoiceItemModelDao invoiceItemModelDao, final InternalCallContext context) throws EntityPersistenceException, InvoiceApiException {
+ private InvoiceItemModelDao createInvoiceItemFromTransaction(final InvoiceItemSqlDao invoiceItemSqlDao, final InvoiceItemModelDao invoiceItemModelDao, final InternalCallContext context) throws EntityPersistenceException, InvoiceApiException {
// There is no efficient way to retrieve an invoice item given an ID today (and invoice plugins can put item adjustments
// on a different invoice than the original item), so it's easier to do the check in the DAO rather than in the API layer
// See also https://github.com/killbill/killbill/issues/7
@@ -1040,7 +1039,7 @@ public class DefaultInvoiceDao extends EntityDaoBase<InvoiceModelDao, Invoice, I
validateInvoiceItemToBeAdjusted(invoiceItemSqlDao, invoiceItemModelDao, context);
}
- invoiceItemSqlDao.create(invoiceItemModelDao, context);
+ return createAndRefresh(invoiceItemSqlDao, invoiceItemModelDao, context);
}
private void validateInvoiceItemToBeAdjusted(final InvoiceItemSqlDao invoiceItemSqlDao, final InvoiceItemModelDao invoiceItemModelDao, final InternalCallContext context) throws InvoiceApiException {
@@ -1114,7 +1113,7 @@ public class DefaultInvoiceDao extends EntityDaoBase<InvoiceModelDao, Invoice, I
@Override
public Void inTransaction(final EntitySqlDaoWrapperFactory entitySqlDaoWrapperFactory) throws Exception {
final InvoiceParentChildrenSqlDao transactional = entitySqlDaoWrapperFactory.become(InvoiceParentChildrenSqlDao.class);
- transactional.create(invoiceRelation, context);
+ createAndRefresh(transactional, invoiceRelation, context);
return null;
}
});
@@ -1230,14 +1229,14 @@ public class DefaultInvoiceDao extends EntityDaoBase<InvoiceModelDao, Invoice, I
// save invoices and invoice items
final InvoiceModelDao childInvoice = new InvoiceModelDao(invoiceForExternalCharge);
- invoiceSqlDao.create(childInvoice, childAccountContext);
+ createAndRefresh(invoiceSqlDao, childInvoice, childAccountContext);
final InvoiceItemModelDao childExternalChargeItem = new InvoiceItemModelDao(externalChargeItem);
createInvoiceItemFromTransaction(transInvoiceItemSqlDao, childExternalChargeItem, childAccountContext);
// Keep invoice up-to-date for CBA below
childInvoice.addInvoiceItem(childExternalChargeItem);
final InvoiceModelDao parentInvoice = new InvoiceModelDao(invoiceForCredit);
- invoiceSqlDao.create(parentInvoice, parentAccountContext);
+ createAndRefresh(invoiceSqlDao, parentInvoice, parentAccountContext);
final InvoiceItemModelDao parentCreditItem = new InvoiceItemModelDao(creditItem);
createInvoiceItemFromTransaction(transInvoiceItemSqlDao, parentCreditItem, parentAccountContext);
// Keep invoice up-to-date for CBA below
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/template/bundles/DefaultResourceBundleFactory.java b/invoice/src/main/java/org/killbill/billing/invoice/template/bundles/DefaultResourceBundleFactory.java
index f989471..b5bcb96 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/template/bundles/DefaultResourceBundleFactory.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/template/bundles/DefaultResourceBundleFactory.java
@@ -51,7 +51,7 @@ public class DefaultResourceBundleFactory implements ResourceBundleFactory {
@Override
public ResourceBundle createBundle(final Locale locale, final String bundlePath, final ResourceBundleType type, final InternalTenantContext tenantContext) {
- if (tenantContext.getTenantRecordId() == InternalCallContextFactory.INTERNAL_TENANT_RECORD_ID) {
+ if (InternalCallContextFactory.INTERNAL_TENANT_RECORD_ID.equals(tenantContext.getTenantRecordId())) {
return getGlobalBundle(locale, bundlePath);
}
final String bundle = getTenantBundleForType(locale, type, tenantContext);
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/template/HtmlInvoiceGenerator.java b/invoice/src/main/java/org/killbill/billing/invoice/template/HtmlInvoiceGenerator.java
index 3e0e0bc..16f391c 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/template/HtmlInvoiceGenerator.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/template/HtmlInvoiceGenerator.java
@@ -101,7 +101,7 @@ public class HtmlInvoiceGenerator {
private String getTemplateText(final Locale locale, final boolean manualPay, final InternalTenantContext context) throws IOException {
- if (context.getTenantRecordId() == InternalCallContextFactory.INTERNAL_TENANT_RECORD_ID) {
+ if (InternalCallContextFactory.INTERNAL_TENANT_RECORD_ID.equals(context.getTenantRecordId())) {
return getDefaultTemplate(manualPay ? config.getManualPayTemplateName() : config.getTemplateName());
}
final String template = manualPay ?
diff --git a/invoice/src/test/java/org/killbill/billing/invoice/notification/TestNextBillingDateNotifier.java b/invoice/src/test/java/org/killbill/billing/invoice/notification/TestNextBillingDateNotifier.java
index 6d083d1..51ddb3a 100644
--- a/invoice/src/test/java/org/killbill/billing/invoice/notification/TestNextBillingDateNotifier.java
+++ b/invoice/src/test/java/org/killbill/billing/invoice/notification/TestNextBillingDateNotifier.java
@@ -1,7 +1,9 @@
/*
* Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014-2017 Groupon, Inc
+ * Copyright 2014-2017 The Billing Project, LLC
*
- * Ning licenses this file to you under the Apache License, version 2.0
+ * 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:
*
@@ -20,14 +22,14 @@ import java.util.UUID;
import java.util.concurrent.Callable;
import org.joda.time.DateTime;
-import org.testng.Assert;
-import org.testng.annotations.Test;
-
-import org.killbill.billing.subscription.api.SubscriptionBase;
+import org.killbill.billing.ObjectType;
+import org.killbill.billing.account.api.Account;
import org.killbill.billing.invoice.InvoiceTestSuiteWithEmbeddedDB;
import org.killbill.billing.invoice.api.DefaultInvoiceService;
+import org.killbill.billing.subscription.api.SubscriptionBase;
import org.killbill.notificationq.api.NotificationQueue;
-import org.killbill.clock.ClockMock;
+import org.testng.Assert;
+import org.testng.annotations.Test;
import static com.jayway.awaitility.Awaitility.await;
import static java.util.concurrent.TimeUnit.MINUTES;
@@ -36,19 +38,19 @@ public class TestNextBillingDateNotifier extends InvoiceTestSuiteWithEmbeddedDB
@Test(groups = "slow")
public void testInvoiceNotifier() throws Exception {
+ final Account account = invoiceUtil.createAccount(callContext);
+ final Long accountRecordId = nonEntityDao.retrieveAccountRecordIdFromObject(account.getId(), ObjectType.ACCOUNT, null);
final SubscriptionBase subscription = invoiceUtil.createSubscription();
final UUID subscriptionId = subscription.getId();
final DateTime now = clock.getUTCNow();
-
final NotificationQueue nextBillingQueue = notificationQueueService.getNotificationQueue(DefaultInvoiceService.INVOICE_SERVICE_NAME, DefaultNextBillingDateNotifier.NEXT_BILLING_DATE_NOTIFIER_QUEUE);
-
- nextBillingQueue.recordFutureNotification(now, new NextBillingDateNotificationKey(subscriptionId, now, Boolean.FALSE), internalCallContext.getUserToken(), internalCallContext.getAccountRecordId(), internalCallContext.getTenantRecordId());
+ nextBillingQueue.recordFutureNotification(now, new NextBillingDateNotificationKey(subscriptionId, now, Boolean.FALSE), internalCallContext.getUserToken(), accountRecordId, internalCallContext.getTenantRecordId());
// Move time in the future after the notification effectiveDate
- ((ClockMock) clock).setDeltaFromReality(3000);
+ clock.setDeltaFromReality(3000);
await().atMost(1, MINUTES).until(new Callable<Boolean>() {
@Override
diff --git a/invoice/src/test/java/org/killbill/billing/invoice/TestInvoiceHelper.java b/invoice/src/test/java/org/killbill/billing/invoice/TestInvoiceHelper.java
index 7708eb6..06ee314 100644
--- a/invoice/src/test/java/org/killbill/billing/invoice/TestInvoiceHelper.java
+++ b/invoice/src/test/java/org/killbill/billing/invoice/TestInvoiceHelper.java
@@ -1,7 +1,7 @@
/*
* Copyright 2010-2013 Ning, Inc.
- * Copyright 2014-2016 Groupon, Inc
- * Copyright 2014-2016 The Billing Project, LLC
+ * 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
@@ -52,8 +52,6 @@ import org.killbill.billing.catalog.api.PlanPhaseSpecifier;
import org.killbill.billing.catalog.api.Usage;
import org.killbill.billing.entitlement.api.SubscriptionEventType;
import org.killbill.billing.entity.EntityPersistenceException;
-import org.killbill.billing.invoice.InvoiceDispatcher.FutureAccountNotifications;
-import org.killbill.billing.invoice.InvoiceDispatcher.FutureAccountNotifications.SubscriptionNotification;
import org.killbill.billing.invoice.api.DryRunArguments;
import org.killbill.billing.invoice.api.DryRunType;
import org.killbill.billing.invoice.api.Invoice;
@@ -95,7 +93,6 @@ import org.testng.Assert;
import com.google.common.base.Function;
import com.google.common.collect.Collections2;
import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableMap;
public class TestInvoiceHelper {
jaxrs/pom.xml 6(+1 -5)
diff --git a/jaxrs/pom.xml b/jaxrs/pom.xml
index b76c36c..fc79a8e 100644
--- a/jaxrs/pom.xml
+++ b/jaxrs/pom.xml
@@ -21,7 +21,7 @@
<parent>
<artifactId>killbill</artifactId>
<groupId>org.kill-bill.billing</groupId>
- <version>0.18.5-SNAPSHOT</version>
+ <version>0.18.7-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>killbill-jaxrs</artifactId>
@@ -76,10 +76,6 @@
<artifactId>joda-time</artifactId>
</dependency>
<dependency>
- <groupId>net.sf.ehcache</groupId>
- <artifactId>ehcache</artifactId>
- </dependency>
- <dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
</dependency>
diff --git a/jaxrs/src/main/java/org/killbill/billing/jaxrs/json/OverdueConditionJson.java b/jaxrs/src/main/java/org/killbill/billing/jaxrs/json/OverdueConditionJson.java
index 048a3c5..a190bd2 100644
--- a/jaxrs/src/main/java/org/killbill/billing/jaxrs/json/OverdueConditionJson.java
+++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/json/OverdueConditionJson.java
@@ -119,13 +119,13 @@ public class OverdueConditionJson {
if (controlTagExclusion != that.controlTagExclusion) {
return false;
}
- if (numberOfUnpaidInvoicesEqualsOrExceeds != that.numberOfUnpaidInvoicesEqualsOrExceeds) {
+ if (!numberOfUnpaidInvoicesEqualsOrExceeds.equals(that.numberOfUnpaidInvoicesEqualsOrExceeds)) {
return false;
}
if (responseForLastFailedPayment != that.responseForLastFailedPayment) {
return false;
}
- return totalUnpaidInvoiceBalanceEqualsOrExceeds == that.totalUnpaidInvoiceBalanceEqualsOrExceeds;
+ return totalUnpaidInvoiceBalanceEqualsOrExceeds.equals(that.totalUnpaidInvoiceBalanceEqualsOrExceeds);
}
diff --git a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/AdminResource.java b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/AdminResource.java
index 27168ba..e9af063 100644
--- a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/AdminResource.java
+++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/AdminResource.java
@@ -47,6 +47,8 @@ import org.joda.time.DateTimeZone;
import org.killbill.billing.ErrorCode;
import org.killbill.billing.ObjectType;
import org.killbill.billing.account.api.AccountUserApi;
+import org.killbill.billing.account.api.ImmutableAccountData;
+import org.killbill.billing.catalog.api.Catalog;
import org.killbill.billing.invoice.api.InvoiceApiException;
import org.killbill.billing.invoice.api.InvoiceUserApi;
import org.killbill.billing.jaxrs.json.AdminPaymentJson;
@@ -67,8 +69,11 @@ import org.killbill.billing.util.api.CustomFieldUserApi;
import org.killbill.billing.util.api.RecordIdApi;
import org.killbill.billing.util.api.TagUserApi;
import org.killbill.billing.util.cache.Cachable.CacheType;
+import org.killbill.billing.util.cache.CacheController;
+import org.killbill.billing.util.cache.CacheControllerDispatcher;
import org.killbill.billing.util.callcontext.CallContext;
import org.killbill.billing.util.callcontext.TenantContext;
+import org.killbill.billing.util.config.tenant.PerTenantConfig;
import org.killbill.billing.util.entity.Pagination;
import org.killbill.billing.util.tag.Tag;
import org.killbill.billing.util.tag.dao.SystemTags;
@@ -82,6 +87,7 @@ import org.killbill.notificationq.api.NotificationQueue;
import org.killbill.notificationq.api.NotificationQueueService;
import com.fasterxml.jackson.core.JsonGenerator;
+import com.google.common.base.Function;
import com.google.common.base.Predicate;
import com.google.common.base.Strings;
import com.google.common.collect.ImmutableList;
@@ -92,8 +98,6 @@ import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiResponse;
import io.swagger.annotations.ApiResponses;
-import net.sf.ehcache.CacheManager;
-import net.sf.ehcache.Ehcache;
import static javax.ws.rs.core.MediaType.APPLICATION_JSON;
@@ -107,7 +111,7 @@ public class AdminResource extends JaxRsResourceBase {
private final AdminPaymentApi adminPaymentApi;
private final InvoiceUserApi invoiceUserApi;
private final TenantUserApi tenantApi;
- private final CacheManager cacheManager;
+ private final CacheControllerDispatcher cacheControllerDispatcher;
private final RecordIdApi recordIdApi;
private final PersistentBus persistentBus;
private final NotificationQueueService notificationQueueService;
@@ -121,7 +125,7 @@ public class AdminResource extends JaxRsResourceBase {
final PaymentApi paymentApi,
final AdminPaymentApi adminPaymentApi,
final InvoiceUserApi invoiceUserApi,
- final CacheManager cacheManager,
+ final CacheControllerDispatcher cacheControllerDispatcher,
final TenantUserApi tenantApi,
final RecordIdApi recordIdApi,
final PersistentBus persistentBus,
@@ -133,7 +137,7 @@ public class AdminResource extends JaxRsResourceBase {
this.invoiceUserApi = invoiceUserApi;
this.tenantApi = tenantApi;
this.recordIdApi = recordIdApi;
- this.cacheManager = cacheManager;
+ this.cacheControllerDispatcher = cacheControllerDispatcher;
this.persistentBus = persistentBus;
this.notificationQueueService = notificationQueueService;
}
@@ -322,17 +326,17 @@ public class AdminResource extends JaxRsResourceBase {
public Response invalidatesCache(@QueryParam("cacheName") final String cacheName,
@javax.ws.rs.core.Context final HttpServletRequest request) {
if (null != cacheName && !cacheName.isEmpty()) {
- final Ehcache cache = cacheManager.getEhcache(cacheName);
- // check if cache is null
- if (cache == null) {
+ // Clear given cache
+ final CacheType cacheType = CacheType.findByName(cacheName);
+ if (cacheType != null) {
+ cacheControllerDispatcher.getCacheController(cacheType).removeAll();
+ } else {
log.warn("Cache for specified cacheName='{}' does not exist or is not alive", cacheName);
return Response.status(Status.BAD_REQUEST).build();
}
- // Clear given cache
- cache.removeAll();
} else {
// if not given a specific cacheName, clear all
- cacheManager.clearAll();
+ cacheControllerDispatcher.clearAll();
}
return Response.status(Status.OK).build();
}
@@ -342,20 +346,23 @@ public class AdminResource extends JaxRsResourceBase {
@Produces(APPLICATION_JSON)
@ApiOperation(value = "Invalidates Caches per account level")
@ApiResponses(value = {})
- public Response invalidatesCacheByAccount(@PathParam("accountId") final String accountId,
+ public Response invalidatesCacheByAccount(@PathParam("accountId") final String accountIdStr,
@javax.ws.rs.core.Context final HttpServletRequest request) {
+ final TenantContext tenantContext = context.createContext(request);
+ final UUID accountId = UUID.fromString(accountIdStr);
+ final Long accountRecordId = recordIdApi.getRecordId(accountId, ObjectType.ACCOUNT, tenantContext);
- // clear account-record-id cache by accountId
- final Ehcache accountRecordIdCache = cacheManager.getEhcache(CacheType.ACCOUNT_RECORD_ID.getCacheName());
- accountRecordIdCache.remove(accountId);
+ // clear account-record-id cache by accountId (note: String!)
+ final CacheController<String, Long> accountRecordIdCacheController = cacheControllerDispatcher.getCacheController(CacheType.ACCOUNT_RECORD_ID);
+ accountRecordIdCacheController.remove(accountIdStr);
- // clear account-immutable cache by accountId
- final Ehcache accountImmutableCache = cacheManager.getEhcache(CacheType.ACCOUNT_IMMUTABLE.getCacheName());
- accountImmutableCache.remove(UUID.fromString(accountId));
+ // clear account-immutable cache by account record id
+ final CacheController<Long, ImmutableAccountData> accountImmutableCacheController = cacheControllerDispatcher.getCacheController(CacheType.ACCOUNT_IMMUTABLE);
+ accountImmutableCacheController.remove(accountRecordId);
- // clear account-bcd cache by accountId
- final Ehcache accountBcdCache = cacheManager.getEhcache(CacheType.ACCOUNT_BCD.getCacheName());
- accountBcdCache.remove(UUID.fromString(accountId));
+ // clear account-bcd cache by accountId (note: UUID!)
+ final CacheController<UUID, Integer> accountBCDCacheController = cacheControllerDispatcher.getCacheController(CacheType.ACCOUNT_BCD);
+ accountBCDCacheController.remove(accountId);
return Response.status(Status.OK).build();
}
@@ -369,52 +376,51 @@ public class AdminResource extends JaxRsResourceBase {
@javax.ws.rs.core.Context final HttpServletRequest request) throws TenantApiException {
// creating Tenant Context from Request
- TenantContext tenantContext = context.createContext(request);
+ final TenantContext tenantContext = context.createContext(request);
- Tenant currentTenant = tenantApi.getTenantById(tenantContext.getTenantId());
+ final Tenant currentTenant = tenantApi.getTenantById(tenantContext.getTenantId());
// getting Tenant Record Id
- Long tenantRecordId = recordIdApi.getRecordId(tenantContext.getTenantId(), ObjectType.TENANT, tenantContext);
+ final Long tenantRecordId = recordIdApi.getRecordId(tenantContext.getTenantId(), ObjectType.TENANT, tenantContext);
+
+ final Function<String, Boolean> tenantKeysMatcher = new Function<String, Boolean>() {
+ @Override
+ public Boolean apply(@Nullable final String key) {
+ return key != null && key.endsWith("::" + tenantRecordId);
+ }
+ };
// clear tenant-record-id cache by tenantId
- final Ehcache tenantRecordIdCache = cacheManager.getEhcache(CacheType.TENANT_RECORD_ID.getCacheName());
- tenantRecordIdCache.remove(currentTenant.getId().toString());
+ final CacheController<String, Long> tenantRecordIdCacheController = cacheControllerDispatcher.getCacheController(CacheType.TENANT_RECORD_ID);
+ tenantRecordIdCacheController.remove(currentTenant.getId().toString());
// clear tenant-payment-state-machine-config cache by tenantRecordId
- final Ehcache tenantPaymentStateMachineConfigCache = cacheManager.getEhcache(CacheType.TENANT_PAYMENT_STATE_MACHINE_CONFIG.getCacheName());
- removeCacheByKey(tenantPaymentStateMachineConfigCache, tenantRecordId.toString());
+ final CacheController<String, Object> tenantPaymentStateMachineConfigCacheController = cacheControllerDispatcher.getCacheController(CacheType.TENANT_PAYMENT_STATE_MACHINE_CONFIG);
+ tenantPaymentStateMachineConfigCacheController.remove(tenantKeysMatcher);
// clear tenant cache by tenantApiKey
- final Ehcache tenantCache = cacheManager.getEhcache(CacheType.TENANT.getCacheName());
- tenantCache.remove(currentTenant.getApiKey());
+ final CacheController<String, Tenant> tenantCacheController = cacheControllerDispatcher.getCacheController(CacheType.TENANT);
+ tenantCacheController.remove(currentTenant.getApiKey());
// clear tenant-kv cache by tenantRecordId
- final Ehcache tenantKvCache = cacheManager.getEhcache(CacheType.TENANT_KV.getCacheName());
- removeCacheByKey(tenantKvCache, tenantRecordId.toString());
+ final CacheController<String, String> tenantKVCacheController = cacheControllerDispatcher.getCacheController(CacheType.TENANT_KV);
+ tenantKVCacheController.remove(tenantKeysMatcher);
// clear tenant-config cache by tenantRecordId
- final Ehcache tenantConfigCache = cacheManager.getEhcache(CacheType.TENANT_CONFIG.getCacheName());
- tenantConfigCache.remove(tenantRecordId);
+ final CacheController<Long, PerTenantConfig> tenantConfigCacheController = cacheControllerDispatcher.getCacheController(CacheType.TENANT_CONFIG);
+ tenantConfigCacheController.remove(tenantRecordId);
// clear tenant-overdue-config cache by tenantRecordId
- final Ehcache tenantOverdueConfigCache = cacheManager.getEhcache(CacheType.TENANT_OVERDUE_CONFIG.getCacheName());
- tenantOverdueConfigCache.remove(tenantRecordId);
+ final CacheController<Long, Object> tenantOverdueConfigCacheController = cacheControllerDispatcher.getCacheController(CacheType.TENANT_OVERDUE_CONFIG);
+ tenantOverdueConfigCacheController.remove(tenantRecordId);
// clear tenant-catalog cache by tenantRecordId
- final Ehcache tenantCatalogCache = cacheManager.getEhcache(CacheType.TENANT_CATALOG.getCacheName());
- tenantCatalogCache.remove(tenantRecordId);
+ final CacheController<Long, Catalog> tenantCatalogCacheController = cacheControllerDispatcher.getCacheController(CacheType.TENANT_CATALOG);
+ tenantCatalogCacheController.remove(tenantRecordId);
return Response.status(Status.OK).build();
}
- private void removeCacheByKey(final Ehcache tenantCache, final String tenantRecordId) {
- for (Object key : tenantCache.getKeys()) {
- if (null != key && key.toString().endsWith("::" + tenantRecordId)) {
- tenantCache.remove(key);
- }
- }
- }
-
private Iterable<NotificationEventWithMetadata<NotificationEvent>> getNotifications(@Nullable final String queueName,
@Nullable final String serviceName,
final boolean includeInProcessing,
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) {
junction/pom.xml 2(+1 -1)
diff --git a/junction/pom.xml b/junction/pom.xml
index 6caedca..40b0ae0 100644
--- a/junction/pom.xml
+++ b/junction/pom.xml
@@ -19,7 +19,7 @@
<parent>
<artifactId>killbill</artifactId>
<groupId>org.kill-bill.billing</groupId>
- <version>0.18.5-SNAPSHOT</version>
+ <version>0.18.7-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>killbill-junction</artifactId>
NEWS 6(+6 -0)
diff --git a/NEWS b/NEWS
index 1ba9b8e..d6374b1 100644
--- a/NEWS
+++ b/NEWS
@@ -1,3 +1,9 @@
+0.18.6
+ See https://github.com/killbill/killbill/releases/tag/killbill-0.18.6
+
+0.18.5
+ See https://github.com/killbill/killbill/releases/tag/killbill-0.18.5
+
0.18.4
See https://github.com/killbill/killbill/releases/tag/killbill-0.18.4
overdue/pom.xml 2(+1 -1)
diff --git a/overdue/pom.xml b/overdue/pom.xml
index 4cd0b83..86f2f21 100644
--- a/overdue/pom.xml
+++ b/overdue/pom.xml
@@ -19,7 +19,7 @@
<parent>
<artifactId>killbill</artifactId>
<groupId>org.kill-bill.billing</groupId>
- <version>0.18.5-SNAPSHOT</version>
+ <version>0.18.7-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>killbill-overdue</artifactId>
diff --git a/overdue/src/main/java/org/killbill/billing/overdue/caching/EhCacheOverdueConfigCache.java b/overdue/src/main/java/org/killbill/billing/overdue/caching/EhCacheOverdueConfigCache.java
index 7285e87..d5bba20 100644
--- a/overdue/src/main/java/org/killbill/billing/overdue/caching/EhCacheOverdueConfigCache.java
+++ b/overdue/src/main/java/org/killbill/billing/overdue/caching/EhCacheOverdueConfigCache.java
@@ -1,6 +1,6 @@
/*
- * Copyright 2014-2015 Groupon, Inc
- * Copyright 2014-2015 The Billing Project, LLC
+ * 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
@@ -44,7 +44,7 @@ public class EhCacheOverdueConfigCache implements OverdueConfigCache {
private static final Logger log = LoggerFactory.getLogger(EhCacheOverdueConfigCache.class);
- private final CacheController cacheController;
+ private final CacheController<Long, OverdueConfig> cacheController;
private final CacheLoaderArgument cacheLoaderArgument;
private OverdueConfig defaultOverdueConfig;
@@ -91,13 +91,13 @@ public class EhCacheOverdueConfigCache implements OverdueConfigCache {
@Override
public OverdueConfig getOverdueConfig(final InternalTenantContext tenantContext) throws OverdueApiException {
- if (tenantContext.getTenantRecordId() == InternalCallContextFactory.INTERNAL_TENANT_RECORD_ID) {
+ if (InternalCallContextFactory.INTERNAL_TENANT_RECORD_ID.equals(tenantContext.getTenantRecordId())) {
return defaultOverdueConfig;
}
// The cache loader might choke on some bad xml -- unlikely since we check its validity prior storing it,
// but to be on the safe side;;
try {
- final OverdueConfig overdueConfig = (OverdueConfig) cacheController.get(tenantContext.getTenantRecordId(), cacheLoaderArgument);
+ final OverdueConfig overdueConfig = cacheController.get(tenantContext.getTenantRecordId(), cacheLoaderArgument);
return (overdueConfig != null) ? overdueConfig : defaultOverdueConfig;
} catch (final IllegalStateException e) {
throw new OverdueApiException(ErrorCode.OVERDUE_INVALID_FOR_TENANT, tenantContext.getTenantRecordId());
@@ -106,7 +106,7 @@ public class EhCacheOverdueConfigCache implements OverdueConfigCache {
@Override
public void clearOverdueConfig(final InternalTenantContext tenantContext) {
- if (tenantContext.getTenantRecordId() != InternalCallContextFactory.INTERNAL_TENANT_RECORD_ID) {
+ if (!InternalCallContextFactory.INTERNAL_TENANT_RECORD_ID.equals(tenantContext.getTenantRecordId())) {
cacheController.remove(tenantContext.getTenantRecordId());
}
}
@@ -114,7 +114,7 @@ public class EhCacheOverdueConfigCache implements OverdueConfigCache {
private CacheLoaderArgument initializeCacheLoaderArgument() {
final LoaderCallback loaderCallback = new LoaderCallback() {
@Override
- public Object loadOverdueConfig(final String overdueConfigXML) throws OverdueApiException {
+ public OverdueConfig loadOverdueConfig(final String overdueConfigXML) throws OverdueApiException {
final InputStream overdueConfigStream = new ByteArrayInputStream(overdueConfigXML.getBytes());
final URI uri;
try {
diff --git a/overdue/src/main/java/org/killbill/billing/overdue/config/DefaultOverdueConfig.java b/overdue/src/main/java/org/killbill/billing/overdue/config/DefaultOverdueConfig.java
index 9ca684d..0aec675 100644
--- a/overdue/src/main/java/org/killbill/billing/overdue/config/DefaultOverdueConfig.java
+++ b/overdue/src/main/java/org/killbill/billing/overdue/config/DefaultOverdueConfig.java
@@ -1,7 +1,9 @@
/*
* Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014-2017 Groupon, Inc
+ * Copyright 2014-2017 The Billing Project, LLC
*
- * Ning licenses this file to you under the Apache License, version 2.0
+ * 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:
*
@@ -16,19 +18,29 @@
package org.killbill.billing.overdue.config;
+import java.io.Externalizable;
+import java.io.IOException;
+import java.io.ObjectInput;
+import java.io.ObjectOutput;
+import java.net.URI;
+
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
-import java.net.URI;
import org.killbill.billing.overdue.api.OverdueConfig;
+import org.killbill.billing.util.cache.ExternalizableInput;
+import org.killbill.billing.util.cache.ExternalizableOutput;
+import org.killbill.billing.util.cache.MapperHolder;
import org.killbill.xmlloader.ValidatingConfig;
import org.killbill.xmlloader.ValidationErrors;
@XmlRootElement(name = "overdueConfig")
@XmlAccessorType(XmlAccessType.NONE)
-public class DefaultOverdueConfig extends ValidatingConfig<DefaultOverdueConfig> implements OverdueConfig {
+public class DefaultOverdueConfig extends ValidatingConfig<DefaultOverdueConfig> implements OverdueConfig, Externalizable {
+
+ private static final long serialVersionUID = 1282636602472877120L;
@XmlElement(required = true, name = "accountOverdueStates")
private DefaultOverdueStatesAccount accountOverdueStates = new DefaultOverdueStatesAccount();
@@ -43,14 +55,25 @@ public class DefaultOverdueConfig extends ValidatingConfig<DefaultOverdueConfig>
return accountOverdueStates.validate(root, errors);
}
+ // For deserialization
+ public DefaultOverdueConfig() {}
+
public DefaultOverdueConfig setOverdueStates(final DefaultOverdueStatesAccount accountOverdueStates) {
this.accountOverdueStates = accountOverdueStates;
return this;
}
-
public URI getURI() {
return null;
}
+ @Override
+ public void readExternal(final ObjectInput in) throws IOException {
+ MapperHolder.mapper().readerForUpdating(this).readValue(new ExternalizableInput(in));
+ }
+
+ @Override
+ public void writeExternal(final ObjectOutput oo) throws IOException {
+ MapperHolder.mapper().writeValue(new ExternalizableOutput(oo), this);
+ }
}
diff --git a/overdue/src/main/java/org/killbill/billing/overdue/listener/OverdueListener.java b/overdue/src/main/java/org/killbill/billing/overdue/listener/OverdueListener.java
index 43f00c1..8bed766 100644
--- a/overdue/src/main/java/org/killbill/billing/overdue/listener/OverdueListener.java
+++ b/overdue/src/main/java/org/killbill/billing/overdue/listener/OverdueListener.java
@@ -1,7 +1,7 @@
/*
* Copyright 2010-2013 Ning, Inc.
- * Copyright 2014-2016 Groupon, Inc
- * Copyright 2014-2016 The Billing Project, LLC
+ * 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
@@ -45,6 +45,7 @@ import org.killbill.billing.overdue.notification.OverdueAsyncBusNotificationKey.
import org.killbill.billing.overdue.notification.OverdueAsyncBusNotifier;
import org.killbill.billing.overdue.notification.OverduePoster;
import org.killbill.billing.util.cache.Cachable.CacheType;
+import org.killbill.billing.util.cache.CacheController;
import org.killbill.billing.util.cache.CacheControllerDispatcher;
import org.killbill.billing.util.callcontext.CallOrigin;
import org.killbill.billing.util.callcontext.InternalCallContextFactory;
@@ -65,7 +66,7 @@ public class OverdueListener {
private static final Logger log = LoggerFactory.getLogger(OverdueListener.class);
private final InternalCallContextFactory internalCallContextFactory;
- private final CacheControllerDispatcher cacheControllerDispatcher;
+ private final CacheController<String, UUID> objectIdCacheController;
private final Clock clock;
private final OverduePoster asyncPoster;
private final OverdueConfigCache overdueConfigCache;
@@ -84,7 +85,7 @@ public class OverdueListener {
this.clock = clock;
this.asyncPoster = asyncPoster;
this.overdueConfigCache = overdueConfigCache;
- this.cacheControllerDispatcher = cacheControllerDispatcher;
+ this.objectIdCacheController = cacheControllerDispatcher.getCacheController(CacheType.OBJECT_ID);
this.internalCallContextFactory = internalCallContextFactory;
this.accountApi = accountApi;
}
@@ -96,7 +97,7 @@ public class OverdueListener {
final InternalCallContext internalCallContext = createCallContext(event.getUserToken(), event.getSearchKey1(), event.getSearchKey2());
insertBusEventIntoNotificationQueue(event.getObjectId(), OverdueAsyncBusNotificationAction.CLEAR, internalCallContext);
} else if (event.getTagDefinition().getName().equals(ControlTagType.WRITTEN_OFF.toString()) && event.getObjectType() == ObjectType.INVOICE) {
- final UUID accountId = nonEntityDao.retrieveIdFromObject(event.getSearchKey1(), ObjectType.ACCOUNT, cacheControllerDispatcher.getCacheController(CacheType.OBJECT_ID));
+ final UUID accountId = nonEntityDao.retrieveIdFromObject(event.getSearchKey1(), ObjectType.ACCOUNT, objectIdCacheController);
insertBusEventIntoNotificationQueue(accountId, event);
}
}
@@ -107,7 +108,7 @@ public class OverdueListener {
if (event.getTagDefinition().getName().equals(ControlTagType.OVERDUE_ENFORCEMENT_OFF.toString()) && event.getObjectType() == ObjectType.ACCOUNT) {
insertBusEventIntoNotificationQueue(event.getObjectId(), event);
} else if (event.getTagDefinition().getName().equals(ControlTagType.WRITTEN_OFF.toString()) && event.getObjectType() == ObjectType.INVOICE) {
- final UUID accountId = nonEntityDao.retrieveIdFromObject(event.getSearchKey1(), ObjectType.ACCOUNT, cacheControllerDispatcher.getCacheController(CacheType.OBJECT_ID));
+ final UUID accountId = nonEntityDao.retrieveIdFromObject(event.getSearchKey1(), ObjectType.ACCOUNT, objectIdCacheController);
insertBusEventIntoNotificationQueue(accountId, event);
}
}
diff --git a/overdue/src/test/java/org/killbill/billing/overdue/notification/TestOverdueCheckNotifier.java b/overdue/src/test/java/org/killbill/billing/overdue/notification/TestOverdueCheckNotifier.java
index ee470c5..9fee915 100644
--- a/overdue/src/test/java/org/killbill/billing/overdue/notification/TestOverdueCheckNotifier.java
+++ b/overdue/src/test/java/org/killbill/billing/overdue/notification/TestOverdueCheckNotifier.java
@@ -1,7 +1,9 @@
/*
* Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014-2017 Groupon, Inc
+ * Copyright 2014-2017 The Billing Project, LLC
*
- * Ning licenses this file to you under the Apache License, version 2.0
+ * 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:
*
@@ -20,18 +22,17 @@ import java.util.UUID;
import java.util.concurrent.Callable;
import org.joda.time.DateTime;
-import org.mockito.Mockito;
-import org.testng.Assert;
-import org.testng.annotations.AfterMethod;
-import org.testng.annotations.BeforeMethod;
-import org.testng.annotations.Test;
-
import org.killbill.billing.account.api.Account;
import org.killbill.billing.callcontext.InternalCallContext;
+import org.killbill.billing.callcontext.InternalTenantContext;
import org.killbill.billing.overdue.OverdueTestSuiteWithEmbeddedDB;
import org.killbill.billing.overdue.listener.OverdueDispatcher;
import org.killbill.billing.util.callcontext.InternalCallContextFactory;
-import org.killbill.billing.callcontext.InternalTenantContext;
+import org.mockito.Mockito;
+import org.testng.Assert;
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
import static com.jayway.awaitility.Awaitility.await;
import static java.util.concurrent.TimeUnit.SECONDS;
@@ -41,9 +42,7 @@ public class TestOverdueCheckNotifier extends OverdueTestSuiteWithEmbeddedDB {
private OverdueDispatcherMock mockDispatcher;
private OverdueNotifier notifierForMock;
-
-
- private static final class OverdueDispatcherMock extends OverdueDispatcher {
+ private static final class OverdueDispatcherMock extends OverdueDispatcher {
int eventCount = 0;
UUID latestAccountId = null;
@@ -73,9 +72,6 @@ public class TestOverdueCheckNotifier extends OverdueTestSuiteWithEmbeddedDB {
//super.beforeMethod();
// We override the parent method on purpose, because we want to register a different OverdueCheckNotifier
- final Account account = Mockito.mock(Account.class);
- Mockito.when(accountApi.getAccountById(Mockito.<UUID>any(), Mockito.<InternalTenantContext>any())).thenReturn(account);
-
mockDispatcher = new OverdueDispatcherMock(internalCallContextFactory);
notifierForMock = new OverdueCheckNotifier(notificationQueueService, overdueProperties, internalCallContextFactory, mockDispatcher);
@@ -95,6 +91,7 @@ public class TestOverdueCheckNotifier extends OverdueTestSuiteWithEmbeddedDB {
final UUID accountId = new UUID(0L, 1L);
final Account account = Mockito.mock(Account.class);
Mockito.when(account.getId()).thenReturn(accountId);
+ Mockito.when(accountApi.getImmutableAccountDataByRecordId(Mockito.<UUID>eq(internalCallContext.getAccountRecordId()), Mockito.<InternalTenantContext>any())).thenReturn(account);
final DateTime now = clock.getUTCNow();
final DateTime readyTime = now.plusMillis(2000);
diff --git a/overdue/src/test/java/org/killbill/billing/overdue/OverdueTestSuiteWithEmbeddedDB.java b/overdue/src/test/java/org/killbill/billing/overdue/OverdueTestSuiteWithEmbeddedDB.java
index f2b590d..b74d629 100644
--- a/overdue/src/test/java/org/killbill/billing/overdue/OverdueTestSuiteWithEmbeddedDB.java
+++ b/overdue/src/test/java/org/killbill/billing/overdue/OverdueTestSuiteWithEmbeddedDB.java
@@ -1,7 +1,7 @@
/*
* Copyright 2010-2013 Ning, Inc.
- * Copyright 2014 Groupon, Inc
- * Copyright 2014 The Billing Project, LLC
+ * 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
@@ -21,7 +21,7 @@ package org.killbill.billing.overdue;
import javax.inject.Named;
import org.killbill.billing.GuicyKillbillTestSuiteWithEmbeddedDB;
-import org.killbill.billing.account.api.AccountInternalApi;
+import org.killbill.billing.account.api.ImmutableAccountInternalApi;
import org.killbill.billing.invoice.api.InvoiceInternalApi;
import org.killbill.billing.junction.BlockingInternalApi;
import org.killbill.billing.lifecycle.api.BusService;
@@ -52,7 +52,7 @@ import com.google.inject.Injector;
public abstract class OverdueTestSuiteWithEmbeddedDB extends GuicyKillbillTestSuiteWithEmbeddedDB {
@Inject
- protected AccountInternalApi accountApi;
+ protected ImmutableAccountInternalApi accountApi;
@Inject
protected BillingStateCalculator calculatorBundle;
@Inject
payment/pom.xml 6(+1 -5)
diff --git a/payment/pom.xml b/payment/pom.xml
index 0fc7d88..2352511 100644
--- a/payment/pom.xml
+++ b/payment/pom.xml
@@ -19,7 +19,7 @@
<parent>
<artifactId>killbill</artifactId>
<groupId>org.kill-bill.billing</groupId>
- <version>0.18.5-SNAPSHOT</version>
+ <version>0.18.7-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>killbill-payment</artifactId>
@@ -97,10 +97,6 @@
<scope>test</scope>
</dependency>
<dependency>
- <groupId>net.sf.ehcache</groupId>
- <artifactId>ehcache</artifactId>
- </dependency>
- <dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
</dependency>
diff --git a/payment/src/main/java/org/killbill/billing/payment/caching/EhCacheStateMachineConfigCache.java b/payment/src/main/java/org/killbill/billing/payment/caching/EhCacheStateMachineConfigCache.java
index ae76cb2..29fa335 100644
--- a/payment/src/main/java/org/killbill/billing/payment/caching/EhCacheStateMachineConfigCache.java
+++ b/payment/src/main/java/org/killbill/billing/payment/caching/EhCacheStateMachineConfigCache.java
@@ -1,6 +1,6 @@
/*
- * Copyright 2016 Groupon, Inc
- * Copyright 2016 The Billing Project, LLC
+ * Copyright 2016-2017 Groupon, Inc
+ * Copyright 2016-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
@@ -51,7 +51,7 @@ public class EhCacheStateMachineConfigCache implements StateMachineConfigCache {
private static final Logger logger = LoggerFactory.getLogger(EhCacheStateMachineConfigCache.class);
private final TenantInternalApi tenantInternalApi;
- private final CacheController cacheController;
+ private final CacheController<String, StateMachineConfig> cacheController;
private final CacheInvalidationCallback cacheInvalidationCallback;
private final LoaderCallback loaderCallback;
@@ -71,7 +71,8 @@ public class EhCacheStateMachineConfigCache implements StateMachineConfigCache {
try {
final InputStream stream = new ByteArrayInputStream(stateMachineConfigXML.getBytes());
- return XMLLoader.getObjectFromStream(new URI("dummy"), stream, DefaultStateMachineConfig.class);
+ final DefaultStateMachineConfig defaultStateMachineConfig = XMLLoader.getObjectFromStream(new URI("dummy"), stream, DefaultStateMachineConfig.class);
+ return new SerializableStateMachineConfig(defaultStateMachineConfig);
} catch (final Exception e) {
// TODO 0.17 proper error code
throw new PaymentApiException(e, ErrorCode.PAYMENT_INTERNAL_ERROR, "Invalid payment state machine config");
@@ -94,18 +95,18 @@ public class EhCacheStateMachineConfigCache implements StateMachineConfigCache {
@Override
public StateMachineConfig getPaymentStateMachineConfig(final String pluginName, final InternalTenantContext tenantContext) throws PaymentApiException {
- if (tenantContext.getTenantRecordId() == InternalCallContextFactory.INTERNAL_TENANT_RECORD_ID || cacheController == null) {
+ if (InternalCallContextFactory.INTERNAL_TENANT_RECORD_ID.equals(tenantContext.getTenantRecordId()) || cacheController == null) {
return defaultPaymentStateMachineConfig;
}
final String pluginConfigKey = getCacheKeyName(pluginName, tenantContext);
final CacheLoaderArgument cacheLoaderArgument = createCacheLoaderArgument(pluginName);
try {
- StateMachineConfig pluginPaymentStateMachineConfig = (StateMachineConfig) cacheController.get(pluginConfigKey, cacheLoaderArgument);
+ StateMachineConfig pluginPaymentStateMachineConfig = cacheController.get(pluginConfigKey, cacheLoaderArgument);
// It means we are using the default state machine config in a multi-tenant deployment
if (pluginPaymentStateMachineConfig == null) {
pluginPaymentStateMachineConfig = defaultPaymentStateMachineConfig;
- cacheController.add(pluginConfigKey, pluginPaymentStateMachineConfig);
+ cacheController.putIfAbsent(pluginConfigKey, pluginPaymentStateMachineConfig);
}
return pluginPaymentStateMachineConfig;
} catch (final IllegalStateException e) {
@@ -125,7 +126,7 @@ public class EhCacheStateMachineConfigCache implements StateMachineConfigCache {
@Override
public void clearPaymentStateMachineConfig(final String pluginName, final InternalTenantContext tenantContext) {
- if (tenantContext.getTenantRecordId() != InternalCallContextFactory.INTERNAL_TENANT_RECORD_ID && cacheController != null) {
+ if (!InternalCallContextFactory.INTERNAL_TENANT_RECORD_ID.equals(tenantContext.getTenantRecordId()) && cacheController != null) {
final String key = getCacheKeyName(pluginName, tenantContext);
cacheController.remove(key);
}
diff --git a/payment/src/main/java/org/killbill/billing/payment/caching/SerializableStateMachineConfig.java b/payment/src/main/java/org/killbill/billing/payment/caching/SerializableStateMachineConfig.java
new file mode 100644
index 0000000..d4044b6
--- /dev/null
+++ b/payment/src/main/java/org/killbill/billing/payment/caching/SerializableStateMachineConfig.java
@@ -0,0 +1,80 @@
+/*
+ * 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.payment.caching;
+
+import java.io.Externalizable;
+import java.io.IOException;
+import java.io.ObjectInput;
+import java.io.ObjectOutput;
+
+import org.killbill.automaton.LinkStateMachine;
+import org.killbill.automaton.MissingEntryException;
+import org.killbill.automaton.StateMachine;
+import org.killbill.automaton.StateMachineConfig;
+import org.killbill.billing.util.cache.ExternalizableInput;
+import org.killbill.billing.util.cache.ExternalizableOutput;
+import org.killbill.billing.util.cache.MapperHolder;
+
+public class SerializableStateMachineConfig implements StateMachineConfig, Externalizable {
+
+ private static final long serialVersionUID = 945320595649172168L;
+
+ private StateMachineConfig stateMachineConfig;
+
+ // For deserialization
+ public SerializableStateMachineConfig() {}
+
+ public SerializableStateMachineConfig(final StateMachineConfig stateMachineConfig) {
+ this.stateMachineConfig = stateMachineConfig;
+ }
+
+ @Override
+ public StateMachine[] getStateMachines() {
+ return stateMachineConfig.getStateMachines();
+ }
+
+ @Override
+ public LinkStateMachine[] getLinkStateMachines() {
+ return stateMachineConfig.getLinkStateMachines();
+ }
+
+ @Override
+ public StateMachine getStateMachineForState(final String stateName) throws MissingEntryException {
+ return stateMachineConfig.getStateMachineForState(stateName);
+ }
+
+ @Override
+ public StateMachine getStateMachine(final String stateMachineName) throws MissingEntryException {
+ return stateMachineConfig.getStateMachine(stateMachineName);
+ }
+
+ @Override
+ public LinkStateMachine getLinkStateMachine(final String linkStateMachineName) throws MissingEntryException {
+ return stateMachineConfig.getLinkStateMachine(linkStateMachineName);
+ }
+
+ @Override
+ public void readExternal(final ObjectInput in) throws IOException {
+ MapperHolder.mapper().readerForUpdating(this).readValue(new ExternalizableInput(in));
+ }
+
+ @Override
+ public void writeExternal(final ObjectOutput oo) throws IOException {
+ MapperHolder.mapper().writeValue(new ExternalizableOutput(oo), this);
+ }
+}
diff --git a/payment/src/main/java/org/killbill/billing/payment/core/PaymentProcessor.java b/payment/src/main/java/org/killbill/billing/payment/core/PaymentProcessor.java
index 4db7ea5..8cc235e 100644
--- a/payment/src/main/java/org/killbill/billing/payment/core/PaymentProcessor.java
+++ b/payment/src/main/java/org/killbill/billing/payment/core/PaymentProcessor.java
@@ -225,14 +225,15 @@ public class PaymentProcessor extends ProcessorBase {
public Payment getPayment(final UUID paymentId, final boolean withPluginInfo, final boolean withAttempts, final Iterable<PluginProperty> properties, final TenantContext tenantContext, final InternalTenantContext internalTenantContext) throws PaymentApiException {
final PaymentModelDao paymentModelDao = paymentDao.getPayment(paymentId, internalTenantContext);
- if (paymentModelDao == null) {
- return null;
- }
- return toPayment(paymentModelDao, withPluginInfo, withAttempts, properties, tenantContext, internalTenantContext);
+ return getPayment(paymentModelDao, withPluginInfo, withAttempts, properties, tenantContext, internalTenantContext);
}
public Payment getPaymentByExternalKey(final String paymentExternalKey, final boolean withPluginInfo, final boolean withAttempts, final Iterable<PluginProperty> properties, final TenantContext tenantContext, final InternalTenantContext internalTenantContext) throws PaymentApiException {
final PaymentModelDao paymentModelDao = paymentDao.getPaymentByExternalKey(paymentExternalKey, internalTenantContext);
+ return getPayment(paymentModelDao, withPluginInfo, withAttempts, properties, tenantContext, internalTenantContext);
+ }
+
+ private Payment getPayment(final PaymentModelDao paymentModelDao, final boolean withPluginInfo, final boolean withAttempts, final Iterable<PluginProperty> properties, final TenantContext tenantContext, final InternalTenantContext internalTenantContext) throws PaymentApiException {
if (paymentModelDao == null) {
return null;
}
@@ -546,9 +547,9 @@ public class PaymentProcessor extends ProcessorBase {
currentStateName = paymentModelDao.getLastSuccessStateName();
}
- final UUID nonNullPaymentId = paymentAutomatonRunner.run(paymentStateContext, daoHelper, currentStateName, transactionType);
+ paymentAutomatonRunner.run(paymentStateContext, daoHelper, currentStateName, transactionType);
- return getPayment(nonNullPaymentId, true, false, properties, callContext, internalCallContext);
+ return getPayment(paymentStateContext.getPaymentModelDao(), true, false, properties, callContext, internalCallContext);
}
private void runSanityOnTransactionExternalKey(final Iterable<PaymentTransactionModelDao> allPaymentTransactionsForKey,
diff --git a/payment/src/main/java/org/killbill/billing/payment/core/sm/PaymentAutomatonDAOHelper.java b/payment/src/main/java/org/killbill/billing/payment/core/sm/PaymentAutomatonDAOHelper.java
index 5b11b15..70c793a 100644
--- a/payment/src/main/java/org/killbill/billing/payment/core/sm/PaymentAutomatonDAOHelper.java
+++ b/payment/src/main/java/org/killbill/billing/payment/core/sm/PaymentAutomatonDAOHelper.java
@@ -31,6 +31,7 @@ import org.killbill.billing.payment.api.PaymentApiException;
import org.killbill.billing.payment.api.TransactionStatus;
import org.killbill.billing.payment.api.TransactionType;
import org.killbill.billing.payment.core.PaymentPluginServiceRegistration;
+import org.killbill.billing.payment.dao.PaymentAndTransactionModelDao;
import org.killbill.billing.payment.dao.PaymentDao;
import org.killbill.billing.payment.dao.PaymentMethodModelDao;
import org.killbill.billing.payment.dao.PaymentModelDao;
@@ -86,8 +87,8 @@ public class PaymentAutomatonDAOHelper {
final PaymentTransactionModelDao newPaymentTransactionModelDao = buildNewPaymentTransactionModelDao(newPaymentModelDao.getId());
existingTransactions = ImmutableList.of();
- final PaymentModelDao paymentModelDao = paymentDao.insertPaymentWithFirstTransaction(newPaymentModelDao, newPaymentTransactionModelDao, internalCallContext);
- paymentTransactionModelDao = paymentDao.getTransactionsForPayment(paymentModelDao.getId(), internalCallContext).get(0);
+ final PaymentAndTransactionModelDao paymentAndTransactionModelDao = paymentDao.insertPaymentWithFirstTransaction(newPaymentModelDao, newPaymentTransactionModelDao, internalCallContext);
+ paymentTransactionModelDao = paymentAndTransactionModelDao.getPaymentTransactionModelDao();
} else {
existingTransactions = paymentDao.getTransactionsForPayment(paymentStateContext.getPaymentId(), internalCallContext);
@@ -131,22 +132,22 @@ public class PaymentAutomatonDAOHelper {
final String gatewayErrorMsg = paymentInfoPlugin == null ? null : paymentInfoPlugin.getGatewayError();
final String lastSuccessPaymentState = paymentSMHelper.isSuccessState(currentPaymentStateName) ? currentPaymentStateName : null;
- paymentDao.updatePaymentAndTransactionOnCompletion(paymentStateContext.getAccount().getId(),
- paymentStateContext.getAttemptId(),
- paymentStateContext.getPaymentId(),
- paymentStateContext.getTransactionType(),
- currentPaymentStateName,
- lastSuccessPaymentState,
- paymentStateContext.getPaymentTransactionModelDao().getId(),
- transactionStatus,
- processedAmount,
- processedCurrency,
- gatewayErrorCode,
- gatewayErrorMsg,
- internalCallContext);
-
+ final PaymentAndTransactionModelDao paymentAndTransactionModelDao = paymentDao.updatePaymentAndTransactionOnCompletion(paymentStateContext.getAccount().getId(),
+ paymentStateContext.getAttemptId(),
+ paymentStateContext.getPaymentId(),
+ paymentStateContext.getTransactionType(),
+ currentPaymentStateName,
+ lastSuccessPaymentState,
+ paymentStateContext.getPaymentTransactionModelDao().getId(),
+ transactionStatus,
+ processedAmount,
+ processedCurrency,
+ gatewayErrorCode,
+ gatewayErrorMsg,
+ internalCallContext);
// Update the context
- paymentStateContext.setPaymentTransactionModelDao(paymentDao.getPaymentTransaction(paymentStateContext.getPaymentTransactionModelDao().getId(), internalCallContext));
+ paymentStateContext.setPaymentModelDao(paymentAndTransactionModelDao.getPaymentModelDao());
+ paymentStateContext.setPaymentTransactionModelDao(paymentAndTransactionModelDao.getPaymentTransactionModelDao());
}
public String getPaymentProviderPluginName(final boolean includeDeleted) throws PaymentApiException {
diff --git a/payment/src/main/java/org/killbill/billing/payment/core/sm/PaymentAutomatonRunner.java b/payment/src/main/java/org/killbill/billing/payment/core/sm/PaymentAutomatonRunner.java
index 331d317..6c2f77a 100644
--- a/payment/src/main/java/org/killbill/billing/payment/core/sm/PaymentAutomatonRunner.java
+++ b/payment/src/main/java/org/killbill/billing/payment/core/sm/PaymentAutomatonRunner.java
@@ -160,7 +160,7 @@ public class PaymentAutomatonRunner {
return new PaymentAutomatonDAOHelper(paymentStateContext, utcNow, paymentDao, paymentPluginServiceRegistration, internalCallContext, eventBus, paymentSMHelper);
}
- public UUID run(final PaymentStateContext paymentStateContext,
+ public void run(final PaymentStateContext paymentStateContext,
final PaymentAutomatonDAOHelper daoHelper,
@Nullable final String currentStateNameOrNull,
final TransactionType transactionType) throws PaymentApiException {
@@ -214,8 +214,6 @@ public class PaymentAutomatonRunner {
}
runStateMachineOperation(currentStateName, transactionType, leavingStateCallback, operationCallback, enteringStateCallback, includeDeletedPaymentMethod, paymentStateContext, daoHelper);
-
- return paymentStateContext.getPaymentId();
}
//
diff --git a/payment/src/main/java/org/killbill/billing/payment/core/sm/PaymentStateContext.java b/payment/src/main/java/org/killbill/billing/payment/core/sm/PaymentStateContext.java
index 632f15d..c0d5709 100644
--- a/payment/src/main/java/org/killbill/billing/payment/core/sm/PaymentStateContext.java
+++ b/payment/src/main/java/org/killbill/billing/payment/core/sm/PaymentStateContext.java
@@ -1,6 +1,6 @@
/*
- * Copyright 2014-2015 Groupon, Inc
- * Copyright 2014-2015 The Billing Project, LLC
+ * 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
@@ -29,6 +29,7 @@ import org.killbill.billing.callcontext.InternalCallContext;
import org.killbill.billing.catalog.api.Currency;
import org.killbill.billing.payment.api.PluginProperty;
import org.killbill.billing.payment.api.TransactionType;
+import org.killbill.billing.payment.dao.PaymentModelDao;
import org.killbill.billing.payment.dao.PaymentTransactionModelDao;
import org.killbill.billing.payment.plugin.api.PaymentTransactionInfoPlugin;
import org.killbill.billing.util.callcontext.CallContext;
@@ -38,7 +39,7 @@ import com.google.common.collect.ImmutableList;
public class PaymentStateContext {
-
+ private PaymentModelDao paymentModelDao;
// The following fields (paymentId, transactionId, amount, currency) may take their value from the paymentTransactionModelDao *when they are not already set*
private PaymentTransactionModelDao paymentTransactionModelDao;
// Initialized in CTOR or only set through paymentTransactionModelDao
@@ -119,6 +120,14 @@ public class PaymentStateContext {
this.paymentMethodId = paymentMethodId;
}
+ public PaymentModelDao getPaymentModelDao() {
+ return paymentModelDao;
+ }
+
+ public void setPaymentModelDao(final PaymentModelDao paymentModelDao) {
+ this.paymentModelDao = paymentModelDao;
+ }
+
public PaymentTransactionModelDao getPaymentTransactionModelDao() {
return paymentTransactionModelDao;
}
diff --git a/payment/src/main/java/org/killbill/billing/payment/dao/DefaultPaymentDao.java b/payment/src/main/java/org/killbill/billing/payment/dao/DefaultPaymentDao.java
index 4d00d40..22baff9 100644
--- a/payment/src/main/java/org/killbill/billing/payment/dao/DefaultPaymentDao.java
+++ b/payment/src/main/java/org/killbill/billing/payment/dao/DefaultPaymentDao.java
@@ -1,7 +1,7 @@
/*
* Copyright 2010-2013 Ning, Inc.
- * Copyright 2014-2016 Groupon, Inc
- * Copyright 2014-2016 The Billing Project, LLC
+ * 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
@@ -107,9 +107,7 @@ public class DefaultPaymentDao extends EntityDaoBase<PaymentModelDao, Payment, P
@Override
public PaymentAttemptModelDao inTransaction(final EntitySqlDaoWrapperFactory entitySqlDaoWrapperFactory) throws Exception {
final PaymentAttemptSqlDao transactional = entitySqlDaoWrapperFactory.become(PaymentAttemptSqlDao.class);
- transactional.create(attempt, context);
- final PaymentAttemptModelDao result = transactional.getById(attempt.getId().toString(), context);
- return result;
+ return createAndRefresh(transactional, attempt, context);
}
});
}
@@ -290,16 +288,22 @@ public class DefaultPaymentDao extends EntityDaoBase<PaymentModelDao, Payment, P
}
@Override
- public PaymentModelDao insertPaymentWithFirstTransaction(final PaymentModelDao payment, final PaymentTransactionModelDao paymentTransaction, final InternalCallContext context) {
+ public PaymentAndTransactionModelDao insertPaymentWithFirstTransaction(final PaymentModelDao payment, final PaymentTransactionModelDao paymentTransaction, final InternalCallContext context) {
+ final PaymentAndTransactionModelDao paymentAndTransactionModelDao = new PaymentAndTransactionModelDao();
- return transactionalSqlDao.execute(new EntitySqlDaoTransactionWrapper<PaymentModelDao>() {
+ return transactionalSqlDao.execute(new EntitySqlDaoTransactionWrapper<PaymentAndTransactionModelDao>() {
@Override
- public PaymentModelDao inTransaction(final EntitySqlDaoWrapperFactory entitySqlDaoWrapperFactory) throws Exception {
+ public PaymentAndTransactionModelDao inTransaction(final EntitySqlDaoWrapperFactory entitySqlDaoWrapperFactory) throws Exception {
final PaymentSqlDao paymentSqlDao = entitySqlDaoWrapperFactory.become(PaymentSqlDao.class);
- paymentSqlDao.create(payment, context);
- entitySqlDaoWrapperFactory.become(TransactionSqlDao.class).create(paymentTransaction, context);
- return paymentSqlDao.getById(payment.getId().toString(), context);
+ final PaymentModelDao paymentModelDao = createAndRefresh(paymentSqlDao, payment, context);
+ paymentAndTransactionModelDao.setPaymentModelDao(paymentModelDao);
+
+ final TransactionSqlDao transactionSqlDao = entitySqlDaoWrapperFactory.become(TransactionSqlDao.class);
+ final PaymentTransactionModelDao paymentTransactionModelDao = createAndRefresh(transactionSqlDao, paymentTransaction, context);
+ paymentAndTransactionModelDao.setPaymentTransactionModelDao(paymentTransactionModelDao);
+
+ return paymentAndTransactionModelDao;
}
});
}
@@ -310,8 +314,7 @@ public class DefaultPaymentDao extends EntityDaoBase<PaymentModelDao, Payment, P
@Override
public PaymentTransactionModelDao inTransaction(final EntitySqlDaoWrapperFactory entitySqlDaoWrapperFactory) throws Exception {
final TransactionSqlDao transactional = entitySqlDaoWrapperFactory.become(TransactionSqlDao.class);
- transactional.create(paymentTransaction, context);
- final PaymentTransactionModelDao paymentTransactionModelDao = transactional.getById(paymentTransaction.getId().toString(), context);
+ final PaymentTransactionModelDao paymentTransactionModelDao = createAndRefresh(transactional, paymentTransaction, context);
entitySqlDaoWrapperFactory.become(PaymentSqlDao.class).updatePaymentForNewTransaction(paymentId.toString(), contextWithUpdatedDate(context));
@@ -321,34 +324,50 @@ public class DefaultPaymentDao extends EntityDaoBase<PaymentModelDao, Payment, P
}
@Override
- public void updatePaymentAndTransactionOnCompletion(final UUID accountId, @Nullable final UUID attemptId, final UUID paymentId, final TransactionType transactionType,
- final String currentPaymentStateName, @Nullable final String lastPaymentSuccessStateName,
- final UUID transactionId, final TransactionStatus transactionStatus,
- final BigDecimal processedAmount, final Currency processedCurrency,
- final String gatewayErrorCode, final String gatewayErrorMsg,
- final InternalCallContext context) {
- transactionalSqlDao.execute(new EntitySqlDaoTransactionWrapper<Void>() {
+ public PaymentAndTransactionModelDao updatePaymentAndTransactionOnCompletion(final UUID accountId, @Nullable final UUID attemptId, final UUID paymentId, final TransactionType transactionType,
+ final String currentPaymentStateName, @Nullable final String lastPaymentSuccessStateName,
+ final UUID transactionId, final TransactionStatus transactionStatus,
+ final BigDecimal processedAmount, final Currency processedCurrency,
+ final String gatewayErrorCode, final String gatewayErrorMsg,
+ final InternalCallContext context) {
+ final PaymentAndTransactionModelDao paymentAndTransactionModelDao = new PaymentAndTransactionModelDao();
+
+ return transactionalSqlDao.execute(new EntitySqlDaoTransactionWrapper<PaymentAndTransactionModelDao>() {
@Override
- public Void inTransaction(final EntitySqlDaoWrapperFactory entitySqlDaoWrapperFactory) throws Exception {
+ public PaymentAndTransactionModelDao inTransaction(final EntitySqlDaoWrapperFactory entitySqlDaoWrapperFactory) throws Exception {
final InternalCallContext contextWithUpdatedDate = contextWithUpdatedDate(context);
+
final TransactionSqlDao transactional = entitySqlDaoWrapperFactory.become(TransactionSqlDao.class);
- final PaymentTransactionModelDao paymentTransactionModelDao = transactional.getById(transactionId.toString(), context);
- transactional.updateTransactionStatus(transactionId.toString(),
- attemptId == null ? (paymentTransactionModelDao.getAttemptId() == null ? null : paymentTransactionModelDao.getAttemptId().toString()) : attemptId.toString(),
- processedAmount,
- processedCurrency == null ? null : processedCurrency.toString(),
- transactionStatus == null ? null : transactionStatus.toString(),
- gatewayErrorCode,
- gatewayErrorMsg,
- contextWithUpdatedDate);
+ final String updatedAttemptId;
+ if (attemptId == null) {
+ final PaymentTransactionModelDao paymentTransactionModelDao = transactional.getById(transactionId.toString(), context);
+ updatedAttemptId = paymentTransactionModelDao.getAttemptId() == null ? null : paymentTransactionModelDao.getAttemptId().toString();
+ } else {
+ updatedAttemptId = attemptId.toString();
+ }
+ final PaymentTransactionModelDao paymentTransactionModelDao = (PaymentTransactionModelDao) transactional.updateTransactionStatus(transactionId.toString(),
+ updatedAttemptId,
+ processedAmount,
+ processedCurrency == null ? null : processedCurrency.toString(),
+ transactionStatus == null ? null : transactionStatus.toString(),
+ gatewayErrorCode,
+ gatewayErrorMsg,
+ contextWithUpdatedDate);
+ paymentAndTransactionModelDao.setPaymentTransactionModelDao(paymentTransactionModelDao);
+
+ final PaymentSqlDao paymentSqlDao = entitySqlDaoWrapperFactory.become(PaymentSqlDao.class);
+ final PaymentModelDao paymentModelDao;
if (lastPaymentSuccessStateName != null) {
- entitySqlDaoWrapperFactory.become(PaymentSqlDao.class).updateLastSuccessPaymentStateName(paymentId.toString(), currentPaymentStateName, lastPaymentSuccessStateName, contextWithUpdatedDate);
+ paymentModelDao = (PaymentModelDao) paymentSqlDao.updateLastSuccessPaymentStateName(paymentId.toString(), currentPaymentStateName, lastPaymentSuccessStateName, contextWithUpdatedDate);
} else {
- entitySqlDaoWrapperFactory.become(PaymentSqlDao.class).updatePaymentStateName(paymentId.toString(), currentPaymentStateName, contextWithUpdatedDate);
+ paymentModelDao = (PaymentModelDao) paymentSqlDao.updatePaymentStateName(paymentId.toString(), currentPaymentStateName, contextWithUpdatedDate);
}
+ paymentAndTransactionModelDao.setPaymentModelDao(paymentModelDao);
+
postPaymentEventFromTransaction(accountId, transactionStatus, transactionType, paymentId, transactionId, processedAmount, processedCurrency, clock.getUTCNow(), gatewayErrorCode, entitySqlDaoWrapperFactory, context);
- return null;
+
+ return paymentAndTransactionModelDao;
}
});
}
@@ -428,9 +447,7 @@ public class DefaultPaymentDao extends EntityDaoBase<PaymentModelDao, Payment, P
private PaymentMethodModelDao insertPaymentMethodInTransaction(final EntitySqlDaoWrapperFactory entitySqlDaoWrapperFactory, final PaymentMethodModelDao paymentMethod, final InternalCallContext context)
throws EntityPersistenceException {
final PaymentMethodSqlDao transactional = entitySqlDaoWrapperFactory.become(PaymentMethodSqlDao.class);
- transactional.create(paymentMethod, context);
-
- return transactional.getById(paymentMethod.getId().toString(), context);
+ return createAndRefresh(transactional, paymentMethod, context);
}
@Override
diff --git a/payment/src/main/java/org/killbill/billing/payment/dao/PaymentAndTransactionModelDao.java b/payment/src/main/java/org/killbill/billing/payment/dao/PaymentAndTransactionModelDao.java
new file mode 100644
index 0000000..96d6987
--- /dev/null
+++ b/payment/src/main/java/org/killbill/billing/payment/dao/PaymentAndTransactionModelDao.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2017 Groupon, Inc
+ * Copyright 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.payment.dao;
+
+public class PaymentAndTransactionModelDao {
+
+ private PaymentModelDao paymentModelDao;
+ private PaymentTransactionModelDao paymentTransactionModelDao;
+
+ public PaymentModelDao getPaymentModelDao() {
+ return paymentModelDao;
+ }
+
+ public void setPaymentModelDao(final PaymentModelDao paymentModelDao) {
+ this.paymentModelDao = paymentModelDao;
+ }
+
+ public PaymentTransactionModelDao getPaymentTransactionModelDao() {
+ return paymentTransactionModelDao;
+ }
+
+ public void setPaymentTransactionModelDao(final PaymentTransactionModelDao paymentTransactionModelDao) {
+ this.paymentTransactionModelDao = paymentTransactionModelDao;
+ }
+}
diff --git a/payment/src/main/java/org/killbill/billing/payment/dao/PaymentDao.java b/payment/src/main/java/org/killbill/billing/payment/dao/PaymentDao.java
index 2c46e15..b7180e8 100644
--- a/payment/src/main/java/org/killbill/billing/payment/dao/PaymentDao.java
+++ b/payment/src/main/java/org/killbill/billing/payment/dao/PaymentDao.java
@@ -1,7 +1,9 @@
/*
* Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014-2017 Groupon, Inc
+ * Copyright 2014-2017 The Billing Project, LLC
*
- * Ning licenses this file to you under the Apache License, version 2.0
+ * 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:
*
@@ -55,13 +57,13 @@ public interface PaymentDao extends EntityDao<PaymentModelDao, Payment, PaymentA
public Pagination<PaymentModelDao> searchPayments(String searchKey, Long offset, Long limit, InternalTenantContext context);
- public PaymentModelDao insertPaymentWithFirstTransaction(PaymentModelDao payment, PaymentTransactionModelDao paymentTransaction, InternalCallContext context);
+ public PaymentAndTransactionModelDao insertPaymentWithFirstTransaction(PaymentModelDao payment, PaymentTransactionModelDao paymentTransaction, InternalCallContext context);
public PaymentTransactionModelDao updatePaymentWithNewTransaction(UUID paymentId, PaymentTransactionModelDao paymentTransaction, InternalCallContext context);
- public void updatePaymentAndTransactionOnCompletion(UUID accountId, UUID attemptId, UUID paymentId, TransactionType transactionType, String currentPaymentStateName, String lastPaymentSuccessStateName, UUID transactionId,
- TransactionStatus paymentStatus, BigDecimal processedAmount, Currency processedCurrency,
- String gatewayErrorCode, String gatewayErrorMsg, InternalCallContext context);
+ public PaymentAndTransactionModelDao updatePaymentAndTransactionOnCompletion(UUID accountId, UUID attemptId, UUID paymentId, TransactionType transactionType, String currentPaymentStateName, String lastPaymentSuccessStateName, UUID transactionId,
+ TransactionStatus paymentStatus, BigDecimal processedAmount, Currency processedCurrency,
+ String gatewayErrorCode, String gatewayErrorMsg, InternalCallContext context);
public PaymentModelDao getPayment(UUID paymentId, InternalTenantContext context);
diff --git a/payment/src/main/java/org/killbill/billing/payment/dao/PaymentSqlDao.java b/payment/src/main/java/org/killbill/billing/payment/dao/PaymentSqlDao.java
index 2be09a6..ee8ad0f 100644
--- a/payment/src/main/java/org/killbill/billing/payment/dao/PaymentSqlDao.java
+++ b/payment/src/main/java/org/killbill/billing/payment/dao/PaymentSqlDao.java
@@ -45,17 +45,16 @@ public interface PaymentSqlDao extends EntitySqlDao<PaymentModelDao, Payment> {
@SqlUpdate
@Audited(ChangeType.UPDATE)
- void updatePaymentStateName(@Bind("id") final String paymentId,
- @Bind("stateName") final String stateName,
- @BindBean final InternalCallContext context);
-
+ Object updatePaymentStateName(@Bind("id") final String paymentId,
+ @Bind("stateName") final String stateName,
+ @BindBean final InternalCallContext context);
@SqlUpdate
@Audited(ChangeType.UPDATE)
- void updateLastSuccessPaymentStateName(@Bind("id") final String paymentId,
- @Bind("stateName") final String stateName,
- @Bind("lastSuccessStateName") final String lastSuccessStateName,
- @BindBean final InternalCallContext context);
+ Object updateLastSuccessPaymentStateName(@Bind("id") final String paymentId,
+ @Bind("stateName") final String stateName,
+ @Bind("lastSuccessStateName") final String lastSuccessStateName,
+ @BindBean final InternalCallContext context);
@SqlQuery
public PaymentModelDao getPaymentByExternalKey(@Bind("externalKey") final String externalKey,
diff --git a/payment/src/main/java/org/killbill/billing/payment/dao/TransactionSqlDao.java b/payment/src/main/java/org/killbill/billing/payment/dao/TransactionSqlDao.java
index 4f687aa..ba8b739 100644
--- a/payment/src/main/java/org/killbill/billing/payment/dao/TransactionSqlDao.java
+++ b/payment/src/main/java/org/killbill/billing/payment/dao/TransactionSqlDao.java
@@ -42,14 +42,14 @@ public interface TransactionSqlDao extends EntitySqlDao<PaymentTransactionModelD
@SqlUpdate
@Audited(ChangeType.UPDATE)
- void updateTransactionStatus(@Bind("id") final String transactionId,
- @Bind("attemptId") final String attemptId,
- @Bind("processedAmount") final BigDecimal processedAmount,
- @Bind("processedCurrency") final String processedCurrency,
- @Bind("transactionStatus") final String transactionStatus,
- @Bind("gatewayErrorCode") final String gatewayErrorCode,
- @Bind("gatewayErrorMsg") final String gatewayErrorMsg,
- @BindBean final InternalCallContext context);
+ Object updateTransactionStatus(@Bind("id") final String transactionId,
+ @Bind("attemptId") final String attemptId,
+ @Bind("processedAmount") final BigDecimal processedAmount,
+ @Bind("processedCurrency") final String processedCurrency,
+ @Bind("transactionStatus") final String transactionStatus,
+ @Bind("gatewayErrorCode") final String gatewayErrorCode,
+ @Bind("gatewayErrorMsg") final String gatewayErrorMsg,
+ @BindBean final InternalCallContext context);
@SqlQuery
List<PaymentTransactionModelDao> getPaymentTransactionsByExternalKey(@Bind("transactionExternalKey") final String transactionExternalKey,
diff --git a/payment/src/test/java/org/killbill/billing/payment/caching/TestStateMachineConfigCache.java b/payment/src/test/java/org/killbill/billing/payment/caching/TestStateMachineConfigCache.java
index 9960e7a..cb2383a 100644
--- a/payment/src/test/java/org/killbill/billing/payment/caching/TestStateMachineConfigCache.java
+++ b/payment/src/test/java/org/killbill/billing/payment/caching/TestStateMachineConfigCache.java
@@ -1,6 +1,6 @@
/*
- * Copyright 2016 Groupon, Inc
- * Copyright 2016 The Billing Project, LLC
+ * Copyright 2016-2017 Groupon, Inc
+ * Copyright 2016-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
@@ -38,7 +38,6 @@ import org.testng.annotations.Test;
import com.google.common.io.ByteStreams;
import com.google.common.io.Resources;
-import net.sf.ehcache.CacheException;
public class TestStateMachineConfigCache extends PaymentTestSuiteNoDB {
@@ -82,7 +81,7 @@ public class TestStateMachineConfigCache extends PaymentTestSuiteNoDB {
@Override
public String answer(final InvocationOnMock invocation) throws Throwable {
if (shouldThrow.get()) {
- throw new RuntimeException();
+ throw new RuntimeException("For test purposes");
}
final InternalTenantContext internalContext = (InternalTenantContext) invocation.getArguments()[1];
if (multiTenantRecordId.equals(internalContext.getTenantRecordId())) {
@@ -141,8 +140,9 @@ public class TestStateMachineConfigCache extends PaymentTestSuiteNoDB {
try {
stateMachineConfigCache.getPaymentStateMachineConfig(pluginName, multiTenantContext);
Assert.fail();
- } catch (final CacheException exception) {
+ } catch (final RuntimeException exception) {
Assert.assertTrue(exception.getCause() instanceof RuntimeException);
+ Assert.assertEquals(exception.getCause().getMessage(), "For test purposes");
}
}
}
diff --git a/payment/src/test/java/org/killbill/billing/payment/caching/TestStateMachineConfigCacheInvalidationCallback.java b/payment/src/test/java/org/killbill/billing/payment/caching/TestStateMachineConfigCacheInvalidationCallback.java
index 9ead7cd..18b1f31 100644
--- a/payment/src/test/java/org/killbill/billing/payment/caching/TestStateMachineConfigCacheInvalidationCallback.java
+++ b/payment/src/test/java/org/killbill/billing/payment/caching/TestStateMachineConfigCacheInvalidationCallback.java
@@ -1,6 +1,6 @@
/*
- * Copyright 2016 Groupon, Inc
- * Copyright 2016 The Billing Project, LLC
+ * Copyright 2016-2017 Groupon, Inc
+ * Copyright 2016-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
@@ -32,8 +32,6 @@ import org.testng.Assert;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;
-import net.sf.ehcache.CacheException;
-
public class TestStateMachineConfigCacheInvalidationCallback extends PaymentTestSuiteNoDB {
private InternalTenantContext multiTenantContext;
@@ -67,7 +65,7 @@ public class TestStateMachineConfigCacheInvalidationCallback extends PaymentTest
@Override
public String answer(final InvocationOnMock invocation) throws Throwable {
if (shouldThrow.get()) {
- throw new RuntimeException();
+ throw new RuntimeException("For test purposes");
}
return null;
}
@@ -87,8 +85,9 @@ public class TestStateMachineConfigCacheInvalidationCallback extends PaymentTest
try {
stateMachineConfigCache.getPaymentStateMachineConfig(pluginName, multiTenantContext);
Assert.fail();
- } catch (final CacheException exception) {
+ } catch (final RuntimeException exception) {
Assert.assertTrue(exception.getCause() instanceof RuntimeException);
+ Assert.assertEquals(exception.getCause().getMessage(), "For test purposes");
}
// No exception (cached)
diff --git a/payment/src/test/java/org/killbill/billing/payment/core/sm/MockRetryAuthorizeOperationCallback.java b/payment/src/test/java/org/killbill/billing/payment/core/sm/MockRetryAuthorizeOperationCallback.java
index d6e79c8..deee81e 100644
--- a/payment/src/test/java/org/killbill/billing/payment/core/sm/MockRetryAuthorizeOperationCallback.java
+++ b/payment/src/test/java/org/killbill/billing/payment/core/sm/MockRetryAuthorizeOperationCallback.java
@@ -87,7 +87,7 @@ public class MockRetryAuthorizeOperationCallback extends AuthorizeControlOperati
paymentStateContext.getCurrency(),
"",
"");
- final PaymentModelDao paymentModelDao = paymentDao.insertPaymentWithFirstTransaction(payment, transaction, paymentStateContext.getInternalCallContext());
+ final PaymentModelDao paymentModelDao = paymentDao.insertPaymentWithFirstTransaction(payment, transaction, paymentStateContext.getInternalCallContext()).getPaymentModelDao();
final PaymentTransaction convertedTransaction = new DefaultPaymentTransaction(transaction.getId(),
paymentStateContext.getAttemptId(),
transaction.getTransactionExternalKey(),
diff --git a/payment/src/test/java/org/killbill/billing/payment/core/TestPaymentMethodProcessorWithDB.java b/payment/src/test/java/org/killbill/billing/payment/core/TestPaymentMethodProcessorWithDB.java
index 79f4b34..3912a51 100644
--- a/payment/src/test/java/org/killbill/billing/payment/core/TestPaymentMethodProcessorWithDB.java
+++ b/payment/src/test/java/org/killbill/billing/payment/core/TestPaymentMethodProcessorWithDB.java
@@ -1,6 +1,6 @@
/*
- * Copyright 2014-2015 Groupon, Inc
- * Copyright 2014-2015 The Billing Project, LLC
+ * 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
@@ -17,12 +17,18 @@
package org.killbill.billing.payment.core;
+import java.util.List;
import java.util.UUID;
import org.killbill.billing.account.api.Account;
import org.killbill.billing.payment.PaymentTestSuiteWithEmbeddedDB;
import org.killbill.billing.payment.api.PaymentApiException;
+import org.killbill.billing.payment.api.PaymentMethod;
import org.killbill.billing.payment.api.PluginProperty;
+import org.killbill.billing.payment.dao.PaymentMethodModelDao;
+import org.killbill.billing.payment.dao.PaymentMethodSqlDao;
+import org.killbill.billing.util.audit.ChangeType;
+import org.killbill.billing.util.dao.EntityHistoryModelDao;
import org.testng.Assert;
import org.testng.annotations.Test;
@@ -55,4 +61,43 @@ public class TestPaymentMethodProcessorWithDB extends PaymentTestSuiteWithEmbedd
final Account accountById = accountApi.getAccountById(account.getId(), internalCallContext);
Assert.assertEquals(accountById.getPaymentMethodId(), newPaymentMethod);
}
+
+ @Test(groups = "slow")
+ public void testDeletePaymentMethod() throws Exception {
+ final Account account = testHelper.createTestAccount("foo@bar.com", true);
+
+ final UUID paymentMethodId = paymentMethodProcessor.createOrGetExternalPaymentMethod("pmExternalKey", account, PLUGIN_PROPERTIES, callContext, internalCallContext);
+ final PaymentMethodModelDao paymentMethodModelDao = paymentDao.getPaymentMethod(paymentMethodId, internalCallContext);
+
+ final List<EntityHistoryModelDao<PaymentMethodModelDao, PaymentMethod>> history1 = getPaymentMethodHistory(paymentMethodModelDao.getRecordId());
+ Assert.assertEquals(history1.size(), 1);
+ Assert.assertEquals(history1.get(0).getChangeType(), ChangeType.INSERT);
+ Assert.assertEquals(history1.get(0).getEntity().getAccountRecordId(), paymentMethodModelDao.getAccountRecordId());
+ Assert.assertEquals(history1.get(0).getEntity().getTenantRecordId(), paymentMethodModelDao.getTenantRecordId());
+ Assert.assertEquals(history1.get(0).getEntity().getExternalKey(), paymentMethodModelDao.getExternalKey());
+ Assert.assertTrue(history1.get(0).getEntity().isActive());
+
+ paymentMethodProcessor.deletedPaymentMethod(account, paymentMethodId, true, true, ImmutableList.<PluginProperty>of(), callContext, internalCallContext);
+
+ final List<EntityHistoryModelDao<PaymentMethodModelDao, PaymentMethod>> history2 = getPaymentMethodHistory(paymentMethodModelDao.getRecordId());
+ Assert.assertEquals(history2.size(), 2);
+ Assert.assertEquals(history2.get(0).getChangeType(), ChangeType.INSERT);
+ Assert.assertEquals(history2.get(0).getEntity().getAccountRecordId(), paymentMethodModelDao.getAccountRecordId());
+ Assert.assertEquals(history2.get(0).getEntity().getTenantRecordId(), paymentMethodModelDao.getTenantRecordId());
+ Assert.assertEquals(history2.get(0).getEntity().getExternalKey(), paymentMethodModelDao.getExternalKey());
+ Assert.assertTrue(history2.get(0).getEntity().isActive());
+ // Note: it looks like we don't consider this as a DELETE, probably because we can un-delete such payment methods?
+ Assert.assertEquals(history2.get(1).getChangeType(), ChangeType.UPDATE);
+ Assert.assertEquals(history2.get(1).getEntity().getAccountRecordId(), paymentMethodModelDao.getAccountRecordId());
+ Assert.assertEquals(history2.get(1).getEntity().getTenantRecordId(), paymentMethodModelDao.getTenantRecordId());
+ Assert.assertEquals(history2.get(1).getEntity().getExternalKey(), paymentMethodModelDao.getExternalKey());
+ // Note: upon deletion, the recorded state is the same as before the delete
+ Assert.assertTrue(history2.get(1).getEntity().isActive());
+ }
+
+ private List<EntityHistoryModelDao<PaymentMethodModelDao, PaymentMethod>> getPaymentMethodHistory(final Long paymentMethodRecordId) {
+ // See https://github.com/killbill/killbill/issues/335
+ final PaymentMethodSqlDao paymentMethodSqlDao = dbi.onDemand(PaymentMethodSqlDao.class);
+ return paymentMethodSqlDao.getHistoryForTargetRecordId(paymentMethodRecordId, internalCallContext);
+ }
}
diff --git a/payment/src/test/java/org/killbill/billing/payment/dao/MockPaymentDao.java b/payment/src/test/java/org/killbill/billing/payment/dao/MockPaymentDao.java
index 12ce2be..f167975 100644
--- a/payment/src/test/java/org/killbill/billing/payment/dao/MockPaymentDao.java
+++ b/payment/src/test/java/org/killbill/billing/payment/dao/MockPaymentDao.java
@@ -1,7 +1,7 @@
/*
* Copyright 2010-2013 Ning, Inc.
- * Copyright 2014-2016 Groupon, Inc
- * Copyright 2014-2016 The Billing Project, LLC
+ * 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
@@ -191,7 +191,8 @@ public class MockPaymentDao extends MockEntityDaoBase<PaymentModelDao, Payment,
}
@Override
- public PaymentModelDao insertPaymentWithFirstTransaction(final PaymentModelDao payment, final PaymentTransactionModelDao paymentTransaction, final InternalCallContext context) {
+ public PaymentAndTransactionModelDao insertPaymentWithFirstTransaction(final PaymentModelDao payment, final PaymentTransactionModelDao paymentTransaction, final InternalCallContext context) {
+ final PaymentAndTransactionModelDao paymentAndTransactionModelDao = new PaymentAndTransactionModelDao();
payment.setTenantRecordId(context.getTenantRecordId());
paymentTransaction.setTenantRecordId(context.getTenantRecordId());
@@ -207,7 +208,11 @@ public class MockPaymentDao extends MockEntityDaoBase<PaymentModelDao, Payment,
mockNonEntityDao.addTenantRecordIdMapping(paymentTransaction.getId(), context);
mockNonEntityDao.addAccountRecordIdMapping((paymentTransaction.getId()), context);
}
- return payment;
+
+ paymentAndTransactionModelDao.setPaymentModelDao(payment);
+ paymentAndTransactionModelDao.setPaymentTransactionModelDao(paymentTransaction);
+
+ return paymentAndTransactionModelDao;
}
@Override
@@ -224,10 +229,12 @@ public class MockPaymentDao extends MockEntityDaoBase<PaymentModelDao, Payment,
}
@Override
- public void updatePaymentAndTransactionOnCompletion(final UUID accountId, final UUID attemptId, final UUID paymentId, final TransactionType transactionType,
- final String currentPaymentStateName, final String lastSuccessPaymentStateName, final UUID transactionId,
- final TransactionStatus paymentStatus, final BigDecimal processedAmount, final Currency processedCurrency,
- final String gatewayErrorCode, final String gatewayErrorMsg, final InternalCallContext context) {
+ public PaymentAndTransactionModelDao updatePaymentAndTransactionOnCompletion(final UUID accountId, final UUID attemptId, final UUID paymentId, final TransactionType transactionType,
+ final String currentPaymentStateName, final String lastSuccessPaymentStateName, final UUID transactionId,
+ final TransactionStatus paymentStatus, final BigDecimal processedAmount, final Currency processedCurrency,
+ final String gatewayErrorCode, final String gatewayErrorMsg, final InternalCallContext context) {
+ final PaymentAndTransactionModelDao paymentAndTransactionModelDao = new PaymentAndTransactionModelDao();
+
synchronized (this) {
final PaymentModelDao payment = payments.get(paymentId);
if (payment != null) {
@@ -242,6 +249,11 @@ public class MockPaymentDao extends MockEntityDaoBase<PaymentModelDao, Payment,
transaction.setGatewayErrorCode(gatewayErrorCode);
transaction.setGatewayErrorMsg(gatewayErrorMsg);
}
+
+ paymentAndTransactionModelDao.setPaymentModelDao(payment);
+ paymentAndTransactionModelDao.setPaymentTransactionModelDao(transaction);
+
+ return paymentAndTransactionModelDao;
}
}
diff --git a/payment/src/test/java/org/killbill/billing/payment/dao/TestDefaultPaymentDao.java b/payment/src/test/java/org/killbill/billing/payment/dao/TestDefaultPaymentDao.java
index 35d2c42..0fa8999 100644
--- a/payment/src/test/java/org/killbill/billing/payment/dao/TestDefaultPaymentDao.java
+++ b/payment/src/test/java/org/killbill/billing/payment/dao/TestDefaultPaymentDao.java
@@ -1,6 +1,6 @@
/*
- * Copyright 2014-2016 Groupon, Inc
- * Copyright 2014-2016 The Billing Project, LLC
+ * 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
@@ -50,7 +50,7 @@ public class TestDefaultPaymentDao extends PaymentTestSuiteWithEmbeddedDB {
final PaymentTransactionModelDao specifiedFirstPaymentTransactionModelDao = generatePaymentTransactionModelDao(specifiedFirstPaymentModelDao.getId());
// Create and verify the payment and transaction
- final PaymentModelDao firstPaymentModelDao = paymentDao.insertPaymentWithFirstTransaction(specifiedFirstPaymentModelDao, specifiedFirstPaymentTransactionModelDao, internalCallContext);
+ final PaymentModelDao firstPaymentModelDao = paymentDao.insertPaymentWithFirstTransaction(specifiedFirstPaymentModelDao, specifiedFirstPaymentTransactionModelDao, internalCallContext).getPaymentModelDao();
verifyPayment(firstPaymentModelDao, specifiedFirstPaymentModelDao);
verifyPaymentAndTransactions(internalCallContext, specifiedFirstPaymentModelDao, specifiedFirstPaymentTransactionModelDao);
@@ -89,7 +89,7 @@ public class TestDefaultPaymentDao extends PaymentTestSuiteWithEmbeddedDB {
final PaymentModelDao paymentModelDao = generatePaymentModelDao(accountId);
final PaymentTransactionModelDao paymentTransactionModelDao = generatePaymentTransactionModelDao(paymentModelDao.getId());
- final PaymentModelDao insertedPaymentModelDao = paymentDao.insertPaymentWithFirstTransaction(paymentModelDao, paymentTransactionModelDao, internalCallContext);
+ final PaymentModelDao insertedPaymentModelDao = paymentDao.insertPaymentWithFirstTransaction(paymentModelDao, paymentTransactionModelDao, internalCallContext).getPaymentModelDao();
verifyPayment(insertedPaymentModelDao, paymentModelDao);
// Verify search APIs
diff --git a/payment/src/test/java/org/killbill/billing/payment/dao/TestPaymentDao.java b/payment/src/test/java/org/killbill/billing/payment/dao/TestPaymentDao.java
index a195593..113c147 100644
--- a/payment/src/test/java/org/killbill/billing/payment/dao/TestPaymentDao.java
+++ b/payment/src/test/java/org/killbill/billing/payment/dao/TestPaymentDao.java
@@ -1,7 +1,7 @@
/*
* Copyright 2010-2013 Ning, Inc.
- * Copyright 2014-2016 Groupon, Inc
- * Copyright 2014-2016 The Billing Project, LLC
+ * 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
@@ -28,13 +28,14 @@ import org.joda.time.DateTime;
import org.killbill.billing.account.api.Account;
import org.killbill.billing.catalog.api.Currency;
import org.killbill.billing.payment.PaymentTestSuiteWithEmbeddedDB;
+import org.killbill.billing.payment.api.Payment;
import org.killbill.billing.payment.api.PluginProperty;
import org.killbill.billing.payment.api.TransactionStatus;
import org.killbill.billing.payment.api.TransactionType;
import org.killbill.billing.payment.dao.PluginPropertySerializer.PluginPropertySerializerException;
+import org.killbill.billing.util.audit.ChangeType;
+import org.killbill.billing.util.dao.EntityHistoryModelDao;
import org.killbill.billing.util.entity.Pagination;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
import org.testng.Assert;
import org.testng.annotations.Test;
@@ -95,7 +96,6 @@ public class TestPaymentDao extends PaymentTestSuiteWithEmbeddedDB {
@Test(groups = "slow")
public void testPaymentAndTransactions() {
-
final UUID paymentMethodId = UUID.randomUUID();
final UUID accountId = UUID.randomUUID();
final String externalKey = "hhhhooo";
@@ -110,13 +110,24 @@ public class TestPaymentDao extends PaymentTestSuiteWithEmbeddedDB {
TransactionStatus.SUCCESS, BigDecimal.TEN, Currency.AED,
"success", "");
- final PaymentModelDao savedPayment = paymentDao.insertPaymentWithFirstTransaction(paymentModelDao, transactionModelDao, internalCallContext);
+ final PaymentModelDao savedPayment = paymentDao.insertPaymentWithFirstTransaction(paymentModelDao, transactionModelDao, internalCallContext).getPaymentModelDao();
assertEquals(savedPayment.getId(), paymentModelDao.getId());
assertEquals(savedPayment.getAccountId(), paymentModelDao.getAccountId());
assertEquals(savedPayment.getExternalKey(), paymentModelDao.getExternalKey());
assertEquals(savedPayment.getPaymentMethodId(), paymentModelDao.getPaymentMethodId());
assertNull(savedPayment.getStateName());
+ final List<EntityHistoryModelDao<PaymentModelDao, Payment>> history1 = getPaymentHistory(savedPayment.getRecordId());
+ Assert.assertEquals(history1.size(), 1);
+ Assert.assertEquals(history1.get(0).getChangeType(), ChangeType.INSERT);
+ Assert.assertEquals(history1.get(0).getEntity().getAccountRecordId(), savedPayment.getAccountRecordId());
+ Assert.assertEquals(history1.get(0).getEntity().getTenantRecordId(), savedPayment.getTenantRecordId());
+ Assert.assertEquals(history1.get(0).getEntity().getExternalKey(), savedPayment.getExternalKey());
+ Assert.assertEquals(history1.get(0).getEntity().getStateName(), savedPayment.getStateName());
+ Assert.assertEquals(history1.get(0).getEntity().getLastSuccessStateName(), savedPayment.getLastSuccessStateName());
+ Assert.assertNull(history1.get(0).getEntity().getStateName());
+ Assert.assertNull(history1.get(0).getEntity().getLastSuccessStateName());
+
final PaymentModelDao savedPayment2 = paymentDao.getPayment(savedPayment.getId(), internalCallContext);
assertEquals(savedPayment2.getId(), paymentModelDao.getId());
assertEquals(savedPayment2.getAccountId(), paymentModelDao.getAccountId());
@@ -162,6 +173,20 @@ public class TestPaymentDao extends PaymentTestSuiteWithEmbeddedDB {
assertEquals(savedTransactionModelDao2.getAmount().compareTo(BigDecimal.TEN), 0);
assertEquals(savedTransactionModelDao2.getCurrency(), Currency.AED);
+ final List<EntityHistoryModelDao<PaymentModelDao, Payment>> history2 = getPaymentHistory(savedPayment.getRecordId());
+ Assert.assertEquals(history2.size(), 2);
+ Assert.assertEquals(history2.get(0).getChangeType(), ChangeType.INSERT);
+ Assert.assertEquals(history2.get(0).getEntity().getAccountRecordId(), savedPayment.getAccountRecordId());
+ Assert.assertEquals(history2.get(0).getEntity().getTenantRecordId(), savedPayment.getTenantRecordId());
+ Assert.assertEquals(history2.get(0).getEntity().getExternalKey(), savedPayment.getExternalKey());
+ Assert.assertEquals(history2.get(1).getChangeType(), ChangeType.UPDATE);
+ Assert.assertEquals(history2.get(1).getEntity().getAccountRecordId(), savedPayment.getAccountRecordId());
+ Assert.assertEquals(history2.get(1).getEntity().getTenantRecordId(), savedPayment.getTenantRecordId());
+ Assert.assertEquals(history2.get(1).getEntity().getExternalKey(), savedPayment.getExternalKey());
+ Assert.assertTrue(history2.get(1).getEntity().getUpdatedDate().compareTo(history2.get(0).getEntity().getUpdatedDate()) >= 0);
+ Assert.assertNull(history2.get(1).getEntity().getStateName());
+ Assert.assertNull(history2.get(1).getEntity().getLastSuccessStateName());
+
final List<PaymentTransactionModelDao> transactions = paymentDao.getTransactionsForPayment(savedPayment.getId(), internalCallContext);
assertEquals(transactions.size(), 2);
@@ -176,6 +201,25 @@ public class TestPaymentDao extends PaymentTestSuiteWithEmbeddedDB {
assertEquals(savedPayment4.getStateName(), "AUTH_ABORTED");
assertEquals(savedPayment4.getLastSuccessStateName(), "AUTH_SUCCESS");
+ final List<EntityHistoryModelDao<PaymentModelDao, Payment>> history3 = getPaymentHistory(savedPayment.getRecordId());
+ Assert.assertEquals(history3.size(), 3);
+ Assert.assertEquals(history3.get(0).getChangeType(), ChangeType.INSERT);
+ Assert.assertEquals(history3.get(0).getEntity().getAccountRecordId(), savedPayment.getAccountRecordId());
+ Assert.assertEquals(history3.get(0).getEntity().getTenantRecordId(), savedPayment.getTenantRecordId());
+ Assert.assertEquals(history3.get(0).getEntity().getExternalKey(), savedPayment.getExternalKey());
+ Assert.assertEquals(history3.get(1).getChangeType(), ChangeType.UPDATE);
+ Assert.assertEquals(history3.get(1).getEntity().getAccountRecordId(), savedPayment.getAccountRecordId());
+ Assert.assertEquals(history3.get(1).getEntity().getTenantRecordId(), savedPayment.getTenantRecordId());
+ Assert.assertEquals(history3.get(1).getEntity().getExternalKey(), savedPayment.getExternalKey());
+ Assert.assertTrue(history3.get(1).getEntity().getUpdatedDate().compareTo(history3.get(0).getEntity().getUpdatedDate()) >= 0);
+ Assert.assertEquals(history3.get(2).getChangeType(), ChangeType.UPDATE);
+ Assert.assertEquals(history3.get(2).getEntity().getAccountRecordId(), savedPayment.getAccountRecordId());
+ Assert.assertEquals(history3.get(2).getEntity().getTenantRecordId(), savedPayment.getTenantRecordId());
+ Assert.assertEquals(history3.get(2).getEntity().getExternalKey(), savedPayment.getExternalKey());
+ Assert.assertTrue(history3.get(2).getEntity().getUpdatedDate().compareTo(history3.get(2).getEntity().getUpdatedDate()) >= 0);
+ Assert.assertEquals(history3.get(2).getEntity().getStateName(), savedPayment4.getStateName());
+ Assert.assertEquals(history3.get(2).getEntity().getLastSuccessStateName(), savedPayment4.getLastSuccessStateName());
+
final PaymentTransactionModelDao savedTransactionModelDao4 = paymentDao.getPaymentTransaction(savedTransactionModelDao2.getId(), internalCallContext);
assertEquals(savedTransactionModelDao4.getTransactionExternalKey(), transactionExternalKey2);
assertEquals(savedTransactionModelDao4.getPaymentId(), paymentModelDao.getId());
@@ -268,7 +312,7 @@ public class TestPaymentDao extends PaymentTestSuiteWithEmbeddedDB {
TransactionStatus.PENDING, BigDecimal.TEN, Currency.AED,
"pending", "");
- final PaymentModelDao payment = paymentDao.insertPaymentWithFirstTransaction(paymentModelDao, transaction1, internalCallContext);
+ final PaymentModelDao payment = paymentDao.insertPaymentWithFirstTransaction(paymentModelDao, transaction1, internalCallContext).getPaymentModelDao();
final PaymentTransactionModelDao transaction2 = new PaymentTransactionModelDao(initialTime, initialTime, null, transactionExternalKey2,
paymentModelDao.getId(), TransactionType.AUTHORIZE, initialTime,
@@ -550,5 +594,11 @@ public class TestPaymentDao extends PaymentTestSuiteWithEmbeddedDB {
}
}));
}
+
+ private List<EntityHistoryModelDao<PaymentModelDao, Payment>> getPaymentHistory(final Long paymentRecordId) {
+ // See https://github.com/killbill/killbill/issues/335
+ final PaymentSqlDao paymentSqlDao = dbi.onDemand(PaymentSqlDao.class);
+ return paymentSqlDao.getHistoryForTargetRecordId(paymentRecordId, internalCallContext);
+ }
}
pom.xml 2(+1 -1)
diff --git a/pom.xml b/pom.xml
index 2c2ec75..af7841c 100644
--- a/pom.xml
+++ b/pom.xml
@@ -24,7 +24,7 @@
<version>0.141-SNAPSHOT</version>
</parent>
<artifactId>killbill</artifactId>
- <version>0.18.5-SNAPSHOT</version>
+ <version>0.18.7-SNAPSHOT</version>
<packaging>pom</packaging>
<name>killbill</name>
<description>Library for managing recurring subscriptions and the associated billing</description>
profiles/killbill/pom.xml 10(+5 -5)
diff --git a/profiles/killbill/pom.xml b/profiles/killbill/pom.xml
index 1e98233..d8ad48c 100644
--- a/profiles/killbill/pom.xml
+++ b/profiles/killbill/pom.xml
@@ -21,7 +21,7 @@
<parent>
<artifactId>killbill-profiles</artifactId>
<groupId>org.kill-bill.billing</groupId>
- <version>0.18.5-SNAPSHOT</version>
+ <version>0.18.7-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>killbill-profiles-killbill</artifactId>
@@ -172,10 +172,6 @@
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
- <artifactId>shiro-ehcache</artifactId>
- </dependency>
- <dependency>
- <groupId>org.apache.shiro</groupId>
<artifactId>shiro-guice</artifactId>
</dependency>
<dependency>
@@ -228,6 +224,10 @@
<scope>test</scope>
</dependency>
<dependency>
+ <groupId>org.ehcache.integrations.shiro</groupId>
+ <artifactId>shiro-ehcache3</artifactId>
+ </dependency>
+ <dependency>
<groupId>org.javassist</groupId>
<artifactId>javassist</artifactId>
<scope>runtime</scope>
diff --git a/profiles/killbill/src/main/java/org/killbill/billing/server/log/obfuscators/PatternObfuscator.java b/profiles/killbill/src/main/java/org/killbill/billing/server/log/obfuscators/PatternObfuscator.java
index 05f7991..a850c5b 100644
--- a/profiles/killbill/src/main/java/org/killbill/billing/server/log/obfuscators/PatternObfuscator.java
+++ b/profiles/killbill/src/main/java/org/killbill/billing/server/log/obfuscators/PatternObfuscator.java
@@ -43,6 +43,7 @@ public class PatternObfuscator extends Obfuscator {
"ccvv",
"cvNumber",
"cvc",
+ "cvv",
"email",
"iban",
"name",
@@ -76,7 +77,7 @@ public class PatternObfuscator extends Obfuscator {
}
private Pattern buildJSONPattern(final String key) {
- return Pattern.compile(key + "\":\\s*([^,{]+)", DEFAULT_PATTERN_FLAGS);
+ return Pattern.compile(key + "\"\\s*:\\s*([^,{}]+)", DEFAULT_PATTERN_FLAGS);
}
private Pattern buildXMLPattern(final String key) {
diff --git a/profiles/killbill/src/main/java/org/killbill/billing/server/modules/KillbillJdbcTenantRealmProvider.java b/profiles/killbill/src/main/java/org/killbill/billing/server/modules/KillbillJdbcTenantRealmProvider.java
index d53b571..5774818 100644
--- a/profiles/killbill/src/main/java/org/killbill/billing/server/modules/KillbillJdbcTenantRealmProvider.java
+++ b/profiles/killbill/src/main/java/org/killbill/billing/server/modules/KillbillJdbcTenantRealmProvider.java
@@ -1,6 +1,6 @@
/*
- * Copyright 2014-2015 Groupon, Inc
- * Copyright 2014-2015 The Billing Project, LLC
+ * 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
@@ -23,7 +23,6 @@ import javax.sql.DataSource;
import org.apache.shiro.cache.CacheManager;
import org.killbill.billing.server.security.KillbillJdbcTenantRealm;
import org.killbill.billing.util.config.definition.SecurityConfig;
-import org.killbill.billing.util.glue.ShiroEhCacheInstrumentor;
import com.google.inject.Inject;
import com.google.inject.Provider;
@@ -32,14 +31,12 @@ public class KillbillJdbcTenantRealmProvider implements Provider<KillbillJdbcTen
private final SecurityConfig securityConfig;
private final CacheManager cacheManager;
- private final ShiroEhCacheInstrumentor ehCacheInstrumentor;
private final DataSource dataSource;
@Inject
- public KillbillJdbcTenantRealmProvider(final SecurityConfig securityConfig, final CacheManager cacheManager, final ShiroEhCacheInstrumentor ehCacheInstrumentor, @Named(KillbillPlatformModule.SHIRO_DATA_SOURCE_ID_NAMED) final DataSource dataSource) {
+ public KillbillJdbcTenantRealmProvider(final SecurityConfig securityConfig, final CacheManager cacheManager, @Named(KillbillPlatformModule.SHIRO_DATA_SOURCE_ID_NAMED) final DataSource dataSource) {
this.securityConfig = securityConfig;
this.cacheManager = cacheManager;
- this.ehCacheInstrumentor = ehCacheInstrumentor;
this.dataSource = dataSource;
}
@@ -52,9 +49,6 @@ public class KillbillJdbcTenantRealmProvider implements Provider<KillbillJdbcTen
// automatically configured with the EhCache manager (see EhCacheManagerProvider)
killbillJdbcTenantRealm.setCacheManager(cacheManager);
- // Instrument the cache
- ehCacheInstrumentor.instrument(killbillJdbcTenantRealm);
-
return killbillJdbcTenantRealm;
}
}
diff --git a/profiles/killbill/src/main/java/org/killbill/billing/server/modules/KillBillShiroWebModule.java b/profiles/killbill/src/main/java/org/killbill/billing/server/modules/KillBillShiroWebModule.java
index a623ff3..49057fb 100644
--- a/profiles/killbill/src/main/java/org/killbill/billing/server/modules/KillBillShiroWebModule.java
+++ b/profiles/killbill/src/main/java/org/killbill/billing/server/modules/KillBillShiroWebModule.java
@@ -1,7 +1,7 @@
/*
* Copyright 2010-2013 Ning, Inc.
- * Copyright 2014 Groupon, Inc
- * Copyright 2014 The Billing Project, LLC
+ * 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
@@ -27,32 +27,27 @@ import org.apache.shiro.authc.pam.ModularRealmAuthenticator;
import org.apache.shiro.authc.pam.ModularRealmAuthenticatorWith540;
import org.apache.shiro.cache.CacheManager;
import org.apache.shiro.guice.web.ShiroWebModuleWith435;
-import org.apache.shiro.realm.Realm;
import org.apache.shiro.session.mgt.SessionManager;
-import org.apache.shiro.session.mgt.eis.CachingSessionDAO;
import org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.apache.shiro.web.mgt.WebSecurityManager;
-import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
import org.apache.shiro.web.util.WebUtils;
import org.killbill.billing.jaxrs.resources.JaxrsResource;
import org.killbill.billing.server.security.FirstSuccessfulStrategyWith540;
+import org.killbill.billing.server.security.KillBillWebSessionManager;
import org.killbill.billing.server.security.KillbillJdbcTenantRealm;
import org.killbill.billing.util.config.definition.RbacConfig;
-import org.killbill.billing.util.glue.EhCacheManagerProvider;
+import org.killbill.billing.util.glue.EhcacheShiroManagerProvider;
import org.killbill.billing.util.glue.IniRealmProvider;
import org.killbill.billing.util.glue.JDBCSessionDaoProvider;
import org.killbill.billing.util.glue.KillBillShiroModule;
-import org.killbill.billing.util.glue.ShiroEhCacheInstrumentor;
import org.killbill.billing.util.security.shiro.dao.JDBCSessionDao;
import org.killbill.billing.util.security.shiro.realm.KillBillJdbcRealm;
import org.killbill.billing.util.security.shiro.realm.KillBillJndiLdapRealm;
import org.skife.config.ConfigSource;
import org.skife.config.ConfigurationObjectFactory;
-import com.google.inject.Inject;
import com.google.inject.Key;
-import com.google.inject.Provider;
import com.google.inject.TypeLiteral;
import com.google.inject.binder.AnnotatedBindingBuilder;
import com.google.inject.matcher.AbstractMatcher;
@@ -73,16 +68,9 @@ public class KillBillShiroWebModule extends ShiroWebModuleWith435 {
}
@Override
- public void configure() {
- super.configure();
-
- bind(ShiroEhCacheInstrumentor.class).asEagerSingleton();
- }
-
- @Override
protected void configureShiroWeb() {
// Magic provider to configure the cache manager
- bind(CacheManager.class).toProvider(EhCacheManagerProvider.class).asEagerSingleton();
+ bind(CacheManager.class).toProvider(EhcacheShiroManagerProvider.class).asEagerSingleton();
configureShiroForRBAC();
@@ -106,7 +94,7 @@ public class KillBillShiroWebModule extends ShiroWebModuleWith435 {
return Matchers.subclassesOf(WebSecurityManager.class).matches(o.getRawType());
}
},
- new DefaultWebSecurityManagerTypeListener(getProvider(ShiroEhCacheInstrumentor.class)));
+ new DefaultWebSecurityManagerTypeListener());
if (KillBillShiroModule.isRBACEnabled()) {
addFilterChain(JaxrsResource.PREFIX + "/**", Key.get(CorsBasicHttpAuthenticationFilter.class));
@@ -123,7 +111,7 @@ public class KillBillShiroWebModule extends ShiroWebModuleWith435 {
protected void bindSessionManager(final AnnotatedBindingBuilder<SessionManager> bind) {
// Bypass the servlet container completely for session management and delegate it to Shiro.
// The default session timeout is 30 minutes.
- bind.to(DefaultWebSessionManager.class).asEagerSingleton();
+ bind.to(KillBillWebSessionManager.class).asEagerSingleton();
// Magic provider to configure the session DAO
bind(JDBCSessionDao.class).toProvider(JDBCSessionDaoProvider.class).asEagerSingleton();
@@ -143,30 +131,16 @@ public class KillBillShiroWebModule extends ShiroWebModuleWith435 {
private static final class DefaultWebSecurityManagerTypeListener implements TypeListener {
- private final Provider<ShiroEhCacheInstrumentor> instrumentorProvider;
-
- @Inject
- public DefaultWebSecurityManagerTypeListener(final Provider<ShiroEhCacheInstrumentor> instrumentorProvider) {
- this.instrumentorProvider = instrumentorProvider;
- }
-
@Override
public <I> void hear(final TypeLiteral<I> typeLiteral, final TypeEncounter<I> typeEncounter) {
typeEncounter.register(new InjectionListener<I>() {
@Override
public void afterInjection(final Object o) {
- final ShiroEhCacheInstrumentor ehCacheInstrumentor = instrumentorProvider.get();
- ehCacheInstrumentor.instrument(CachingSessionDAO.ACTIVE_SESSION_CACHE_NAME);
-
final DefaultWebSecurityManager webSecurityManager = (DefaultWebSecurityManager) o;
if (webSecurityManager.getAuthenticator() instanceof ModularRealmAuthenticator) {
final ModularRealmAuthenticator authenticator = (ModularRealmAuthenticator) webSecurityManager.getAuthenticator();
authenticator.setAuthenticationStrategy(new FirstSuccessfulStrategyWith540());
webSecurityManager.setAuthenticator(new ModularRealmAuthenticatorWith540(authenticator));
-
- for (final Realm realm : webSecurityManager.getRealms()) {
- ehCacheInstrumentor.instrument(realm);
- }
}
}
});
diff --git a/profiles/killbill/src/main/java/org/killbill/billing/server/security/KillbillJdbcTenantRealm.java b/profiles/killbill/src/main/java/org/killbill/billing/server/security/KillbillJdbcTenantRealm.java
index aa2e377..7faf4f6 100644
--- a/profiles/killbill/src/main/java/org/killbill/billing/server/security/KillbillJdbcTenantRealm.java
+++ b/profiles/killbill/src/main/java/org/killbill/billing/server/security/KillbillJdbcTenantRealm.java
@@ -1,7 +1,7 @@
/*
* Copyright 2010-2013 Ning, Inc.
- * Copyright 2014-2015 Groupon, Inc
- * Copyright 2014-2015 The Billing Project, LLC
+ * 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
@@ -18,6 +18,11 @@
package org.killbill.billing.server.security;
+import java.io.Externalizable;
+import java.io.IOException;
+import java.io.ObjectInput;
+import java.io.ObjectOutput;
+
import javax.sql.DataSource;
import org.apache.shiro.authc.AuthenticationException;
@@ -25,8 +30,12 @@ import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.codec.Base64;
+import org.apache.shiro.codec.Hex;
import org.apache.shiro.realm.jdbc.JdbcRealm;
import org.apache.shiro.util.ByteSource;
+import org.killbill.billing.util.cache.ExternalizableInput;
+import org.killbill.billing.util.cache.ExternalizableOutput;
+import org.killbill.billing.util.cache.MapperHolder;
import org.killbill.billing.util.config.definition.SecurityConfig;
import org.killbill.billing.util.security.shiro.KillbillCredentialsMatcher;
@@ -61,7 +70,9 @@ public class KillbillJdbcTenantRealm extends JdbcRealm {
// We store the salt bytes in Base64 (because the JdbcRealm retrieves it as a String)
final ByteSource base64Salt = authenticationInfo.getCredentialsSalt();
- authenticationInfo.setCredentialsSalt(ByteSource.Util.bytes(Base64.decode(base64Salt.getBytes())));
+ final byte[] bytes = Base64.decode(base64Salt.getBytes());
+ // SimpleByteSource isn't Serializable
+ authenticationInfo.setCredentialsSalt(new SerializableSimpleByteSource(bytes));
return authenticationInfo;
}
@@ -78,4 +89,56 @@ public class KillbillJdbcTenantRealm extends JdbcRealm {
private void configureDataSource() {
setDataSource(dataSource);
}
+
+ private static final class SerializableSimpleByteSource implements ByteSource, Externalizable {
+
+ private static final long serialVersionUID = 4498655519894503985L;
+
+ private byte[] bytes;
+ private String cachedHex;
+ private String cachedBase64;
+
+ // For deserialization
+ public SerializableSimpleByteSource() {}
+
+ SerializableSimpleByteSource(final byte[] bytes) {
+ this.bytes = bytes;
+ }
+
+ @Override
+ public byte[] getBytes() {
+ return bytes;
+ }
+
+ @Override
+ public String toHex() {
+ if (this.cachedHex == null) {
+ this.cachedHex = Hex.encodeToString(getBytes());
+ }
+ return this.cachedHex;
+ }
+
+ @Override
+ public String toBase64() {
+ if (this.cachedBase64 == null) {
+ this.cachedBase64 = Base64.encodeToString(getBytes());
+ }
+ return this.cachedBase64;
+ }
+
+ @Override
+ public boolean isEmpty() {
+ return this.bytes == null || this.bytes.length == 0;
+ }
+
+ @Override
+ public void readExternal(final ObjectInput in) throws IOException {
+ MapperHolder.mapper().readerForUpdating(this).readValue(new ExternalizableInput(in));
+ }
+
+ @Override
+ public void writeExternal(final ObjectOutput oo) throws IOException {
+ MapperHolder.mapper().writeValue(new ExternalizableOutput(oo), this);
+ }
+ }
}
diff --git a/profiles/killbill/src/main/java/org/killbill/billing/server/security/KillBillWebSessionManager.java b/profiles/killbill/src/main/java/org/killbill/billing/server/security/KillBillWebSessionManager.java
new file mode 100644
index 0000000..e67ec0e
--- /dev/null
+++ b/profiles/killbill/src/main/java/org/killbill/billing/server/security/KillBillWebSessionManager.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2017 Groupon, Inc
+ * Copyright 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.server.security;
+
+import org.apache.shiro.session.Session;
+import org.apache.shiro.session.mgt.SessionContext;
+import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
+
+public class KillBillWebSessionManager extends DefaultWebSessionManager {
+
+ @Override
+ protected Session newSessionInstance(final SessionContext context) {
+ final Session session = super.newSessionInstance(context);
+
+ // DefaultWebSessionManager will call applyGlobalSessionTimeout() in
+ // start() below instead, which in turn calls onChange() and triggers a DAO UPDATE call
+ session.setTimeout(getGlobalSessionTimeout());
+
+ return session;
+ }
+
+ @Override
+ public Session start(final SessionContext context) {
+ final Session session = createSession(context);
+
+ // See above
+ //applyGlobalSessionTimeout(session);
+
+ onStart(session, context);
+ notifyStart(session);
+ return createExposedSession(session, context);
+ }
+}
diff --git a/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestCache.java b/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestCache.java
index 23325ad..c00915a 100644
--- a/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestCache.java
+++ b/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestCache.java
@@ -16,11 +16,13 @@
package org.killbill.billing.jaxrs;
-import java.util.List;
import java.util.UUID;
import org.joda.time.LocalDate;
+import org.killbill.automaton.StateMachineConfig;
+import org.killbill.billing.account.api.ImmutableAccountData;
import org.killbill.billing.catalog.api.BillingPeriod;
+import org.killbill.billing.catalog.api.Catalog;
import org.killbill.billing.catalog.api.PriceListSet;
import org.killbill.billing.catalog.api.ProductCategory;
import org.killbill.billing.client.RequestOptions;
@@ -29,12 +31,14 @@ import org.killbill.billing.client.model.PaymentMethod;
import org.killbill.billing.client.model.PaymentMethodPluginDetail;
import org.killbill.billing.client.model.Subscription;
import org.killbill.billing.client.model.Tenant;
+import org.killbill.billing.overdue.api.OverdueConfig;
import org.killbill.billing.util.cache.Cachable.CacheType;
+import org.killbill.billing.util.cache.CacheController;
+import org.killbill.billing.util.config.tenant.PerTenantConfig;
import org.testng.Assert;
import org.testng.annotations.Test;
import com.google.common.io.Resources;
-import net.sf.ehcache.Ehcache;
import static org.testng.Assert.assertFalse;
import static org.testng.Assert.assertNotNull;
@@ -45,31 +49,31 @@ public class TestCache extends TestJaxrsBase {
@Test(groups = "slow", description = "Can Invalidate (clear) a Cache by name")
public void testInvalidateCacheByName() throws Exception {
// get Ehcache item with name "record-id"
- final Ehcache cache = cacheManager.getEhcache(CacheType.RECORD_ID.getCacheName());
+ final CacheController<String, Long> cache = cacheControllerDispatcher.getCacheController(CacheType.RECORD_ID);
// verify that it is not null and has one stored key (the default tenant created for all integration tests)
assertNotNull(cache);
- Assert.assertEquals(cache.getSize(), 1);
+ Assert.assertEquals(cache.size(), 1);
// invalidate the specified cache
- killBillClient.invalidateCacheByName(cache.getName(), requestOptions);
+ killBillClient.invalidateCacheByName(CacheType.RECORD_ID.getCacheName(), requestOptions);
// verify that now the cache is empty and has no keys stored
- Assert.assertEquals(cache.getSize(), 0);
+ Assert.assertEquals(cache.size(), 0);
}
@Test(groups = "slow", description = "Can Invalidate (clear) all available Caches")
public void testInvalidateAllCaches() throws Exception {
// get Ehcache item with name "record-id"
- final Ehcache cache = cacheManager.getEhcache(CacheType.RECORD_ID.getCacheName());
+ final CacheController<String, Long> cache = cacheControllerDispatcher.getCacheController(CacheType.RECORD_ID);
// verify that it is not null and has one stored key (the default tenant created for all integration tests)
assertNotNull(cache);
- Assert.assertEquals(cache.getSize(), 1);
+ Assert.assertEquals(cache.size(), 1);
// invalidate all caches
killBillClient.invalidateAllCaches(requestOptions);
// verify that now the cache is empty and has no keys stored
- Assert.assertEquals(cache.getSize(), 0);
+ Assert.assertEquals(cache.size(), 0);
}
@Test(groups = "slow", description = "Can Invalidate (clear) all Account Caches by accountId")
@@ -77,25 +81,23 @@ public class TestCache extends TestJaxrsBase {
final Account input = createAccountNoPMBundleAndSubscription();
// get all caches per account level
- final Ehcache accountRecordIdCache = cacheManager.getEhcache(CacheType.ACCOUNT_RECORD_ID.getCacheName());
- final Ehcache accountImmutableCache = cacheManager.getEhcache(CacheType.ACCOUNT_IMMUTABLE.getCacheName());
- final Ehcache accountBcdCache = cacheManager.getEhcache(CacheType.ACCOUNT_BCD.getCacheName());
+ final CacheController<String, Long> accountRecordIdCache = cacheControllerDispatcher.getCacheController(CacheType.ACCOUNT_RECORD_ID);
+ final CacheController<Long, ImmutableAccountData> accountImmutableCache = cacheControllerDispatcher.getCacheController(CacheType.ACCOUNT_IMMUTABLE);
+ final CacheController<UUID, Integer> accountBcdCache = cacheControllerDispatcher.getCacheController(CacheType.ACCOUNT_BCD);
// verify that they are not null and have the accountId stored as a key (the account created before)
- assertNotNull(accountRecordIdCache);
- assertNotNull(accountRecordIdCache.get(input.getAccountId().toString()));
- assertNotNull(accountImmutableCache);
- assertNotNull(accountImmutableCache.get(input.getAccountId()));
- assertNotNull(accountBcdCache);
- assertNotNull(accountBcdCache.get(input.getAccountId()));
+ assertTrue(accountRecordIdCache.isKeyInCache(input.getAccountId().toString()));
+ final Long accountRecordId = accountRecordIdCache.get(input.getAccountId().toString(), null);
+ assertTrue(accountImmutableCache.isKeyInCache(accountRecordId));
+ assertTrue(accountBcdCache.isKeyInCache(input.getAccountId()));
// invalidate caches per account level by accountId
killBillClient.invalidateCacheByAccount(input.getAccountId().toString(), requestOptions);
// verify that now the caches don't have the accountId key stored
- Assert.assertNull(accountRecordIdCache.get(input.getAccountId().toString()));
- Assert.assertNull(accountImmutableCache.get(input.getAccountId()));
- Assert.assertNull(accountBcdCache.get(input.getAccountId()));
+ Assert.assertFalse(accountRecordIdCache.isKeyInCache(input.getAccountId().toString()));
+ Assert.assertFalse(accountImmutableCache.isKeyInCache(accountRecordId));
+ Assert.assertFalse(accountBcdCache.isKeyInCache(input.getAccountId()));
}
@Test(groups = "slow", description = "Can Invalidate (clear) all Tenant Caches for current Tenant")
@@ -125,48 +127,40 @@ public class TestCache extends TestJaxrsBase {
createAccountWithPMBundleAndSubscriptionAndWaitForFirstInvoiceWithInputOptions(inputOptions);
// get all caches per tenant level
- final Ehcache tenantRecordIdCache = cacheManager.getEhcache(CacheType.TENANT_RECORD_ID.getCacheName());
- final Ehcache tenantPaymentStateMachineConfigCache = cacheManager.getEhcache(CacheType.TENANT_PAYMENT_STATE_MACHINE_CONFIG.getCacheName());
- final Ehcache tenantCache = cacheManager.getEhcache(CacheType.TENANT.getCacheName());
- final Ehcache tenantKvCache = cacheManager.getEhcache(CacheType.TENANT_KV.getCacheName());
- final Ehcache tenantConfigCache = cacheManager.getEhcache(CacheType.TENANT_CONFIG.getCacheName());
- final Ehcache tenantOverdueConfigCache = cacheManager.getEhcache(CacheType.TENANT_OVERDUE_CONFIG.getCacheName());
- final Ehcache tenantCatalogCache = cacheManager.getEhcache(CacheType.TENANT_CATALOG.getCacheName());
-
- // getting current Tenant's record Id from the specific Cache
- Long tenantRecordId = (Long) tenantRecordIdCache.get(currentTenant.getTenantId().toString()).getObjectValue();
+ final CacheController<String, Long> tenantRecordIdCache = cacheControllerDispatcher.getCacheController(CacheType.TENANT_RECORD_ID);
+ final CacheController<String, StateMachineConfig> tenantPaymentStateMachineConfigCache = cacheControllerDispatcher.getCacheController(CacheType.TENANT_PAYMENT_STATE_MACHINE_CONFIG);
+ final CacheController<String, org.killbill.billing.tenant.api.Tenant> tenantCache = cacheControllerDispatcher.getCacheController(CacheType.TENANT);
+ final CacheController<String, String> tenantKvCache = cacheControllerDispatcher.getCacheController(CacheType.TENANT_KV);
+ final CacheController<Long, PerTenantConfig> tenantConfigCache = cacheControllerDispatcher.getCacheController(CacheType.TENANT_CONFIG);
+ final CacheController<Long, OverdueConfig> tenantOverdueConfigCache = cacheControllerDispatcher.getCacheController(CacheType.TENANT_OVERDUE_CONFIG);
+ final CacheController<Long, Catalog> tenantCatalogCache = cacheControllerDispatcher.getCacheController(CacheType.TENANT_CATALOG);
// verify that they are not null and have the expected tenant information
- assertNotNull(tenantRecordIdCache);
- assertNotNull(tenantRecordIdCache.get(currentTenant.getTenantId().toString()));
- assertNotNull(tenantPaymentStateMachineConfigCache);
+ assertTrue(tenantRecordIdCache.isKeyInCache(currentTenant.getTenantId().toString()));
+ final Long tenantRecordId = tenantRecordIdCache.get(currentTenant.getTenantId().toString(), null);
+
assertTrue(hasKeysByTenantRecordId(tenantPaymentStateMachineConfigCache, tenantRecordId.toString()));
- assertNotNull(tenantCache);
- assertNotNull(tenantCache.get(testApiKey));
- assertNotNull(tenantKvCache);
+ assertTrue(tenantCache.isKeyInCache(testApiKey));
assertTrue(hasKeysByTenantRecordId(tenantKvCache, tenantRecordId.toString()));
- assertNotNull(tenantConfigCache);
- assertNotNull(tenantConfigCache.get(tenantRecordId));
- assertNotNull(tenantOverdueConfigCache);
- assertNotNull(tenantOverdueConfigCache.get(tenantRecordId));
- assertNotNull(tenantCatalogCache);
- assertNotNull(tenantCatalogCache.get(tenantRecordId));
+ assertTrue(tenantConfigCache.isKeyInCache(tenantRecordId));
+ assertTrue(tenantOverdueConfigCache.isKeyInCache(tenantRecordId));
+ assertTrue(tenantCatalogCache.isKeyInCache(tenantRecordId));
// invalidate caches per tenant level
killBillClient.invalidateCacheByTenant(inputOptions);
// verify that now the caches don't have the previous values
- Assert.assertNull(tenantRecordIdCache.get(currentTenant.getTenantId().toString()));
+ assertFalse(tenantRecordIdCache.isKeyInCache(currentTenant.getTenantId().toString()));
assertFalse(hasKeysByTenantRecordId(tenantPaymentStateMachineConfigCache, tenantRecordId.toString()));
- Assert.assertNull(tenantCache.get(testApiKey));
+ assertFalse(tenantCache.isKeyInCache(testApiKey));
assertFalse(hasKeysByTenantRecordId(tenantKvCache, tenantRecordId.toString()));
- Assert.assertNull(tenantConfigCache.get(tenantRecordId));
- Assert.assertNull(tenantOverdueConfigCache.get(tenantRecordId));
- Assert.assertNull(tenantCatalogCache.get(tenantRecordId));
+ assertFalse(tenantConfigCache.isKeyInCache(tenantRecordId));
+ assertFalse(tenantOverdueConfigCache.isKeyInCache(tenantRecordId));
+ assertFalse(tenantCatalogCache.isKeyInCache(tenantRecordId));
}
- private boolean hasKeysByTenantRecordId(final Ehcache tenantCache, final String tenantRecordId) {
- for (String key : (List<String>) tenantCache.getKeys()) {
+ private boolean hasKeysByTenantRecordId(final CacheController<String, ?> tenantCache, final String tenantRecordId) {
+ for (final String key : tenantCache.getKeys()) {
if (key.endsWith("::" + tenantRecordId)) {
return true;
}
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 bd9c2cf..4427ea6 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;
@@ -72,7 +75,7 @@ public class TestEntitlement extends TestJaxrsBase {
ProductCategory.BASE, term, true);
// Retrieves with GET
- Subscription objFromJson = killBillClient.getSubscription(entitlementJson.getSubscriptionId());
+ Subscription objFromJson = killBillClient.getSubscription(entitlementJson.getSubscriptionId(), requestOptions);
Assert.assertEquals(objFromJson.getPriceOverrides().size(), 2);
Assert.assertEquals(objFromJson.getPriceOverrides().get(0).getFixedPrice(), BigDecimal.ZERO);
Assert.assertNull(objFromJson.getPriceOverrides().get(0).getRecurringPrice());
@@ -95,7 +98,7 @@ public class TestEntitlement extends TestJaxrsBase {
newInput.setProductCategory(ProductCategory.BASE);
newInput.setBillingPeriod(entitlementJson.getBillingPeriod());
newInput.setPriceList(entitlementJson.getPriceList());
- objFromJson = killBillClient.updateSubscription(newInput, CALL_COMPLETION_TIMEOUT_SEC, createdBy, reason, comment);
+ objFromJson = killBillClient.updateSubscription(newInput, CALL_COMPLETION_TIMEOUT_SEC, requestOptions);
Assert.assertNotNull(objFromJson);
// MOVE AFTER TRIAL
@@ -105,10 +108,10 @@ public class TestEntitlement extends TestJaxrsBase {
crappyWaitForLackOfProperSynchonization();
// Cancel IMM (Billing EOT)
- killBillClient.cancelSubscription(newInput.getSubscriptionId(), CALL_COMPLETION_TIMEOUT_SEC, createdBy, reason, comment);
+ killBillClient.cancelSubscription(newInput.getSubscriptionId(), CALL_COMPLETION_TIMEOUT_SEC, requestOptions);
// Retrieves to check EndDate
- objFromJson = killBillClient.getSubscription(entitlementJson.getSubscriptionId());
+ objFromJson = killBillClient.getSubscription(entitlementJson.getSubscriptionId(), requestOptions);
assertNotNull(objFromJson.getCancelledDate());
assertTrue(objFromJson.getCancelledDate().compareTo(new LocalDate(clock.getUTCNow())) == 0);
}
@@ -166,7 +169,7 @@ public class TestEntitlement extends TestJaxrsBase {
// Uncancel
killBillClient.uncancelSubscription(entitlementJson.getSubscriptionId(), requestOptions);
- objFromJson = killBillClient.getSubscription(entitlementJson.getSubscriptionId());
+ objFromJson = killBillClient.getSubscription(entitlementJson.getSubscriptionId(), requestOptions);
assertNull(objFromJson.getCancelledDate());
Assert.assertEquals(objFromJson.getPriceOverrides().size(), 2);
Assert.assertEquals(objFromJson.getPriceOverrides().get(0).getPhaseName(), "shotgun-monthly-trial");
@@ -188,11 +191,11 @@ public class TestEntitlement extends TestJaxrsBase {
subscription.setBillingPeriod(BillingPeriod.ANNUAL);
subscription.setPriceList(PriceListSet.DEFAULT_PRICELIST_NAME);
- assertNull(killBillClient.updateSubscription(subscription, CALL_COMPLETION_TIMEOUT_SEC, createdBy, reason, comment));
+ assertNull(killBillClient.updateSubscription(subscription, CALL_COMPLETION_TIMEOUT_SEC, requestOptions));
- killBillClient.cancelSubscription(subscriptionId, createdBy, reason, comment);
+ killBillClient.cancelSubscription(subscriptionId, requestOptions);
- assertNull(killBillClient.getSubscription(subscriptionId));
+ assertNull(killBillClient.getSubscription(subscriptionId, requestOptions));
}
@Test(groups = "slow", description = "Can override billing policy on change")
@@ -209,7 +212,7 @@ public class TestEntitlement extends TestJaxrsBase {
ProductCategory.BASE, term, true);
// Retrieves with GET
- Subscription objFromJson = killBillClient.getSubscription(subscriptionJson.getSubscriptionId());
+ Subscription objFromJson = killBillClient.getSubscription(subscriptionJson.getSubscriptionId(), requestOptions);
// Equality in java client is not correctly implemented so manually check PriceOverrides section and then reset before equality
objFromJson.setPriceOverrides(null);
subscriptionJson.setPriceOverrides(null);
@@ -225,7 +228,7 @@ public class TestEntitlement extends TestJaxrsBase {
newInput.setProductCategory(ProductCategory.BASE);
newInput.setBillingPeriod(BillingPeriod.MONTHLY);
newInput.setPriceList(subscriptionJson.getPriceList());
- objFromJson = killBillClient.updateSubscription(newInput, BillingActionPolicy.IMMEDIATE, CALL_COMPLETION_TIMEOUT_SEC, createdBy, reason, comment);
+ objFromJson = killBillClient.updateSubscription(newInput, BillingActionPolicy.IMMEDIATE, CALL_COMPLETION_TIMEOUT_SEC, requestOptions );
Assert.assertNotNull(objFromJson);
assertEquals(objFromJson.getBillingPeriod(), BillingPeriod.MONTHLY);
}
@@ -354,15 +357,17 @@ public class TestEntitlement extends TestJaxrsBase {
subscriptions.add(base);
subscriptions.add(addOn1);
subscriptions.add(addOn2);
- final Bundle bundle = killBillClient.createSubscriptionWithAddOns(subscriptions, null, 10, "createdBy", "", "");
+ final Bundle bundle = killBillClient.createSubscriptionWithAddOns(subscriptions, null, 10, requestOptions);
assertNotNull(bundle);
assertEquals(bundle.getExternalKey(), "base");
assertEquals(bundle.getSubscriptions().size(), 3);
- final List<Invoice> invoices = killBillClient.getInvoicesForAccount(accountJson.getAccountId(), true, false, false, AuditLevel.FULL);
+ final List<Invoice> invoices = killBillClient.getInvoicesForAccount(accountJson.getAccountId(), true, false, false, AuditLevel.FULL, requestOptions);
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);
@@ -518,7 +581,7 @@ public class TestEntitlement extends TestJaxrsBase {
killBillClient.updateSubscriptionBCD(updatedSubscription, null, DEFAULT_WAIT_COMPLETION_TIMEOUT_SEC, requestOptions);
- final Subscription result = killBillClient.getSubscription(entitlementJson.getSubscriptionId());
+ final Subscription result = killBillClient.getSubscription(entitlementJson.getSubscriptionId(), requestOptions);
// Still shows as the 4 (BCD did not take effect)
Assert.assertEquals(result.getBillCycleDayLocal(), new Integer(25));
@@ -526,7 +589,7 @@ public class TestEntitlement extends TestJaxrsBase {
clock.addDays(14);
crappyWaitForLackOfProperSynchonization();
- final Subscription result2 = killBillClient.getSubscription(entitlementJson.getSubscriptionId());
+ final Subscription result2 = killBillClient.getSubscription(entitlementJson.getSubscriptionId(), requestOptions);
// Still shows as the 4 (BCD did not take effect)
Assert.assertEquals(result2.getBillCycleDayLocal(), new Integer(9));
}
diff --git a/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestJaxrsBase.java b/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestJaxrsBase.java
index d4ec7bf..66007ef 100644
--- a/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestJaxrsBase.java
+++ b/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestJaxrsBase.java
@@ -1,7 +1,7 @@
/*
* Copyright 2010-2013 Ning, Inc.
- * Copyright 2014-2015 Groupon, Inc
- * Copyright 2014-2015 The Billing Project, LLC
+ * 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
@@ -74,7 +74,6 @@ import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Iterables;
import com.google.inject.Module;
import com.google.inject.util.Modules;
-import net.sf.ehcache.CacheManager;
public class TestJaxrsBase extends KillbillClient {
@@ -107,9 +106,6 @@ public class TestJaxrsBase extends KillbillClient {
@Inject
protected TenantCacheInvalidation tenantCacheInvalidation;
- @Inject
- protected CacheManager cacheManager;
-
protected DaoConfig daoConfig;
protected KillbillServerConfig serverConfig;
diff --git a/profiles/killbill/src/test/java/org/killbill/billing/server/log/obfuscators/TestPatternObfuscator.java b/profiles/killbill/src/test/java/org/killbill/billing/server/log/obfuscators/TestPatternObfuscator.java
index c50ae34..934b81e 100644
--- a/profiles/killbill/src/test/java/org/killbill/billing/server/log/obfuscators/TestPatternObfuscator.java
+++ b/profiles/killbill/src/test/java/org/killbill/billing/server/log/obfuscators/TestPatternObfuscator.java
@@ -181,6 +181,10 @@ public class TestPatternObfuscator extends ServerTestSuiteNoDB {
" \"id\": \"card_483etw4er9fg4vF3sQdrt3FG\",\n" +
" \"object\": \"card\",\n" +
" \"banknumber\": 4111111111111111,\n" +
+ " \"cvv\" : 111,\n" +
+ " \"cvv\": 111,\n" +
+ " \"cvv\": \"111\",\n" +
+ " \"data\": {\"cvv\" : 111 },\n" +
" \"last4\": \"0000\",\n" +
" \"brand\": \"Visa\",\n" +
" \"funding\": \"credit\",\n" +
@@ -204,6 +208,10 @@ public class TestPatternObfuscator extends ServerTestSuiteNoDB {
" \"id\": \"card_483etw4er9fg4vF3sQdrt3FG\",\n" +
" \"object\": \"card\",\n" +
" \"banknumber\": ****************,\n" +
+ " \"cvv\" : ***,\n" +
+ " \"cvv\": ***,\n" +
+ " \"cvv\": *****,\n" +
+ " \"data\": {\"cvv\" : ****},\n" +
" \"last4\": \"0000\",\n" +
" \"brand\": \"Visa\",\n" +
" \"funding\": \"credit\",\n" +
profiles/killpay/pom.xml 2(+1 -1)
diff --git a/profiles/killpay/pom.xml b/profiles/killpay/pom.xml
index d6cd1b3..1b05a82 100644
--- a/profiles/killpay/pom.xml
+++ b/profiles/killpay/pom.xml
@@ -20,7 +20,7 @@
<parent>
<artifactId>killbill-profiles</artifactId>
<groupId>org.kill-bill.billing</groupId>
- <version>0.18.5-SNAPSHOT</version>
+ <version>0.18.7-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>killbill-profiles-killpay</artifactId>
profiles/pom.xml 2(+1 -1)
diff --git a/profiles/pom.xml b/profiles/pom.xml
index 1e1e897..1be89b4 100644
--- a/profiles/pom.xml
+++ b/profiles/pom.xml
@@ -19,7 +19,7 @@
<parent>
<artifactId>killbill</artifactId>
<groupId>org.kill-bill.billing</groupId>
- <version>0.18.5-SNAPSHOT</version>
+ <version>0.18.7-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>killbill-profiles</artifactId>
subscription/pom.xml 2(+1 -1)
diff --git a/subscription/pom.xml b/subscription/pom.xml
index c01bb38..f878deb 100644
--- a/subscription/pom.xml
+++ b/subscription/pom.xml
@@ -19,7 +19,7 @@
<parent>
<artifactId>killbill</artifactId>
<groupId>org.kill-bill.billing</groupId>
- <version>0.18.5-SNAPSHOT</version>
+ <version>0.18.7-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>killbill-subscription</artifactId>
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 ccc67eb..d3bdedf 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
@@ -23,6 +23,7 @@ import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
+import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
@@ -54,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;
@@ -65,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;
@@ -207,12 +205,15 @@ public class DefaultSubscriptionInternalApi extends SubscriptionApiBase implemen
private List<SubscriptionSpecifier> verifyAndBuildSubscriptionSpecifiers(final UUID bundleId, final String externalKey, final Iterable<EntitlementSpecifier> entitlements, final boolean isMigrated, final InternalCallContext context, final DateTime now, final DateTime effectiveDate, final Catalog catalog, final CallContext callContext) throws SubscriptionBaseApiException, CatalogApiException {
final List<SubscriptionSpecifier> subscriptions = new ArrayList<SubscriptionSpecifier>();
- boolean first = true;
final List<SubscriptionBase> subscriptionsForBundle = getSubscriptionsForBundle(bundleId, null, context);
for (final EntitlementSpecifier entitlement : entitlements) {
final PlanPhaseSpecifier spec = entitlement.getPlanPhaseSpecifier();
+ if (spec == null) {
+ // BP already exists
+ continue;
+ }
final PlanPhasePriceOverridesWithCallContext overridesWithContext = new DefaultPlanPhasePriceOverridesWithCallContext(entitlement.getOverrides(), callContext);
@@ -223,12 +224,6 @@ public class DefaultSubscriptionInternalApi extends SubscriptionApiBase implemen
spec.getProductName(), spec.getBillingPeriod().toString(), plan.getPriceListName()));
}
- if (first) {
- first = false;
- if (plan.getProduct().getCategory() != ProductCategory.BASE) {
- throw new SubscriptionBaseApiException(new IllegalArgumentException(), ErrorCode.SUB_CREATE_NO_BP.getCode(), "Missing Base Subscription.");
- }
- }
// verify the number of subscriptions (of the same kind) allowed per bundle and the existing ones
if (ProductCategory.ADD_ON.toString().equalsIgnoreCase(plan.getProduct().getCategory().toString())) {
@@ -262,6 +257,37 @@ public class DefaultSubscriptionInternalApi extends SubscriptionApiBase implemen
return subscriptions;
}
+ private boolean sanityAndReorderBPSpecFirst(final Catalog catalog, final BaseEntitlementWithAddOnsSpecifier entitlementWithAddOnsSpecifier, final DateTime effectiveDate, final List<EntitlementSpecifier> outputEntitlementSpecifier) throws SubscriptionBaseApiException {
+
+
+ EntitlementSpecifier basePlanSpecifier = null;
+ final List<EntitlementSpecifier> addOnSpecifiers = new ArrayList<EntitlementSpecifier>();
+ try {
+ for (final EntitlementSpecifier cur : entitlementWithAddOnsSpecifier.getEntitlementSpecifier()) {
+ final Plan inputPlan = catalog.createOrFindPlan(cur.getPlanPhaseSpecifier(), null, effectiveDate);
+ final boolean isBaseSpecifier = inputPlan.getProduct().getCategory() == ProductCategory.BASE;
+ if (isBaseSpecifier) {
+ if (basePlanSpecifier == null) {
+ basePlanSpecifier = cur;
+ } else {
+ throw new SubscriptionBaseApiException(ErrorCode.SUB_CREATE_INVALID_ENTITLEMENT_SPECIFIER);
+ }
+ } else {
+ addOnSpecifiers.add(cur);
+ }
+ }
+ } catch (final CatalogApiException e) {
+ throw new SubscriptionBaseApiException(e);
+ }
+
+ if (basePlanSpecifier != null) {
+ outputEntitlementSpecifier.add(basePlanSpecifier);
+ }
+ outputEntitlementSpecifier.addAll(addOnSpecifiers);
+ return basePlanSpecifier != null;
+ }
+
+
@Override
public List<SubscriptionBaseWithAddOns> createBaseSubscriptionsWithAddOns(final UUID accountId, final Iterable<BaseEntitlementWithAddOnsSpecifier> baseEntitlementWithAddOnsSpecifier, final InternalCallContext context) throws SubscriptionBaseApiException {
try {
@@ -274,14 +300,32 @@ public class DefaultSubscriptionInternalApi extends SubscriptionApiBase implemen
final DateTime effectiveDate = (entitlementWithAddOnsSpecifier.getBillingEffectiveDate() != null) ?
DefaultClock.truncateMs(entitlementWithAddOnsSpecifier.getBillingEffectiveDate().toDateTimeAtStartOfDay()) : now;
- final SubscriptionBaseBundle bundle = createBundleForAccount(accountId, entitlementWithAddOnsSpecifier.getExternalKey(), context);
+
+
+ final List<EntitlementSpecifier> reorderedSpecifiers = new ArrayList<EntitlementSpecifier>();
+ final boolean isBaseSpecifierExists = sanityAndReorderBPSpecFirst(catalog, entitlementWithAddOnsSpecifier, effectiveDate, reorderedSpecifiers);
+
+ final SubscriptionBaseBundle bundle;
+ if (isBaseSpecifierExists) {
+ bundle = createBundleForAccount(accountId, entitlementWithAddOnsSpecifier.getExternalKey(), context);
+ } else {
+ final List<SubscriptionBaseBundle> existingBundles = dao.getSubscriptionBundlesForKey(entitlementWithAddOnsSpecifier.getExternalKey(), context);
+ final SubscriptionBaseBundle tmp = getActiveBundleForKeyNotException(existingBundles, dao, clock, context);
+ if (tmp == null) {
+ throw new SubscriptionBaseApiException(ErrorCode.SUB_CREATE_NO_BP, entitlementWithAddOnsSpecifier.getExternalKey());
+ } else if (!tmp.getAccountId().equals(accountId)) {
+ throw new SubscriptionBaseApiException(ErrorCode.SUB_CREATE_ACTIVE_BUNDLE_KEY_EXISTS, entitlementWithAddOnsSpecifier.getExternalKey());
+ } else {
+ bundle = tmp;
+ }
+ }
final SubscriptionAndAddOnsSpecifier subscriptionAndAddOnsSpecifier = new SubscriptionAndAddOnsSpecifier(
bundle.getId(),
effectiveDate,
verifyAndBuildSubscriptionSpecifiers(bundle.getId(),
bundle.getExternalKey(),
- entitlementWithAddOnsSpecifier.getEntitlementSpecifier(),
+ reorderedSpecifiers,
entitlementWithAddOnsSpecifier.isMigrated(),
context,
now,
@@ -336,6 +380,7 @@ public class DefaultSubscriptionInternalApi extends SubscriptionApiBase implemen
context);
}
+
@Override
public SubscriptionBaseBundle createBundleForAccount(final UUID accountId, final String bundleKey, final InternalCallContext context) throws SubscriptionBaseApiException {
@@ -823,6 +868,7 @@ public class DefaultSubscriptionInternalApi extends SubscriptionApiBase implemen
return requestedDate == null ? clock.getUTCNow() : internalCallContext.toUTCDateTime(requestedDate);
}
+
private DateTime getBundleStartDateWithSanity(final UUID bundleId, @Nullable final DefaultSubscriptionBase baseSubscription, final Plan plan,
final DateTime effectiveDate, final InternalTenantContext context) throws SubscriptionBaseApiException, CatalogApiException {
switch (plan.getProduct().getCategory()) {
diff --git a/subscription/src/main/java/org/killbill/billing/subscription/api/user/DefaultSubscriptionBaseApiService.java b/subscription/src/main/java/org/killbill/billing/subscription/api/user/DefaultSubscriptionBaseApiService.java
index 77b73c9..f6dac4c 100644
--- a/subscription/src/main/java/org/killbill/billing/subscription/api/user/DefaultSubscriptionBaseApiService.java
+++ b/subscription/src/main/java/org/killbill/billing/subscription/api/user/DefaultSubscriptionBaseApiService.java
@@ -143,11 +143,17 @@ public class DefaultSubscriptionBaseApiService implements SubscriptionBaseApiSer
final InternalCallContext internalCallContext = createCallContextFromAccountId(accountId, context);
dao.createSubscriptionsWithAddOns(allSubscriptions, eventsMap, internalCallContext);
- for (final List<SubscriptionBase> subscriptions : subscriptionBaseAndAddOnsList) {
- final SubscriptionBase baseSubscription = findBaseSubscription(subscriptions);
- rebuildTransitions(internalCallContext, subscriptions, baseSubscription);
+ try {
+ for (final List<SubscriptionBase> subscriptions : subscriptionBaseAndAddOnsList) {
+ for (final SubscriptionBase input : subscriptions) {
+ ((DefaultSubscriptionBase) input).rebuildTransitions(dao.getEventsForSubscription(input.getId(), internalCallContext),
+ catalogService.getFullCatalog(true, true, internalCallContext));
+ }
+ }
+ return allSubscriptions;
+ } catch (final CatalogApiException e) {
+ throw new SubscriptionBaseApiException(e);
}
- return allSubscriptions;
}
private void createEvents(final Iterable<SubscriptionSpecifier> subscriptions, final CallContext context, final Map<UUID, List<SubscriptionBaseEvent>> eventsMap, final Collection<SubscriptionBase> subscriptionBaseList) throws SubscriptionBaseApiException {
@@ -167,35 +173,6 @@ public class DefaultSubscriptionBaseApiService implements SubscriptionBaseApiSer
}
}
- private void rebuildTransitions(final InternalCallContext internalCallContext, final Iterable<SubscriptionBase> subscriptions, final SubscriptionBase baseSubscription) throws SubscriptionBaseApiException {
- try {
- // Safe cast
- ((DefaultSubscriptionBase) baseSubscription).rebuildTransitions(dao.getEventsForSubscription(baseSubscription.getId(), internalCallContext),
- catalogService.getFullCatalog(true, true, internalCallContext));
-
- for (final SubscriptionBase input : subscriptions) {
- if (input.getId().equals(baseSubscription.getId())) {
- continue;
- }
-
- // Safe cast
- ((DefaultSubscriptionBase) input).rebuildTransitions(dao.getEventsForSubscription(input.getId(), internalCallContext),
- catalogService.getFullCatalog(true, true, internalCallContext));
- }
- } catch (final CatalogApiException e) {
- throw new SubscriptionBaseApiException(e);
- }
- }
-
- private SubscriptionBase findBaseSubscription(final Iterable<SubscriptionBase> subscriptionBaseList) {
- return Iterables.tryFind(subscriptionBaseList, new Predicate<SubscriptionBase>() {
- @Override
- public boolean apply(final SubscriptionBase subscription) {
- return ProductCategory.BASE.equals(subscription.getCategory());
- }
- }).orNull();
- }
-
@Override
public boolean cancel(final DefaultSubscriptionBase subscription, final CallContext context) throws SubscriptionBaseApiException {
final EntitlementState currentState = subscription.getState();
diff --git a/subscription/src/main/java/org/killbill/billing/subscription/engine/dao/DefaultSubscriptionDao.java b/subscription/src/main/java/org/killbill/billing/subscription/engine/dao/DefaultSubscriptionDao.java
index af140fb..332d497 100644
--- a/subscription/src/main/java/org/killbill/billing/subscription/engine/dao/DefaultSubscriptionDao.java
+++ b/subscription/src/main/java/org/killbill/billing/subscription/engine/dao/DefaultSubscriptionDao.java
@@ -1,7 +1,7 @@
/*
* Copyright 2010-2013 Ning, Inc.
- * Copyright 2014-2016 Groupon, Inc
- * Copyright 2014-2016 The Billing Project, LLC
+ * 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
@@ -258,10 +258,9 @@ public class DefaultSubscriptionDao extends EntityDaoBase<SubscriptionBundleMode
return transactionalSqlDao.execute(new EntitySqlDaoTransactionWrapper<SubscriptionBaseBundle>() {
@Override
public SubscriptionBaseBundle inTransaction(final EntitySqlDaoWrapperFactory entitySqlDaoWrapperFactory) throws EntityPersistenceException {
-
final SubscriptionBundleModelDao model = new SubscriptionBundleModelDao(bundle);
- entitySqlDaoWrapperFactory.become(BundleSqlDao.class).create(model, context);
- final SubscriptionBundleModelDao result = entitySqlDaoWrapperFactory.become(BundleSqlDao.class).getById(bundle.getId().toString(), context);
+ final BundleSqlDao bundleSqlDao = entitySqlDaoWrapperFactory.become(BundleSqlDao.class);
+ final SubscriptionBundleModelDao result = createAndRefresh(bundleSqlDao, model, context);
return SubscriptionBundleModelDao.toSubscriptionbundle(result);
}
});
@@ -425,7 +424,7 @@ public class DefaultSubscriptionDao extends EntityDaoBase<SubscriptionBundleMode
final SubscriptionEventSqlDao transactional = entitySqlDaoWrapperFactory.become(SubscriptionEventSqlDao.class);
final UUID subscriptionId = subscription.getId();
cancelNextPhaseEventFromTransaction(subscriptionId, entitySqlDaoWrapperFactory, context);
- transactional.create(new SubscriptionEventModelDao(nextPhaseEvent), context);
+ createAndRefresh(transactional, new SubscriptionEventModelDao(nextPhaseEvent), context);
recordFutureNotificationFromTransaction(entitySqlDaoWrapperFactory,
nextPhaseEvent.getEffectiveDate(),
new SubscriptionNotificationKey(nextPhaseEvent.getId()), context);
@@ -503,11 +502,11 @@ public class DefaultSubscriptionDao extends EntityDaoBase<SubscriptionBundleMode
@Override
public Void inTransaction(final EntitySqlDaoWrapperFactory entitySqlDaoWrapperFactory) throws Exception {
final SubscriptionSqlDao transactional = entitySqlDaoWrapperFactory.become(SubscriptionSqlDao.class);
- transactional.create(new SubscriptionModelDao(subscription), context);
+ createAndRefresh(transactional, new SubscriptionModelDao(subscription), context);
final SubscriptionEventSqlDao eventsDaoFromSameTransaction = entitySqlDaoWrapperFactory.become(SubscriptionEventSqlDao.class);
for (final SubscriptionBaseEvent cur : initialEvents) {
- eventsDaoFromSameTransaction.create(new SubscriptionEventModelDao(cur), context);
+ createAndRefresh(eventsDaoFromSameTransaction, new SubscriptionEventModelDao(cur), context);
final boolean isBusEvent = cur.getEffectiveDate().compareTo(clock.getUTCNow()) <= 0 && (cur.getType() == EventType.API_USER);
recordBusOrFutureNotificationFromTransaction(subscription, cur, entitySqlDaoWrapperFactory, isBusEvent, 0, context);
@@ -534,12 +533,11 @@ public class DefaultSubscriptionDao extends EntityDaoBase<SubscriptionBundleMode
for (final SubscriptionBase subscriptionBase : subscription.getSubscriptionBaseList()) {
// Safe cast
final DefaultSubscriptionBase defaultSubscriptionBase = (DefaultSubscriptionBase) subscriptionBase;
-
- transactional.create(new SubscriptionModelDao(defaultSubscriptionBase), context);
+ createAndRefresh(transactional, new SubscriptionModelDao(defaultSubscriptionBase), context);
final List<SubscriptionBaseEvent> initialEvents = initialEventsMap.get(defaultSubscriptionBase.getId());
for (final SubscriptionBaseEvent cur : initialEvents) {
- eventsDaoFromSameTransaction.create(new SubscriptionEventModelDao(cur), context);
+ createAndRefresh(eventsDaoFromSameTransaction, new SubscriptionEventModelDao(cur), context);
final boolean isBusEvent = cur.getEffectiveDate().compareTo(clock.getUTCNow()) <= 0 && (cur.getType() == EventType.API_USER);
recordBusOrFutureNotificationFromTransaction(defaultSubscriptionBase, cur, entitySqlDaoWrapperFactory, isBusEvent, 0, context);
@@ -654,7 +652,7 @@ public class DefaultSubscriptionDao extends EntityDaoBase<SubscriptionBundleMode
cancelFutureEventsFromTransaction(subscriptionId, changeEvents.get(0).getEffectiveDate(), entitySqlDaoWrapperFactory, false, context);
for (final SubscriptionBaseEvent cur : changeEvents) {
- transactional.create(new SubscriptionEventModelDao(cur), context);
+ createAndRefresh(transactional, new SubscriptionEventModelDao(cur), context);
final boolean isBusEvent = cur.getEffectiveDate().compareTo(clock.getUTCNow()) <= 0 && (cur.getType() == EventType.API_USER);
recordBusOrFutureNotificationFromTransaction(subscription, cur, entitySqlDaoWrapperFactory, isBusEvent, 0, context);
@@ -701,7 +699,8 @@ public class DefaultSubscriptionDao extends EntityDaoBase<SubscriptionBundleMode
throws EntityPersistenceException {
final UUID subscriptionId = subscription.getId();
cancelFutureEventsFromTransaction(subscriptionId, cancelEvent.getEffectiveDate(), entitySqlDaoWrapperFactory, true, context);
- entitySqlDaoWrapperFactory.become(SubscriptionEventSqlDao.class).create(new SubscriptionEventModelDao(cancelEvent), context);
+ final SubscriptionEventSqlDao subscriptionEventSqlDao = entitySqlDaoWrapperFactory.become(SubscriptionEventSqlDao.class);
+ createAndRefresh(subscriptionEventSqlDao, new SubscriptionEventModelDao(cancelEvent), context);
final boolean isBusEvent = cancelEvent.getEffectiveDate().compareTo(clock.getUTCNow()) <= 0;
recordBusOrFutureNotificationFromTransaction(subscription, cancelEvent, entitySqlDaoWrapperFactory, isBusEvent, seqId, context);
@@ -957,7 +956,7 @@ public class DefaultSubscriptionDao extends EntityDaoBase<SubscriptionBundleMode
@Override
public Void inTransaction(final EntitySqlDaoWrapperFactory entitySqlDaoWrapperFactory) throws Exception {
final SubscriptionEventSqlDao transactional = entitySqlDaoWrapperFactory.become(SubscriptionEventSqlDao.class);
- transactional.create(new SubscriptionEventModelDao(bcdEvent), context);
+ createAndRefresh(transactional, new SubscriptionEventModelDao(bcdEvent), context);
// Notify the Bus
notifyBusOfRequestedChange(entitySqlDaoWrapperFactory, subscription, bcdEvent, SubscriptionBaseTransitionType.BCD_CHANGE, context);
@@ -1072,20 +1071,20 @@ public class DefaultSubscriptionDao extends EntityDaoBase<SubscriptionBundleMode
for (final SubscriptionTransferData curSubscription : bundleTransferData.getSubscriptions()) {
final DefaultSubscriptionBase subData = curSubscription.getData();
for (final SubscriptionBaseEvent curEvent : curSubscription.getInitialEvents()) {
- transactional.create(new SubscriptionEventModelDao(curEvent), context);
+ createAndRefresh(transactional, new SubscriptionEventModelDao(curEvent), context);
recordFutureNotificationFromTransaction(entitySqlDaoWrapperFactory,
curEvent.getEffectiveDate(),
new SubscriptionNotificationKey(curEvent.getId()),
context);
}
- transSubDao.create(new SubscriptionModelDao(subData), context);
+ createAndRefresh(transSubDao, new SubscriptionModelDao(subData), context);
// Notify the Bus of the latest requested change
final SubscriptionBaseEvent finalEvent = curSubscription.getInitialEvents().get(curSubscription.getInitialEvents().size() - 1);
notifyBusOfRequestedChange(entitySqlDaoWrapperFactory, subData, finalEvent, SubscriptionBaseTransitionType.TRANSFER, context);
}
- transBundleDao.create(new SubscriptionBundleModelDao(bundleData), context);
+ createAndRefresh(transBundleDao, new SubscriptionBundleModelDao(bundleData), context);
}
//
diff --git a/subscription/src/test/java/org/killbill/billing/subscription/api/user/TestUserApiError.java b/subscription/src/test/java/org/killbill/billing/subscription/api/user/TestUserApiError.java
index cd663dd..968376a 100644
--- a/subscription/src/test/java/org/killbill/billing/subscription/api/user/TestUserApiError.java
+++ b/subscription/src/test/java/org/killbill/billing/subscription/api/user/TestUserApiError.java
@@ -1,7 +1,7 @@
/*
* Copyright 2010-2013 Ning, Inc.
- * Copyright 2014-2015 Groupon, Inc
- * Copyright 2014-2015 The Billing Project, LLC
+ * 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
@@ -77,6 +77,9 @@ public class TestUserApiError extends SubscriptionTestSuiteNoDB {
@Test(groups = "fast")
public void testCreateSubscriptionAddOnNotAvailable() throws SubscriptionBaseApiException {
final SubscriptionBaseBundle aoBundle = subscriptionInternalApi.createBundleForAccount(bundle.getAccountId(), "myAOBundle", internalCallContext);
+ mockNonEntityDao.addTenantRecordIdMapping(aoBundle.getId(), internalCallContext);
+ mockNonEntityDao.addAccountRecordIdMapping(aoBundle.getId(), internalCallContext);
+
testUtil.createSubscriptionWithBundle(aoBundle.getId(), "Pistol", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, null);
tCreateSubscriptionInternal(aoBundle.getId(), "Telescopic-Scope", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, ErrorCode.SUB_CREATE_AO_NOT_AVAILABLE);
}
@@ -84,6 +87,9 @@ public class TestUserApiError extends SubscriptionTestSuiteNoDB {
@Test(groups = "fast")
public void testCreateSubscriptionAddOnIncluded() throws SubscriptionBaseApiException {
final SubscriptionBaseBundle aoBundle = subscriptionInternalApi.createBundleForAccount(bundle.getAccountId(), "myAOBundle", internalCallContext);
+ mockNonEntityDao.addTenantRecordIdMapping(aoBundle.getId(), internalCallContext);
+ mockNonEntityDao.addAccountRecordIdMapping(aoBundle.getId(), internalCallContext);
+
testUtil.createSubscriptionWithBundle(aoBundle.getId(), "Assault-Rifle", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, null);
tCreateSubscriptionInternal(aoBundle.getId(), "Telescopic-Scope", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, ErrorCode.SUB_CREATE_AO_ALREADY_INCLUDED);
}
diff --git a/subscription/src/test/java/org/killbill/billing/subscription/SubscriptionTestSuiteNoDB.java b/subscription/src/test/java/org/killbill/billing/subscription/SubscriptionTestSuiteNoDB.java
index 0ace9f9..b90587c 100644
--- a/subscription/src/test/java/org/killbill/billing/subscription/SubscriptionTestSuiteNoDB.java
+++ b/subscription/src/test/java/org/killbill/billing/subscription/SubscriptionTestSuiteNoDB.java
@@ -1,7 +1,7 @@
/*
* Copyright 2010-2013 Ning, Inc.
- * Copyright 2014-2016 Groupon, Inc
- * Copyright 2014-2016 The Billing Project, LLC
+ * 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
@@ -24,7 +24,10 @@ import javax.inject.Inject;
import org.killbill.billing.GuicyKillbillTestSuiteNoDB;
import org.killbill.billing.account.api.AccountData;
+import org.killbill.billing.account.api.ImmutableAccountData;
+import org.killbill.billing.account.api.ImmutableAccountInternalApi;
import org.killbill.billing.api.TestApiListener;
+import org.killbill.billing.callcontext.InternalTenantContext;
import org.killbill.billing.catalog.api.Catalog;
import org.killbill.billing.catalog.api.CatalogService;
import org.killbill.billing.dao.MockNonEntityDao;
@@ -62,6 +65,8 @@ public class SubscriptionTestSuiteNoDB extends GuicyKillbillTestSuiteNoDB {
protected static final Logger log = LoggerFactory.getLogger(SubscriptionTestSuiteNoDB.class);
@Inject
+ protected ImmutableAccountInternalApi immutableAccountInternalApi;
+ @Inject
protected SubscriptionBaseService subscriptionBaseService;
@Inject
protected SubscriptionBaseInternalApi subscriptionInternalApi;
@@ -132,7 +137,13 @@ public class SubscriptionTestSuiteNoDB extends GuicyKillbillTestSuiteNoDB {
this.accountData = subscriptionTestInitializer.initAccountData();
final UUID accountId = UUIDs.randomUUID();
mockNonEntityDao.addTenantRecordIdMapping(accountId, internalCallContext);
+
+ final ImmutableAccountData immutableAccountData = Mockito.mock(ImmutableAccountData.class);
+ Mockito.when(immutableAccountInternalApi.getImmutableAccountDataByRecordId(Mockito.<Long>eq(internalCallContext.getAccountRecordId()), Mockito.<InternalTenantContext>any())).thenReturn(immutableAccountData);
+
this.bundle = subscriptionTestInitializer.initBundle(accountId, subscriptionInternalApi, internalCallContext);
+ mockNonEntityDao.addTenantRecordIdMapping(bundle.getId(), internalCallContext);
+ mockNonEntityDao.addAccountRecordIdMapping(bundle.getId(), internalCallContext);
}
@AfterMethod(groups = "fast")
tenant/pom.xml 2(+1 -1)
diff --git a/tenant/pom.xml b/tenant/pom.xml
index b7db50f..c866d9f 100644
--- a/tenant/pom.xml
+++ b/tenant/pom.xml
@@ -19,7 +19,7 @@
<parent>
<artifactId>killbill</artifactId>
<groupId>org.kill-bill.billing</groupId>
- <version>0.18.5-SNAPSHOT</version>
+ <version>0.18.7-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>killbill-tenant</artifactId>
diff --git a/tenant/src/main/java/org/killbill/billing/tenant/api/DefaultTenant.java b/tenant/src/main/java/org/killbill/billing/tenant/api/DefaultTenant.java
index a7f56b6..dbda74f 100644
--- a/tenant/src/main/java/org/killbill/billing/tenant/api/DefaultTenant.java
+++ b/tenant/src/main/java/org/killbill/billing/tenant/api/DefaultTenant.java
@@ -16,22 +16,35 @@
package org.killbill.billing.tenant.api;
+import java.io.Externalizable;
+import java.io.IOException;
+import java.io.ObjectInput;
+import java.io.ObjectOutput;
import java.util.UUID;
import javax.annotation.Nullable;
import org.joda.time.DateTime;
-
import org.killbill.billing.tenant.dao.TenantModelDao;
-import org.killbill.billing.entity.EntityBase;
import org.killbill.billing.util.UUIDs;
+import org.killbill.billing.util.cache.ExternalizableInput;
+import org.killbill.billing.util.cache.ExternalizableOutput;
+import org.killbill.billing.util.cache.MapperHolder;
+
+public class DefaultTenant implements Tenant, Externalizable {
-public class DefaultTenant extends EntityBase implements Tenant {
+ private static final long serialVersionUID = -6662488328218280007L;
- private final String externalKey;
- private final String apiKey;
+ private UUID id;
+ private DateTime createdDate;
+ private DateTime updatedDate;
+ private String externalKey;
+ private String apiKey;
// Decrypted (clear) secret
- private final String apiSecret;
+ private transient String apiSecret;
+
+ // For deserialization
+ public DefaultTenant() {}
/**
* This call is used to create a tenant
@@ -54,7 +67,9 @@ public class DefaultTenant extends EntityBase implements Tenant {
public DefaultTenant(final UUID id, @Nullable final DateTime createdDate, @Nullable final DateTime updatedDate,
final String externalKey, final String apiKey, final String apiSecret) {
- super(id, createdDate, updatedDate);
+ this.id = id;
+ this.createdDate = createdDate;
+ this.updatedDate = updatedDate;
this.externalKey = externalKey;
this.apiKey = apiKey;
this.apiSecret = apiSecret;
@@ -66,6 +81,21 @@ public class DefaultTenant extends EntityBase implements Tenant {
}
@Override
+ public UUID getId() {
+ return id;
+ }
+
+ @Override
+ public DateTime getCreatedDate() {
+ return createdDate;
+ }
+
+ @Override
+ public DateTime getUpdatedDate() {
+ return updatedDate;
+ }
+
+ @Override
public String getExternalKey() {
return externalKey;
}
@@ -82,9 +112,11 @@ public class DefaultTenant extends EntityBase implements Tenant {
@Override
public String toString() {
- final StringBuilder sb = new StringBuilder();
- sb.append("DefaultTenant");
- sb.append("{externalKey='").append(externalKey).append('\'');
+ final StringBuilder sb = new StringBuilder("DefaultTenant{");
+ sb.append("id=").append(id);
+ sb.append(", createdDate=").append(createdDate);
+ sb.append(", updatedDate=").append(updatedDate);
+ sb.append(", externalKey='").append(externalKey).append('\'');
sb.append(", apiKey='").append(apiKey).append('\'');
// Don't print the secret
sb.append('}');
@@ -102,24 +134,42 @@ public class DefaultTenant extends EntityBase implements Tenant {
final DefaultTenant that = (DefaultTenant) o;
- if (apiKey != null ? !apiKey.equals(that.apiKey) : that.apiKey != null) {
+ if (id != null ? !id.equals(that.id) : that.id != null) {
+ return false;
+ }
+ if (createdDate != null ? !createdDate.equals(that.createdDate) : that.createdDate != null) {
+ return false;
+ }
+ if (updatedDate != null ? !updatedDate.equals(that.updatedDate) : that.updatedDate != null) {
return false;
}
if (externalKey != null ? !externalKey.equals(that.externalKey) : that.externalKey != null) {
return false;
}
- if (apiSecret != null ? !apiSecret.equals(that.apiSecret) : that.apiSecret != null) {
+ if (apiKey != null ? !apiKey.equals(that.apiKey) : that.apiKey != null) {
return false;
}
-
- return true;
+ return apiSecret != null ? apiSecret.equals(that.apiSecret) : that.apiSecret == null;
}
@Override
public int hashCode() {
- int result = externalKey != null ? externalKey.hashCode() : 0;
+ int result = id != null ? id.hashCode() : 0;
+ result = 31 * result + (createdDate != null ? createdDate.hashCode() : 0);
+ result = 31 * result + (updatedDate != null ? updatedDate.hashCode() : 0);
+ result = 31 * result + (externalKey != null ? externalKey.hashCode() : 0);
result = 31 * result + (apiKey != null ? apiKey.hashCode() : 0);
result = 31 * result + (apiSecret != null ? apiSecret.hashCode() : 0);
return result;
}
+
+ @Override
+ public void readExternal(final ObjectInput in) throws IOException {
+ MapperHolder.mapper().readerForUpdating(this).readValue(new ExternalizableInput(in));
+ }
+
+ @Override
+ public void writeExternal(final ObjectOutput oo) throws IOException {
+ MapperHolder.mapper().writeValue(new ExternalizableOutput(oo), this);
+ }
}
diff --git a/tenant/src/main/java/org/killbill/billing/tenant/api/TenantCacheInvalidationCallback.java b/tenant/src/main/java/org/killbill/billing/tenant/api/TenantCacheInvalidationCallback.java
index f07122f..ffc662c 100644
--- a/tenant/src/main/java/org/killbill/billing/tenant/api/TenantCacheInvalidationCallback.java
+++ b/tenant/src/main/java/org/killbill/billing/tenant/api/TenantCacheInvalidationCallback.java
@@ -1,6 +1,6 @@
/*
- * Copyright 2014-2015 Groupon, Inc
- * Copyright 2014-2015 The Billing Project, LLC
+ * 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
@@ -32,7 +32,7 @@ public class TenantCacheInvalidationCallback implements CacheInvalidationCallbac
private final Logger log = LoggerFactory.getLogger(TenantCacheInvalidationCallback.class);
- private final CacheController<Object, Object> tenantKVCache;
+ private final CacheController<String, String> tenantKVCache;
@Inject
public TenantCacheInvalidationCallback(final CacheControllerDispatcher cacheControllerDispatcher) {
diff --git a/tenant/src/main/java/org/killbill/billing/tenant/api/user/DefaultTenantUserApi.java b/tenant/src/main/java/org/killbill/billing/tenant/api/user/DefaultTenantUserApi.java
index b9a22bc..eb3f5ba 100644
--- a/tenant/src/main/java/org/killbill/billing/tenant/api/user/DefaultTenantUserApi.java
+++ b/tenant/src/main/java/org/killbill/billing/tenant/api/user/DefaultTenantUserApi.java
@@ -1,7 +1,7 @@
/*
* Copyright 2010-2013 Ning, Inc.
- * Copyright 2014-2015 Groupon, Inc
- * Copyright 2014-2015 The Billing Project, LLC
+ * 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
@@ -71,8 +71,8 @@ public class DefaultTenantUserApi implements TenantUserApi {
private final TenantDao tenantDao;
private final InternalCallContextFactory internalCallContextFactory;
- private final CacheController<Object, Object> tenantKVCache;
- private final CacheController<Object, Object> tenantCache;
+ private final CacheController<String, String> tenantKVCache;
+ private final CacheController<String, Tenant> tenantCache;
@Inject
@@ -101,7 +101,7 @@ public class DefaultTenantUserApi implements TenantUserApi {
@Override
public Tenant getTenantByApiKey(final String key) throws TenantApiException {
- final Tenant tenant = (Tenant) tenantCache.get(key, new CacheLoaderArgument(ObjectType.TENANT));
+ final Tenant tenant = tenantCache.get(key, new CacheLoaderArgument(ObjectType.TENANT));
if (tenant == null) {
throw new TenantApiException(ErrorCode.TENANT_DOES_NOT_EXIST_FOR_API_KEY, key);
}
diff --git a/tenant/src/main/java/org/killbill/billing/tenant/dao/DefaultTenantDao.java b/tenant/src/main/java/org/killbill/billing/tenant/dao/DefaultTenantDao.java
index 81477ce..18f2238 100644
--- a/tenant/src/main/java/org/killbill/billing/tenant/dao/DefaultTenantDao.java
+++ b/tenant/src/main/java/org/killbill/billing/tenant/dao/DefaultTenantDao.java
@@ -1,7 +1,7 @@
/*
* Copyright 2010-2013 Ning, Inc.
- * Copyright 2014-2016 Groupon, Inc
- * Copyright 2014-2016 The Billing Project, LLC
+ * 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
@@ -98,7 +98,8 @@ public class DefaultTenantDao extends EntityDaoBase<TenantModelDao, Tenant, Tena
final TenantModelDao tenantModelDaoWithSecret = new TenantModelDao(entity.getId(), context.getCreatedDate(), context.getUpdatedDate(),
entity.getExternalKey(), entity.getApiKey(),
hashedPasswordBase64, salt.toBase64());
- entitySqlDaoWrapperFactory.become(TenantSqlDao.class).create(tenantModelDaoWithSecret, context);
+ final TenantSqlDao tenantSqlDao = entitySqlDaoWrapperFactory.become(TenantSqlDao.class);
+ createAndRefresh(tenantSqlDao, tenantModelDaoWithSecret, context);
return null;
}
});
@@ -145,8 +146,7 @@ public class DefaultTenantDao extends EntityDaoBase<TenantModelDao, Tenant, Tena
if (uniqueKey) {
deleteFromTransaction(key, entitySqlDaoWrapperFactory, context);
}
- tenantKVSqlDao.create(tenantKVModelDao, context);
- final TenantKVModelDao rehydrated = tenantKVSqlDao.getById(tenantKVModelDao.getId().toString(), context);
+ final TenantKVModelDao rehydrated = createAndRefresh(tenantKVSqlDao, tenantKVModelDao, context);
broadcastConfigurationChangeFromTransaction(rehydrated.getRecordId(), key, entitySqlDaoWrapperFactory, context);
return null;
}
@@ -164,14 +164,14 @@ public class DefaultTenantDao extends EntityDaoBase<TenantModelDao, Tenant, Tena
// Retrieve all values for key ordered with recordId (last at the end)
final List<TenantKVModelDao> tenantKV = tenantKVSqlDao.getTenantValueForKey(key, context);
final String id;
+ final TenantKVModelDao rehydrated;
if (!tenantKV.isEmpty()) {
id = tenantKV.get(tenantKV.size() - 1).getId().toString();
- tenantKVSqlDao.updateTenantValueKey(id, value, context);
+ rehydrated = (TenantKVModelDao) tenantKVSqlDao.updateTenantValueKey(id, value, context);
} else {
id = tenantKVModelDao.getId().toString();
- tenantKVSqlDao.create(tenantKVModelDao, context);
+ rehydrated = createAndRefresh(tenantKVSqlDao, tenantKVModelDao, context);
}
- final TenantKVModelDao rehydrated = tenantKVSqlDao.getById(id, context);
broadcastConfigurationChangeFromTransaction(rehydrated.getRecordId(), key, entitySqlDaoWrapperFactory, context);
return null;
}
@@ -225,7 +225,8 @@ public class DefaultTenantDao extends EntityDaoBase<TenantModelDao, Tenant, Tena
final InternalCallContext context) throws EntityPersistenceException {
if (isSystemKey(key)) {
final TenantBroadcastModelDao broadcast = new TenantBroadcastModelDao(kvRecordId, key, context.getUserToken());
- entitySqlDaoWrapperFactory.become(TenantBroadcastSqlDao.class).create(broadcast, context);
+ final TenantBroadcastSqlDao tenantBroadcastSqlDao = entitySqlDaoWrapperFactory.become(TenantBroadcastSqlDao.class);
+ createAndRefresh(tenantBroadcastSqlDao, broadcast, context);
}
}
diff --git a/tenant/src/main/java/org/killbill/billing/tenant/dao/TenantKVSqlDao.java b/tenant/src/main/java/org/killbill/billing/tenant/dao/TenantKVSqlDao.java
index fd523cb..c0cac34 100644
--- a/tenant/src/main/java/org/killbill/billing/tenant/dao/TenantKVSqlDao.java
+++ b/tenant/src/main/java/org/killbill/billing/tenant/dao/TenantKVSqlDao.java
@@ -49,9 +49,7 @@ public interface TenantKVSqlDao extends EntitySqlDao<TenantKVModelDao, TenantKV>
@SqlUpdate
@Audited(ChangeType.UPDATE)
- public void updateTenantValueKey(@Bind("id") final String id,
- @Bind("tenantValue") final String tenantValue,
- @BindBean final InternalCallContext context);
-
-
+ public Object updateTenantValueKey(@Bind("id") final String id,
+ @Bind("tenantValue") final String tenantValue,
+ @BindBean final InternalCallContext context);
}
usage/pom.xml 2(+1 -1)
diff --git a/usage/pom.xml b/usage/pom.xml
index 345bc3c..d500534 100644
--- a/usage/pom.xml
+++ b/usage/pom.xml
@@ -19,7 +19,7 @@
<parent>
<artifactId>killbill</artifactId>
<groupId>org.kill-bill.billing</groupId>
- <version>0.18.5-SNAPSHOT</version>
+ <version>0.18.7-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>killbill-usage</artifactId>
util/pom.xml 31(+22 -9)
diff --git a/util/pom.xml b/util/pom.xml
index 7743895..9d9fb0a 100644
--- a/util/pom.xml
+++ b/util/pom.xml
@@ -21,7 +21,7 @@
<parent>
<artifactId>killbill</artifactId>
<groupId>org.kill-bill.billing</groupId>
- <version>0.18.5-SNAPSHOT</version>
+ <version>0.18.7-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>killbill-util</artifactId>
@@ -50,6 +50,10 @@
<artifactId>jackson-dataformat-csv</artifactId>
</dependency>
<dependency>
+ <groupId>com.fasterxml.jackson.dataformat</groupId>
+ <artifactId>jackson-dataformat-smile</artifactId>
+ </dependency>
+ <dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-joda</artifactId>
</dependency>
@@ -104,7 +108,11 @@
</dependency>
<dependency>
<groupId>io.dropwizard.metrics</groupId>
- <artifactId>metrics-ehcache</artifactId>
+ <artifactId>metrics-jcache</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>javax.cache</groupId>
+ <artifactId>cache-api</artifactId>
</dependency>
<dependency>
<groupId>javax.inject</groupId>
@@ -126,10 +134,6 @@
<scope>test</scope>
</dependency>
<dependency>
- <groupId>net.sf.ehcache</groupId>
- <artifactId>ehcache</artifactId>
- </dependency>
- <dependency>
<groupId>org.antlr</groupId>
<artifactId>stringtemplate</artifactId>
</dependency>
@@ -143,11 +147,20 @@
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
- <artifactId>shiro-ehcache</artifactId>
+ <artifactId>shiro-guice</artifactId>
</dependency>
<dependency>
- <groupId>org.apache.shiro</groupId>
- <artifactId>shiro-guice</artifactId>
+ <groupId>org.ehcache</groupId>
+ <artifactId>ehcache</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.ehcache</groupId>
+ <artifactId>ehcache-clustered</artifactId>
+ <scope>runtime</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.ehcache.integrations.shiro</groupId>
+ <artifactId>shiro-ehcache3</artifactId>
</dependency>
<dependency>
<groupId>org.flywaydb</groupId>
diff --git a/util/src/main/java/org/killbill/billing/util/account/AccountDateTimeUtils.java b/util/src/main/java/org/killbill/billing/util/account/AccountDateTimeUtils.java
index 6ab4322..58f9157 100644
--- a/util/src/main/java/org/killbill/billing/util/account/AccountDateTimeUtils.java
+++ b/util/src/main/java/org/killbill/billing/util/account/AccountDateTimeUtils.java
@@ -1,6 +1,6 @@
/*
- * Copyright 2016 Groupon, Inc
- * Copyright 2016 The Billing Project, LLC
+ * Copyright 2016-2017 Groupon, Inc
+ * Copyright 2016-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
@@ -20,11 +20,20 @@ package org.killbill.billing.util.account;
import org.joda.time.DateTime;
import org.joda.time.DateTimeZone;
import org.killbill.billing.account.api.Account;
+import org.killbill.billing.util.entity.Entity;
+import org.killbill.billing.util.entity.dao.TimeZoneAwareEntity;
public abstract class AccountDateTimeUtils {
+ public static DateTimeZone getFixedOffsetTimeZone(final TimeZoneAwareEntity account) {
+ return getFixedOffsetTimeZone(account.getTimeZone(), account);
+ }
+
public static DateTimeZone getFixedOffsetTimeZone(final Account account) {
- final DateTimeZone referenceDateTimeZone = account.getTimeZone();
+ return getFixedOffsetTimeZone(account.getTimeZone(), account);
+ }
+
+ public static DateTimeZone getFixedOffsetTimeZone(final DateTimeZone referenceDateTimeZone, final Entity account) {
final DateTime referenceDateTime = getReferenceDateTime(account);
// Check if DST was in effect at the reference date time
@@ -36,7 +45,7 @@ public abstract class AccountDateTimeUtils {
}
}
- public static DateTime getReferenceDateTime(final Account account) {
+ public static DateTime getReferenceDateTime(final Entity account) {
return account.getCreatedDate();
}
}
diff --git a/util/src/main/java/org/killbill/billing/util/audit/dao/AuditLogModelDao.java b/util/src/main/java/org/killbill/billing/util/audit/dao/AuditLogModelDao.java
index 7ce819b..0c67fb0 100644
--- a/util/src/main/java/org/killbill/billing/util/audit/dao/AuditLogModelDao.java
+++ b/util/src/main/java/org/killbill/billing/util/audit/dao/AuditLogModelDao.java
@@ -1,7 +1,9 @@
/*
* Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014-2017 Groupon, Inc
+ * Copyright 2014-2017 The Billing Project, LLC
*
- * Ning licenses this file to you under the Apache License, version 2.0
+ * 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:
*
@@ -16,26 +18,98 @@
package org.killbill.billing.util.audit.dao;
+import java.io.Externalizable;
+import java.io.IOException;
+import java.io.ObjectInput;
+import java.io.ObjectOutput;
+import java.util.UUID;
+
+import org.joda.time.DateTime;
import org.killbill.billing.util.audit.AuditLog;
+import org.killbill.billing.util.audit.ChangeType;
+import org.killbill.billing.util.cache.ExternalizableInput;
+import org.killbill.billing.util.cache.ExternalizableOutput;
+import org.killbill.billing.util.cache.MapperHolder;
import org.killbill.billing.util.callcontext.CallContext;
import org.killbill.billing.util.dao.EntityAudit;
import org.killbill.billing.util.dao.TableName;
import org.killbill.billing.util.entity.dao.EntityModelDao;
-public class AuditLogModelDao extends EntityAudit implements EntityModelDao<AuditLog> {
+public class AuditLogModelDao implements EntityModelDao<AuditLog>, Externalizable {
+
+ private UUID id;
+ private DateTime createdDate;
+ private DateTime updatedDate;
+ private TableName tableName;
+ private Long targetRecordId;
+ private ChangeType changeType;
+ private CallContext callContext;
- private final CallContext callContext;
+ private Long recordId;
+ private Long accountRecordId;
+ private Long tenantRecordId;
+
+ // For deserialization
+ public AuditLogModelDao() {}
public AuditLogModelDao(final EntityAudit entityAudit, final CallContext callContext) {
- super(entityAudit.getId(), entityAudit.getTableName(), entityAudit.getTargetRecordId(), entityAudit.getChangeType(), entityAudit.getCreatedDate());
+ this.id = entityAudit.getId();
+ this.tableName = entityAudit.getTableName();
+ this.targetRecordId = entityAudit.getTargetRecordId();
+ this.changeType = entityAudit.getChangeType();
+ this.createdDate = entityAudit.getCreatedDate();
+ this.updatedDate = null;
this.callContext = callContext;
}
+ @Override
+ public UUID getId() {
+ return id;
+ }
+
+ @Override
+ public DateTime getCreatedDate() {
+ return createdDate;
+ }
+
+ @Override
+ public DateTime getUpdatedDate() {
+ return updatedDate;
+ }
+
+ @Override
+ public TableName getTableName() {
+ return tableName;
+ }
+
+ public Long getTargetRecordId() {
+ return targetRecordId;
+ }
+
+ public ChangeType getChangeType() {
+ return changeType;
+ }
+
public CallContext getCallContext() {
return callContext;
}
@Override
+ public Long getRecordId() {
+ return recordId;
+ }
+
+ @Override
+ public Long getAccountRecordId() {
+ return accountRecordId;
+ }
+
+ @Override
+ public Long getTenantRecordId() {
+ return tenantRecordId;
+ }
+
+ @Override
public TableName getHistoryTableName() {
return null;
}
@@ -43,7 +117,16 @@ public class AuditLogModelDao extends EntityAudit implements EntityModelDao<Audi
@Override
public String toString() {
final StringBuilder sb = new StringBuilder("AuditLogModelDao{");
- sb.append("callContext=").append(callContext);
+ sb.append("id=").append(id);
+ sb.append(", createdDate=").append(createdDate);
+ sb.append(", updatedDate=").append(updatedDate);
+ sb.append(", tableName=").append(tableName);
+ sb.append(", targetRecordId=").append(targetRecordId);
+ sb.append(", changeType=").append(changeType);
+ sb.append(", callContext=").append(callContext);
+ sb.append(", recordId=").append(recordId);
+ sb.append(", accountRecordId=").append(accountRecordId);
+ sb.append(", tenantRecordId=").append(tenantRecordId);
sb.append('}');
return sb.toString();
}
@@ -56,23 +139,61 @@ public class AuditLogModelDao extends EntityAudit implements EntityModelDao<Audi
if (o == null || getClass() != o.getClass()) {
return false;
}
- if (!super.equals(o)) {
- return false;
- }
final AuditLogModelDao that = (AuditLogModelDao) o;
+ if (id != null ? !id.equals(that.id) : that.id != null) {
+ return false;
+ }
+ if (createdDate != null ? createdDate.compareTo(that.createdDate) != 0 : that.createdDate != null) {
+ return false;
+ }
+ if (updatedDate != null ? updatedDate.compareTo(that.updatedDate) != 0 : that.updatedDate != null) {
+ return false;
+ }
+ if (tableName != that.tableName) {
+ return false;
+ }
+ if (targetRecordId != null ? !targetRecordId.equals(that.targetRecordId) : that.targetRecordId != null) {
+ return false;
+ }
+ if (changeType != that.changeType) {
+ return false;
+ }
if (callContext != null ? !callContext.equals(that.callContext) : that.callContext != null) {
return false;
}
-
- return true;
+ if (recordId != null ? !recordId.equals(that.recordId) : that.recordId != null) {
+ return false;
+ }
+ if (accountRecordId != null ? !accountRecordId.equals(that.accountRecordId) : that.accountRecordId != null) {
+ return false;
+ }
+ return tenantRecordId != null ? tenantRecordId.equals(that.tenantRecordId) : that.tenantRecordId == null;
}
@Override
public int hashCode() {
- int result = super.hashCode();
+ int result = id != null ? id.hashCode() : 0;
+ result = 31 * result + (createdDate != null ? createdDate.hashCode() : 0);
+ result = 31 * result + (updatedDate != null ? updatedDate.hashCode() : 0);
+ result = 31 * result + (tableName != null ? tableName.hashCode() : 0);
+ result = 31 * result + (targetRecordId != null ? targetRecordId.hashCode() : 0);
+ result = 31 * result + (changeType != null ? changeType.hashCode() : 0);
result = 31 * result + (callContext != null ? callContext.hashCode() : 0);
+ result = 31 * result + (recordId != null ? recordId.hashCode() : 0);
+ result = 31 * result + (accountRecordId != null ? accountRecordId.hashCode() : 0);
+ result = 31 * result + (tenantRecordId != null ? tenantRecordId.hashCode() : 0);
return result;
}
+
+ @Override
+ public void readExternal(final ObjectInput in) throws IOException {
+ MapperHolder.mapper().readerForUpdating(this).readValue(new ExternalizableInput(in));
+ }
+
+ @Override
+ public void writeExternal(final ObjectOutput oo) throws IOException {
+ MapperHolder.mapper().writeValue(new ExternalizableOutput(oo), this);
+ }
}
diff --git a/util/src/main/java/org/killbill/billing/util/cache/AccountBCDCacheLoader.java b/util/src/main/java/org/killbill/billing/util/cache/AccountBCDCacheLoader.java
index d88ea69..6e9955d 100644
--- a/util/src/main/java/org/killbill/billing/util/cache/AccountBCDCacheLoader.java
+++ b/util/src/main/java/org/killbill/billing/util/cache/AccountBCDCacheLoader.java
@@ -1,6 +1,6 @@
/*
- * Copyright 2014-2015 Groupon, Inc
- * Copyright 2014-2015 The Billing Project, LLC
+ * 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
@@ -22,7 +22,7 @@ import java.util.UUID;
import org.killbill.billing.callcontext.InternalTenantContext;
import org.killbill.billing.util.cache.Cachable.CacheType;
-public class AccountBCDCacheLoader extends BaseCacheLoader {
+public class AccountBCDCacheLoader extends BaseCacheLoader<UUID, Integer> {
@Override
public CacheType getCacheType() {
@@ -30,30 +30,18 @@ public class AccountBCDCacheLoader extends BaseCacheLoader {
}
@Override
- public Object load(final Object key, final Object argument) {
-
- checkCacheLoaderStatus();
-
- if (!(key instanceof UUID)) {
- throw new IllegalArgumentException("Unexpected key type of " + key.getClass().getName());
- }
-
- if (!(argument instanceof CacheLoaderArgument)) {
- throw new IllegalArgumentException("Unexpected argument type of " + argument.getClass().getName());
- }
-
- final CacheLoaderArgument cacheLoaderArgument = (CacheLoaderArgument) argument;
-
+ public Integer compute(final UUID key, final CacheLoaderArgument cacheLoaderArgument) {
if (cacheLoaderArgument.getArgs() == null ||
!(cacheLoaderArgument.getArgs()[0] instanceof LoaderCallback)) {
throw new IllegalArgumentException("Missing LoaderCallback from the arguments ");
}
final LoaderCallback callback = (LoaderCallback) cacheLoaderArgument.getArgs()[0];
- return callback.loadAccountBCD((UUID) key, cacheLoaderArgument.getInternalTenantContext());
+ return callback.loadAccountBCD(key, cacheLoaderArgument.getInternalTenantContext());
}
public interface LoaderCallback {
- Object loadAccountBCD(final UUID accountId, final InternalTenantContext context);
+
+ Integer loadAccountBCD(final UUID accountId, final InternalTenantContext context);
}
}
diff --git a/util/src/main/java/org/killbill/billing/util/cache/AccountRecordIdCacheLoader.java b/util/src/main/java/org/killbill/billing/util/cache/AccountRecordIdCacheLoader.java
index 352a2d7..7cb8757 100644
--- a/util/src/main/java/org/killbill/billing/util/cache/AccountRecordIdCacheLoader.java
+++ b/util/src/main/java/org/killbill/billing/util/cache/AccountRecordIdCacheLoader.java
@@ -1,7 +1,7 @@
/*
* Copyright 2010-2013 Ning, Inc.
- * Copyright 2014-2015 Groupon, Inc
- * Copyright 2014-2015 The Billing Project, LLC
+ * 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
@@ -28,10 +28,8 @@ import org.killbill.billing.util.cache.Cachable.CacheType;
import org.killbill.billing.util.dao.NonEntityDao;
import org.skife.jdbi.v2.Handle;
-import net.sf.ehcache.loader.CacheLoader;
-
@Singleton
-public class AccountRecordIdCacheLoader extends BaseIdCacheLoader implements CacheLoader {
+public class AccountRecordIdCacheLoader extends BaseIdCacheLoader<Long> {
private final NonEntityDao nonEntityDao;
@@ -47,7 +45,7 @@ public class AccountRecordIdCacheLoader extends BaseIdCacheLoader implements Cac
}
@Override
- protected Object doRetrieveOperation(final String rawKey, final ObjectType objectType, final Handle handle) {
+ protected Long doRetrieveOperation(final String rawKey, final ObjectType objectType, final Handle handle) {
return nonEntityDao.retrieveAccountRecordIdFromObjectInTransaction(UUID.fromString(rawKey), objectType, null, handle);
}
}
diff --git a/util/src/main/java/org/killbill/billing/util/cache/AuditLogCacheLoader.java b/util/src/main/java/org/killbill/billing/util/cache/AuditLogCacheLoader.java
index bc0761d..fac8514 100644
--- a/util/src/main/java/org/killbill/billing/util/cache/AuditLogCacheLoader.java
+++ b/util/src/main/java/org/killbill/billing/util/cache/AuditLogCacheLoader.java
@@ -1,7 +1,9 @@
/*
- * Copyright 2010-2012 Ning, Inc.
+ * Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014-2017 Groupon, Inc
+ * Copyright 2014-2017 The Billing Project, LLC
*
- * Ning licenses this file to you under the Apache License, version 2.0
+ * 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:
*
@@ -16,25 +18,24 @@
package org.killbill.billing.util.cache;
+import java.util.List;
+
import javax.inject.Inject;
import javax.inject.Singleton;
-import org.skife.jdbi.v2.IDBI;
-
import org.killbill.billing.callcontext.InternalTenantContext;
+import org.killbill.billing.util.audit.dao.AuditLogModelDao;
import org.killbill.billing.util.cache.Cachable.CacheType;
import org.killbill.billing.util.dao.AuditSqlDao;
-import org.killbill.billing.util.dao.NonEntityDao;
-
-import net.sf.ehcache.loader.CacheLoader;
+import org.skife.jdbi.v2.IDBI;
@Singleton
-public class AuditLogCacheLoader extends BaseCacheLoader implements CacheLoader {
+public class AuditLogCacheLoader extends BaseCacheLoader<String, List<AuditLogModelDao>> {
private final AuditSqlDao auditSqlDao;
@Inject
- public AuditLogCacheLoader(final IDBI dbi, final NonEntityDao nonEntityDao) {
+ public AuditLogCacheLoader(final IDBI dbi) {
super();
this.auditSqlDao = dbi.onDemand(AuditSqlDao.class);
}
@@ -45,17 +46,8 @@ public class AuditLogCacheLoader extends BaseCacheLoader implements CacheLoader
}
@Override
- public Object load(final Object key, final Object argument) {
- checkCacheLoaderStatus();
-
- if (!(key instanceof String)) {
- throw new IllegalArgumentException("Unexpected key type of " + key.getClass().getName());
- }
- if (!(argument instanceof CacheLoaderArgument)) {
- throw new IllegalArgumentException("Unexpected key type of " + argument.getClass().getName());
- }
-
- final Object[] args = ((CacheLoaderArgument) argument).getArgs();
+ public List<AuditLogModelDao> compute(final String key, final CacheLoaderArgument cacheLoaderArgument) {
+ final Object[] args = cacheLoaderArgument.getArgs();
final String tableName = (String) args[0];
final Long targetRecordId = (Long) args[1];
final InternalTenantContext internalTenantContext = (InternalTenantContext) args[2];
diff --git a/util/src/main/java/org/killbill/billing/util/cache/AuditLogViaHistoryCacheLoader.java b/util/src/main/java/org/killbill/billing/util/cache/AuditLogViaHistoryCacheLoader.java
index b819148..26d215a 100644
--- a/util/src/main/java/org/killbill/billing/util/cache/AuditLogViaHistoryCacheLoader.java
+++ b/util/src/main/java/org/killbill/billing/util/cache/AuditLogViaHistoryCacheLoader.java
@@ -1,7 +1,9 @@
/*
- * Copyright 2010-2012 Ning, Inc.
+ * Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014-2017 Groupon, Inc
+ * Copyright 2014-2017 The Billing Project, LLC
*
- * Ning licenses this file to you under the Apache License, version 2.0
+ * 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:
*
@@ -16,25 +18,24 @@
package org.killbill.billing.util.cache;
+import java.util.List;
+
import javax.inject.Inject;
import javax.inject.Singleton;
-import org.skife.jdbi.v2.IDBI;
-
import org.killbill.billing.callcontext.InternalTenantContext;
+import org.killbill.billing.util.audit.dao.AuditLogModelDao;
import org.killbill.billing.util.cache.Cachable.CacheType;
import org.killbill.billing.util.dao.AuditSqlDao;
-import org.killbill.billing.util.dao.NonEntityDao;
-
-import net.sf.ehcache.loader.CacheLoader;
+import org.skife.jdbi.v2.IDBI;
@Singleton
-public class AuditLogViaHistoryCacheLoader extends BaseCacheLoader implements CacheLoader {
+public class AuditLogViaHistoryCacheLoader extends BaseCacheLoader<String, List<AuditLogModelDao>> {
private final AuditSqlDao auditSqlDao;
@Inject
- public AuditLogViaHistoryCacheLoader(final IDBI dbi, final NonEntityDao nonEntityDao) {
+ public AuditLogViaHistoryCacheLoader(final IDBI dbi) {
super();
this.auditSqlDao = dbi.onDemand(AuditSqlDao.class);
}
@@ -45,17 +46,8 @@ public class AuditLogViaHistoryCacheLoader extends BaseCacheLoader implements Ca
}
@Override
- public Object load(final Object key, final Object argument) {
- checkCacheLoaderStatus();
-
- if (!(key instanceof String)) {
- throw new IllegalArgumentException("Unexpected key type of " + key.getClass().getName());
- }
- if (!(argument instanceof CacheLoaderArgument)) {
- throw new IllegalArgumentException("Unexpected key type of " + argument.getClass().getName());
- }
-
- final Object[] args = ((CacheLoaderArgument) argument).getArgs();
+ public List<AuditLogModelDao> compute(final String key, final CacheLoaderArgument cacheLoaderArgument) {
+ final Object[] args = cacheLoaderArgument.getArgs();
final String tableName = (String) args[0];
final String historyTableName = (String) args[1];
final Long targetRecordId = (Long) args[2];
diff --git a/util/src/main/java/org/killbill/billing/util/cache/BaseCacheLoader.java b/util/src/main/java/org/killbill/billing/util/cache/BaseCacheLoader.java
index c0044c6..973f9f0 100644
--- a/util/src/main/java/org/killbill/billing/util/cache/BaseCacheLoader.java
+++ b/util/src/main/java/org/killbill/billing/util/cache/BaseCacheLoader.java
@@ -1,7 +1,7 @@
/*
* Copyright 2010-2013 Ning, Inc.
- * Copyright 2014-2015 Groupon, Inc
- * Copyright 2014-2015 The Billing Project, LLC
+ * 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
@@ -18,77 +18,13 @@
package org.killbill.billing.util.cache;
-import java.util.Collection;
-import java.util.Map;
-
-import javax.inject.Inject;
-
import org.killbill.billing.util.cache.Cachable.CacheType;
-import net.sf.ehcache.CacheException;
-import net.sf.ehcache.Ehcache;
-import net.sf.ehcache.Status;
-import net.sf.ehcache.loader.CacheLoader;
-
-public abstract class BaseCacheLoader implements CacheLoader {
+public abstract class BaseCacheLoader<K, V> {
static final String EMPTY_VALUE_PLACEHOLDER = "__#VALEUR!__";
- private Status cacheLoaderStatus;
-
- @Inject
- public BaseCacheLoader() {
- this.cacheLoaderStatus = Status.STATUS_UNINITIALISED;
- }
-
public abstract CacheType getCacheType();
- @Override
- public abstract Object load(final Object key, final Object argument);
-
- @Override
- public Object load(final Object key) throws CacheException {
- throw new IllegalStateException("Method load is not implemented ");
- }
-
- @Override
- public Map loadAll(final Collection keys) {
- throw new IllegalStateException("Method loadAll is not implemented ");
- }
-
- @Override
- public Map loadAll(final Collection keys, final Object argument) {
- throw new IllegalStateException("Method loadAll is not implemented ");
- }
-
- @Override
- public String getName() {
- return this.getClass().getName();
- }
-
- @Override
- public CacheLoader clone(final Ehcache cache) throws CloneNotSupportedException {
- throw new IllegalStateException("Method clone is not implemented ");
- }
-
- @Override
- public void init() {
- this.cacheLoaderStatus = Status.STATUS_ALIVE;
- }
-
- @Override
- public void dispose() throws CacheException {
- cacheLoaderStatus = Status.STATUS_SHUTDOWN;
- }
-
- @Override
- public Status getStatus() {
- return cacheLoaderStatus;
- }
-
- protected void checkCacheLoaderStatus() {
- if (getStatus() != Status.STATUS_ALIVE) {
- throw new CacheException("CacheLoader is not available!");
- }
- }
+ public abstract V compute(final K key, final CacheLoaderArgument cacheLoaderArgument);
}
diff --git a/util/src/main/java/org/killbill/billing/util/cache/BaseIdCacheLoader.java b/util/src/main/java/org/killbill/billing/util/cache/BaseIdCacheLoader.java
index c0329ae..e2cb993 100644
--- a/util/src/main/java/org/killbill/billing/util/cache/BaseIdCacheLoader.java
+++ b/util/src/main/java/org/killbill/billing/util/cache/BaseIdCacheLoader.java
@@ -1,6 +1,6 @@
/*
- * Copyright 2014-2015 Groupon, Inc
- * Copyright 2014-2015 The Billing Project, LLC
+ * 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
@@ -18,40 +18,28 @@
package org.killbill.billing.util.cache;
import org.killbill.billing.ObjectType;
-import org.killbill.billing.util.cache.Cachable.CacheType;
import org.skife.jdbi.v2.Handle;
-public abstract class BaseIdCacheLoader extends BaseCacheLoader {
+public abstract class BaseIdCacheLoader<V> extends BaseCacheLoader<String, V> {
protected BaseIdCacheLoader() {
super();
}
- @Override
- public abstract CacheType getCacheType();
-
- protected abstract Object doRetrieveOperation(final String rawKey, final ObjectType objectType, final Handle handle);
+ protected abstract V doRetrieveOperation(final String rawKey, final ObjectType objectType, final Handle handle);
@Override
- public Object load(final Object key, final Object argument) {
- checkCacheLoaderStatus();
-
- if (!(key instanceof String)) {
- throw new IllegalArgumentException("Unexpected key type of " + key.getClass().getName());
- }
- if (!(argument instanceof CacheLoaderArgument)) {
- throw new IllegalArgumentException("Unexpected key type of " + argument.getClass().getName());
- }
-
+ public V compute(final String key, final CacheLoaderArgument cacheLoaderArgument) {
final String rawKey;
if (getCacheType().isKeyPrefixedWithTableName()) {
- final String[] parts = ((String) key).split(CacheControllerDispatcher.CACHE_KEY_SEPARATOR);
+ final String[] parts = key.split(CacheControllerDispatcher.CACHE_KEY_SEPARATOR);
rawKey = parts[1];
} else {
- rawKey = (String) key;
+ rawKey = key;
}
- final ObjectType objectType = ((CacheLoaderArgument) argument).getObjectType();
- final Handle handle = ((CacheLoaderArgument) argument).getHandle();
+
+ final ObjectType objectType = cacheLoaderArgument.getObjectType();
+ final Handle handle = cacheLoaderArgument.getHandle();
return doRetrieveOperation(rawKey, objectType, handle);
}
}
diff --git a/util/src/main/java/org/killbill/billing/util/cache/Cachable.java b/util/src/main/java/org/killbill/billing/util/cache/Cachable.java
index a70556c..97d5cb6 100644
--- a/util/src/main/java/org/killbill/billing/util/cache/Cachable.java
+++ b/util/src/main/java/org/killbill/billing/util/cache/Cachable.java
@@ -1,7 +1,9 @@
/*
- * Copyright 2010-2012 Ning, Inc.
+ * Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014-2017 Groupon, Inc
+ * Copyright 2014-2017 The Billing Project, LLC
*
- * Ning licenses this file to you under the Apache License, version 2.0
+ * 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:
*
@@ -20,6 +22,14 @@ import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
+import java.util.List;
+import java.util.UUID;
+
+import org.killbill.billing.account.api.ImmutableAccountData;
+import org.killbill.billing.catalog.api.Catalog;
+import org.killbill.billing.catalog.api.Plan;
+import org.killbill.billing.tenant.api.Tenant;
+import org.killbill.billing.util.config.tenant.PerTenantConfig;
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
@@ -43,58 +53,63 @@ public @interface Cachable {
CacheType value();
+ // Make sure both the key and value are Serializable
enum CacheType {
- /* Mapping from object 'id (UUID)' -> object 'recordId (Long' */
- RECORD_ID(RECORD_ID_CACHE_NAME, false),
+ /* Mapping from object 'id (UUID as String)' -> object 'recordId (Long)' */
+ RECORD_ID(RECORD_ID_CACHE_NAME, String.class, Long.class, false),
- /* Mapping from object 'id (UUID)' -> matching account object 'accountRecordId (Long)' */
- ACCOUNT_RECORD_ID(ACCOUNT_RECORD_ID_CACHE_NAME, false),
+ /* Mapping from object 'id (UUID as String)' -> matching account object 'accountRecordId (Long)' */
+ ACCOUNT_RECORD_ID(ACCOUNT_RECORD_ID_CACHE_NAME, String.class, Long.class, false),
- /* Mapping from object 'id (UUID)' -> matching object 'tenantRecordId (Long)' */
- TENANT_RECORD_ID(TENANT_RECORD_ID_CACHE_NAME, false),
+ /* Mapping from object 'id (UUID as String)' -> matching object 'tenantRecordId (Long)' */
+ TENANT_RECORD_ID(TENANT_RECORD_ID_CACHE_NAME, String.class, Long.class, false),
- /* Mapping from object 'recordId (Long') -> object 'id (UUID)' */
- OBJECT_ID(OBJECT_ID_CACHE_NAME, true),
+ /* Mapping from object 'recordId (Long as String)' -> object 'id (UUID)' */
+ OBJECT_ID(OBJECT_ID_CACHE_NAME, String.class, UUID.class, true),
- /* Mapping from object 'tableName::targetRecordId' -> matching objects 'Iterable<AuditLog>' */
- AUDIT_LOG(AUDIT_LOG_CACHE_NAME, true),
+ /* Mapping from object 'tableName::targetRecordId' -> matching objects 'List<AuditLogModelDao>' */
+ AUDIT_LOG(AUDIT_LOG_CACHE_NAME, String.class, List.class, true),
- /* Mapping from object 'tableName::historyTableName::targetRecordId' -> matching objects 'Iterable<AuditLog>' */
- AUDIT_LOG_VIA_HISTORY(AUDIT_LOG_VIA_HISTORY_CACHE_NAME, true),
+ /* Mapping from object 'tableName::historyTableName::targetRecordId' -> matching objects 'List<AuditLogModelDao>' */
+ AUDIT_LOG_VIA_HISTORY(AUDIT_LOG_VIA_HISTORY_CACHE_NAME, String.class, List.class, true),
/* Tenant catalog cache */
- TENANT_CATALOG(TENANT_CATALOG_CACHE_NAME, false),
+ TENANT_CATALOG(TENANT_CATALOG_CACHE_NAME, Long.class, Catalog.class, false),
- /* Tenant payment state machine config cache */
- TENANT_PAYMENT_STATE_MACHINE_CONFIG(TENANT_PAYMENT_STATE_MACHINE_CONFIG_CACHE_NAME, false),
+ /* Tenant payment state machine config cache (String -> SerializableStateMachineConfig) */
+ TENANT_PAYMENT_STATE_MACHINE_CONFIG(TENANT_PAYMENT_STATE_MACHINE_CONFIG_CACHE_NAME, String.class, Object.class, false),
- /* Tenant overdue config cache */
- TENANT_OVERDUE_CONFIG(TENANT_OVERDUE_CONFIG_CACHE_NAME, false),
+ /* Tenant overdue config cache (String -> DefaultOverdueConfig) */
+ TENANT_OVERDUE_CONFIG(TENANT_OVERDUE_CONFIG_CACHE_NAME, Long.class, Object.class, false),
/* Tenant overdue config cache */
- TENANT_CONFIG(TENANT_CONFIG_CACHE_NAME, false),
+ TENANT_CONFIG(TENANT_CONFIG_CACHE_NAME, Long.class, PerTenantConfig.class, false),
/* Tenant config cache */
- TENANT_KV(TENANT_KV_CACHE_NAME, false),
+ TENANT_KV(TENANT_KV_CACHE_NAME, String.class, String.class, false),
- /* Tenant config cache */
- TENANT(TENANT_CACHE_NAME, false),
+ /* Tenant cache */
+ TENANT(TENANT_CACHE_NAME, String.class, Tenant.class, false),
/* Overwritten plans */
- OVERRIDDEN_PLAN(OVERRIDDEN_PLAN_CACHE_NAME, false),
+ OVERRIDDEN_PLAN(OVERRIDDEN_PLAN_CACHE_NAME, String.class, Plan.class, false),
/* Immutable account data config cache */
- ACCOUNT_IMMUTABLE(ACCOUNT_IMMUTABLE_CACHE_NAME, false),
+ ACCOUNT_IMMUTABLE(ACCOUNT_IMMUTABLE_CACHE_NAME, Long.class, ImmutableAccountData.class, false),
/* Account BCD config cache */
- ACCOUNT_BCD(ACCOUNT_BCD_CACHE_NAME, false);
+ ACCOUNT_BCD(ACCOUNT_BCD_CACHE_NAME, UUID.class, Integer.class, false);
private final String cacheName;
+ private final Class keyType;
+ private final Class valueType;
private final boolean isKeyPrefixedWithTableName;
- CacheType(final String cacheName, final boolean isKeyPrefixedWithTableName) {
+ CacheType(final String cacheName, final Class keyType, final Class valueType, final boolean isKeyPrefixedWithTableName) {
this.cacheName = cacheName;
+ this.keyType = keyType;
+ this.valueType = valueType;
this.isKeyPrefixedWithTableName = isKeyPrefixedWithTableName;
}
@@ -102,6 +117,14 @@ public @interface Cachable {
return cacheName;
}
+ public Class<?> getKeyType() {
+ return keyType;
+ }
+
+ public Class<?> getValueType() {
+ return valueType;
+ }
+
public boolean isKeyPrefixedWithTableName() { return isKeyPrefixedWithTableName; }
public static CacheType findByName(final String input) {
diff --git a/util/src/main/java/org/killbill/billing/util/cache/CacheController.java b/util/src/main/java/org/killbill/billing/util/cache/CacheController.java
index 2c6be1e..46ee5dc 100644
--- a/util/src/main/java/org/killbill/billing/util/cache/CacheController.java
+++ b/util/src/main/java/org/killbill/billing/util/cache/CacheController.java
@@ -16,18 +16,24 @@
package org.killbill.billing.util.cache;
+import java.util.List;
+
import org.killbill.billing.util.cache.Cachable.CacheType;
+import com.google.common.base.Function;
+
public interface CacheController<K, V> {
- void add(K key, V value);
+ List<K> getKeys();
- V get(K key, CacheLoaderArgument objectType);
+ boolean isKeyInCache(K key);
- V get(K key);
+ V get(K key, CacheLoaderArgument objectType);
boolean remove(K key);
+ void remove(Function<K, Boolean> keyMatcher);
+
void putIfAbsent(final K key, V value);
int size();
diff --git a/util/src/main/java/org/killbill/billing/util/cache/CacheControllerDispatcher.java b/util/src/main/java/org/killbill/billing/util/cache/CacheControllerDispatcher.java
index bdf1302..2d9b966 100644
--- a/util/src/main/java/org/killbill/billing/util/cache/CacheControllerDispatcher.java
+++ b/util/src/main/java/org/killbill/billing/util/cache/CacheControllerDispatcher.java
@@ -44,8 +44,8 @@ public class CacheControllerDispatcher {
caches = new HashMap<CacheType, CacheController<Object, Object>>();
}
- public CacheController<Object, Object> getCacheController(final CacheType cacheType) {
- return caches.get(cacheType);
+ public <K, V> CacheController<K, V> getCacheController(final CacheType cacheType) {
+ return cast(caches.get(cacheType));
}
public void clearAll() {
@@ -53,4 +53,9 @@ public class CacheControllerDispatcher {
cacheController.removeAll();
}
}
+
+ @SuppressWarnings("unchecked")
+ private static <K, V> CacheController<K, V> cast(final CacheController<?, ?> cache) {
+ return (CacheController<K, V>) cache;
+ }
}
diff --git a/util/src/main/java/org/killbill/billing/util/cache/CacheControllerDispatcherProvider.java b/util/src/main/java/org/killbill/billing/util/cache/CacheControllerDispatcherProvider.java
index f980ad7..7d9ce76 100644
--- a/util/src/main/java/org/killbill/billing/util/cache/CacheControllerDispatcherProvider.java
+++ b/util/src/main/java/org/killbill/billing/util/cache/CacheControllerDispatcherProvider.java
@@ -1,7 +1,7 @@
/*
* Copyright 2010-2013 Ning, Inc.
- * Copyright 2014-2015 Groupon, Inc
- * Copyright 2014-2015 The Billing Project, LLC
+ * 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
@@ -18,10 +18,12 @@
package org.killbill.billing.util.cache;
-import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.Map;
+import java.util.Set;
+import javax.cache.Cache;
+import javax.cache.CacheManager;
import javax.inject.Inject;
import javax.inject.Provider;
@@ -29,53 +31,37 @@ import org.killbill.billing.util.cache.Cachable.CacheType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import com.google.common.base.Function;
-import com.google.common.collect.Collections2;
-import com.google.common.collect.ImmutableList;
-import net.sf.ehcache.CacheManager;
-import net.sf.ehcache.Ehcache;
-import net.sf.ehcache.loader.CacheLoader;
-
// Build the abstraction layer between EhCache and Kill Bill
public class CacheControllerDispatcherProvider implements Provider<CacheControllerDispatcher> {
private static final Logger logger = LoggerFactory.getLogger(CacheControllerDispatcherProvider.class);
private final CacheManager cacheManager;
+ private final Set<BaseCacheLoader> cacheLoaders;
@Inject
- public CacheControllerDispatcherProvider(final CacheManager cacheManager) {
+ public CacheControllerDispatcherProvider(final CacheManager cacheManager,
+ final Set<BaseCacheLoader> cacheLoaders) {
this.cacheManager = cacheManager;
+ this.cacheLoaders = cacheLoaders;
}
@Override
public CacheControllerDispatcher get() {
final Map<CacheType, CacheController<Object, Object>> cacheControllers = new LinkedHashMap<CacheType, CacheController<Object, Object>>();
- for (final String cacheName : cacheManager.getCacheNames()) {
- final CacheType cacheType = CacheType.findByName(cacheName);
+ for (final BaseCacheLoader cacheLoader : cacheLoaders) {
+ final CacheType cacheType = cacheLoader.getCacheType();
- final Collection<EhCacheBasedCacheController<Object, Object>> cacheControllersForCacheName = getCacheControllersForCacheName(cacheName, cacheType);
- // EhCache supports multiple cache loaders per type, but not Kill Bill - take the first one
- if (cacheControllersForCacheName.size() > 0) {
- final EhCacheBasedCacheController<Object, Object> ehCacheBasedCacheController = cacheControllersForCacheName.iterator().next();
- cacheControllers.put(cacheType, ehCacheBasedCacheController);
+ final Cache cache = cacheManager.getCache(cacheType.getCacheName(), cacheType.getKeyType(), cacheType.getValueType());
+ if (cache == null) {
+ logger.warn("Cache for cacheName='{}' not configured - check your ehcache.xml", cacheLoader.getCacheType().getCacheName());
+ continue;
}
- }
- return new CacheControllerDispatcher(cacheControllers);
- }
- private Collection<EhCacheBasedCacheController<Object, Object>> getCacheControllersForCacheName(final String name, final CacheType cacheType) {
- final Ehcache cache = cacheManager.getEhcache(name);
- if (cache == null) {
- logger.warn("No cache configured for name {}", name);
- return ImmutableList.<EhCacheBasedCacheController<Object, Object>>of();
+ final CacheController<Object, Object> ehCacheBasedCacheController = new EhCacheBasedCacheController<Object, Object>(cache, cacheLoader);
+ cacheControllers.put(cacheType, ehCacheBasedCacheController);
}
- // The CacheLoaders were registered in EhCacheCacheManagerProvider
- return Collections2.transform(cache.getRegisteredCacheLoaders(), new Function<CacheLoader, EhCacheBasedCacheController<Object, Object>>() {
- @Override
- public EhCacheBasedCacheController<Object, Object> apply(final CacheLoader input) {
- return new EhCacheBasedCacheController<Object, Object>(cache, cacheType);
- }
- });
+
+ return new CacheControllerDispatcher(cacheControllers);
}
}
diff --git a/util/src/main/java/org/killbill/billing/util/cache/CacheLoaderArgument.java b/util/src/main/java/org/killbill/billing/util/cache/CacheLoaderArgument.java
index f559c0f..1724c59 100644
--- a/util/src/main/java/org/killbill/billing/util/cache/CacheLoaderArgument.java
+++ b/util/src/main/java/org/killbill/billing/util/cache/CacheLoaderArgument.java
@@ -1,7 +1,7 @@
/*
* Copyright 2010-2013 Ning, Inc.
- * Copyright 2014-2015 Groupon, Inc
- * Copyright 2014-2015 The Billing Project, LLC
+ * 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
@@ -18,6 +18,8 @@
package org.killbill.billing.util.cache;
+import java.util.Arrays;
+
import javax.annotation.Nullable;
import org.killbill.billing.ObjectType;
@@ -61,4 +63,14 @@ public class CacheLoaderArgument {
public Handle getHandle() {
return handle;
}
+
+ @Override
+ public String toString() {
+ final StringBuilder sb = new StringBuilder("CacheLoaderArgument{");
+ sb.append("objectType=").append(objectType);
+ sb.append(", args=").append(Arrays.toString(args));
+ sb.append(", internalTenantContext=").append(internalTenantContext);
+ sb.append('}');
+ return sb.toString();
+ }
}
diff --git a/util/src/main/java/org/killbill/billing/util/cache/EhCacheBasedCacheController.java b/util/src/main/java/org/killbill/billing/util/cache/EhCacheBasedCacheController.java
index 08d04d2..c53b879 100644
--- a/util/src/main/java/org/killbill/billing/util/cache/EhCacheBasedCacheController.java
+++ b/util/src/main/java/org/killbill/billing/util/cache/EhCacheBasedCacheController.java
@@ -1,7 +1,7 @@
/*
* Copyright 2010-2012 Ning, Inc.
- * Copyright 2014-2015 Groupon, Inc
- * Copyright 2014-2015 The Billing Project, LLC
+ * 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
@@ -18,69 +18,127 @@
package org.killbill.billing.util.cache;
-import javax.annotation.Nullable;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import javax.cache.Cache;
+import javax.cache.Cache.Entry;
import org.killbill.billing.util.cache.Cachable.CacheType;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
-import net.sf.ehcache.Ehcache;
-import net.sf.ehcache.Element;
+import com.google.common.base.Function;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterables;
public class EhCacheBasedCacheController<K, V> implements CacheController<K, V> {
- private final Ehcache cache;
- private final CacheType cacheType;
+ private static final Logger logger = LoggerFactory.getLogger(EhCacheBasedCacheController.class);
+
+ private final Cache<K, V> cache;
+ private final BaseCacheLoader<K, V> baseCacheLoader;
- public EhCacheBasedCacheController(final Ehcache cache, final CacheType cacheType) {
+ public EhCacheBasedCacheController(final Cache<K, V> cache, final BaseCacheLoader<K, V> baseCacheLoader) {
this.cache = cache;
- this.cacheType = cacheType;
+ this.baseCacheLoader = baseCacheLoader;
}
@Override
- public void add(final K key, final V value) {
- cache.putIfAbsent(new Element(key, value));
+ public List<K> getKeys() {
+ final Iterable<K> kIterable = Iterables.<Entry<K, V>, K>transform(cache,
+ new Function<Entry<K, V>, K>() {
+ @Override
+ public K apply(final Entry<K, V> input) {
+ return input.getKey();
+ }
+ });
+ return ImmutableList.<K>copyOf(kIterable);
}
@Override
- public V get(final K key, @Nullable final CacheLoaderArgument cacheLoaderArgument) {
- return getWithOrWithoutCacheLoaderArgument(key, cacheLoaderArgument);
+ public boolean isKeyInCache(final K key) {
+ return cache.containsKey(key);
}
@Override
- public V get(final K key) {
- return getWithOrWithoutCacheLoaderArgument(key, null);
+ public V get(final K key, final CacheLoaderArgument cacheLoaderArgument) {
+ if (key == null) {
+ return null;
+ }
+
+ final V value;
+ if (!isKeyInCache(key)) {
+ value = computeAndCacheValue(key, cacheLoaderArgument);
+ } else {
+ value = cache.get(key);
+ }
+
+ if (value == null || value.equals(BaseCacheLoader.EMPTY_VALUE_PLACEHOLDER)) {
+ return null;
+ } else {
+ return value;
+ }
}
- public void putIfAbsent(final K key, V value) {
- final Element element = new Element(key, value);
- cache.putIfAbsent(element);
+ @Override
+ public void putIfAbsent(final K key, final V value) {
+ cache.putIfAbsent(key, value);
}
@Override
public boolean remove(final K key) {
- return cache.remove(key);
+ if (isKeyInCache(key)) {
+ cache.remove(key);
+ return true;
+ } else {
+ return false;
+ }
}
@Override
- public int size() {
- return cache.getSize();
+ public void remove(final Function<K, Boolean> keyMatcher) {
+ final Set<K> toRemove = new HashSet<K>();
+ for (final Object key : getKeys()) {
+ if (keyMatcher.apply((K) key) == Boolean.TRUE) {
+ toRemove.add((K) key);
+ }
+ }
+ cache.removeAll(toRemove);
}
@Override
public void removeAll() {
- cache.removeAll();
+ cache.clear();
+ }
+
+ @Override
+ public int size() {
+ return Iterables.<Cache.Entry<K, V>>size(cache);
}
@Override
public CacheType getCacheType() {
- return cacheType;
+ return baseCacheLoader.getCacheType();
}
- private V getWithOrWithoutCacheLoaderArgument(final K key, @Nullable final CacheLoaderArgument cacheLoaderArgument) {
- final Element element = cacheLoaderArgument != null ? cache.getWithLoader(key, null, cacheLoaderArgument) : cache.get(key);
- if (element == null || element.getObjectValue() == null || element.getObjectValue().equals(BaseCacheLoader.EMPTY_VALUE_PLACEHOLDER)) {
+ private V computeAndCacheValue(final K key, final CacheLoaderArgument cacheLoaderArgument) {
+ final V value;
+ try {
+ value = baseCacheLoader.compute(key, cacheLoaderArgument);
+ } catch (final Exception e) {
+ logger.warn("Unable to compute cached value for key='{}' and cacheLoaderArgument='{}'", key, cacheLoaderArgument, e);
+ throw new RuntimeException(e);
+ }
+
+ if (value == null) {
return null;
}
- return (V) element.getObjectValue();
- }
+ // Race condition, we may compute it for nothing
+ putIfAbsent(key, value);
+
+ return value;
+ }
}
diff --git a/util/src/main/java/org/killbill/billing/util/cache/ExternalizableInput.java b/util/src/main/java/org/killbill/billing/util/cache/ExternalizableInput.java
new file mode 100644
index 0000000..73e45a7
--- /dev/null
+++ b/util/src/main/java/org/killbill/billing/util/cache/ExternalizableInput.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright 2017 Groupon, Inc
+ * Copyright 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.util.cache;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.ObjectInput;
+
+// See http://www.cowtowncoder.com/blog/archives/2012/08/entry_477.html
+public class ExternalizableInput extends InputStream {
+
+ private final ObjectInput in;
+
+ public ExternalizableInput(final ObjectInput in) {
+ this.in = in;
+ }
+
+ @Override
+ public int available() throws IOException {
+ return in.available();
+ }
+
+ @Override
+ public void close() throws IOException {
+ in.close();
+ }
+
+ @Override
+ public boolean markSupported() {
+ return false;
+ }
+
+ @Override
+ public int read() throws IOException {
+ return in.read();
+ }
+
+ @Override
+ public int read(final byte[] buffer) throws IOException {
+ return in.read(buffer);
+ }
+
+ @Override
+ public int read(final byte[] buffer, final int offset, final int len) throws IOException {
+ return in.read(buffer, offset, len);
+ }
+
+ @Override
+ public long skip(final long n) throws IOException {
+ return in.skip(n);
+ }
+}
diff --git a/util/src/main/java/org/killbill/billing/util/cache/ExternalizableOutput.java b/util/src/main/java/org/killbill/billing/util/cache/ExternalizableOutput.java
new file mode 100644
index 0000000..e5297d4
--- /dev/null
+++ b/util/src/main/java/org/killbill/billing/util/cache/ExternalizableOutput.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2017 Groupon, Inc
+ * Copyright 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.util.cache;
+
+import java.io.IOException;
+import java.io.ObjectOutput;
+import java.io.OutputStream;
+
+// See http://www.cowtowncoder.com/blog/archives/2012/08/entry_477.html
+public class ExternalizableOutput extends OutputStream {
+
+ private final ObjectOutput out;
+
+ public ExternalizableOutput(final ObjectOutput out) {
+ this.out = out;
+ }
+
+ @Override
+ public void flush() throws IOException {
+ out.flush();
+ }
+
+ @Override
+ public void close() throws IOException {
+ out.close();
+ }
+
+ @Override
+ public void write(final int ch) throws IOException {
+ out.write(ch);
+ }
+
+ @Override
+ public void write(final byte[] data) throws IOException {
+ out.write(data);
+ }
+
+ @Override
+ public void write(final byte[] data, final int offset, final int len) throws IOException {
+ out.write(data, offset, len);
+ }
+}
diff --git a/util/src/main/java/org/killbill/billing/util/cache/ImmutableAccountCacheLoader.java b/util/src/main/java/org/killbill/billing/util/cache/ImmutableAccountCacheLoader.java
index 866c44e..8634a01 100644
--- a/util/src/main/java/org/killbill/billing/util/cache/ImmutableAccountCacheLoader.java
+++ b/util/src/main/java/org/killbill/billing/util/cache/ImmutableAccountCacheLoader.java
@@ -1,6 +1,6 @@
/*
- * Copyright 2014-2015 Groupon, Inc
- * Copyright 2014-2015 The Billing Project, LLC
+ * 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
@@ -17,10 +17,11 @@
package org.killbill.billing.util.cache;
+import org.killbill.billing.account.api.ImmutableAccountData;
import org.killbill.billing.callcontext.InternalTenantContext;
import org.killbill.billing.util.cache.Cachable.CacheType;
-public class ImmutableAccountCacheLoader extends BaseCacheLoader {
+public class ImmutableAccountCacheLoader extends BaseCacheLoader<Long, ImmutableAccountData> {
@Override
public CacheType getCacheType() {
@@ -28,30 +29,18 @@ public class ImmutableAccountCacheLoader extends BaseCacheLoader {
}
@Override
- public Object load(final Object key, final Object argument) {
-
- checkCacheLoaderStatus();
-
- if (!(key instanceof Long)) {
- throw new IllegalArgumentException("Unexpected key type of " + key.getClass().getName());
- }
-
- if (!(argument instanceof CacheLoaderArgument)) {
- throw new IllegalArgumentException("Unexpected argument type of " + argument.getClass().getName());
- }
-
- final CacheLoaderArgument cacheLoaderArgument = (CacheLoaderArgument) argument;
-
+ public ImmutableAccountData compute(final Long key, final CacheLoaderArgument cacheLoaderArgument) {
if (cacheLoaderArgument.getArgs() == null ||
!(cacheLoaderArgument.getArgs()[0] instanceof LoaderCallback)) {
throw new IllegalArgumentException("Missing LoaderCallback from the arguments ");
}
final LoaderCallback callback = (LoaderCallback) cacheLoaderArgument.getArgs()[0];
- return callback.loadAccount((Long) key, cacheLoaderArgument.getInternalTenantContext());
+ return callback.loadAccount(key, cacheLoaderArgument.getInternalTenantContext());
}
public interface LoaderCallback {
- Object loadAccount(final Long recordId, final InternalTenantContext context);
+
+ ImmutableAccountData loadAccount(final Long recordId, final InternalTenantContext context);
}
}
diff --git a/util/src/main/java/org/killbill/billing/util/cache/MapperHolder.java b/util/src/main/java/org/killbill/billing/util/cache/MapperHolder.java
new file mode 100644
index 0000000..65a8f8a
--- /dev/null
+++ b/util/src/main/java/org/killbill/billing/util/cache/MapperHolder.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2017 Groupon, Inc
+ * Copyright 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.util.cache;
+
+import org.killbill.billing.util.jackson.ObjectMapper;
+
+import com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility;
+import com.fasterxml.jackson.annotation.PropertyAccessor;
+import com.fasterxml.jackson.dataformat.smile.SmileFactory;
+
+import static com.fasterxml.jackson.core.JsonGenerator.Feature.AUTO_CLOSE_TARGET;
+
+// See http://www.cowtowncoder.com/blog/archives/2012/08/entry_477.html
+public class MapperHolder {
+
+ private static final MapperHolder instance = new MapperHolder();
+
+ static {
+ instance.mapper.setVisibility(PropertyAccessor.ALL, Visibility.NONE);
+ instance.mapper.setVisibility(PropertyAccessor.FIELD, Visibility.ANY);
+ // Stream is NOT owned by Jackson
+ instance.mapper.disable(AUTO_CLOSE_TARGET);
+ }
+
+ private final SmileFactory f = new SmileFactory();
+ private final ObjectMapper mapper = new ObjectMapper(f);
+
+ public static ObjectMapper mapper() { return instance.mapper; }
+}
diff --git a/util/src/main/java/org/killbill/billing/util/cache/ObjectIdCacheLoader.java b/util/src/main/java/org/killbill/billing/util/cache/ObjectIdCacheLoader.java
index fd357ab..cda30b6 100644
--- a/util/src/main/java/org/killbill/billing/util/cache/ObjectIdCacheLoader.java
+++ b/util/src/main/java/org/killbill/billing/util/cache/ObjectIdCacheLoader.java
@@ -1,6 +1,6 @@
/*
- * Copyright 2014-2015 Groupon, Inc
- * Copyright 2014-2015 The Billing Project, LLC
+ * 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
@@ -17,6 +17,8 @@
package org.killbill.billing.util.cache;
+import java.util.UUID;
+
import javax.inject.Inject;
import javax.inject.Singleton;
@@ -25,10 +27,8 @@ import org.killbill.billing.util.cache.Cachable.CacheType;
import org.killbill.billing.util.dao.NonEntityDao;
import org.skife.jdbi.v2.Handle;
-import net.sf.ehcache.loader.CacheLoader;
-
@Singleton
-public class ObjectIdCacheLoader extends BaseIdCacheLoader implements CacheLoader {
+public class ObjectIdCacheLoader extends BaseIdCacheLoader<UUID> {
private final NonEntityDao nonEntityDao;
@@ -44,7 +44,7 @@ public class ObjectIdCacheLoader extends BaseIdCacheLoader implements CacheLoade
}
@Override
- protected Object doRetrieveOperation(final String rawKey, final ObjectType objectType, final Handle handle) {
+ protected UUID doRetrieveOperation(final String rawKey, final ObjectType objectType, final Handle handle) {
final Long recordId = Long.valueOf(rawKey);
return nonEntityDao.retrieveIdFromObjectInTransaction(recordId, objectType, null, handle);
}
diff --git a/util/src/main/java/org/killbill/billing/util/cache/OverriddenPlanCacheLoader.java b/util/src/main/java/org/killbill/billing/util/cache/OverriddenPlanCacheLoader.java
index 52c4a07..79d73b8 100644
--- a/util/src/main/java/org/killbill/billing/util/cache/OverriddenPlanCacheLoader.java
+++ b/util/src/main/java/org/killbill/billing/util/cache/OverriddenPlanCacheLoader.java
@@ -1,6 +1,6 @@
/*
- * Copyright 2014-2016 Groupon, Inc
- * Copyright 2014-2016 The Billing Project, LLC
+ * 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
@@ -22,13 +22,14 @@ import javax.inject.Singleton;
import org.killbill.billing.callcontext.InternalTenantContext;
import org.killbill.billing.catalog.api.CatalogApiException;
+import org.killbill.billing.catalog.api.Plan;
import org.killbill.billing.catalog.api.StaticCatalog;
import org.killbill.billing.util.cache.Cachable.CacheType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@Singleton
-public class OverriddenPlanCacheLoader extends BaseCacheLoader {
+public class OverriddenPlanCacheLoader extends BaseCacheLoader<String, Plan> {
private final Logger log = LoggerFactory.getLogger(OverriddenPlanCacheLoader.class);
@@ -43,18 +44,7 @@ public class OverriddenPlanCacheLoader extends BaseCacheLoader {
}
@Override
- public Object load(final Object key, final Object argument) {
- checkCacheLoaderStatus();
-
- if (!(key instanceof String)) {
- throw new IllegalArgumentException("Unexpected key type of " + key.getClass().getName());
- }
- if (!(argument instanceof CacheLoaderArgument)) {
- throw new IllegalArgumentException("Unexpected argument type of " + argument.getClass().getName());
- }
-
-
- final CacheLoaderArgument cacheLoaderArgument = (CacheLoaderArgument) argument;
+ public Plan compute(final String key, final CacheLoaderArgument cacheLoaderArgument) {
if (cacheLoaderArgument.getArgs() == null || cacheLoaderArgument.getArgs().length != 2) {
throw new IllegalArgumentException("Invalid arguments for overridden plans");
}
@@ -66,11 +56,10 @@ public class OverriddenPlanCacheLoader extends BaseCacheLoader {
throw new IllegalArgumentException("Invalid arguments for overridden plans: missing catalog from argument");
}
-
- final String planName = (String) key;
+ final String planName = key;
final LoaderCallback callback = (LoaderCallback) cacheLoaderArgument.getArgs()[0];
final StaticCatalog catalog = (StaticCatalog) cacheLoaderArgument.getArgs()[1];
- final InternalTenantContext internalTenantContext = ((CacheLoaderArgument) argument).getInternalTenantContext();
+ final InternalTenantContext internalTenantContext = cacheLoaderArgument.getInternalTenantContext();
try {
log.info("Loading overridden plan {} for tenant {}", planName, internalTenantContext.getTenantRecordId());
@@ -82,6 +71,7 @@ public class OverriddenPlanCacheLoader extends BaseCacheLoader {
}
public interface LoaderCallback {
- public Object loadPlan(final String planName, final StaticCatalog catalog, final InternalTenantContext context) throws CatalogApiException;
+
+ public Plan loadPlan(final String planName, final StaticCatalog catalog, final InternalTenantContext context) throws CatalogApiException;
}
}
diff --git a/util/src/main/java/org/killbill/billing/util/cache/RecordIdCacheLoader.java b/util/src/main/java/org/killbill/billing/util/cache/RecordIdCacheLoader.java
index 08efe2e..a7027a8 100644
--- a/util/src/main/java/org/killbill/billing/util/cache/RecordIdCacheLoader.java
+++ b/util/src/main/java/org/killbill/billing/util/cache/RecordIdCacheLoader.java
@@ -1,7 +1,7 @@
/*
* Copyright 2010-2012 Ning, Inc.
- * Copyright 2014-2015 Groupon, Inc
- * Copyright 2014-2015 The Billing Project, LLC
+ * 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
@@ -28,10 +28,8 @@ import org.killbill.billing.util.cache.Cachable.CacheType;
import org.killbill.billing.util.dao.NonEntityDao;
import org.skife.jdbi.v2.Handle;
-import net.sf.ehcache.loader.CacheLoader;
-
@Singleton
-public class RecordIdCacheLoader extends BaseIdCacheLoader implements CacheLoader {
+public class RecordIdCacheLoader extends BaseIdCacheLoader<Long> {
private final NonEntityDao nonEntityDao;
@@ -47,7 +45,7 @@ public class RecordIdCacheLoader extends BaseIdCacheLoader implements CacheLoade
}
@Override
- protected Object doRetrieveOperation(final String rawKey, final ObjectType objectType, final Handle handle) {
+ protected Long doRetrieveOperation(final String rawKey, final ObjectType objectType, final Handle handle) {
return nonEntityDao.retrieveRecordIdFromObjectInTransaction(UUID.fromString(rawKey), objectType, null, handle);
}
}
diff --git a/util/src/main/java/org/killbill/billing/util/cache/TenantCacheLoader.java b/util/src/main/java/org/killbill/billing/util/cache/TenantCacheLoader.java
index bacafa8..1ed03a5 100644
--- a/util/src/main/java/org/killbill/billing/util/cache/TenantCacheLoader.java
+++ b/util/src/main/java/org/killbill/billing/util/cache/TenantCacheLoader.java
@@ -1,6 +1,6 @@
/*
- * Copyright 2014-2015 Groupon, Inc
- * Copyright 2014-2015 The Billing Project, LLC
+ * 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
@@ -26,7 +26,7 @@ import org.killbill.billing.tenant.api.TenantInternalApi;
import org.killbill.billing.util.cache.Cachable.CacheType;
@Singleton
-public class TenantCacheLoader extends BaseCacheLoader {
+public class TenantCacheLoader extends BaseCacheLoader<String, Tenant> {
private final TenantInternalApi tenantApi;
@@ -42,19 +42,10 @@ public class TenantCacheLoader extends BaseCacheLoader {
}
@Override
- public Object load(final Object key, final Object argument) {
- checkCacheLoaderStatus();
-
- if (!(key instanceof String)) {
- throw new IllegalArgumentException("Unexpected key type of " + key.getClass().getName());
- }
- if (!(argument instanceof CacheLoaderArgument)) {
- throw new IllegalArgumentException("Unexpected key type of " + argument.getClass().getName());
- }
-
+ public Tenant compute(final String key, final CacheLoaderArgument cacheLoaderArgument) {
try {
- return tenantApi.getTenantByApiKey((String) key);
- } catch (TenantApiException e) {
+ return tenantApi.getTenantByApiKey(key);
+ } catch (final TenantApiException e) {
throw new IllegalStateException("TenantCacheLoader cannot find value for key " + key);
}
}
diff --git a/util/src/main/java/org/killbill/billing/util/cache/TenantCatalogCacheLoader.java b/util/src/main/java/org/killbill/billing/util/cache/TenantCatalogCacheLoader.java
index b3f9305..03a4ee3 100644
--- a/util/src/main/java/org/killbill/billing/util/cache/TenantCatalogCacheLoader.java
+++ b/util/src/main/java/org/killbill/billing/util/cache/TenantCatalogCacheLoader.java
@@ -1,6 +1,6 @@
/*
- * Copyright 2014-2015 Groupon, Inc
- * Copyright 2014-2015 The Billing Project, LLC
+ * 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
@@ -23,6 +23,7 @@ import javax.inject.Inject;
import javax.inject.Singleton;
import org.killbill.billing.callcontext.InternalTenantContext;
+import org.killbill.billing.catalog.api.Catalog;
import org.killbill.billing.catalog.api.CatalogApiException;
import org.killbill.billing.tenant.api.TenantInternalApi;
import org.killbill.billing.util.cache.Cachable.CacheType;
@@ -30,7 +31,7 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@Singleton
-public class TenantCatalogCacheLoader extends BaseCacheLoader {
+public class TenantCatalogCacheLoader extends BaseCacheLoader<Long, Catalog> {
private final Logger log = LoggerFactory.getLogger(TenantCatalogCacheLoader.class);
@@ -48,19 +49,9 @@ public class TenantCatalogCacheLoader extends BaseCacheLoader {
}
@Override
- public Object load(final Object key, final Object argument) {
- checkCacheLoaderStatus();
-
- if (!(key instanceof Long)) {
- throw new IllegalArgumentException("Unexpected key type of " + key.getClass().getName());
- }
- if (!(argument instanceof CacheLoaderArgument)) {
- throw new IllegalArgumentException("Unexpected argument type of " + argument.getClass().getName());
- }
-
- final Long tenantRecordId = (Long) key;
+ public Catalog compute(final Long key, final CacheLoaderArgument cacheLoaderArgument) {
+ final Long tenantRecordId = key;
final InternalTenantContext internalTenantContext = new InternalTenantContext(tenantRecordId);
- final CacheLoaderArgument cacheLoaderArgument = (CacheLoaderArgument) argument;
if (cacheLoaderArgument.getArgs() == null || !(cacheLoaderArgument.getArgs()[0] instanceof LoaderCallback)) {
throw new IllegalArgumentException("Missing LoaderCallback from the arguments ");
@@ -80,6 +71,7 @@ public class TenantCatalogCacheLoader extends BaseCacheLoader {
}
public interface LoaderCallback {
- public Object loadCatalog(final List<String> catalogXMLs, final Long tenantRecordId) throws CatalogApiException;
+
+ public Catalog loadCatalog(final List<String> catalogXMLs, final Long tenantRecordId) throws CatalogApiException;
}
}
diff --git a/util/src/main/java/org/killbill/billing/util/cache/TenantConfigCacheLoader.java b/util/src/main/java/org/killbill/billing/util/cache/TenantConfigCacheLoader.java
index caced95..d71ad0d 100644
--- a/util/src/main/java/org/killbill/billing/util/cache/TenantConfigCacheLoader.java
+++ b/util/src/main/java/org/killbill/billing/util/cache/TenantConfigCacheLoader.java
@@ -1,6 +1,6 @@
/*
- * Copyright 2014-2016 Groupon, Inc
- * Copyright 2014-2016 The Billing Project, LLC
+ * 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
@@ -25,11 +25,12 @@ import javax.inject.Singleton;
import org.killbill.billing.callcontext.InternalTenantContext;
import org.killbill.billing.tenant.api.TenantInternalApi;
import org.killbill.billing.util.cache.Cachable.CacheType;
+import org.killbill.billing.util.config.tenant.PerTenantConfig;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@Singleton
-public class TenantConfigCacheLoader extends BaseCacheLoader {
+public class TenantConfigCacheLoader extends BaseCacheLoader<Long, PerTenantConfig> {
private final Logger log = LoggerFactory.getLogger(TenantConfigCacheLoader.class);
@@ -47,19 +48,9 @@ public class TenantConfigCacheLoader extends BaseCacheLoader {
}
@Override
- public Object load(final Object key, final Object argument) {
- checkCacheLoaderStatus();
-
- if (!(key instanceof Long)) {
- throw new IllegalArgumentException("Unexpected key type of " + key.getClass().getName());
- }
- if (!(argument instanceof CacheLoaderArgument)) {
- throw new IllegalArgumentException("Unexpected argument type of " + argument.getClass().getName());
- }
-
- final Long tenantRecordId = (Long) key;
+ public PerTenantConfig compute(final Long key, final CacheLoaderArgument cacheLoaderArgument) {
+ final Long tenantRecordId = key;
final InternalTenantContext internalTenantContext = new InternalTenantContext(tenantRecordId);
- final CacheLoaderArgument cacheLoaderArgument = (CacheLoaderArgument) argument;
if (cacheLoaderArgument.getArgs() == null || !(cacheLoaderArgument.getArgs()[0] instanceof LoaderCallback)) {
throw new IllegalArgumentException("Missing LoaderCallback from the arguments ");
@@ -77,7 +68,7 @@ public class TenantConfigCacheLoader extends BaseCacheLoader {
}
public interface LoaderCallback {
- public Object loadConfig(final String inputJson) throws IOException;
- }
+ public PerTenantConfig loadConfig(final String inputJson) throws IOException;
+ }
}
diff --git a/util/src/main/java/org/killbill/billing/util/cache/TenantKVCacheLoader.java b/util/src/main/java/org/killbill/billing/util/cache/TenantKVCacheLoader.java
index a949445..5dcbca5 100644
--- a/util/src/main/java/org/killbill/billing/util/cache/TenantKVCacheLoader.java
+++ b/util/src/main/java/org/killbill/billing/util/cache/TenantKVCacheLoader.java
@@ -1,6 +1,6 @@
/*
- * Copyright 2014-2015 Groupon, Inc
- * Copyright 2014-2015 The Billing Project, LLC
+ * 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
@@ -27,7 +27,7 @@ import org.killbill.billing.tenant.api.TenantInternalApi;
import org.killbill.billing.util.cache.Cachable.CacheType;
@Singleton
-public class TenantKVCacheLoader extends BaseCacheLoader {
+public class TenantKVCacheLoader extends BaseCacheLoader<String, String> {
private final TenantInternalApi tenantApi;
@@ -43,16 +43,8 @@ public class TenantKVCacheLoader extends BaseCacheLoader {
}
@Override
- public Object load(final Object key, final Object argument) {
- checkCacheLoaderStatus();
-
- if (!(key instanceof String)) {
- throw new IllegalArgumentException("Unexpected key type of " + key.getClass().getName());
- }
- if (!(argument instanceof CacheLoaderArgument)) {
- throw new IllegalArgumentException("Unexpected key type of " + argument.getClass().getName());
- }
- final String[] parts = ((String) key).split(CacheControllerDispatcher.CACHE_KEY_SEPARATOR);
+ public String compute(final String key, final CacheLoaderArgument cacheLoaderArgument) {
+ final String[] parts = key.split(CacheControllerDispatcher.CACHE_KEY_SEPARATOR);
final String rawKey = parts[0];
final String tenantRecordId = parts[1];
diff --git a/util/src/main/java/org/killbill/billing/util/cache/TenantOverdueConfigCacheLoader.java b/util/src/main/java/org/killbill/billing/util/cache/TenantOverdueConfigCacheLoader.java
index 81ebe00..6c95814 100644
--- a/util/src/main/java/org/killbill/billing/util/cache/TenantOverdueConfigCacheLoader.java
+++ b/util/src/main/java/org/killbill/billing/util/cache/TenantOverdueConfigCacheLoader.java
@@ -1,6 +1,6 @@
/*
- * Copyright 2014-2015 Groupon, Inc
- * Copyright 2014-2015 The Billing Project, LLC
+ * 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
@@ -22,13 +22,14 @@ import javax.inject.Singleton;
import org.killbill.billing.callcontext.InternalTenantContext;
import org.killbill.billing.overdue.api.OverdueApiException;
+import org.killbill.billing.overdue.api.OverdueConfig;
import org.killbill.billing.tenant.api.TenantInternalApi;
import org.killbill.billing.util.cache.Cachable.CacheType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@Singleton
-public class TenantOverdueConfigCacheLoader extends BaseCacheLoader {
+public class TenantOverdueConfigCacheLoader extends BaseCacheLoader<Long, Object> {
private static final Logger log = LoggerFactory.getLogger(TenantOverdueConfigCacheLoader.class);
@@ -46,19 +47,9 @@ public class TenantOverdueConfigCacheLoader extends BaseCacheLoader {
}
@Override
- public Object load(final Object key, final Object argument) {
- checkCacheLoaderStatus();
-
- if (!(key instanceof Long)) {
- throw new IllegalArgumentException("Unexpected key type of " + key.getClass().getName());
- }
- if (!(argument instanceof CacheLoaderArgument)) {
- throw new IllegalArgumentException("Unexpected argument type of " + argument.getClass().getName());
- }
-
- final Long tenantRecordId = (Long) key;
+ public Object compute(final Long key, final CacheLoaderArgument cacheLoaderArgument) {
+ final Long tenantRecordId = key;
final InternalTenantContext internalTenantContext = new InternalTenantContext(tenantRecordId);
- final CacheLoaderArgument cacheLoaderArgument = (CacheLoaderArgument) argument;
if (cacheLoaderArgument.getArgs() == null || !(cacheLoaderArgument.getArgs()[0] instanceof LoaderCallback)) {
throw new IllegalArgumentException("Missing LoaderCallback from the arguments");
@@ -81,6 +72,6 @@ public class TenantOverdueConfigCacheLoader extends BaseCacheLoader {
public interface LoaderCallback {
- public Object loadOverdueConfig(final String overdueConfigXML) throws OverdueApiException;
+ public OverdueConfig loadOverdueConfig(final String overdueConfigXML) throws OverdueApiException;
}
}
diff --git a/util/src/main/java/org/killbill/billing/util/cache/TenantRecordIdCacheLoader.java b/util/src/main/java/org/killbill/billing/util/cache/TenantRecordIdCacheLoader.java
index 17a62af..1144e59 100644
--- a/util/src/main/java/org/killbill/billing/util/cache/TenantRecordIdCacheLoader.java
+++ b/util/src/main/java/org/killbill/billing/util/cache/TenantRecordIdCacheLoader.java
@@ -1,7 +1,7 @@
/*
* Copyright 2010-2013 Ning, Inc.
- * Copyright 2014-2015 Groupon, Inc
- * Copyright 2014-2015 The Billing Project, LLC
+ * 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
@@ -28,10 +28,8 @@ import org.killbill.billing.util.cache.Cachable.CacheType;
import org.killbill.billing.util.dao.NonEntityDao;
import org.skife.jdbi.v2.Handle;
-import net.sf.ehcache.loader.CacheLoader;
-
@Singleton
-public class TenantRecordIdCacheLoader extends BaseIdCacheLoader implements CacheLoader {
+public class TenantRecordIdCacheLoader extends BaseIdCacheLoader<Long> {
private final NonEntityDao nonEntityDao;
@@ -47,7 +45,7 @@ public class TenantRecordIdCacheLoader extends BaseIdCacheLoader implements Cach
}
@Override
- protected Object doRetrieveOperation(final String rawKey, final ObjectType objectType, final Handle handle) {
+ protected Long doRetrieveOperation(final String rawKey, final ObjectType objectType, final Handle handle) {
return nonEntityDao.retrieveTenantRecordIdFromObjectInTransaction(UUID.fromString(rawKey), objectType, null, handle);
}
}
diff --git a/util/src/main/java/org/killbill/billing/util/cache/TenantStateMachineConfigCacheLoader.java b/util/src/main/java/org/killbill/billing/util/cache/TenantStateMachineConfigCacheLoader.java
index c6d5489..6ef8e00 100644
--- a/util/src/main/java/org/killbill/billing/util/cache/TenantStateMachineConfigCacheLoader.java
+++ b/util/src/main/java/org/killbill/billing/util/cache/TenantStateMachineConfigCacheLoader.java
@@ -1,6 +1,6 @@
/*
- * Copyright 2016 Groupon, Inc
- * Copyright 2016 The Billing Project, LLC
+ * Copyright 2016-2017 Groupon, Inc
+ * Copyright 2016-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
@@ -32,7 +32,7 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@Singleton
-public class TenantStateMachineConfigCacheLoader extends BaseCacheLoader {
+public class TenantStateMachineConfigCacheLoader extends BaseCacheLoader<String, Object> {
private static final Pattern PATTERN = Pattern.compile(TenantKey.PLUGIN_PAYMENT_STATE_MACHINE_.toString() + "(.*)");
private static final Logger log = LoggerFactory.getLogger(TenantStateMachineConfigCacheLoader.class);
@@ -51,17 +51,8 @@ public class TenantStateMachineConfigCacheLoader extends BaseCacheLoader {
}
@Override
- public Object load(final Object key, final Object argument) {
- checkCacheLoaderStatus();
-
- if (!(key instanceof String)) {
- throw new IllegalArgumentException("Unexpected key type of " + key.getClass().getName());
- }
- if (!(argument instanceof CacheLoaderArgument)) {
- throw new IllegalArgumentException("Unexpected key type of " + argument.getClass().getName());
- }
-
- final String[] parts = ((String) key).split(CacheControllerDispatcher.CACHE_KEY_SEPARATOR);
+ public Object compute(final String key, final CacheLoaderArgument cacheLoaderArgument) {
+ final String[] parts = key.split(CacheControllerDispatcher.CACHE_KEY_SEPARATOR);
final String rawKey = parts[0];
final Matcher matcher = PATTERN.matcher(rawKey);
if (!matcher.matches()) {
@@ -70,7 +61,6 @@ public class TenantStateMachineConfigCacheLoader extends BaseCacheLoader {
final String pluginName = matcher.group(1);
final String tenantRecordId = parts[1];
- final CacheLoaderArgument cacheLoaderArgument = (CacheLoaderArgument) argument;
final LoaderCallback callback = (LoaderCallback) cacheLoaderArgument.getArgs()[0];
final InternalTenantContext internalTenantContext = new InternalTenantContext(Long.valueOf(tenantRecordId));
diff --git a/util/src/main/java/org/killbill/billing/util/callcontext/InternalCallContextFactory.java b/util/src/main/java/org/killbill/billing/util/callcontext/InternalCallContextFactory.java
index f85796b..a42fe24 100644
--- a/util/src/main/java/org/killbill/billing/util/callcontext/InternalCallContextFactory.java
+++ b/util/src/main/java/org/killbill/billing/util/callcontext/InternalCallContextFactory.java
@@ -1,7 +1,7 @@
/*
* Copyright 2010-2012 Ning, Inc.
- * Copyright 2014-2016 Groupon, Inc
- * Copyright 2014-2016 The Billing Project, LLC
+ * 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
@@ -31,18 +31,23 @@ import org.killbill.billing.account.api.ImmutableAccountData;
import org.killbill.billing.account.api.ImmutableAccountInternalApi;
import org.killbill.billing.callcontext.InternalCallContext;
import org.killbill.billing.callcontext.InternalTenantContext;
+import org.killbill.billing.util.account.AccountDateTimeUtils;
import org.killbill.billing.util.cache.Cachable.CacheType;
+import org.killbill.billing.util.cache.CacheController;
import org.killbill.billing.util.cache.CacheControllerDispatcher;
import org.killbill.billing.util.dao.NonEntityDao;
+import org.killbill.billing.util.entity.dao.TimeZoneAwareEntity;
import org.killbill.clock.Clock;
import org.slf4j.MDC;
import com.google.common.base.MoreObjects;
+import com.google.common.base.Preconditions;
// Internal contexts almost always expect accountRecordId and tenantRecordId to be populated
public class InternalCallContextFactory {
- public static final long INTERNAL_TENANT_RECORD_ID = 0L;
+ // Long, not long, to avoid NPE with ==
+ public static final Long INTERNAL_TENANT_RECORD_ID = 0L;
public static final String MDC_KB_ACCOUNT_RECORD_ID = "kb.accountRecordId";
public static final String MDC_KB_TENANT_RECORD_ID = "kb.tenantRecordId";
@@ -50,7 +55,10 @@ public class InternalCallContextFactory {
private final ImmutableAccountInternalApi accountInternalApi;
private final Clock clock;
private final NonEntityDao nonEntityDao;
- private final CacheControllerDispatcher cacheControllerDispatcher;
+ private final CacheController<String, UUID> objectIdCacheController;
+ private final CacheController<String, Long> recordIdCacheController;
+ private final CacheController<String, Long> accountRecordIdCacheController;
+ private final CacheController<String, Long> tenantRecordIdCacheController;
@Inject
public InternalCallContextFactory(@Nullable final ImmutableAccountInternalApi accountInternalApi,
@@ -60,7 +68,17 @@ public class InternalCallContextFactory {
this.accountInternalApi = accountInternalApi;
this.clock = clock;
this.nonEntityDao = nonEntityDao;
- this.cacheControllerDispatcher = cacheControllerDispatcher;
+ if (cacheControllerDispatcher == null) {
+ this.objectIdCacheController = null;
+ this.recordIdCacheController = null;
+ this.accountRecordIdCacheController = null;
+ this.tenantRecordIdCacheController = null;
+ } else {
+ this.objectIdCacheController = cacheControllerDispatcher.getCacheController(CacheType.OBJECT_ID);
+ this.recordIdCacheController = cacheControllerDispatcher.getCacheController(CacheType.RECORD_ID);
+ this.accountRecordIdCacheController = cacheControllerDispatcher.getCacheController(CacheType.ACCOUNT_RECORD_ID);
+ this.tenantRecordIdCacheController = cacheControllerDispatcher.getCacheController(CacheType.TENANT_RECORD_ID);
+ }
}
//
@@ -138,7 +156,10 @@ public class InternalCallContextFactory {
if (accountRecordId == null) {
return new InternalTenantContext(tenantRecordId);
} else {
- return new InternalTenantContext(tenantRecordId, accountRecordId, getFixedOffsetTimeZone(accountRecordId, tenantRecordId), getReferenceTime(accountRecordId, tenantRecordId));
+ final ImmutableAccountData immutableAccountData = getImmutableAccountData(accountRecordId, tenantRecordId);
+ final DateTimeZone fixedOffsetTimeZone = immutableAccountData.getFixedOffsetTimeZone();
+ final DateTime referenceTime = immutableAccountData.getReferenceTime();
+ return new InternalTenantContext(tenantRecordId, accountRecordId, fixedOffsetTimeZone, referenceTime);
}
}
@@ -222,8 +243,23 @@ public class InternalCallContextFactory {
// Used when we need to re-hydrate the callcontext with the account_record_id (when creating the account)
public InternalCallContext createInternalCallContext(final Long accountRecordId, final InternalCallContext context) {
- final DateTimeZone fixedOffsetTimeZone = getFixedOffsetTimeZone(accountRecordId, context.getTenantRecordId());
- final DateTime referenceTime = getReferenceTime(accountRecordId, context.getTenantRecordId());
+ final ImmutableAccountData immutableAccountData = getImmutableAccountData(accountRecordId, context.getTenantRecordId());
+ final DateTimeZone fixedOffsetTimeZone = immutableAccountData.getFixedOffsetTimeZone();
+ final DateTime referenceTime = immutableAccountData.getReferenceTime();
+ populateMDCContext(accountRecordId, context.getTenantRecordId());
+ return new InternalCallContext(context, accountRecordId, fixedOffsetTimeZone, referenceTime, clock.getUTCNow());
+ }
+
+ // Used during the account creation transaction (account not visible outside of the transaction yet)
+ public InternalCallContext createInternalCallContext(final TimeZoneAwareEntity accountModelDao, final Long accountRecordId, final InternalCallContext context) {
+ // See DefaultImmutableAccountData implementation
+ final DateTimeZone fixedOffsetTimeZone = AccountDateTimeUtils.getFixedOffsetTimeZone(accountModelDao);
+ final DateTime referenceTime = AccountDateTimeUtils.getReferenceDateTime(accountModelDao);
+ populateMDCContext(accountRecordId, context.getTenantRecordId());
+ return new InternalCallContext(context, accountRecordId, fixedOffsetTimeZone, referenceTime, clock.getUTCNow());
+ }
+
+ public InternalCallContext createInternalCallContext(final DateTimeZone fixedOffsetTimeZone, final DateTime referenceTime, final Long accountRecordId, final InternalCallContext context) {
populateMDCContext(accountRecordId, context.getTenantRecordId());
return new InternalCallContext(context, accountRecordId, fixedOffsetTimeZone, referenceTime, clock.getUTCNow());
}
@@ -242,8 +278,18 @@ public class InternalCallContextFactory {
final CallOrigin callOrigin, final UserType userType, @Nullable final UUID userToken,
@Nullable final String reasonCode, @Nullable final String comment) {
final Long nonNulTenantRecordId = MoreObjects.firstNonNull(tenantRecordId, INTERNAL_TENANT_RECORD_ID);
- final DateTimeZone fixedOffsetTimeZone = getFixedOffsetTimeZone(accountRecordId, tenantRecordId);
- final DateTime referenceTime = getReferenceTime(accountRecordId, tenantRecordId);
+
+ final DateTimeZone fixedOffsetTimeZone;
+ final DateTime referenceTime;
+ if (accountRecordId == null) {
+ // TENANT_CONFIG_CHANGE event for instance
+ fixedOffsetTimeZone = null;
+ referenceTime = null;
+ } else {
+ final ImmutableAccountData immutableAccountData = getImmutableAccountData(accountRecordId, nonNulTenantRecordId);
+ fixedOffsetTimeZone = immutableAccountData.getFixedOffsetTimeZone();
+ referenceTime = immutableAccountData.getReferenceTime();
+ }
populateMDCContext(accountRecordId, nonNulTenantRecordId);
@@ -261,34 +307,15 @@ public class InternalCallContextFactory {
clock.getUTCNow());
}
- private DateTimeZone getFixedOffsetTimeZone(@Nullable final Long accountRecordId, final Long tenantRecordId) {
- if (accountRecordId == null || accountInternalApi == null) {
- return null;
- }
-
- populateMDCContext(accountRecordId, tenantRecordId);
-
- final ImmutableAccountData immutableAccountData = getImmutableAccountData(accountRecordId, tenantRecordId);
- // Will be null while creating the account
- return immutableAccountData == null ? null : immutableAccountData.getFixedOffsetTimeZone();
- }
-
- private DateTime getReferenceTime(@Nullable final Long accountRecordId, final Long tenantRecordId) {
- if (accountRecordId == null || accountInternalApi == null) {
- return null;
- }
-
- final ImmutableAccountData immutableAccountData = getImmutableAccountData(accountRecordId, tenantRecordId);
- // Will be null while creating the account
- return immutableAccountData == null ? null : immutableAccountData.getReferenceTime();
- }
-
- private ImmutableAccountData getImmutableAccountData(@Nullable final Long accountRecordId, final Long tenantRecordId) {
+ private ImmutableAccountData getImmutableAccountData(final Long accountRecordId, final Long tenantRecordId) {
+ Preconditions.checkNotNull(accountRecordId, "Missing accountRecordId");
final InternalTenantContext tmp = new InternalTenantContext(tenantRecordId, accountRecordId, null, null);
try {
- return accountInternalApi.getImmutableAccountDataByRecordId(accountRecordId, tmp);
+ final ImmutableAccountData immutableAccountData = accountInternalApi.getImmutableAccountDataByRecordId(accountRecordId, tmp);
+ Preconditions.checkNotNull(immutableAccountData, "Unable to retrieve immutableAccountData");
+ return immutableAccountData;
} catch (final AccountApiException e) {
- return null;
+ throw new RuntimeException(e);
}
}
@@ -307,7 +334,7 @@ public class InternalCallContextFactory {
public UUID getAccountId(final UUID objectId, final ObjectType objectType, final TenantContext context) {
final Long accountRecordId = getAccountRecordIdSafe(objectId, objectType, context);
if (accountRecordId != null) {
- return nonEntityDao.retrieveIdFromObject(accountRecordId, ObjectType.ACCOUNT, cacheControllerDispatcher == null ? null : cacheControllerDispatcher.getCacheController(CacheType.OBJECT_ID));
+ return nonEntityDao.retrieveIdFromObject(accountRecordId, ObjectType.ACCOUNT, objectIdCacheController);
} else {
return null;
}
@@ -317,7 +344,7 @@ public class InternalCallContextFactory {
public Long getRecordIdFromObject(final UUID objectId, final ObjectType objectType, final TenantContext context) {
try {
if (objectBelongsToTheRightTenant(objectId, objectType, context)) {
- return nonEntityDao.retrieveRecordIdFromObject(objectId, objectType, cacheControllerDispatcher == null ? null : cacheControllerDispatcher.getCacheController(CacheType.RECORD_ID));
+ return nonEntityDao.retrieveRecordIdFromObject(objectId, objectType, recordIdCacheController);
} else {
return null;
}
@@ -358,7 +385,7 @@ public class InternalCallContextFactory {
}
private UUID getTenantIdSafe(final InternalTenantContext context) {
- return nonEntityDao.retrieveIdFromObject(context.getTenantRecordId(), ObjectType.TENANT, cacheControllerDispatcher == null ? null : cacheControllerDispatcher.getCacheController(CacheType.OBJECT_ID));
+ return nonEntityDao.retrieveIdFromObject(context.getTenantRecordId(), ObjectType.TENANT, objectIdCacheController);
}
//
@@ -386,11 +413,11 @@ public class InternalCallContextFactory {
//
private Long getAccountRecordIdUnsafe(final UUID objectId, final ObjectType objectType) {
- return nonEntityDao.retrieveAccountRecordIdFromObject(objectId, objectType, cacheControllerDispatcher == null ? null : cacheControllerDispatcher.getCacheController(CacheType.ACCOUNT_RECORD_ID));
+ return nonEntityDao.retrieveAccountRecordIdFromObject(objectId, objectType, accountRecordIdCacheController);
}
private Long getTenantRecordIdUnsafe(final UUID objectId, final ObjectType objectType) {
- return nonEntityDao.retrieveTenantRecordIdFromObject(objectId, objectType, cacheControllerDispatcher == null ? null : cacheControllerDispatcher.getCacheController(CacheType.TENANT_RECORD_ID));
+ return nonEntityDao.retrieveTenantRecordIdFromObject(objectId, objectType, tenantRecordIdCacheController);
}
public static final class ObjectDoesNotExist extends IllegalStateException {
diff --git a/util/src/main/java/org/killbill/billing/util/config/tenant/CacheConfig.java b/util/src/main/java/org/killbill/billing/util/config/tenant/CacheConfig.java
index 1338b65..789cc0b 100644
--- a/util/src/main/java/org/killbill/billing/util/config/tenant/CacheConfig.java
+++ b/util/src/main/java/org/killbill/billing/util/config/tenant/CacheConfig.java
@@ -1,6 +1,6 @@
/*
- * Copyright 2014-2016 Groupon, Inc
- * Copyright 2014-2016 The Billing Project, LLC
+ * 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
@@ -34,7 +34,7 @@ import com.google.inject.Inject;
public class CacheConfig {
- private final CacheController cacheController;
+ private final CacheController<Long, PerTenantConfig> cacheController;
private final CacheLoaderArgument cacheLoaderArgument;
private final ObjectMapper objectMapper;
@@ -48,7 +48,7 @@ public class CacheConfig {
}
public PerTenantConfig getPerTenantConfig(final InternalTenantContext tenantContext) {
- final PerTenantConfig perTenantConfig = (PerTenantConfig) cacheController.get(tenantContext.getTenantRecordId(), cacheLoaderArgument);
+ final PerTenantConfig perTenantConfig = cacheController.get(tenantContext.getTenantRecordId(), cacheLoaderArgument);
return perTenantConfig;
}
@@ -59,7 +59,7 @@ public class CacheConfig {
private CacheLoaderArgument initializeCacheLoaderArgument() {
final LoaderCallback loaderCallback = new LoaderCallback() {
@Override
- public Object loadConfig(@Nullable final String inputJson) throws IOException {
+ public PerTenantConfig loadConfig(@Nullable final String inputJson) throws IOException {
return inputJson != null ? objectMapper.readValue(inputJson, PerTenantConfig.class) : new PerTenantConfig();
}
};
@@ -69,5 +69,4 @@ public class CacheConfig {
final InternalTenantContext notUsed = null;
return new CacheLoaderArgument(irrelevant, args, notUsed);
}
-
}
diff --git a/util/src/main/java/org/killbill/billing/util/config/tenant/PerTenantConfig.java b/util/src/main/java/org/killbill/billing/util/config/tenant/PerTenantConfig.java
index 0d04b4a..25a3f34 100644
--- a/util/src/main/java/org/killbill/billing/util/config/tenant/PerTenantConfig.java
+++ b/util/src/main/java/org/killbill/billing/util/config/tenant/PerTenantConfig.java
@@ -1,6 +1,6 @@
/*
- * Copyright 2014-2016 Groupon, Inc
- * Copyright 2014-2016 The Billing Project, LLC
+ * 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
@@ -17,11 +17,30 @@
package org.killbill.billing.util.config.tenant;
+import java.io.Externalizable;
+import java.io.IOException;
+import java.io.ObjectInput;
+import java.io.ObjectOutput;
import java.util.HashMap;
-public class PerTenantConfig extends HashMap<String, String> {
+import org.killbill.billing.util.cache.ExternalizableInput;
+import org.killbill.billing.util.cache.ExternalizableOutput;
+import org.killbill.billing.util.cache.MapperHolder;
+
+public class PerTenantConfig extends HashMap<String, String> implements Externalizable {
+
+ private static final long serialVersionUID = 3887971108446630172L;
public PerTenantConfig() {
}
+ @Override
+ public void readExternal(final ObjectInput in) throws IOException {
+ MapperHolder.mapper().readerForUpdating(this).readValue(new ExternalizableInput(in));
+ }
+
+ @Override
+ public void writeExternal(final ObjectOutput oo) throws IOException {
+ MapperHolder.mapper().writeValue(new ExternalizableOutput(oo), this);
+ }
}
diff --git a/util/src/main/java/org/killbill/billing/util/dao/AuditSqlDao.java b/util/src/main/java/org/killbill/billing/util/dao/AuditSqlDao.java
index 30cee0c..7ca13e0 100644
--- a/util/src/main/java/org/killbill/billing/util/dao/AuditSqlDao.java
+++ b/util/src/main/java/org/killbill/billing/util/dao/AuditSqlDao.java
@@ -1,7 +1,9 @@
/*
- * Copyright 2010-2011 Ning, Inc.
+ * Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014-2017 Groupon, Inc
+ * Copyright 2014-2017 The Billing Project, LLC
*
- * Ning licenses this file to you under the Apache License, version 2.0
+ * 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:
*
@@ -36,7 +38,7 @@ import org.killbill.billing.util.entity.dao.EntitySqlDaoStringTemplate;
/**
* Note 1: cache invalidation has to happen for audit logs (which is tricky in the multi-nodes scenario).
- * For now, we're using a time-based eviction strategy (see timeToIdleSeconds and timeToLiveSeconds in ehcache.xml)
+ * For now, we're using a time-based eviction strategy (see ehcache.xml)
* which is good enough: the cache will always get at least the initial CREATION audit log entry, which is the one
* we really care about (both for Analytics and for Kaui's endpoints). Besides, we do cache invalidation properly
* on our own node (see EntitySqlDaoWrapperInvocationHandler).
diff --git a/util/src/main/java/org/killbill/billing/util/dao/DefaultNonEntityDao.java b/util/src/main/java/org/killbill/billing/util/dao/DefaultNonEntityDao.java
index 043805e..7cec9b9 100644
--- a/util/src/main/java/org/killbill/billing/util/dao/DefaultNonEntityDao.java
+++ b/util/src/main/java/org/killbill/billing/util/dao/DefaultNonEntityDao.java
@@ -1,7 +1,7 @@
/*
* Copyright 2010-2013 Ning, Inc.
- * Copyright 2014-2015 Groupon, Inc
- * Copyright 2014-2015 The Billing Project, LLC
+ * 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
@@ -40,22 +40,22 @@ import com.google.common.base.Preconditions;
public class DefaultNonEntityDao implements NonEntityDao {
private final NonEntitySqlDao nonEntitySqlDao;
- private final WithCaching<UUID, Long> withCachingObjectId;
- private final WithCaching<Long, UUID> withCachingRecordId;
+ private final WithCaching<String, Long> withCachingObjectId;
+ private final WithCaching<String, UUID> withCachingRecordId;
@Inject
public DefaultNonEntityDao(final IDBI dbi) {
this.nonEntitySqlDao = dbi.onDemand(NonEntitySqlDao.class);
- this.withCachingObjectId = new WithCaching<UUID, Long>();
- this.withCachingRecordId = new WithCaching<Long, UUID>();
+ this.withCachingObjectId = new WithCaching<String, Long>();
+ this.withCachingRecordId = new WithCaching<String, UUID>();
}
@Override
- public Long retrieveRecordIdFromObject(@Nullable final UUID objectId, final ObjectType objectType, @Nullable final CacheController<Object, Object> cache) {
+ public Long retrieveRecordIdFromObject(@Nullable final UUID objectId, final ObjectType objectType, @Nullable final CacheController<String, Long> cache) {
return retrieveRecordIdFromObjectInTransaction(objectId, objectType, cache, null);
}
- public Long retrieveRecordIdFromObjectInTransaction(@Nullable final UUID objectId, final ObjectType objectType, @Nullable final CacheController<Object, Object> cache, @Nullable final Handle handle) {
+ public Long retrieveRecordIdFromObjectInTransaction(@Nullable final UUID objectId, final ObjectType objectType, @Nullable final CacheController<String, Long> cache, @Nullable final Handle handle) {
if (objectId == null) {
return null;
}
@@ -63,28 +63,29 @@ public class DefaultNonEntityDao implements NonEntityDao {
final TableName tableName = TableName.fromObjectType(objectType);
Preconditions.checkNotNull(tableName, "%s is not a valid ObjectType", objectType);
- return withCachingObjectId.withCaching(new OperationRetrieval<UUID, Long>() {
+ return withCachingObjectId.withCaching(new OperationRetrieval<Long>() {
@Override
- public Long doRetrieve(final UUID objectOrRecordId, final ObjectType objectType) {
+ public Long doRetrieve(final ObjectType objectType) {
final NonEntitySqlDao inTransactionNonEntitySqlDao = handle == null ? nonEntitySqlDao : SqlObjectBuilder.attach(handle, NonEntitySqlDao.class);
return inTransactionNonEntitySqlDao.getRecordIdFromObject(objectId.toString(), tableName.getTableName());
}
- }, objectId, objectType, tableName, cache);
+ }, objectId.toString(), objectType, tableName, cache);
}
@Override
- public Long retrieveAccountRecordIdFromObject(@Nullable final UUID objectId, final ObjectType objectType, @Nullable final CacheController<Object, Object> cache) {
+ public Long retrieveAccountRecordIdFromObject(@Nullable final UUID objectId, final ObjectType objectType, @Nullable final CacheController<String, Long> cache) {
return retrieveAccountRecordIdFromObjectInTransaction(objectId, objectType, cache, null);
}
@Override
- public Long retrieveAccountRecordIdFromObjectInTransaction(@Nullable final UUID objectId, final ObjectType objectType, @Nullable final CacheController<Object, Object> cache, @Nullable final Handle handle) {
+ public Long retrieveAccountRecordIdFromObjectInTransaction(@Nullable final UUID objectId, final ObjectType objectType, @Nullable final CacheController<String, Long> cache, @Nullable final Handle handle) {
final TableName tableName = TableName.fromObjectType(objectType);
Preconditions.checkNotNull(tableName, "%s is not a valid ObjectType", objectType);
- return withCachingObjectId.withCaching(new OperationRetrieval<UUID, Long>() {
+ final String objectIdOrNull = objectId != null ? objectId.toString() : null;
+ return withCachingObjectId.withCaching(new OperationRetrieval<Long>() {
@Override
- public Long doRetrieve(final UUID objectId, final ObjectType objectType) {
+ public Long doRetrieve(final ObjectType objectType) {
final NonEntitySqlDao inTransactionNonEntitySqlDao = handle == null ? nonEntitySqlDao : SqlObjectBuilder.attach(handle, NonEntitySqlDao.class);
switch (tableName) {
@@ -94,64 +95,65 @@ public class DefaultNonEntityDao implements NonEntityDao {
return null;
case ACCOUNT:
- return inTransactionNonEntitySqlDao.getAccountRecordIdFromAccount(objectId.toString());
+ return inTransactionNonEntitySqlDao.getAccountRecordIdFromAccount(objectIdOrNull);
default:
- return inTransactionNonEntitySqlDao.getAccountRecordIdFromObjectOtherThanAccount(objectId.toString(), tableName.getTableName());
+ return inTransactionNonEntitySqlDao.getAccountRecordIdFromObjectOtherThanAccount(objectIdOrNull, tableName.getTableName());
}
}
- }, objectId, objectType, tableName, cache);
+ }, objectIdOrNull, objectType, tableName, cache);
}
@Override
- public Long retrieveTenantRecordIdFromObject(@Nullable final UUID objectId, final ObjectType objectType, @Nullable final CacheController<Object, Object> cache) {
+ public Long retrieveTenantRecordIdFromObject(@Nullable final UUID objectId, final ObjectType objectType, @Nullable final CacheController<String, Long> cache) {
return retrieveTenantRecordIdFromObjectInTransaction(objectId, objectType, cache, null);
}
@Override
- public Long retrieveTenantRecordIdFromObjectInTransaction(@Nullable final UUID objectId, final ObjectType objectType, @Nullable final CacheController<Object, Object> cache, @Nullable final Handle handle) {
+ public Long retrieveTenantRecordIdFromObjectInTransaction(@Nullable final UUID objectId, final ObjectType objectType, @Nullable final CacheController<String, Long> cache, @Nullable final Handle handle) {
final TableName tableName = TableName.fromObjectType(objectType);
Preconditions.checkNotNull(tableName, "%s is not a valid ObjectType", objectType);
- return withCachingObjectId.withCaching(new OperationRetrieval<UUID, Long>() {
+ final String objectIdOrNull = objectId != null ? objectId.toString() : null;
+ return withCachingObjectId.withCaching(new OperationRetrieval<Long>() {
@Override
- public Long doRetrieve(final UUID objectId, final ObjectType objectType) {
+ public Long doRetrieve(final ObjectType objectType) {
final NonEntitySqlDao inTransactionNonEntitySqlDao = handle == null ? nonEntitySqlDao : SqlObjectBuilder.attach(handle, NonEntitySqlDao.class);
switch (tableName) {
case TENANT:
// Explicit cast to Long to avoid NPE (unboxing to long)
- return objectId == null ? (Long) 0L : inTransactionNonEntitySqlDao.getTenantRecordIdFromTenant(objectId.toString());
+ return objectId == null ? (Long) 0L : inTransactionNonEntitySqlDao.getTenantRecordIdFromTenant(objectIdOrNull);
default:
- return inTransactionNonEntitySqlDao.getTenantRecordIdFromObjectOtherThanTenant(objectId.toString(), tableName.getTableName());
+ return inTransactionNonEntitySqlDao.getTenantRecordIdFromObjectOtherThanTenant(objectIdOrNull, tableName.getTableName());
}
}
- }, objectId, objectType, tableName, cache);
+ }, objectIdOrNull, objectType, tableName, cache);
}
@Override
- public UUID retrieveIdFromObject(final Long recordId, final ObjectType objectType, @Nullable final CacheController<Object, Object> cache) {
+ public UUID retrieveIdFromObject(final Long recordId, final ObjectType objectType, @Nullable final CacheController<String, UUID> cache) {
return retrieveIdFromObjectInTransaction(recordId, objectType, cache, null);
}
@Override
- public UUID retrieveIdFromObjectInTransaction(final Long recordId, final ObjectType objectType, @Nullable final CacheController<Object, Object> cache, @Nullable final Handle handle) {
- if (objectType == ObjectType.TENANT && recordId == InternalCallContextFactory.INTERNAL_TENANT_RECORD_ID) {
+ public UUID retrieveIdFromObjectInTransaction(final Long recordId, final ObjectType objectType, @Nullable final CacheController<String, UUID> cache, @Nullable final Handle handle) {
+ if (objectType == ObjectType.TENANT && InternalCallContextFactory.INTERNAL_TENANT_RECORD_ID.equals(recordId)) {
return null;
}
final TableName tableName = TableName.fromObjectType(objectType);
Preconditions.checkNotNull(tableName, "%s is not a valid ObjectType", objectType);
- return withCachingRecordId.withCaching(new OperationRetrieval<Long, UUID>() {
+ return withCachingRecordId.withCaching(new OperationRetrieval<UUID>() {
@Override
- public UUID doRetrieve(final Long objectOrRecordId, final ObjectType objectType) {
+ public UUID doRetrieve(final ObjectType objectType) {
final NonEntitySqlDao inTransactionNonEntitySqlDao = handle == null ? nonEntitySqlDao : SqlObjectBuilder.attach(handle, NonEntitySqlDao.class);
return inTransactionNonEntitySqlDao.getIdFromObject(recordId, tableName.getTableName());
}
- }, recordId, objectType, tableName, cache);
+ }, String.valueOf(recordId), objectType, tableName, cache);
}
@Override
@@ -165,31 +167,31 @@ public class DefaultNonEntityDao implements NonEntityDao {
return nonEntitySqlDao.getHistoryTargetRecordId(recordId, tableName.getTableName());
}
- private interface OperationRetrieval<TypeIn, TypeOut> {
+ private interface OperationRetrieval<TypeOut> {
- public TypeOut doRetrieve(final TypeIn objectOrRecordId, final ObjectType objectType);
+ public TypeOut doRetrieve(final ObjectType objectType);
}
// 'cache' will be null for the CacheLoader classes -- or if cache is not configured.
private class WithCaching<TypeIn, TypeOut> {
- private TypeOut withCaching(final OperationRetrieval<TypeIn, TypeOut> op, @Nullable final TypeIn objectOrRecordId, final ObjectType objectType, final TableName tableName, @Nullable final CacheController<Object, Object> cache) {
+ private TypeOut withCaching(final OperationRetrieval<TypeOut> op, @Nullable final TypeIn objectOrRecordId, final ObjectType objectType, final TableName tableName, @Nullable final CacheController<TypeIn, TypeOut> cache) {
final Profiling<TypeOut, RuntimeException> prof = new Profiling<TypeOut, RuntimeException>();
if (objectOrRecordId == null) {
return null;
}
if (cache != null) {
- final String key = (cache.getCacheType().isKeyPrefixedWithTableName()) ?
- tableName + CacheControllerDispatcher.CACHE_KEY_SEPARATOR + objectOrRecordId.toString() :
- objectOrRecordId.toString();
- return (TypeOut) cache.get(key, new CacheLoaderArgument(objectType));
+ final TypeIn key = (cache.getCacheType().isKeyPrefixedWithTableName()) ?
+ (TypeIn) (tableName + CacheControllerDispatcher.CACHE_KEY_SEPARATOR + objectOrRecordId.toString()) :
+ objectOrRecordId;
+ return cache.get(key, new CacheLoaderArgument(objectType));
}
final TypeOut result;
result = prof.executeWithProfiling(ProfilingFeatureType.DAO_DETAILS, "NonEntityDao (type = " + objectType + ") cache miss", new WithProfilingCallback<TypeOut, RuntimeException>() {
@Override
public TypeOut execute() throws RuntimeException {
- return op.doRetrieve(objectOrRecordId, objectType);
+ return op.doRetrieve(objectType);
}
});
return result;
diff --git a/util/src/main/java/org/killbill/billing/util/dao/EntityHistoryModelDaoMapper.java b/util/src/main/java/org/killbill/billing/util/dao/EntityHistoryModelDaoMapper.java
new file mode 100644
index 0000000..5ff331d
--- /dev/null
+++ b/util/src/main/java/org/killbill/billing/util/dao/EntityHistoryModelDaoMapper.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright 2017 Groupon, Inc
+ * Copyright 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.util.dao;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.UUID;
+
+import org.joda.time.DateTime;
+import org.killbill.billing.util.audit.ChangeType;
+import org.killbill.billing.util.entity.Entity;
+import org.killbill.billing.util.entity.dao.EntityModelDao;
+import org.killbill.billing.util.entity.dao.EntityModelDaoBase;
+import org.skife.jdbi.v2.StatementContext;
+import org.skife.jdbi.v2.tweak.ResultSetMapper;
+
+public class EntityHistoryModelDaoMapper<M extends EntityModelDao<E>, E extends Entity> extends MapperBase implements ResultSetMapper<EntityHistoryModelDao<M, E>> {
+
+ private final ResultSetMapper<M> entityMapper;
+
+ public EntityHistoryModelDaoMapper(final ResultSetMapper<M> entityMapper) {
+ this.entityMapper = entityMapper;
+ }
+
+ @Override
+ public EntityHistoryModelDao<M, E> map(final int index, final ResultSet r, final StatementContext ctx) throws SQLException {
+ final UUID id = getUUID(r, "id");
+ final long targetRecordId = r.getLong("target_record_id");
+ final String changeType = r.getString("change_type");
+ final DateTime createdDate = getDateTime(r, "created_date");
+
+ final M entityModelDao = entityMapper.map(index, r, ctx);
+
+ // Hack -- remove the id as it is the history id, not the entity id
+ ((EntityModelDaoBase) entityModelDao).setId(null);
+ // Hack -- similarly, populate the right record_id
+ ((EntityModelDaoBase) entityModelDao).setRecordId(targetRecordId);
+ // Hack -- account is special
+ if (entityModelDao.getAccountRecordId() == null) {
+ ((EntityModelDaoBase) entityModelDao).setAccountRecordId(targetRecordId);
+ }
+
+ return new EntityHistoryModelDao(id, entityModelDao, targetRecordId, ChangeType.valueOf(changeType), createdDate);
+ }
+}
diff --git a/util/src/main/java/org/killbill/billing/util/dao/EntityHistoryModelDaoMapperFactory.java b/util/src/main/java/org/killbill/billing/util/dao/EntityHistoryModelDaoMapperFactory.java
new file mode 100644
index 0000000..38cc98d
--- /dev/null
+++ b/util/src/main/java/org/killbill/billing/util/dao/EntityHistoryModelDaoMapperFactory.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2017 Groupon, Inc
+ * Copyright 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.util.dao;
+
+import org.killbill.commons.jdbi.mapper.LowerToCamelBeanMapper;
+import org.skife.jdbi.v2.ResultSetMapperFactory;
+import org.skife.jdbi.v2.StatementContext;
+import org.skife.jdbi.v2.tweak.ResultSetMapper;
+
+public class EntityHistoryModelDaoMapperFactory implements ResultSetMapperFactory {
+
+ private final Class<?> sqlObjectType;
+ private final Class<?> modelClazz;
+
+ public EntityHistoryModelDaoMapperFactory(final Class modelClazz, final Class<?> sqlObjectType) {
+ this.sqlObjectType = sqlObjectType;
+ this.modelClazz = modelClazz;
+ }
+
+ @Override
+ public boolean accepts(final Class type, final StatementContext ctx) {
+ return type.isAssignableFrom(EntityHistoryModelDao.class) && sqlObjectType.equals(ctx.getSqlObjectType());
+ }
+
+ @Override
+ public ResultSetMapper mapperFor(final Class type, final StatementContext ctx) {
+ return new EntityHistoryModelDaoMapper(new LowerToCamelBeanMapper(modelClazz));
+ }
+}
+
diff --git a/util/src/main/java/org/killbill/billing/util/dao/HistorySqlDao.java b/util/src/main/java/org/killbill/billing/util/dao/HistorySqlDao.java
index c829280..5f6d2d1 100644
--- a/util/src/main/java/org/killbill/billing/util/dao/HistorySqlDao.java
+++ b/util/src/main/java/org/killbill/billing/util/dao/HistorySqlDao.java
@@ -1,7 +1,9 @@
/*
- * Copyright 2010-2011 Ning, Inc.
+ * Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014-2017 Groupon, Inc
+ * Copyright 2014-2017 The Billing Project, LLC
*
- * Ning licenses this file to you under the Apache License, version 2.0
+ * 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:
*
@@ -16,16 +18,24 @@
package org.killbill.billing.util.dao;
-import org.skife.jdbi.v2.sqlobject.BindBean;
-import org.skife.jdbi.v2.sqlobject.SqlUpdate;
+import java.util.List;
import org.killbill.billing.callcontext.InternalCallContext;
import org.killbill.billing.util.entity.Entity;
import org.killbill.billing.util.entity.dao.EntityModelDao;
+import org.skife.jdbi.v2.sqlobject.Bind;
+import org.skife.jdbi.v2.sqlobject.BindBean;
+import org.skife.jdbi.v2.sqlobject.GetGeneratedKeys;
+import org.skife.jdbi.v2.sqlobject.SqlQuery;
+import org.skife.jdbi.v2.sqlobject.SqlUpdate;
public interface HistorySqlDao<M extends EntityModelDao<E>, E extends Entity> {
+ @SqlQuery
+ public List<EntityHistoryModelDao<M, E>> getHistoryForTargetRecordId(@Bind("targetRecordId") final long targetRecordId,
+ @BindBean InternalCallContext context);
@SqlUpdate
- public void addHistoryFromTransaction(@EntityHistoryBinder EntityHistoryModelDao<M, E> history,
+ @GetGeneratedKeys
+ public Long addHistoryFromTransaction(@EntityHistoryBinder EntityHistoryModelDao<M, E> history,
@BindBean InternalCallContext context);
}
diff --git a/util/src/main/java/org/killbill/billing/util/dao/NonEntityDao.java b/util/src/main/java/org/killbill/billing/util/dao/NonEntityDao.java
index 72c9cf2..0261192 100644
--- a/util/src/main/java/org/killbill/billing/util/dao/NonEntityDao.java
+++ b/util/src/main/java/org/killbill/billing/util/dao/NonEntityDao.java
@@ -1,7 +1,7 @@
/*
* Copyright 2010-2013 Ning, Inc.
- * Copyright 2014-2015 Groupon, Inc
- * Copyright 2014-2015 The Billing Project, LLC
+ * 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
@@ -29,21 +29,21 @@ import org.skife.jdbi.v2.Handle;
// This should only be used for internal operations (trusted code, not API), because the context will not be validated!
public interface NonEntityDao {
- public Long retrieveRecordIdFromObject(final UUID objectId, final ObjectType objectType, @Nullable final CacheController<Object, Object> cache);
+ public Long retrieveRecordIdFromObject(final UUID objectId, final ObjectType objectType, @Nullable final CacheController<String, Long> cache);
- public Long retrieveRecordIdFromObjectInTransaction(final UUID objectId, final ObjectType objectType, @Nullable final CacheController<Object, Object> cache, @Nullable final Handle handle);
+ public Long retrieveRecordIdFromObjectInTransaction(final UUID objectId, final ObjectType objectType, @Nullable final CacheController<String, Long> cache, @Nullable final Handle handle);
- public Long retrieveAccountRecordIdFromObject(final UUID objectId, final ObjectType objectType, @Nullable final CacheController<Object, Object> cache);
+ public Long retrieveAccountRecordIdFromObject(final UUID objectId, final ObjectType objectType, @Nullable final CacheController<String, Long> cache);
- public Long retrieveAccountRecordIdFromObjectInTransaction(final UUID objectId, final ObjectType objectType, @Nullable final CacheController<Object, Object> cache, @Nullable final Handle handle);
+ public Long retrieveAccountRecordIdFromObjectInTransaction(final UUID objectId, final ObjectType objectType, @Nullable final CacheController<String, Long> cache, @Nullable final Handle handle);
- public Long retrieveTenantRecordIdFromObject(final UUID objectId, final ObjectType objectType, @Nullable final CacheController<Object, Object> cache);
+ public Long retrieveTenantRecordIdFromObject(final UUID objectId, final ObjectType objectType, @Nullable final CacheController<String, Long> cache);
- public Long retrieveTenantRecordIdFromObjectInTransaction(final UUID objectId, final ObjectType objectType, @Nullable final CacheController<Object, Object> cache, @Nullable final Handle handle);
+ public Long retrieveTenantRecordIdFromObjectInTransaction(final UUID objectId, final ObjectType objectType, @Nullable final CacheController<String, Long> cache, @Nullable final Handle handle);
- public UUID retrieveIdFromObject(final Long recordId, final ObjectType objectType, @Nullable final CacheController<Object, Object> cache);
+ public UUID retrieveIdFromObject(final Long recordId, final ObjectType objectType, @Nullable final CacheController<String, UUID> cache);
- public UUID retrieveIdFromObjectInTransaction(final Long recordId, final ObjectType objectType, @Nullable final CacheController<Object, Object> cache, @Nullable final Handle handle);
+ public UUID retrieveIdFromObjectInTransaction(final Long recordId, final ObjectType objectType, @Nullable final CacheController<String, UUID> cache, @Nullable final Handle handle);
// This retrieves from the history table the latest record for which targetId matches the one we are passing
public Long retrieveLastHistoryRecordIdFromTransaction(final Long targetRecordId, final TableName tableName, final NonEntitySqlDao transactional);
diff --git a/util/src/main/java/org/killbill/billing/util/entity/dao/EntityDaoBase.java b/util/src/main/java/org/killbill/billing/util/entity/dao/EntityDaoBase.java
index 32ad513..f06589f 100644
--- a/util/src/main/java/org/killbill/billing/util/entity/dao/EntityDaoBase.java
+++ b/util/src/main/java/org/killbill/billing/util/entity/dao/EntityDaoBase.java
@@ -1,7 +1,9 @@
/*
- * Copyright 2010-2012 Ning, Inc.
+ * Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014-2017 Groupon, Inc
+ * Copyright 2014-2017 The Billing Project, LLC
*
- * Ning licenses this file to you under the Apache License, version 2.0
+ * 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:
*
@@ -22,6 +24,7 @@ import java.util.UUID;
import org.killbill.billing.BillingExceptionBase;
import org.killbill.billing.callcontext.InternalCallContext;
import org.killbill.billing.callcontext.InternalTenantContext;
+import org.killbill.billing.entity.EntityPersistenceException;
import org.killbill.billing.util.audit.ChangeType;
import org.killbill.billing.util.entity.DefaultPagination;
import org.killbill.billing.util.entity.Entity;
@@ -44,30 +47,35 @@ public abstract class EntityDaoBase<M extends EntityModelDao<E>, E extends Entit
@Override
public void create(final M entity, final InternalCallContext context) throws U {
- transactionalSqlDao.execute(getCreateEntitySqlDaoTransactionWrapper(entity, context));
+ final M refreshedEntity = transactionalSqlDao.execute(getCreateEntitySqlDaoTransactionWrapper(entity, context));
+ // Populate the caches only after the transaction has been committed, in case of rollbacks
+ transactionalSqlDao.populateCaches(refreshedEntity);
}
- protected EntitySqlDaoTransactionWrapper<Void> getCreateEntitySqlDaoTransactionWrapper(final M entity, final InternalCallContext context) {
- return new EntitySqlDaoTransactionWrapper<Void>() {
+ protected EntitySqlDaoTransactionWrapper<M> getCreateEntitySqlDaoTransactionWrapper(final M entity, final InternalCallContext context) {
+ return new EntitySqlDaoTransactionWrapper<M>() {
@Override
- public Void inTransaction(final EntitySqlDaoWrapperFactory entitySqlDaoWrapperFactory) throws Exception {
+ public M inTransaction(final EntitySqlDaoWrapperFactory entitySqlDaoWrapperFactory) throws Exception {
final EntitySqlDao<M, E> transactional = entitySqlDaoWrapperFactory.become(realSqlDao);
if (checkEntityAlreadyExists(transactional, entity, context)) {
throw generateAlreadyExistsException(entity, context);
}
- transactional.create(entity, context);
-
- final M refreshedEntity = transactional.getById(entity.getId().toString(), context);
+ final M refreshedEntity = createAndRefresh(transactional, entity, context);
postBusEventFromTransaction(entity, refreshedEntity, ChangeType.INSERT, entitySqlDaoWrapperFactory, context);
- return null;
+ return refreshedEntity;
}
};
}
+ protected <F extends EntityModelDao> F createAndRefresh(final EntitySqlDao transactional, final F entity, final InternalCallContext context) throws EntityPersistenceException {
+ // We have overridden the jDBI return type in EntitySqlDaoWrapperInvocationHandler
+ return (F) transactional.create(entity, context);
+ }
+
protected boolean checkEntityAlreadyExists(final EntitySqlDao<M, E> transactional, final M entity, final InternalCallContext context) {
- return transactional.getById(entity.getId().toString(), context) != null;
+ return transactional.getRecordId(entity.getId().toString(), context) != null;
}
protected void postBusEventFromTransaction(final M entity, final M savedEntity, final ChangeType changeType,
diff --git a/util/src/main/java/org/killbill/billing/util/entity/dao/EntitySqlDao.java b/util/src/main/java/org/killbill/billing/util/entity/dao/EntitySqlDao.java
index d20a5e7..ede762c 100644
--- a/util/src/main/java/org/killbill/billing/util/entity/dao/EntitySqlDao.java
+++ b/util/src/main/java/org/killbill/billing/util/entity/dao/EntitySqlDao.java
@@ -45,8 +45,8 @@ public interface EntitySqlDao<M extends EntityModelDao<E>, E extends Entity> ext
@SqlUpdate
@Audited(ChangeType.INSERT)
- public void create(@BindBean final M entity,
- @BindBean final InternalCallContext context) throws EntityPersistenceException;
+ public Object create(@BindBean final M entity,
+ @BindBean final InternalCallContext context) throws EntityPersistenceException;
@SqlQuery
public M getById(@Bind("id") final String id,
diff --git a/util/src/main/java/org/killbill/billing/util/entity/dao/EntitySqlDaoStringTemplate.java b/util/src/main/java/org/killbill/billing/util/entity/dao/EntitySqlDaoStringTemplate.java
index 12d9517..ce2fd8d 100644
--- a/util/src/main/java/org/killbill/billing/util/entity/dao/EntitySqlDaoStringTemplate.java
+++ b/util/src/main/java/org/killbill/billing/util/entity/dao/EntitySqlDaoStringTemplate.java
@@ -1,7 +1,9 @@
/*
- * Copyright 2010-2012 Ning, Inc.
+ * Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014-2017 Groupon, Inc
+ * Copyright 2014-2017 The Billing Project, LLC
*
- * Ning licenses this file to you under the Apache License, version 2.0
+ * 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:
*
@@ -28,6 +30,8 @@ import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.regex.Matcher;
+import org.killbill.billing.util.dao.EntityHistoryModelDaoMapperFactory;
+import org.killbill.billing.util.entity.Entity;
import org.killbill.commons.jdbi.mapper.LowerToCamelBeanMapperFactory;
import org.skife.jdbi.v2.Query;
import org.skife.jdbi.v2.SQLStatement;
@@ -38,8 +42,6 @@ import org.skife.jdbi.v2.sqlobject.stringtemplate.StringTemplate3StatementLocato
import org.skife.jdbi.v2.sqlobject.stringtemplate.UseStringTemplate3StatementLocator;
import org.skife.jdbi.v2.tweak.StatementLocator;
-import org.killbill.billing.util.entity.Entity;
-
@SqlStatementCustomizingAnnotation(EntitySqlDaoStringTemplate.EntitySqlDaoLocatorFactory.class)
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@@ -114,6 +116,7 @@ public @interface EntitySqlDaoStringTemplate {
final Class modelClazz = (Class) modelType;
if (Entity.class.isAssignableFrom(modelClazz)) {
query.registerMapper(new LowerToCamelBeanMapperFactory(modelClazz));
+ query.registerMapper(new EntityHistoryModelDaoMapperFactory(modelClazz, sqlObjectType));
}
}
}
diff --git a/util/src/main/java/org/killbill/billing/util/entity/dao/EntitySqlDaoTransactionalJdbiWrapper.java b/util/src/main/java/org/killbill/billing/util/entity/dao/EntitySqlDaoTransactionalJdbiWrapper.java
index e70fcba..b16601d 100644
--- a/util/src/main/java/org/killbill/billing/util/entity/dao/EntitySqlDaoTransactionalJdbiWrapper.java
+++ b/util/src/main/java/org/killbill/billing/util/entity/dao/EntitySqlDaoTransactionalJdbiWrapper.java
@@ -49,6 +49,10 @@ public class EntitySqlDaoTransactionalJdbiWrapper {
this.internalCallContextFactory = internalCallContextFactory;
}
+ public <M extends EntityModelDao> void populateCaches(final M refreshedEntity) {
+ EntitySqlDaoWrapperInvocationHandler.populateCaches(cacheControllerDispatcher, refreshedEntity);
+ }
+
class JdbiTransaction<ReturnType, M extends EntityModelDao<E>, E extends Entity> implements Transaction<ReturnType, EntitySqlDao<M, E>> {
private final Handle h;
diff --git a/util/src/main/java/org/killbill/billing/util/entity/dao/EntitySqlDaoWrapperInvocationHandler.java b/util/src/main/java/org/killbill/billing/util/entity/dao/EntitySqlDaoWrapperInvocationHandler.java
index 872ba10..654d0da 100644
--- a/util/src/main/java/org/killbill/billing/util/entity/dao/EntitySqlDaoWrapperInvocationHandler.java
+++ b/util/src/main/java/org/killbill/billing/util/entity/dao/EntitySqlDaoWrapperInvocationHandler.java
@@ -1,7 +1,7 @@
/*
* Copyright 2010-2012 Ning, Inc.
- * Copyright 2014-2016 Groupon, Inc
- * Copyright 2014-2016 The Billing Project, LLC
+ * 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
@@ -21,6 +21,7 @@ package org.killbill.billing.util.entity.dao;
import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
+import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.sql.PreparedStatement;
import java.sql.SQLException;
@@ -30,6 +31,7 @@ import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
+import java.util.UUID;
import javax.annotation.Nullable;
@@ -47,7 +49,6 @@ import org.killbill.billing.util.callcontext.InternalCallContextFactory;
import org.killbill.billing.util.dao.EntityAudit;
import org.killbill.billing.util.dao.EntityHistoryModelDao;
import org.killbill.billing.util.dao.NonEntityDao;
-import org.killbill.billing.util.dao.NonEntitySqlDao;
import org.killbill.billing.util.dao.TableName;
import org.killbill.billing.util.entity.Entity;
import org.killbill.billing.util.tag.dao.UUIDCollectionBinder;
@@ -61,11 +62,11 @@ import org.skife.jdbi.v2.StatementContext;
import org.skife.jdbi.v2.exceptions.DBIException;
import org.skife.jdbi.v2.exceptions.StatementException;
import org.skife.jdbi.v2.sqlobject.Bind;
-import org.skife.jdbi.v2.sqlobject.SqlObjectBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.base.MoreObjects;
+import com.google.common.base.Preconditions;
import com.google.common.base.Predicate;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableList.Builder;
@@ -91,7 +92,7 @@ public class EntitySqlDaoWrapperInvocationHandler<S extends EntitySqlDao<M, E>,
private final Clock clock;
private final NonEntityDao nonEntityDao;
private final InternalCallContextFactory internalCallContextFactory;
- private final Profiling prof;
+ private final Profiling<Object, Throwable> prof;
public EntitySqlDaoWrapperInvocationHandler(final Class<S> sqlDaoClass,
final S sqlDao,
@@ -114,7 +115,7 @@ public class EntitySqlDaoWrapperInvocationHandler<S extends EntitySqlDao<M, E>,
@Override
public Object invoke(final Object proxy, final Method method, final Object[] args) throws Throwable {
try {
- return prof.executeWithProfiling(ProfilingFeatureType.DAO, sqlDaoClass.getSimpleName() + ":" + method.getName(), new WithProfilingCallback() {
+ return prof.executeWithProfiling(ProfilingFeatureType.DAO, getProfilingId(null, method), new WithProfilingCallback<Object, Throwable>() {
@Override
public Object execute() throws Throwable {
return invokeSafely(proxy, method, args);
@@ -201,7 +202,7 @@ public class EntitySqlDaoWrapperInvocationHandler<S extends EntitySqlDao<M, E>,
}
private Object invokeRaw(final Method method, final Object[] args) throws Throwable {
- return prof.executeWithProfiling(ProfilingFeatureType.DAO_DETAILS, sqlDaoClass.getSimpleName() + " (raw):" + method.getName(), new WithProfilingCallback() {
+ return prof.executeWithProfiling(ProfilingFeatureType.DAO_DETAILS, getProfilingId("raw", method), new WithProfilingCallback<Object, Throwable>() {
@Override
public Object execute() throws Throwable {
Object result = method.invoke(sqlDao, args);
@@ -222,8 +223,8 @@ public class EntitySqlDaoWrapperInvocationHandler<S extends EntitySqlDao<M, E>,
final ObjectType objectType = getObjectType();
final CacheType cacheType = cachableAnnotation.value();
final CacheController<Object, Object> cache = cacheControllerDispatcher.getCacheController(cacheType);
- Object result = null;
- if (cache != null) {
+ // TODO Change NonEntityDao to take in TableName instead to cache things like TenantBroadcastModelDao (no ObjectType)
+ if (cache != null && objectType != null) {
// Find all arguments marked with @CachableKey
final Map<Integer, Object> keyPieces = new LinkedHashMap<Integer, Object>();
final Annotation[][] annotations = method.getParameterAnnotations();
@@ -249,16 +250,9 @@ public class EntitySqlDaoWrapperInvocationHandler<S extends EntitySqlDao<M, E>,
}, null);
final CacheLoaderArgument cacheLoaderArgument = new CacheLoaderArgument(objectType, args, internalTenantContext, handle);
return cache.get(cacheKey, cacheLoaderArgument);
+ } else {
+ return invokeRaw(method, args);
}
- if (result == null) {
- result = prof.executeWithProfiling(ProfilingFeatureType.DAO_DETAILS, sqlDaoClass.getSimpleName() + "(raw) :" + method.getName(), new WithProfilingCallback() {
- @Override
- public Object execute() throws Throwable {
- return method.invoke(sqlDao, args);
- }
- });
- }
- return result;
}
/**
@@ -292,9 +286,14 @@ public class EntitySqlDaoWrapperInvocationHandler<S extends EntitySqlDao<M, E>,
int foundIndexForEntityModelDao = -1;
for (int i = 0; i < types.length; i++) {
final Class clz = ((Class) types[i]);
- if (EntityModelDao.class.getName().equals(((Class) ((java.lang.reflect.ParameterizedType) clz.getGenericInterfaces()[0]).getRawType()).getName())) {
- foundIndexForEntityModelDao = i;
- break;
+ final Type[] genericInterfaces = clz.getGenericInterfaces();
+ for (final Type genericInterface : genericInterfaces) {
+ if (genericInterface instanceof ParameterizedType) {
+ if (EntityModelDao.class.getName().equals(((Class) ((ParameterizedType) genericInterface).getRawType()).getName())) {
+ foundIndexForEntityModelDao = i;
+ break;
+ }
+ }
}
}
@@ -311,87 +310,101 @@ public class EntitySqlDaoWrapperInvocationHandler<S extends EntitySqlDao<M, E>,
}
private Object invokeWithAuditAndHistory(final Audited auditedAnnotation, final Method method, final Object[] args) throws Throwable {
- InternalCallContext context = null;
- List<String> entityIds = null;
- final Map<String, M> entities = new HashMap<String, M>();
- final Map<String, Long> entityRecordIds = new HashMap<String, Long>();
- if (auditedAnnotation != null) {
- // There will be some work required after the statement is executed,
- // get the id before in case the change is a delete
- context = retrieveContextFromArguments(args);
- entityIds = retrieveEntityIdsFromArguments(method, args);
+ final InternalCallContext context = retrieveContextFromArguments(args);
+ final List<String> entityIds = retrieveEntityIdsFromArguments(method, args);
+
+ final ChangeType changeType = auditedAnnotation.value();
+
+ // Get the current state before deletion for the history tables
+ final Map<String, M> deletedEntities = new HashMap<String, M>();
+ // Unfortunately, we cannot just look at DELETE as "markAsInactive" operations are often treated as UPDATE
+ if (changeType == ChangeType.UPDATE || changeType == ChangeType.DELETE) {
for (final String entityId : entityIds) {
- entities.put(entityId, sqlDao.getById(entityId, context));
- entityRecordIds.put(entityId, sqlDao.getRecordId(entityId, context));
+ deletedEntities.put(entityId, sqlDao.getById(entityId, context));
}
}
// Real jdbc call
- final Object obj = prof.executeWithProfiling(ProfilingFeatureType.DAO_DETAILS, sqlDaoClass.getSimpleName() + " (raw) :", new WithProfilingCallback() {
+ final Object obj = prof.executeWithProfiling(ProfilingFeatureType.DAO_DETAILS, getProfilingId("raw", method), new WithProfilingCallback<Object, Throwable>() {
@Override
public Object execute() throws Throwable {
return method.invoke(sqlDao, args);
}
});
- final ChangeType changeType = auditedAnnotation.value();
-
+ M m = null;
for (final String entityId : entityIds) {
- updateHistoryAndAudit(entityId, entities, entityRecordIds, changeType, context);
+ m = updateHistoryAndAudit(entityId, deletedEntities.get(entityId), changeType, context);
+ }
+
+ // PERF: override the return value with the reHydrated entity to avoid an extra 'get' in the transaction,
+ // (see EntityDaoBase#createAndRefresh for an example, but it works for updates as well).
+ if (entityIds.size() == 1) {
+ return m;
+ } else {
+ // jDBI will return the number of rows modified otherwise
+ return obj;
}
- return obj;
}
- private void populateCacheOnGetByIdInvocation(M model) {
+ private void populateCacheOnGetByIdInvocation(final M model) {
+ populateCaches(cacheControllerDispatcher, model);
+ }
- final CacheController<Object, Object> cacheRecordId = cacheControllerDispatcher.getCacheController(CacheType.RECORD_ID);
- cacheRecordId.add(getKey(model.getId().toString(), CacheType.RECORD_ID, model.getTableName()), model.getRecordId());
+ public static void populateCaches(final CacheControllerDispatcher cacheControllerDispatcher, final EntityModelDao model) {
+ final CacheController<String, Long> cacheRecordId = cacheControllerDispatcher.getCacheController(CacheType.RECORD_ID);
+ cacheRecordId.putIfAbsent(getKey(model.getId().toString(), CacheType.RECORD_ID, model.getTableName()), model.getRecordId());
- final CacheController<Object, Object> cacheObjectId = cacheControllerDispatcher.getCacheController(CacheType.OBJECT_ID);
- cacheObjectId.add(getKey(model.getRecordId().toString(), CacheType.OBJECT_ID, model.getTableName()), model.getId());
+ final CacheController<String, UUID> cacheObjectId = cacheControllerDispatcher.getCacheController(CacheType.OBJECT_ID);
+ cacheObjectId.putIfAbsent(getKey(model.getRecordId().toString(), CacheType.OBJECT_ID, model.getTableName()), model.getId());
if (model.getTenantRecordId() != null) {
- final CacheController<Object, Object> cacheTenantRecordId = cacheControllerDispatcher.getCacheController(CacheType.TENANT_RECORD_ID);
- cacheTenantRecordId.add(getKey(model.getId().toString(), CacheType.TENANT_RECORD_ID, model.getTableName()), model.getTenantRecordId());
+ final CacheController<String, Long> cacheTenantRecordId = cacheControllerDispatcher.getCacheController(CacheType.TENANT_RECORD_ID);
+ cacheTenantRecordId.putIfAbsent(getKey(model.getId().toString(), CacheType.TENANT_RECORD_ID, model.getTableName()), model.getTenantRecordId());
}
if (model.getAccountRecordId() != null) {
- final CacheController<Object, Object> cacheAccountRecordId = cacheControllerDispatcher.getCacheController(CacheType.ACCOUNT_RECORD_ID);
- cacheAccountRecordId.add(getKey(model.getId().toString(), CacheType.ACCOUNT_RECORD_ID, model.getTableName()), model.getAccountRecordId());
+ final CacheController<String, Long> cacheAccountRecordId = cacheControllerDispatcher.getCacheController(CacheType.ACCOUNT_RECORD_ID);
+ cacheAccountRecordId.putIfAbsent(getKey(model.getId().toString(), CacheType.ACCOUNT_RECORD_ID, model.getTableName()), model.getAccountRecordId());
}
}
- private String getKey(final String rawKey, final CacheType cacheType, final TableName tableName) {
+ private static String getKey(final String rawKey, final CacheType cacheType, final TableName tableName) {
return cacheType.isKeyPrefixedWithTableName() ?
tableName + CacheControllerDispatcher.CACHE_KEY_SEPARATOR + rawKey :
rawKey;
}
- private void updateHistoryAndAudit(final String entityId, final Map<String, M> entities, final Map<String, Long> entityRecordIds,
- final ChangeType changeType, final InternalCallContext context) throws Throwable {
-
- prof.executeWithProfiling(ProfilingFeatureType.DAO_DETAILS, sqlDaoClass.getSimpleName() + " (history/audit) :", new WithProfilingCallback() {
+ private M updateHistoryAndAudit(final String entityId, @Nullable final M deletedEntity, final ChangeType changeType, final InternalCallContext context) throws Throwable {
+ final Object reHydratedEntity = prof.executeWithProfiling(ProfilingFeatureType.DAO_DETAILS, getProfilingId("history/audit", null), new WithProfilingCallback<Object, Throwable>() {
@Override
- public Object execute() {
- final M reHydratedEntity = sqlDao.getById(entityId, context);
- final Long reHydratedEntityRecordId = sqlDao.getRecordId(entityId, context);
- final M entity = MoreObjects.firstNonNull(reHydratedEntity, entities.get(entityId));
- final Long entityRecordId = MoreObjects.firstNonNull(reHydratedEntityRecordId, entityRecordIds.get(entityId));
- final TableName tableName = entity.getTableName();
+ public M execute() throws Throwable {
+ final M reHydratedEntity;
+ if (changeType == ChangeType.DELETE) {
+ reHydratedEntity = deletedEntity;
+ } else {
+ // See note above regarding "markAsInactive" operations
+ reHydratedEntity = MoreObjects.firstNonNull(sqlDao.getById(entityId, context), deletedEntity);
+ }
+ Preconditions.checkNotNull(reHydratedEntity, "reHydratedEntity cannot be null");
+ final Long entityRecordId = reHydratedEntity.getRecordId();
+ final TableName tableName = reHydratedEntity.getTableName();
// Note: audit entries point to the history record id
final Long historyRecordId;
if (tableName.getHistoryTableName() != null) {
- historyRecordId = insertHistory(entityRecordId, entity, changeType, context);
+ historyRecordId = insertHistory(entityRecordId, reHydratedEntity, changeType, context);
} else {
historyRecordId = entityRecordId;
}
// Make sure to re-hydrate the object (especially needed for create calls)
- insertAudits(tableName, entityRecordId, historyRecordId, changeType, context);
- return null;
+ insertAudits(tableName, reHydratedEntity, entityRecordId, historyRecordId, changeType, context);
+ return reHydratedEntity;
}
});
+ //noinspection unchecked
+ return (M) reHydratedEntity;
}
private List<String> retrieveEntityIdsFromArguments(final Method method, final Object[] args) {
@@ -422,7 +435,7 @@ public class EntitySqlDaoWrapperInvocationHandler<S extends EntitySqlDao<M, E>,
}
}
}
- return null;
+ return ImmutableList.<String>of();
}
private Builder<String> extractEntityIdsFromBatchArgument(final Iterable arg) {
@@ -453,23 +466,19 @@ public class EntitySqlDaoWrapperInvocationHandler<S extends EntitySqlDao<M, E>,
private Long insertHistory(final Long entityRecordId, final M entityModelDao, final ChangeType changeType, final InternalCallContext context) {
final EntityHistoryModelDao<M, E> history = new EntityHistoryModelDao<M, E>(entityModelDao, entityRecordId, changeType, clock.getUTCNow());
-
- sqlDao.addHistoryFromTransaction(history, context);
-
- final NonEntitySqlDao transactional = SqlObjectBuilder.attach(handle, NonEntitySqlDao.class);
-
- /* return transactional.getLastHistoryRecordId(entityRecordId, entityModelDao.getHistoryTableName().getTableName()); */
- return nonEntityDao.retrieveLastHistoryRecordIdFromTransaction(entityRecordId, entityModelDao.getHistoryTableName(), transactional);
+ return sqlDao.addHistoryFromTransaction(history, context);
}
- private void insertAudits(final TableName tableName, final Long entityRecordId, final Long historyRecordId, final ChangeType changeType, final InternalCallContext contextMaybeWithoutAccountRecordId) {
+ private void insertAudits(final TableName tableName, final M entityModelDao, final Long entityRecordId, final Long historyRecordId, final ChangeType changeType, final InternalCallContext contextMaybeWithoutAccountRecordId) {
final TableName destinationTableName = MoreObjects.firstNonNull(tableName.getHistoryTableName(), tableName);
final EntityAudit audit = new EntityAudit(destinationTableName, historyRecordId, changeType, clock.getUTCNow());
final InternalCallContext context;
// Populate the account record id when creating the account record
if (TableName.ACCOUNT.equals(tableName) && ChangeType.INSERT.equals(changeType)) {
- context = internalCallContextFactory.createInternalCallContext(entityRecordId, contextMaybeWithoutAccountRecordId);
+ // AccountModelDao in practice
+ final TimeZoneAwareEntity accountModelDao = (TimeZoneAwareEntity) entityModelDao;
+ context = internalCallContextFactory.createInternalCallContext(accountModelDao, entityRecordId, contextMaybeWithoutAccountRecordId);
} else {
context = contextMaybeWithoutAccountRecordId;
}
@@ -478,13 +487,13 @@ public class EntitySqlDaoWrapperInvocationHandler<S extends EntitySqlDao<M, E>,
// We need to invalidate the caches. There is a small window of doom here where caches will be stale.
// TODO Knowledge on how the key is constructed is also in AuditSqlDao
if (tableName.getHistoryTableName() != null) {
- final CacheController<Object, Object> cacheController = cacheControllerDispatcher.getCacheController(CacheType.AUDIT_LOG_VIA_HISTORY);
+ final CacheController<String, List> cacheController = cacheControllerDispatcher.getCacheController(CacheType.AUDIT_LOG_VIA_HISTORY);
if (cacheController != null) {
final String key = buildCacheKey(ImmutableMap.<Integer, Object>of(0, tableName.getHistoryTableName(), 1, tableName.getHistoryTableName(), 2, entityRecordId));
cacheController.remove(key);
}
} else {
- final CacheController<Object, Object> cacheController = cacheControllerDispatcher.getCacheController(CacheType.AUDIT_LOG);
+ final CacheController<String, List> cacheController = cacheControllerDispatcher.getCacheController(CacheType.AUDIT_LOG);
if (cacheController != null) {
final String key = buildCacheKey(ImmutableMap.<Integer, Object>of(0, tableName, 1, entityRecordId));
cacheController.remove(key);
@@ -506,4 +515,20 @@ public class EntitySqlDaoWrapperInvocationHandler<S extends EntitySqlDao<M, E>,
}
return cacheKey.toString();
}
+
+ private String getProfilingId(@Nullable final String prefix, @Nullable final Method method) {
+ final StringBuilder stringBuilder = new StringBuilder().append(sqlDaoClass.getSimpleName());
+
+ if (prefix != null) {
+ stringBuilder.append(" (")
+ .append(prefix)
+ .append(")");
+ }
+
+ if (method != null) {
+ stringBuilder.append(": ").append(method.getName());
+ }
+
+ return stringBuilder.toString();
+ }
}
diff --git a/util/src/main/java/org/killbill/billing/util/entity/dao/TimeZoneAwareEntity.java b/util/src/main/java/org/killbill/billing/util/entity/dao/TimeZoneAwareEntity.java
new file mode 100644
index 0000000..feff107
--- /dev/null
+++ b/util/src/main/java/org/killbill/billing/util/entity/dao/TimeZoneAwareEntity.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2017 Groupon, Inc
+ * Copyright 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.util.entity.dao;
+
+import org.joda.time.DateTimeZone;
+import org.killbill.billing.util.entity.Entity;
+
+public interface TimeZoneAwareEntity extends Entity {
+
+ public DateTimeZone getTimeZone();
+}
diff --git a/util/src/main/java/org/killbill/billing/util/glue/CacheModule.java b/util/src/main/java/org/killbill/billing/util/glue/CacheModule.java
index 5a3b660..d4ddf25 100644
--- a/util/src/main/java/org/killbill/billing/util/glue/CacheModule.java
+++ b/util/src/main/java/org/killbill/billing/util/glue/CacheModule.java
@@ -18,14 +18,31 @@
package org.killbill.billing.util.glue;
+import javax.cache.CacheManager;
+
import org.killbill.billing.platform.api.KillbillConfigSource;
+import org.killbill.billing.util.cache.AccountBCDCacheLoader;
+import org.killbill.billing.util.cache.AccountRecordIdCacheLoader;
+import org.killbill.billing.util.cache.AuditLogCacheLoader;
+import org.killbill.billing.util.cache.AuditLogViaHistoryCacheLoader;
+import org.killbill.billing.util.cache.BaseCacheLoader;
import org.killbill.billing.util.cache.CacheControllerDispatcher;
import org.killbill.billing.util.cache.CacheControllerDispatcherProvider;
-import org.killbill.billing.util.cache.EhCacheCacheManagerProvider;
+import org.killbill.billing.util.cache.ImmutableAccountCacheLoader;
+import org.killbill.billing.util.cache.ObjectIdCacheLoader;
+import org.killbill.billing.util.cache.OverriddenPlanCacheLoader;
+import org.killbill.billing.util.cache.RecordIdCacheLoader;
+import org.killbill.billing.util.cache.TenantCacheLoader;
+import org.killbill.billing.util.cache.TenantCatalogCacheLoader;
+import org.killbill.billing.util.cache.TenantConfigCacheLoader;
+import org.killbill.billing.util.cache.TenantKVCacheLoader;
+import org.killbill.billing.util.cache.TenantOverdueConfigCacheLoader;
+import org.killbill.billing.util.cache.TenantRecordIdCacheLoader;
+import org.killbill.billing.util.cache.TenantStateMachineConfigCacheLoader;
import org.killbill.billing.util.config.definition.EhCacheConfig;
import org.skife.config.ConfigurationObjectFactory;
-import net.sf.ehcache.CacheManager;
+import com.google.inject.multibindings.Multibinder;
public class CacheModule extends KillBillModule {
@@ -39,9 +56,26 @@ public class CacheModule extends KillBillModule {
bind(EhCacheConfig.class).toInstance(config);
// EhCache specifics
- bind(CacheManager.class).toProvider(EhCacheCacheManagerProvider.class).asEagerSingleton();
+ bind(CacheManager.class).toProvider(Eh107CacheManagerProvider.class).asEagerSingleton();
// Kill Bill generic cache dispatcher
bind(CacheControllerDispatcher.class).toProvider(CacheControllerDispatcherProvider.class).asEagerSingleton();
+
+ final Multibinder<BaseCacheLoader> resultSetMapperSetBinder = Multibinder.newSetBinder(binder(), BaseCacheLoader.class);
+ resultSetMapperSetBinder.addBinding().to(ImmutableAccountCacheLoader.class).asEagerSingleton();
+ resultSetMapperSetBinder.addBinding().to(AccountBCDCacheLoader.class).asEagerSingleton();
+ resultSetMapperSetBinder.addBinding().to(RecordIdCacheLoader.class).asEagerSingleton();
+ resultSetMapperSetBinder.addBinding().to(AccountRecordIdCacheLoader.class).asEagerSingleton();
+ resultSetMapperSetBinder.addBinding().to(TenantRecordIdCacheLoader.class).asEagerSingleton();
+ resultSetMapperSetBinder.addBinding().to(ObjectIdCacheLoader.class).asEagerSingleton();
+ resultSetMapperSetBinder.addBinding().to(AuditLogCacheLoader.class).asEagerSingleton();
+ resultSetMapperSetBinder.addBinding().to(AuditLogViaHistoryCacheLoader.class).asEagerSingleton();
+ resultSetMapperSetBinder.addBinding().to(TenantCatalogCacheLoader.class).asEagerSingleton();
+ resultSetMapperSetBinder.addBinding().to(TenantConfigCacheLoader.class).asEagerSingleton();
+ resultSetMapperSetBinder.addBinding().to(TenantOverdueConfigCacheLoader.class).asEagerSingleton();
+ resultSetMapperSetBinder.addBinding().to(TenantKVCacheLoader.class).asEagerSingleton();
+ resultSetMapperSetBinder.addBinding().to(TenantCacheLoader.class).asEagerSingleton();
+ resultSetMapperSetBinder.addBinding().to(OverriddenPlanCacheLoader.class).asEagerSingleton();
+ resultSetMapperSetBinder.addBinding().to(TenantStateMachineConfigCacheLoader.class).asEagerSingleton();
}
}
diff --git a/util/src/main/java/org/killbill/billing/util/glue/CacheProviderBase.java b/util/src/main/java/org/killbill/billing/util/glue/CacheProviderBase.java
new file mode 100644
index 0000000..24f2dbf
--- /dev/null
+++ b/util/src/main/java/org/killbill/billing/util/glue/CacheProviderBase.java
@@ -0,0 +1,76 @@
+/*
+ * 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.util.glue;
+
+import java.io.IOException;
+import java.net.URISyntaxException;
+import java.net.URL;
+
+import javax.cache.Cache;
+import javax.cache.CacheManager;
+import javax.cache.configuration.MutableConfiguration;
+
+import org.killbill.billing.util.config.definition.EhCacheConfig;
+import org.killbill.xmlloader.UriAccessor;
+
+import com.codahale.metrics.Metric;
+import com.codahale.metrics.MetricFilter;
+import com.codahale.metrics.MetricRegistry;
+import com.codahale.metrics.jcache.JCacheGaugeSet;
+
+abstract class CacheProviderBase {
+
+ private static final String PROP_METRIC_REG_JCACHE_STATISTICS = "jcache.statistics";
+
+ private final MetricRegistry metricRegistry;
+
+ final URL xmlConfigurationURL;
+
+ CacheProviderBase(final MetricRegistry metricRegistry, final EhCacheConfig cacheConfig) {
+ this.metricRegistry = metricRegistry;
+
+ try {
+ xmlConfigurationURL = UriAccessor.toURL(cacheConfig.getCacheConfigLocation());
+ } catch (final IOException e) {
+ throw new RuntimeException(e);
+ } catch (final URISyntaxException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ <K, V> Cache<K, V> createCache(final CacheManager cacheManager, final String cacheName, final Class<K> keyType, final Class<V> valueType) {
+ // Make sure we start from a clean state - this is mainly useful for tests
+ cacheManager.destroyCache(cacheName);
+
+ // All other configuration options come from the ehcache.xml
+ final MutableConfiguration<K, V> configuration = new MutableConfiguration<K, V>().setTypes(keyType, valueType)
+ .setStoreByValue(false); // Store by reference to avoid copying large objects (e.g. catalog)
+ final Cache<K, V> cache = cacheManager.createCache(cacheName, configuration);
+
+ // Re-create the metrics to support dynamically created caches (e.g. for Shiro)
+ metricRegistry.removeMatching(new MetricFilter() {
+ @Override
+ public boolean matches(final String name, final Metric metric) {
+ return name != null && name.startsWith(PROP_METRIC_REG_JCACHE_STATISTICS);
+ }
+ });
+ metricRegistry.register(PROP_METRIC_REG_JCACHE_STATISTICS, new JCacheGaugeSet());
+
+ return cache;
+ }
+}
diff --git a/util/src/main/java/org/killbill/billing/util/glue/Eh107CacheManagerProvider.java b/util/src/main/java/org/killbill/billing/util/glue/Eh107CacheManagerProvider.java
new file mode 100644
index 0000000..005afa3
--- /dev/null
+++ b/util/src/main/java/org/killbill/billing/util/glue/Eh107CacheManagerProvider.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright 2010-2012 Ning, Inc.
+ * 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.util.glue;
+
+import java.net.URISyntaxException;
+import java.util.Set;
+
+import javax.cache.CacheManager;
+import javax.cache.Caching;
+import javax.cache.spi.CachingProvider;
+import javax.inject.Inject;
+import javax.inject.Provider;
+
+import org.killbill.billing.util.cache.BaseCacheLoader;
+import org.killbill.billing.util.config.definition.EhCacheConfig;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.codahale.metrics.MetricRegistry;
+
+// EhCache specific provider
+public class Eh107CacheManagerProvider extends CacheProviderBase implements Provider<CacheManager> {
+
+ private static final Logger logger = LoggerFactory.getLogger(Eh107CacheManagerProvider.class);
+
+ private final Set<BaseCacheLoader> cacheLoaders;
+
+ @Inject
+ public Eh107CacheManagerProvider(final MetricRegistry metricRegistry,
+ final EhCacheConfig cacheConfig,
+ final Set<BaseCacheLoader> cacheLoaders) {
+ super(metricRegistry, cacheConfig);
+ this.cacheLoaders = cacheLoaders;
+ }
+
+ @Override
+ public CacheManager get() {
+ // JSR-107 registration, required for JMX integration
+ final CachingProvider cachingProvider = Caching.getCachingProvider();
+
+ CacheManager cacheManager;
+ try {
+ cacheManager = cachingProvider.getCacheManager(xmlConfigurationURL.toURI(), getClass().getClassLoader());
+ } catch (final RuntimeException e) {
+ logger.error("Unable to read ehcache.xml, using default configuration", e);
+ cacheManager = cachingProvider.getCacheManager();
+ } catch (final URISyntaxException e) {
+ logger.error("Unable to read ehcache.xml, using default configuration", e);
+ cacheManager = cachingProvider.getCacheManager();
+ }
+
+ for (final BaseCacheLoader<?, ?> cacheLoader : cacheLoaders) {
+ createCache(cacheManager,
+ cacheLoader.getCacheType().getCacheName(),
+ cacheLoader.getCacheType().getKeyType(),
+ cacheLoader.getCacheType().getValueType());
+ }
+
+ return cacheManager;
+ }
+}
diff --git a/util/src/main/java/org/killbill/billing/util/glue/EhcacheShiroManagerProvider.java b/util/src/main/java/org/killbill/billing/util/glue/EhcacheShiroManagerProvider.java
new file mode 100644
index 0000000..943450c
--- /dev/null
+++ b/util/src/main/java/org/killbill/billing/util/glue/EhcacheShiroManagerProvider.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright 2010-2013 Ning, Inc.
+ * 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.util.glue;
+
+import java.lang.reflect.Field;
+
+import javax.cache.CacheManager;
+import javax.inject.Inject;
+import javax.inject.Provider;
+
+import org.apache.shiro.cache.Cache;
+import org.apache.shiro.cache.CacheException;
+import org.apache.shiro.mgt.DefaultSecurityManager;
+import org.apache.shiro.mgt.SecurityManager;
+import org.ehcache.integrations.shiro.EhcacheShiro;
+import org.ehcache.integrations.shiro.EhcacheShiroManager;
+import org.killbill.billing.util.config.definition.EhCacheConfig;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.codahale.metrics.MetricRegistry;
+
+public class EhcacheShiroManagerProvider extends CacheProviderBase implements Provider<EhcacheShiroManager> {
+
+ private final SecurityManager securityManager;
+ private final CacheManager eh107CacheManager;
+ private final org.ehcache.CacheManager ehcacheCacheManager;
+
+ @Inject
+ public EhcacheShiroManagerProvider(final SecurityManager securityManager,
+ final CacheManager eh107CacheManager,
+ final MetricRegistry metricRegistry,
+ final EhCacheConfig cacheConfig) {
+ super(metricRegistry, cacheConfig);
+ this.securityManager = securityManager;
+ this.eh107CacheManager = eh107CacheManager;
+ this.ehcacheCacheManager = getEhcacheManager();
+ }
+
+ @Override
+ public EhcacheShiroManager get() {
+ final EhcacheShiroManager shiroEhCacheManager = new EhcacheShiroManagerWrapper(this);
+ // Same EhCache manager instance as the rest of the system
+ shiroEhCacheManager.setCacheManager(ehcacheCacheManager);
+
+ if (securityManager instanceof DefaultSecurityManager) {
+ // For RBAC only (see also KillbillJdbcTenantRealmProvider)
+ final DefaultSecurityManager securityManager = (DefaultSecurityManager) this.securityManager;
+ securityManager.setCacheManager(shiroEhCacheManager);
+ securityManager.setSubjectDAO(new KillBillSubjectDAO());
+ }
+
+ return shiroEhCacheManager;
+ }
+
+ // Shiro isn't JCache compatible
+ private org.ehcache.CacheManager getEhcacheManager() {
+ try {
+ final Field f = eh107CacheManager.getClass().getDeclaredField("ehCacheManager");
+ f.setAccessible(true);
+
+ return (org.ehcache.CacheManager) f.get(eh107CacheManager);
+ } catch (final IllegalAccessException e) {
+ throw new RuntimeException(e);
+ } catch (final NoSuchFieldException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ // Custom createCache implementation going through JCache layer to enable stats, etc.
+ private final class EhcacheShiroManagerWrapper extends EhcacheShiroManager {
+
+ private final Logger log = LoggerFactory.getLogger(EhcacheShiroManagerWrapper.class);
+
+ private final EhcacheShiroManagerProvider ehcacheShiroManagerProvider;
+
+ EhcacheShiroManagerWrapper(final EhcacheShiroManagerProvider ehcacheShiroManagerProvider) {
+ this.ehcacheShiroManagerProvider = ehcacheShiroManagerProvider;
+ }
+
+ public <K, V> Cache<K, V> getCache(final String name) throws CacheException {
+ log.trace("Acquiring EhcacheShiro instance named [{}]", name);
+
+ org.ehcache.Cache<Object, Object> cache = getCacheManager().getCache(name, Object.class, Object.class);
+
+ if (cache == null) {
+ log.info("Cache with name {} does not yet exist. Creating now.", name);
+ ehcacheShiroManagerProvider.createCache(eh107CacheManager, name, Object.class, Object.class);
+ cache = getCacheManager().getCache(name, Object.class, Object.class);
+ log.info("Added EhcacheShiro named [{}]", name);
+ } else {
+ log.info("Using existing EhcacheShiro named [{}]", name);
+ }
+
+ return new EhcacheShiro<K, V>(cache);
+ }
+ }
+}
diff --git a/util/src/main/java/org/killbill/billing/util/glue/KillBillShiroModule.java b/util/src/main/java/org/killbill/billing/util/glue/KillBillShiroModule.java
index 4e48913..bd9ceb2 100644
--- a/util/src/main/java/org/killbill/billing/util/glue/KillBillShiroModule.java
+++ b/util/src/main/java/org/killbill/billing/util/glue/KillBillShiroModule.java
@@ -86,7 +86,7 @@ public class KillBillShiroModule extends ShiroModule {
super.bindSecurityManager(bind);
// Magic provider to configure the cache manager
- bind(CacheManager.class).toProvider(EhCacheManagerProvider.class).asEagerSingleton();
+ bind(CacheManager.class).toProvider(EhcacheShiroManagerProvider.class).asEagerSingleton();
}
@Override
diff --git a/util/src/main/java/org/killbill/billing/util/glue/KillBillSubjectDAO.java b/util/src/main/java/org/killbill/billing/util/glue/KillBillSubjectDAO.java
new file mode 100644
index 0000000..176956f
--- /dev/null
+++ b/util/src/main/java/org/killbill/billing/util/glue/KillBillSubjectDAO.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright 2017 Groupon, Inc
+ * Copyright 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.util.glue;
+
+import org.apache.shiro.mgt.DefaultSubjectDAO;
+import org.apache.shiro.mgt.SessionsSecurityManager;
+import org.apache.shiro.session.Session;
+import org.apache.shiro.session.mgt.DefaultSessionManager;
+import org.apache.shiro.subject.Subject;
+import org.apache.shiro.subject.support.DelegatingSubject;
+import org.apache.shiro.util.CollectionUtils;
+import org.killbill.billing.util.security.shiro.dao.JDBCSessionDao;
+
+public class KillBillSubjectDAO extends DefaultSubjectDAO {
+
+ @Override
+ protected void saveToSession(final Subject subject) {
+ boolean updatesDisabled = false;
+
+ Session session = subject.getSession(false);
+ if (session == null && !CollectionUtils.isEmpty(subject.getPrincipals())) {
+ // Force the creation of the session here to get the id
+ session = subject.getSession();
+ // Optimize the session creation path: the default saveToSession implementation
+ // will call setAttribute() several times in a row, causing unnecessary DAO UPDATE queries
+ updatesDisabled = disableUpdatesForSession(subject, session);
+ }
+
+ super.saveToSession(subject);
+
+ if (updatesDisabled) {
+ enableUpdatesForSession(subject, session);
+ }
+ }
+
+ private boolean disableUpdatesForSession(final Subject subject, final Session session) {
+ final JDBCSessionDao sessionDAO = getJDBCSessionDao(subject);
+ if (sessionDAO != null) {
+ sessionDAO.disableUpdatesForSession(session);
+ return true;
+ }
+ return false;
+ }
+
+ private void enableUpdatesForSession(final Subject subject, final Session session) {
+ final JDBCSessionDao sessionDAO = getJDBCSessionDao(subject);
+ if (sessionDAO != null) {
+ sessionDAO.enableUpdatesForSession(session);
+ }
+ }
+
+ private JDBCSessionDao getJDBCSessionDao(final Subject subject) {
+ if (subject instanceof DelegatingSubject) {
+ final DelegatingSubject delegatingSubject = (DelegatingSubject) subject;
+ if (delegatingSubject.getSecurityManager() instanceof SessionsSecurityManager) {
+ final SessionsSecurityManager securityManager = (SessionsSecurityManager) delegatingSubject.getSecurityManager();
+ if (securityManager.getSessionManager() instanceof DefaultSessionManager) {
+ final DefaultSessionManager sessionManager = (DefaultSessionManager) securityManager.getSessionManager();
+ if (sessionManager.getSessionDAO() instanceof JDBCSessionDao) {
+ return (JDBCSessionDao) sessionManager.getSessionDAO();
+ }
+ }
+ }
+ }
+
+ return null;
+ }
+}
diff --git a/util/src/main/java/org/killbill/billing/util/jackson/ObjectMapper.java b/util/src/main/java/org/killbill/billing/util/jackson/ObjectMapper.java
index 2e69423..4ff3026 100644
--- a/util/src/main/java/org/killbill/billing/util/jackson/ObjectMapper.java
+++ b/util/src/main/java/org/killbill/billing/util/jackson/ObjectMapper.java
@@ -1,7 +1,9 @@
/*
- * Copyright 2010-2011 Ning, Inc.
+ * Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014-2017 Groupon, Inc
+ * Copyright 2014-2017 The Billing Project, LLC
*
- * Ning licenses this file to you under the Apache License, version 2.0
+ * 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:
*
@@ -17,11 +19,18 @@
package org.killbill.billing.util.jackson;
import com.fasterxml.jackson.databind.SerializationFeature;
+import com.fasterxml.jackson.dataformat.smile.SmileFactory;
import com.fasterxml.jackson.datatype.joda.JodaModule;
public class ObjectMapper extends com.fasterxml.jackson.databind.ObjectMapper {
- public ObjectMapper() {
+
+ public ObjectMapper(final SmileFactory f) {
+ super(f);
this.registerModule(new JodaModule());
this.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
}
+
+ public ObjectMapper() {
+ this(null);
+ }
}
diff --git a/util/src/main/java/org/killbill/billing/util/security/shiro/dao/JDBCSessionDao.java b/util/src/main/java/org/killbill/billing/util/security/shiro/dao/JDBCSessionDao.java
index 41a05eb..a6a5a34 100644
--- a/util/src/main/java/org/killbill/billing/util/security/shiro/dao/JDBCSessionDao.java
+++ b/util/src/main/java/org/killbill/billing/util/security/shiro/dao/JDBCSessionDao.java
@@ -1,7 +1,7 @@
/*
* Copyright 2010-2013 Ning, Inc.
- * Copyright 2014-2016 Groupon, Inc
- * Copyright 2014-2016 The Billing Project, LLC
+ * 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
@@ -21,6 +21,7 @@ package org.killbill.billing.util.security.shiro.dao;
import java.io.IOException;
import java.io.Serializable;
import java.util.UUID;
+import java.util.concurrent.TimeUnit;
import javax.inject.Inject;
@@ -31,12 +32,17 @@ import org.skife.jdbi.v2.IDBI;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import com.google.common.cache.Cache;
+import com.google.common.cache.CacheBuilder;
+
public class JDBCSessionDao extends CachingSessionDAO {
private static final Logger log = LoggerFactory.getLogger(JDBCSessionDao.class);
private final JDBCSessionSqlDao jdbcSessionSqlDao;
+ private final Cache<Serializable, Boolean> noUpdateSessionsCache = CacheBuilder.<Serializable, Boolean>newBuilder().expireAfterWrite(5, TimeUnit.SECONDS).build();
+
@Inject
public JDBCSessionDao(final IDBI dbi) {
this.jdbcSessionSqlDao = dbi.onDemand(JDBCSessionSqlDao.class);
@@ -44,7 +50,9 @@ public class JDBCSessionDao extends CachingSessionDAO {
@Override
protected void doUpdate(final Session session) {
- jdbcSessionSqlDao.update(new SessionModelDao(session));
+ if (shouldUpdateSession(session)) {
+ jdbcSessionSqlDao.update(new SessionModelDao(session));
+ }
}
@Override
@@ -56,9 +64,12 @@ public class JDBCSessionDao extends CachingSessionDAO {
protected Serializable doCreate(final Session session) {
final UUID sessionId = UUIDs.randomUUID();
// See SessionModelDao#toSimpleSession for why we use toString()
- assignSessionId(session, sessionId.toString());
+ final String sessionIdAsString = sessionId.toString();
+ assignSessionId(session, sessionIdAsString);
jdbcSessionSqlDao.create(new SessionModelDao(session));
- return sessionId;
+ // Make sure to return a String here as well, or Shiro will cache the Session with a UUID key
+ // while it is expecting String
+ return sessionIdAsString;
}
@Override
@@ -82,4 +93,17 @@ public class JDBCSessionDao extends CachingSessionDAO {
return null;
}
}
+
+ public void disableUpdatesForSession(final Session session) {
+ noUpdateSessionsCache.put(session.getId(), Boolean.TRUE);
+ }
+
+ public void enableUpdatesForSession(final Session session) {
+ noUpdateSessionsCache.invalidate(session.getId());
+ doUpdate(session);
+ }
+
+ private boolean shouldUpdateSession(final Session session) {
+ return noUpdateSessionsCache.getIfPresent(session.getId()) == Boolean.TRUE ? Boolean.FALSE : Boolean.TRUE;
+ }
}
diff --git a/util/src/main/java/org/killbill/billing/util/tag/dao/DefaultTagDefinitionDao.java b/util/src/main/java/org/killbill/billing/util/tag/dao/DefaultTagDefinitionDao.java
index 6c78854..90d2d75 100644
--- a/util/src/main/java/org/killbill/billing/util/tag/dao/DefaultTagDefinitionDao.java
+++ b/util/src/main/java/org/killbill/billing/util/tag/dao/DefaultTagDefinitionDao.java
@@ -155,7 +155,7 @@ public class DefaultTagDefinitionDao extends EntityDaoBase<TagDefinitionModelDao
// Create it
final TagDefinitionModelDao tagDefinition = new TagDefinitionModelDao(context.getCreatedDate(), definitionName, description);
- tagDefinitionSqlDao.create(tagDefinition, context);
+ createAndRefresh(tagDefinitionSqlDao, tagDefinition, context);
// Post an event to the bus
final boolean isControlTag = TagModelDaoHelper.isControlTag(tagDefinition.getName());
util/src/main/resources/ehcache.xml 259(+40 -219)
diff --git a/util/src/main/resources/ehcache.xml b/util/src/main/resources/ehcache.xml
index 61d098d..b2d732c 100644
--- a/util/src/main/resources/ehcache.xml
+++ b/util/src/main/resources/ehcache.xml
@@ -2,8 +2,8 @@
<!--
~ Copyright 2010-2014 Ning, Inc.
- ~ Copyright 2014-2016 Groupon, Inc
- ~ Copyright 2014-2016 The Billing Project, LLC
+ ~ 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
@@ -18,221 +18,42 @@
~ under the License.
-->
-<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
- xsi:noNamespaceSchemaLocation="ehcache.xsd">
-
- <defaultCache
- maxElementsInMemory="100000"
- maxElementsOnDisk="0"
- eternal="true"
- overflowToDisk="false"
- diskPersistent="false"
- memoryStoreEvictionPolicy="LFU"
- statistics="true"
- />
-
- <cache name="record-id"
- maxElementsInMemory="100000"
- maxElementsOnDisk="0"
- eternal="true"
- overflowToDisk="false"
- diskPersistent="false"
- memoryStoreEvictionPolicy="LFU"
- statistics="true"
- >
- <cacheEventListenerFactory
- class="org.killbill.billing.util.cache.ExpirationListenerFactory"
- properties=""/>
- </cache>
-
- <cache name="tenant-record-id"
- maxElementsInMemory="100000"
- maxElementsOnDisk="0"
- eternal="true"
- overflowToDisk="false"
- diskPersistent="false"
- memoryStoreEvictionPolicy="LFU"
- statistics="true"
- >
- <cacheEventListenerFactory
- class="org.killbill.billing.util.cache.ExpirationListenerFactory"
- properties=""/>
- </cache>
-
- <cache name="account-record-id"
- maxElementsInMemory="100000"
- maxElementsOnDisk="0"
- eternal="true"
- overflowToDisk="false"
- diskPersistent="false"
- memoryStoreEvictionPolicy="LFU"
- statistics="true"
- >
- <cacheEventListenerFactory
- class="org.killbill.billing.util.cache.ExpirationListenerFactory"
- properties=""/>
- </cache>
-
- <cache name="object-id"
- maxElementsInMemory="100000"
- maxElementsOnDisk="0"
- eternal="true"
- overflowToDisk="false"
- diskPersistent="false"
- memoryStoreEvictionPolicy="LFU"
- statistics="true"
- >
- <cacheEventListenerFactory
- class="org.killbill.billing.util.cache.ExpirationListenerFactory"
- properties=""/>
- </cache>
-
-
- <cache name="audit-log"
- maxElementsInMemory="500000"
- maxElementsOnDisk="0"
- timeToIdleSeconds="600"
- timeToLiveSeconds="600"
- overflowToDisk="false"
- diskPersistent="false"
- memoryStoreEvictionPolicy="LFU"
- statistics="true"
- >
- <cacheEventListenerFactory
- class="org.killbill.billing.util.cache.ExpirationListenerFactory"
- properties=""/>
- </cache>
-
- <cache name="audit-log-via-history"
- maxElementsInMemory="500000"
- maxElementsOnDisk="0"
- timeToIdleSeconds="600"
- timeToLiveSeconds="600"
- overflowToDisk="false"
- diskPersistent="false"
- memoryStoreEvictionPolicy="LFU"
- statistics="true"
- >
- <cacheEventListenerFactory
- class="org.killbill.billing.util.cache.ExpirationListenerFactory"
- properties=""/>
- </cache>
-
- <cache name="tenant-catalog"
- maxElementsInMemory="1000"
- maxElementsOnDisk="0"
- overflowToDisk="false"
- diskPersistent="false"
- memoryStoreEvictionPolicy="LFU"
- statistics="true"
- >
- <cacheEventListenerFactory
- class="org.killbill.billing.util.cache.ExpirationListenerFactory"
- properties=""/>
- </cache>
-
- <cache name="tenant-overdue-config"
- maxElementsInMemory="1000"
- maxElementsOnDisk="0"
- overflowToDisk="false"
- diskPersistent="false"
- memoryStoreEvictionPolicy="LFU"
- statistics="true">
- <cacheEventListenerFactory
- class="org.killbill.billing.util.cache.ExpirationListenerFactory"
- properties=""/>
- </cache>
-
- <cache name="tenant-config"
- maxElementsInMemory="1000"
- maxElementsOnDisk="0"
- overflowToDisk="false"
- diskPersistent="false"
- memoryStoreEvictionPolicy="LFU"
- statistics="true">
- <cacheEventListenerFactory
- class="org.killbill.billing.util.cache.ExpirationListenerFactory"
- properties=""/>
- </cache>
-
- <cache name="tenant-kv"
- maxElementsInMemory="1000"
- maxElementsOnDisk="0"
- overflowToDisk="false"
- diskPersistent="false"
- memoryStoreEvictionPolicy="LFU"
- statistics="true"
- >
- <cacheEventListenerFactory
- class="org.killbill.billing.util.cache.ExpirationListenerFactory"
- properties=""/>
- </cache>
-
- <cache name="overridden-plan"
- maxElementsInMemory="1000"
- maxElementsOnDisk="0"
- overflowToDisk="false"
- diskPersistent="false"
- memoryStoreEvictionPolicy="LFU"
- statistics="true"
- >
- <cacheEventListenerFactory
- class="org.killbill.billing.util.cache.ExpirationListenerFactory"
- properties=""/>
- </cache>
-
- <cache name="account-immutable"
- maxElementsInMemory="1000"
- maxElementsOnDisk="0"
- overflowToDisk="false"
- diskPersistent="false"
- memoryStoreEvictionPolicy="LFU"
- statistics="true"
- >
- <cacheEventListenerFactory
- class="org.killbill.billing.util.cache.ExpirationListenerFactory"
- properties=""/>
- </cache>
-
- <cache name="account-bcd"
- maxElementsInMemory="1000"
- maxElementsOnDisk="0"
- overflowToDisk="false"
- diskPersistent="false"
- memoryStoreEvictionPolicy="LFU"
- statistics="true"
- >
- <cacheEventListenerFactory
- class="org.killbill.billing.util.cache.ExpirationListenerFactory"
- properties=""/>
- </cache>
-
-
- <cache name="tenant"
- maxElementsInMemory="100"
- maxElementsOnDisk="0"
- overflowToDisk="false"
- diskPersistent="false"
- memoryStoreEvictionPolicy="LFU"
- statistics="true"
- >
- <cacheEventListenerFactory
- class="org.killbill.billing.util.cache.ExpirationListenerFactory"
- properties=""/>
- </cache>
-
- <cache name="tenant-payment-state-machine-config"
- maxElementsInMemory="100"
- maxElementsOnDisk="0"
- overflowToDisk="false"
- diskPersistent="false"
- memoryStoreEvictionPolicy="LFU"
- statistics="true"
- >
- <cacheEventListenerFactory
- class="org.killbill.billing.util.cache.ExpirationListenerFactory"
- properties=""/>
- </cache>
-
-</ehcache>
+<ehcache:config xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance'
+ xmlns:ehcache='http://www.ehcache.org/v3'
+ xmlns:jsr107='http://www.ehcache.org/v3/jsr107'
+ xmlns:terracotta='http://www.ehcache.org/v3/clustered'
+ xsi:schemaLocation="http://www.ehcache.org/v3
+ http://www.ehcache.org/schema/ehcache-core-3.3.xsd
+ http://www.ehcache.org/v3/jsr107
+ http://www.ehcache.org/schema/ehcache-107-ext-3.3.xsd
+ http://www.ehcache.org/v3/clustered
+ http://www.ehcache.org/schema/ehcache-clustered-ext-3.3.xsd">
+ <ehcache:service>
+ <jsr107:defaults default-template="defaultCacheConfiguration" enable-management="true" enable-statistics="true">
+ <!-- See AuditSqlDao -->
+ <jsr107:cache name="audit-log" template="defaultShortTTLCacheConfiguration"/>
+ <jsr107:cache name="audit-log-via-history" template="defaultShortTTLCacheConfiguration"/>
+ </jsr107:defaults>
+ </ehcache:service>
+
+ <ehcache:cache-template name="defaultShortTTLCacheConfiguration">
+ <ehcache:expiry>
+ <ehcache:ttl unit="seconds">20</ehcache:ttl>
+ </ehcache:expiry>
+
+ <ehcache:resources>
+ <ehcache:heap unit="entries">100000</ehcache:heap>
+ </ehcache:resources>
+ </ehcache:cache-template>
+
+ <ehcache:cache-template name="defaultCacheConfiguration">
+ <ehcache:expiry>
+ <ehcache:none/>
+ </ehcache:expiry>
+
+ <ehcache:resources>
+ <ehcache:heap unit="entries">100000</ehcache:heap>
+ </ehcache:resources>
+ </ehcache:cache-template>
+</ehcache:config>
diff --git a/util/src/main/resources/org/killbill/billing/util/entity/dao/EntitySqlDao.sql.stg b/util/src/main/resources/org/killbill/billing/util/entity/dao/EntitySqlDao.sql.stg
index a79883d..2d96592 100644
--- a/util/src/main/resources/org/killbill/billing/util/entity/dao/EntitySqlDao.sql.stg
+++ b/util/src/main/resources/org/killbill/billing/util/entity/dao/EntitySqlDao.sql.stg
@@ -336,6 +336,18 @@ auditTableValues() ::= <<
<if(tenantRecordIdField(""))>, <tenantRecordIdValue()><endif>
>>
+getHistoryForTargetRecordId() ::= <<
+select
+ <idField()>
+, <historyTableFields("t.")>
+<accountRecordIdFieldWithComma("t.")>
+<tenantRecordIdFieldWithComma("t.")>
+from <historyTableName()> t
+where <targetRecordIdField("t.")> = :targetRecordId
+<AND_CHECK_TENANT("t.")>
+order by <recordIdField("t.")> ASC
+;
+>>
addHistoryFromTransaction() ::= <<
insert into <historyTableName()> (
diff --git a/util/src/test/java/org/killbill/billing/dao/MockNonEntityDao.java b/util/src/test/java/org/killbill/billing/dao/MockNonEntityDao.java
index 5893704..0a1812e 100644
--- a/util/src/test/java/org/killbill/billing/dao/MockNonEntityDao.java
+++ b/util/src/test/java/org/killbill/billing/dao/MockNonEntityDao.java
@@ -46,42 +46,42 @@ public class MockNonEntityDao implements NonEntityDao {
}
@Override
- public Long retrieveRecordIdFromObject(final UUID objectId, final ObjectType objectType, @Nullable final CacheController<Object, Object> cache) {
+ public Long retrieveRecordIdFromObject(final UUID objectId, final ObjectType objectType, @Nullable final CacheController<String, Long> cache) {
return null;
}
@Override
- public Long retrieveRecordIdFromObjectInTransaction(final UUID objectId, final ObjectType objectType, @Nullable final CacheController<Object, Object> cache, @Nullable final Handle handle) {
+ public Long retrieveRecordIdFromObjectInTransaction(final UUID objectId, final ObjectType objectType, @Nullable final CacheController<String, Long> cache, @Nullable final Handle handle) {
return null;
}
@Override
- public Long retrieveAccountRecordIdFromObject(final UUID objectId, final ObjectType objectType, @Nullable final CacheController<Object, Object> cache) {
+ public Long retrieveAccountRecordIdFromObject(final UUID objectId, final ObjectType objectType, @Nullable final CacheController<String, Long> cache) {
return accountRecordIdMappings.get(objectId);
}
@Override
- public Long retrieveAccountRecordIdFromObjectInTransaction(final UUID objectId, final ObjectType objectType, @Nullable final CacheController<Object, Object> cache, @Nullable final Handle handle) {
+ public Long retrieveAccountRecordIdFromObjectInTransaction(final UUID objectId, final ObjectType objectType, @Nullable final CacheController<String, Long> cache, @Nullable final Handle handle) {
return null;
}
@Override
- public Long retrieveTenantRecordIdFromObject(final UUID objectId, final ObjectType objectType, @Nullable final CacheController<Object, Object> cache) {
+ public Long retrieveTenantRecordIdFromObject(final UUID objectId, final ObjectType objectType, @Nullable final CacheController<String, Long> cache) {
return tenantRecordIdMappings.get(objectId);
}
@Override
- public Long retrieveTenantRecordIdFromObjectInTransaction(final UUID objectId, final ObjectType objectType, @Nullable final CacheController<Object, Object> cache, @Nullable final Handle handle) {
+ public Long retrieveTenantRecordIdFromObjectInTransaction(final UUID objectId, final ObjectType objectType, @Nullable final CacheController<String, Long> cache, @Nullable final Handle handle) {
return null;
}
@Override
- public UUID retrieveIdFromObject(final Long recordId, final ObjectType objectType, @Nullable final CacheController<Object, Object> cache) {
+ public UUID retrieveIdFromObject(final Long recordId, final ObjectType objectType, @Nullable final CacheController<String, UUID> cache) {
return null;
}
@Override
- public UUID retrieveIdFromObjectInTransaction(final Long recordId, final ObjectType objectType, @Nullable final CacheController<Object, Object> cache, @Nullable final Handle handle) {
+ public UUID retrieveIdFromObjectInTransaction(final Long recordId, final ObjectType objectType, @Nullable final CacheController<String, UUID> cache, @Nullable final Handle handle) {
return null;
}
diff --git a/util/src/test/java/org/killbill/billing/util/callcontext/TestInternalCallContextFactory.java b/util/src/test/java/org/killbill/billing/util/callcontext/TestInternalCallContextFactory.java
index 9a5c01e..40e940e 100644
--- a/util/src/test/java/org/killbill/billing/util/callcontext/TestInternalCallContextFactory.java
+++ b/util/src/test/java/org/killbill/billing/util/callcontext/TestInternalCallContextFactory.java
@@ -1,7 +1,9 @@
/*
- * Copyright 2010-2012 Ning, Inc.
+ * Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014-2017 Groupon, Inc
+ * Copyright 2014-2017 The Billing Project, LLC
*
- * Ning licenses this file to you under the Apache License, version 2.0
+ * 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:
*
@@ -19,6 +21,9 @@ package org.killbill.billing.util.callcontext;
import java.util.Date;
import java.util.UUID;
+import org.killbill.billing.account.api.ImmutableAccountData;
+import org.killbill.billing.callcontext.InternalTenantContext;
+import org.mockito.Mockito;
import org.skife.jdbi.v2.Handle;
import org.skife.jdbi.v2.tweak.HandleCallback;
import org.testng.Assert;
@@ -36,6 +41,9 @@ public class TestInternalCallContextFactory extends UtilTestSuiteWithEmbeddedDB
final UUID invoiceId = UUID.randomUUID();
final Long accountRecordId = 19384012L;
+ final ImmutableAccountData immutableAccountData = Mockito.mock(ImmutableAccountData.class);
+ Mockito.when(immutableAccountInternalApi.getImmutableAccountDataByRecordId(Mockito.<Long>eq(accountRecordId), Mockito.<InternalTenantContext>any())).thenReturn(immutableAccountData);
+
dbi.withHandle(new HandleCallback<Void>() {
@Override
public Void withHandle(final Handle handle) throws Exception {
@@ -71,6 +79,9 @@ public class TestInternalCallContextFactory extends UtilTestSuiteWithEmbeddedDB
final UUID accountId = UUID.randomUUID();
final Long accountRecordId = 19384012L;
+ final ImmutableAccountData immutableAccountData = Mockito.mock(ImmutableAccountData.class);
+ Mockito.when(immutableAccountInternalApi.getImmutableAccountDataByRecordId(Mockito.<Long>eq(accountRecordId), Mockito.<InternalTenantContext>any())).thenReturn(immutableAccountData);
+
dbi.withHandle(new HandleCallback<Void>() {
@Override
public Void withHandle(final Handle handle) throws Exception {
diff --git a/util/src/test/java/org/killbill/billing/util/customfield/api/TestDefaultCustomFieldUserApi.java b/util/src/test/java/org/killbill/billing/util/customfield/api/TestDefaultCustomFieldUserApi.java
index 2dccd4c..ffe76e3 100644
--- a/util/src/test/java/org/killbill/billing/util/customfield/api/TestDefaultCustomFieldUserApi.java
+++ b/util/src/test/java/org/killbill/billing/util/customfield/api/TestDefaultCustomFieldUserApi.java
@@ -1,7 +1,7 @@
/*
* Copyright 2010-2013 Ning, Inc.
- * Copyright 2014-2015 Groupon, Inc
- * Copyright 2014-2015 The Billing Project, LLC
+ * 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
@@ -24,11 +24,14 @@ import java.util.Map;
import java.util.UUID;
import org.killbill.billing.ObjectType;
+import org.killbill.billing.account.api.ImmutableAccountData;
import org.killbill.billing.api.TestApiListener.NextEvent;
+import org.killbill.billing.callcontext.InternalTenantContext;
import org.killbill.billing.util.UtilTestSuiteWithEmbeddedDB;
import org.killbill.billing.util.customfield.CustomField;
import org.killbill.billing.util.customfield.StringCustomField;
import org.killbill.billing.util.entity.Pagination;
+import org.mockito.Mockito;
import org.skife.jdbi.v2.Handle;
import org.skife.jdbi.v2.tweak.HandleCallback;
import org.testng.Assert;
@@ -43,6 +46,9 @@ public class TestDefaultCustomFieldUserApi extends UtilTestSuiteWithEmbeddedDB {
final UUID accountId = UUID.randomUUID();
final Long accountRecordId = 19384012L;
+ final ImmutableAccountData immutableAccountData = Mockito.mock(ImmutableAccountData.class);
+ Mockito.when(immutableAccountInternalApi.getImmutableAccountDataByRecordId(Mockito.<Long>eq(accountRecordId), Mockito.<InternalTenantContext>any())).thenReturn(immutableAccountData);
+
dbi.withHandle(new HandleCallback<Void>() {
@Override
public Void withHandle(final Handle handle) throws Exception {
diff --git a/util/src/test/java/org/killbill/billing/util/security/TestPermissionAnnotationMethodInterceptor.java b/util/src/test/java/org/killbill/billing/util/security/TestPermissionAnnotationMethodInterceptor.java
index 566518d..c4a8c52 100644
--- a/util/src/test/java/org/killbill/billing/util/security/TestPermissionAnnotationMethodInterceptor.java
+++ b/util/src/test/java/org/killbill/billing/util/security/TestPermissionAnnotationMethodInterceptor.java
@@ -1,7 +1,9 @@
/*
* Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014-2017 Groupon, Inc
+ * Copyright 2014-2017 The Billing Project, LLC
*
- * Ning licenses this file to you under the Apache License, version 2.0
+ * 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:
*
@@ -20,6 +22,13 @@ import javax.inject.Singleton;
import org.apache.shiro.authz.AuthorizationException;
import org.apache.shiro.authz.UnauthenticatedException;
+import org.killbill.billing.security.Permission;
+import org.killbill.billing.security.RequiresPermissions;
+import org.killbill.billing.tenant.api.TenantInternalApi;
+import org.killbill.billing.util.UtilTestSuiteNoDB;
+import org.killbill.billing.util.dao.NonEntityDao;
+import org.killbill.billing.util.glue.CacheModule;
+import org.killbill.billing.util.glue.KillBillShiroAopModule;
import org.killbill.billing.util.glue.TestSecurityModuleNoDB;
import org.killbill.billing.util.glue.TestUtilModuleNoDB.ShiroModuleNoDB;
import org.mockito.Mockito;
@@ -27,18 +36,10 @@ import org.skife.jdbi.v2.IDBI;
import org.testng.Assert;
import org.testng.annotations.Test;
-import org.killbill.billing.security.Permission;
-import org.killbill.billing.security.RequiresPermissions;
-import org.killbill.billing.util.UtilTestSuiteNoDB;
-import org.killbill.billing.util.glue.KillBillShiroAopModule;
-import org.killbill.billing.util.glue.KillBillShiroModule;
-import org.killbill.billing.util.glue.SecurityModule;
-
import com.google.inject.AbstractModule;
import com.google.inject.Guice;
import com.google.inject.Injector;
import com.google.inject.Stage;
-import net.sf.ehcache.CacheManager;
public class TestPermissionAnnotationMethodInterceptor extends UtilTestSuiteNoDB {
@@ -73,16 +74,18 @@ public class TestPermissionAnnotationMethodInterceptor extends UtilTestSuiteNoDB
// Now, verify the interception works
configureShiro();
- // Shutdown the cache manager to avoid duplicate exceptions
- CacheManager.getInstance().shutdown();
+
final Injector injector = Guice.createInjector(Stage.PRODUCTION,
new ShiroModuleNoDB(configSource),
new KillBillShiroAopModule(),
new TestSecurityModuleNoDB(configSource),
+ new CacheModule(configSource),
new AbstractModule() {
@Override
protected void configure() {
bind(IDBI.class).toInstance(Mockito.mock(IDBI.class));
+ bind(TenantInternalApi.class).toInstance(Mockito.mock(TenantInternalApi.class));
+ bind(NonEntityDao.class).toInstance(Mockito.mock(NonEntityDao.class));
}
});
final AopTester aopedTester = injector.getInstance(AopTester.class);
@@ -101,17 +104,19 @@ public class TestPermissionAnnotationMethodInterceptor extends UtilTestSuiteNoDB
// Now, verify the interception works
configureShiro();
- // Shutdown the cache manager to avoid duplicate exceptions
- CacheManager.getInstance().shutdown();
+
final Injector injector = Guice.createInjector(Stage.PRODUCTION,
new ShiroModuleNoDB(configSource),
new KillBillShiroAopModule(),
new TestSecurityModuleNoDB(configSource),
+ new CacheModule(configSource),
new AbstractModule() {
@Override
public void configure() {
bind(IDBI.class).toInstance(Mockito.mock(IDBI.class));
bind(IAopTester.class).to(AopTesterImpl.class).asEagerSingleton();
+ bind(TenantInternalApi.class).toInstance(Mockito.mock(TenantInternalApi.class));
+ bind(NonEntityDao.class).toInstance(Mockito.mock(NonEntityDao.class));
}
});
final IAopTester aopedTester = injector.getInstance(IAopTester.class);
diff --git a/util/src/test/java/org/killbill/billing/util/tag/api/TestDefaultTagUserApi.java b/util/src/test/java/org/killbill/billing/util/tag/api/TestDefaultTagUserApi.java
index 978f5bb..bbdfb0f 100644
--- a/util/src/test/java/org/killbill/billing/util/tag/api/TestDefaultTagUserApi.java
+++ b/util/src/test/java/org/killbill/billing/util/tag/api/TestDefaultTagUserApi.java
@@ -1,6 +1,6 @@
/*
- * Copyright 2014-2015 Groupon, Inc
- * Copyright 2014-2015 The Billing Project, LLC
+ * 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
@@ -23,11 +23,14 @@ import java.util.Map;
import java.util.UUID;
import org.killbill.billing.ObjectType;
+import org.killbill.billing.account.api.ImmutableAccountData;
import org.killbill.billing.api.TestApiListener.NextEvent;
+import org.killbill.billing.callcontext.InternalTenantContext;
import org.killbill.billing.util.UtilTestSuiteWithEmbeddedDB;
import org.killbill.billing.util.entity.Pagination;
import org.killbill.billing.util.tag.ControlTagType;
import org.killbill.billing.util.tag.Tag;
+import org.mockito.Mockito;
import org.skife.jdbi.v2.Handle;
import org.skife.jdbi.v2.tweak.HandleCallback;
import org.testng.Assert;
@@ -42,6 +45,9 @@ public class TestDefaultTagUserApi extends UtilTestSuiteWithEmbeddedDB {
final UUID accountId = UUID.randomUUID();
final Long accountRecordId = 19384012L;
+ final ImmutableAccountData immutableAccountData = Mockito.mock(ImmutableAccountData.class);
+ Mockito.when(immutableAccountInternalApi.getImmutableAccountDataByRecordId(Mockito.<Long>eq(accountRecordId), Mockito.<InternalTenantContext>any())).thenReturn(immutableAccountData);
+
dbi.withHandle(new HandleCallback<Void>() {
@Override
public Void withHandle(final Handle handle) throws Exception {
diff --git a/util/src/test/java/org/killbill/billing/util/UtilTestSuiteWithEmbeddedDB.java b/util/src/test/java/org/killbill/billing/util/UtilTestSuiteWithEmbeddedDB.java
index 17ce189..503a229 100644
--- a/util/src/test/java/org/killbill/billing/util/UtilTestSuiteWithEmbeddedDB.java
+++ b/util/src/test/java/org/killbill/billing/util/UtilTestSuiteWithEmbeddedDB.java
@@ -1,7 +1,7 @@
/*
* Copyright 2010-2014 Ning, Inc.
- * Copyright 2014-2015 Groupon, Inc
- * Copyright 2014-2015 The Billing Project, LLC
+ * 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
@@ -21,6 +21,7 @@ package org.killbill.billing.util;
import javax.inject.Inject;
import org.killbill.billing.GuicyKillbillTestSuiteWithEmbeddedDB;
+import org.killbill.billing.account.api.ImmutableAccountInternalApi;
import org.killbill.billing.api.TestApiListener;
import org.killbill.billing.security.api.SecurityApi;
import org.killbill.billing.util.audit.dao.AuditDao;
@@ -66,6 +67,8 @@ public abstract class UtilTestSuiteWithEmbeddedDB extends GuicyKillbillTestSuite
@Inject
protected InternalCallContextFactory internalCallContextFactory;
@Inject
+ protected ImmutableAccountInternalApi immutableAccountInternalApi;
+ @Inject
protected DefaultTagUserApi tagUserApi;
@Inject
protected DefaultCustomFieldUserApi customFieldUserApi;