killbill-memoizeit
Changes
payment/src/main/java/org/killbill/billing/payment/core/sm/control/CompletionControlOperation.java 11(+10 -1)
payment/src/main/java/org/killbill/billing/payment/core/sm/control/ControlPluginRunner.java 7(+7 -0)
payment/src/main/java/org/killbill/billing/payment/core/sm/control/OperationControlCallback.java 8(+6 -2)
payment/src/main/java/org/killbill/billing/payment/core/sm/PaymentAutomatonDAOHelper.java 26(+15 -11)
Details
diff --git a/payment/src/main/java/org/killbill/billing/payment/core/PaymentGatewayProcessor.java b/payment/src/main/java/org/killbill/billing/payment/core/PaymentGatewayProcessor.java
index cf9e41f..896a7c9 100644
--- a/payment/src/main/java/org/killbill/billing/payment/core/PaymentGatewayProcessor.java
+++ b/payment/src/main/java/org/killbill/billing/payment/core/PaymentGatewayProcessor.java
@@ -78,10 +78,12 @@ public class PaymentGatewayProcessor extends ProcessorBase {
public GatewayNotification processNotification(final String notification, final String pluginName, final Iterable<PluginProperty> properties, final CallContext callContext) throws PaymentApiException {
return dispatchWithExceptionHandling(null,
+ pluginName,
new Callable<PluginDispatcherReturnType<GatewayNotification>>() {
@Override
public PluginDispatcherReturnType<GatewayNotification> call() throws PaymentApiException {
final PaymentPluginApi plugin = getPaymentPluginApi(pluginName);
+
try {
final GatewayNotification result = plugin.processNotification(notification, properties, callContext);
return PluginDispatcher.createPluginDispatcherReturnType(result == null ? new DefaultNoOpGatewayNotification() : result);
@@ -93,11 +95,14 @@ public class PaymentGatewayProcessor extends ProcessorBase {
}
public HostedPaymentPageFormDescriptor buildFormDescriptor(final Account account, final UUID paymentMethodId, final Iterable<PluginProperty> customFields, final Iterable<PluginProperty> properties, final CallContext callContext, final InternalCallContext internalCallContext) throws PaymentApiException {
+ final String pluginName = getPaymentProviderPluginName(paymentMethodId, internalCallContext);
+
return dispatchWithExceptionHandling(account,
+ pluginName,
new Callable<PluginDispatcherReturnType<HostedPaymentPageFormDescriptor>>() {
@Override
public PluginDispatcherReturnType<HostedPaymentPageFormDescriptor> call() throws PaymentApiException {
- final PaymentPluginApi plugin = getPaymentProviderPlugin(paymentMethodId, internalCallContext);
+ final PaymentPluginApi plugin = getPaymentPluginApi(pluginName);
try {
final HostedPaymentPageFormDescriptor result = plugin.buildFormDescriptor(account.getId(), customFields, properties, callContext);
diff --git a/payment/src/main/java/org/killbill/billing/payment/core/PaymentMethodProcessor.java b/payment/src/main/java/org/killbill/billing/payment/core/PaymentMethodProcessor.java
index 2e644a2..19be993 100644
--- a/payment/src/main/java/org/killbill/billing/payment/core/PaymentMethodProcessor.java
+++ b/payment/src/main/java/org/killbill/billing/payment/core/PaymentMethodProcessor.java
@@ -103,6 +103,7 @@ public class PaymentMethodProcessor extends ProcessorBase {
final Iterable<PluginProperty> properties, final CallContext callContext, final InternalCallContext context)
throws PaymentApiException {
return dispatchWithExceptionHandling(account,
+ paymentPluginServiceName,
new CallableWithAccountLock<UUID, PaymentApiException>(locker,
account.getExternalKey(),
paymentConfig,
@@ -111,10 +112,9 @@ public class PaymentMethodProcessor extends ProcessorBase {
@Override
public PluginDispatcherReturnType<UUID> doOperation() throws PaymentApiException {
PaymentMethod pm = null;
- final PaymentPluginApi pluginApi;
try {
- pluginApi = getPaymentPluginApi(paymentPluginServiceName);
pm = new DefaultPaymentMethod(paymentMethodExternalKey, account.getId(), paymentPluginServiceName, paymentMethodProps);
+ final PaymentPluginApi pluginApi = getPaymentPluginApi(paymentPluginServiceName);
pluginApi.addPaymentMethod(account.getId(), pm.getId(), paymentMethodProps, setDefault, properties, callContext);
final String actualPaymentMethodExternalKey = retrieveActualPaymentMethodExternalKey(account, pm, pluginApi, properties, callContext, context);
diff --git a/payment/src/main/java/org/killbill/billing/payment/core/ProcessorBase.java b/payment/src/main/java/org/killbill/billing/payment/core/ProcessorBase.java
index 1e8be54..78f1d61 100644
--- a/payment/src/main/java/org/killbill/billing/payment/core/ProcessorBase.java
+++ b/payment/src/main/java/org/killbill/billing/payment/core/ProcessorBase.java
@@ -54,6 +54,7 @@ import org.killbill.clock.Clock;
import org.killbill.commons.locker.GlobalLock;
import org.killbill.commons.locker.GlobalLocker;
import org.killbill.commons.locker.LockFailedException;
+import org.killbill.commons.request.Request;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -126,12 +127,17 @@ public abstract class ProcessorBase {
}
protected PaymentPluginApi getPaymentProviderPlugin(final UUID paymentMethodId, final InternalTenantContext context) throws PaymentApiException {
+ final String pluginName = getPaymentProviderPluginName(paymentMethodId, context);
+ return getPaymentPluginApi(pluginName);
+ }
+
+ protected String getPaymentProviderPluginName(final UUID paymentMethodId, final InternalTenantContext context) throws PaymentApiException {
final PaymentMethodModelDao methodDao = paymentDao.getPaymentMethodIncludedDeleted(paymentMethodId, context);
if (methodDao == null) {
log.error("PaymentMethod does not exist", paymentMethodId);
throw new PaymentApiException(ErrorCode.PAYMENT_NO_SUCH_PAYMENT_METHOD, paymentMethodId);
}
- return getPaymentPluginApi(methodDao.getPluginName());
+ return methodDao.getPluginName();
}
protected PaymentPluginApi getPaymentProviderPlugin(final Account account, final InternalTenantContext context) throws PaymentApiException {
@@ -204,22 +210,30 @@ public abstract class ProcessorBase {
}
}
- protected static <ReturnType> ReturnType dispatchWithExceptionHandling(@Nullable final Account account, final Callable<PluginDispatcherReturnType<ReturnType>> callable, PluginDispatcher<ReturnType> pluginFormDispatcher) throws PaymentApiException {
+ protected static <ReturnType> ReturnType dispatchWithExceptionHandling(@Nullable final Account account, final String pluginName, final Callable<PluginDispatcherReturnType<ReturnType>> callable, PluginDispatcher<ReturnType> pluginFormDispatcher) throws PaymentApiException {
final UUID accountId = account != null ? account.getId() : null;
final String accountExternalKey = account != null ? account.getExternalKey() : "";
+
try {
- return pluginFormDispatcher.dispatchWithTimeout(callable);
+ log.debug("Calling plugin {}", pluginName);
+ final ReturnType result = pluginFormDispatcher.dispatchWithTimeout(callable);
+ log.debug("Successful call of plugin {} for account {} with result {}", pluginName, accountExternalKey, result);
+ return result;
} catch (final TimeoutException e) {
- throw new PaymentApiException(ErrorCode.PAYMENT_PLUGIN_TIMEOUT, accountId, null);
+ final String errorMessage = String.format("TimeoutException during the execution of plugin %s", pluginName);
+ log.warn(errorMessage, e);
+ throw new PaymentApiException(ErrorCode.PAYMENT_PLUGIN_TIMEOUT, accountId, errorMessage);
} catch (final InterruptedException e) {
Thread.currentThread().interrupt();
- throw new PaymentApiException(ErrorCode.PAYMENT_INTERNAL_ERROR, Objects.firstNonNull(e.getMessage(), ""));
+ final String errorMessage = String.format("InterruptedException during the execution of plugin %s", pluginName);
+ log.warn(errorMessage, e);
+ throw new PaymentApiException(ErrorCode.PAYMENT_INTERNAL_ERROR, Objects.firstNonNull(e.getMessage(), errorMessage));
} catch (final ExecutionException e) {
if (e.getCause() instanceof PaymentApiException) {
throw (PaymentApiException) e.getCause();
} else if (e.getCause() instanceof LockFailedException) {
final String format = String.format("Failed to lock account %s", accountExternalKey);
- log.error(String.format(format), e);
+ log.error(format, e);
throw new PaymentApiException(ErrorCode.PAYMENT_INTERNAL_ERROR, format);
} else {
throw new PaymentApiException(e, ErrorCode.PAYMENT_INTERNAL_ERROR, Objects.firstNonNull(e.getMessage(), ""));
diff --git a/payment/src/main/java/org/killbill/billing/payment/core/sm/control/CompletionControlOperation.java b/payment/src/main/java/org/killbill/billing/payment/core/sm/control/CompletionControlOperation.java
index d7cdac9..c8bbf8f 100644
--- a/payment/src/main/java/org/killbill/billing/payment/core/sm/control/CompletionControlOperation.java
+++ b/payment/src/main/java/org/killbill/billing/payment/core/sm/control/CompletionControlOperation.java
@@ -17,6 +17,8 @@
package org.killbill.billing.payment.core.sm.control;
+import java.util.List;
+
import org.killbill.automaton.OperationException;
import org.killbill.automaton.OperationResult;
import org.killbill.billing.control.plugin.api.PaymentApiType;
@@ -32,11 +34,15 @@ import org.killbill.billing.control.plugin.api.PaymentControlContext;
import org.killbill.billing.util.config.PaymentConfig;
import org.killbill.commons.locker.GlobalLocker;
+import com.google.common.base.Joiner;
+
//
// Used from AttemptCompletionTask to resume an incomplete payment that went through control API.
//
public class CompletionControlOperation extends OperationControlCallback {
+ private static final Joiner JOINER = Joiner.on(", ");
+
public CompletionControlOperation(final GlobalLocker locker,
final PluginDispatcher<OperationResult> paymentPluginDispatcher,
final PaymentConfig paymentConfig,
@@ -49,7 +55,10 @@ public class CompletionControlOperation extends OperationControlCallback {
@Override
public OperationResult doOperationCallback() throws OperationException {
- return dispatchWithAccountLockAndTimeout(new DispatcherCallback<PluginDispatcherReturnType<OperationResult>, OperationException>() {
+ final List<String> controlPluginNameList = paymentStateControlContext.getPaymentControlPluginNames();
+ final String controlPluginNames = JOINER.join(controlPluginNameList);
+
+ return dispatchWithAccountLockAndTimeout(controlPluginNames, new DispatcherCallback<PluginDispatcherReturnType<OperationResult>, OperationException>() {
@Override
public PluginDispatcherReturnType<OperationResult> doOperation() throws OperationException {
final PaymentTransactionModelDao transaction = paymentStateContext.getPaymentTransactionModelDao();
diff --git a/payment/src/main/java/org/killbill/billing/payment/core/sm/control/ControlPluginRunner.java b/payment/src/main/java/org/killbill/billing/payment/core/sm/control/ControlPluginRunner.java
index 9d037f9..2624d3e 100644
--- a/payment/src/main/java/org/killbill/billing/payment/core/sm/control/ControlPluginRunner.java
+++ b/payment/src/main/java/org/killbill/billing/payment/core/sm/control/ControlPluginRunner.java
@@ -98,7 +98,9 @@ public class ControlPluginRunner {
log.warn("Skipping unknown payment control plugin {} when fetching results", pluginName);
continue;
}
+ log.debug("Calling priorCall of plugin {}", pluginName);
prevResult = plugin.priorCall(inputPaymentControlContext, inputPluginProperties);
+ log.debug("Successful executed priorCall of plugin {}", pluginName);
if (prevResult.getAdjustedPluginProperties() != null) {
inputPluginProperties = prevResult.getAdjustedPluginProperties();
}
@@ -159,12 +161,15 @@ public class ControlPluginRunner {
processedCurrency,
isApiPayment,
callContext);
+
Iterable<PluginProperty> inputPluginProperties = pluginProperties;
for (final String pluginName : paymentControlPluginNames) {
final PaymentControlPluginApi plugin = paymentControlPluginRegistry.getServiceForName(pluginName);
if (plugin != null) {
try {
+ log.debug("Calling onSuccessCall of plugin {}", pluginName);
final OnSuccessPaymentControlResult result = plugin.onSuccessCall(inputPaymentControlContext, inputPluginProperties);
+ log.debug("Successful executed onSuccessCall of plugin {}", pluginName);
if (result.getAdjustedPluginProperties() != null) {
inputPluginProperties = result.getAdjustedPluginProperties();
}
@@ -216,7 +221,9 @@ public class ControlPluginRunner {
final PaymentControlPluginApi plugin = paymentControlPluginRegistry.getServiceForName(pluginName);
if (plugin != null) {
try {
+ log.debug("Calling onSuccessCall of plugin {}", pluginName);
final OnFailurePaymentControlResult result = plugin.onFailureCall(inputPaymentControlContext, inputPluginProperties);
+ log.debug("Successful executed onSuccessCall of plugin {}", pluginName);
if (candidate == null) {
candidate = result.getNextRetryDate();
} else if (result.getNextRetryDate() != null) {
diff --git a/payment/src/main/java/org/killbill/billing/payment/core/sm/control/OperationControlCallback.java b/payment/src/main/java/org/killbill/billing/payment/core/sm/control/OperationControlCallback.java
index 025bef2..a463954 100644
--- a/payment/src/main/java/org/killbill/billing/payment/core/sm/control/OperationControlCallback.java
+++ b/payment/src/main/java/org/killbill/billing/payment/core/sm/control/OperationControlCallback.java
@@ -17,7 +17,6 @@
package org.killbill.billing.payment.core.sm.control;
-import java.math.BigDecimal;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeoutException;
@@ -52,12 +51,15 @@ import org.killbill.commons.locker.LockFailedException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import com.google.common.base.Joiner;
import com.google.common.base.MoreObjects;
public abstract class OperationControlCallback extends OperationCallbackBase<Payment, PaymentApiException> implements OperationCallback {
private static final Logger logger = LoggerFactory.getLogger(OperationControlCallback.class);
+ private static final Joiner JOINER = Joiner.on(", ");
+
protected final PaymentProcessor paymentProcessor;
protected final PaymentStateControlContext paymentStateControlContext;
private final ControlPluginRunner controlPluginRunner;
@@ -79,8 +81,10 @@ public abstract class OperationControlCallback extends OperationCallbackBase<Pay
@Override
public OperationResult doOperationCallback() throws OperationException {
+ final List<String> pluginNameList = paymentStateControlContext.getPaymentControlPluginNames();
+ final String pluginNames = JOINER.join(pluginNameList);
- return dispatchWithAccountLockAndTimeout(new DispatcherCallback<PluginDispatcherReturnType<OperationResult>, OperationException>() {
+ return dispatchWithAccountLockAndTimeout(pluginNames, new DispatcherCallback<PluginDispatcherReturnType<OperationResult>, OperationException>() {
@Override
public PluginDispatcherReturnType<OperationResult> doOperation() throws OperationException {
diff --git a/payment/src/main/java/org/killbill/billing/payment/core/sm/OperationCallbackBase.java b/payment/src/main/java/org/killbill/billing/payment/core/sm/OperationCallbackBase.java
index 34c8c1b..f5c542d 100644
--- a/payment/src/main/java/org/killbill/billing/payment/core/sm/OperationCallbackBase.java
+++ b/payment/src/main/java/org/killbill/billing/payment/core/sm/OperationCallbackBase.java
@@ -58,7 +58,7 @@ public abstract class OperationCallbackBase<CallbackOperationResult, CallbackOpe
// The dispatcher may throw a TimeoutException, ExecutionException, or InterruptedException; those will be handled in specific
// callback to eventually throw a OperationException, that will be used to drive the state machine in the right direction.
//
- protected <ExceptionType extends Exception> OperationResult dispatchWithAccountLockAndTimeout(final DispatcherCallback<PluginDispatcherReturnType<OperationResult>, ExceptionType> callback) throws OperationException {
+ protected <ExceptionType extends Exception> OperationResult dispatchWithAccountLockAndTimeout(final String pluginNames, final DispatcherCallback<PluginDispatcherReturnType<OperationResult>, ExceptionType> callback) throws OperationException {
final Account account = paymentStateContext.getAccount();
logger.debug("Dispatching plugin call for account {}", account.getExternalKey());
@@ -67,15 +67,18 @@ public abstract class OperationCallbackBase<CallbackOperationResult, CallbackOpe
account.getExternalKey(),
paymentConfig,
callback);
+ logger.debug("Calling plugin(s) {}", pluginNames);
final OperationResult operationResult = paymentPluginDispatcher.dispatchWithTimeout(task);
- logger.debug("Successful plugin call for account {} with result {}", account.getExternalKey(), operationResult);
+ logger.debug("Successful plugin(s) call of {} for account {} with result {}", pluginNames, account.getExternalKey(), operationResult);
return operationResult;
} catch (final ExecutionException e) {
throw unwrapExceptionFromDispatchedTask(paymentStateContext, e);
} catch (final TimeoutException e) {
+ logger.warn("TimeoutException while executing the plugin(s) {}", pluginNames);
throw unwrapExceptionFromDispatchedTask(paymentStateContext, e);
} catch (final InterruptedException e) {
Thread.currentThread().interrupt();
+ logger.warn("InterruptedException while executing the following plugin(s): {}", pluginNames);
throw unwrapExceptionFromDispatchedTask(paymentStateContext, e);
}
}
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 8db48a9..2b3a215 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
@@ -126,14 +126,26 @@ public class PaymentAutomatonDAOHelper {
paymentStateContext.setPaymentTransactionModelDao(paymentDao.getPaymentTransaction(paymentStateContext.getPaymentTransactionModelDao().getId(), internalCallContext));
}
- public PaymentPluginApi getPaymentProviderPlugin() throws PaymentApiException {
-
+ public String getPaymentProviderPluginName() throws PaymentApiException {
final UUID paymentMethodId = paymentStateContext.getPaymentMethodId();
final PaymentMethodModelDao methodDao = paymentDao.getPaymentMethodIncludedDeleted(paymentMethodId, internalCallContext);
if (methodDao == null) {
throw new PaymentApiException(ErrorCode.PAYMENT_NO_SUCH_PAYMENT_METHOD, paymentMethodId);
}
- return getPaymentPluginApi(methodDao.getPluginName());
+ return methodDao.getPluginName();
+ }
+
+ public PaymentPluginApi getPaymentProviderPlugin() throws PaymentApiException {
+ final String pluginName = getPaymentProviderPluginName();
+ return getPaymentPluginApi(pluginName);
+ }
+
+ public PaymentPluginApi getPaymentPluginApi(final String pluginName) throws PaymentApiException {
+ final PaymentPluginApi pluginApi = pluginRegistry.getServiceForName(pluginName);
+ if (pluginApi == null) {
+ throw new PaymentApiException(ErrorCode.PAYMENT_NO_SUCH_PAYMENT_PLUGIN, pluginName);
+ }
+ return pluginApi;
}
public PaymentModelDao getPayment() throws PaymentApiException {
@@ -184,12 +196,4 @@ public class PaymentAutomatonDAOHelper {
gatewayErrorCode,
gatewayErrorMsg);
}
-
- private PaymentPluginApi getPaymentPluginApi(final String pluginName) throws PaymentApiException {
- final PaymentPluginApi pluginApi = pluginRegistry.getServiceForName(pluginName);
- if (pluginApi == null) {
- throw new PaymentApiException(ErrorCode.PAYMENT_NO_SUCH_PAYMENT_PLUGIN, pluginName);
- }
- return pluginApi;
- }
}
diff --git a/payment/src/main/java/org/killbill/billing/payment/core/sm/payments/PaymentOperation.java b/payment/src/main/java/org/killbill/billing/payment/core/sm/payments/PaymentOperation.java
index c39facf..1240635 100644
--- a/payment/src/main/java/org/killbill/billing/payment/core/sm/payments/PaymentOperation.java
+++ b/payment/src/main/java/org/killbill/billing/payment/core/sm/payments/PaymentOperation.java
@@ -68,12 +68,12 @@ public abstract class PaymentOperation extends OperationCallbackBase<PaymentTran
@Override
public OperationResult doOperationCallback() throws OperationException {
-
try {
- this.plugin = daoHelper.getPaymentProviderPlugin();
+ final String pluginName = daoHelper.getPaymentProviderPluginName();
+ this.plugin = daoHelper.getPaymentPluginApi(pluginName);
if (paymentStateContext.shouldLockAccountAndDispatch()) {
- return doOperationCallbackWithDispatchAndAccountLock();
+ return doOperationCallbackWithDispatchAndAccountLock(pluginName);
} else {
return doSimpleOperationCallback();
}
@@ -152,8 +152,8 @@ public abstract class PaymentOperation extends OperationCallbackBase<PaymentTran
return result;
}
- private OperationResult doOperationCallbackWithDispatchAndAccountLock() throws OperationException {
- return dispatchWithAccountLockAndTimeout(new DispatcherCallback<PluginDispatcherReturnType<OperationResult>, OperationException>() {
+ private OperationResult doOperationCallbackWithDispatchAndAccountLock(String pluginName) throws OperationException {
+ return dispatchWithAccountLockAndTimeout(pluginName, new DispatcherCallback<PluginDispatcherReturnType<OperationResult>, OperationException>() {
@Override
public PluginDispatcherReturnType<OperationResult> doOperation() throws OperationException {
final OperationResult result = doSimpleOperationCallback();
diff --git a/payment/src/main/java/org/killbill/billing/payment/dispatcher/PluginDispatcher.java b/payment/src/main/java/org/killbill/billing/payment/dispatcher/PluginDispatcher.java
index bda52e0..d2de676 100644
--- a/payment/src/main/java/org/killbill/billing/payment/dispatcher/PluginDispatcher.java
+++ b/payment/src/main/java/org/killbill/billing/payment/dispatcher/PluginDispatcher.java
@@ -20,7 +20,6 @@ package org.killbill.billing.payment.dispatcher;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
-import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
diff --git a/payment/src/test/java/org/killbill/billing/payment/api/TestPaymentApi.java b/payment/src/test/java/org/killbill/billing/payment/api/TestPaymentApi.java
index cea0072..71ea328 100644
--- a/payment/src/test/java/org/killbill/billing/payment/api/TestPaymentApi.java
+++ b/payment/src/test/java/org/killbill/billing/payment/api/TestPaymentApi.java
@@ -23,6 +23,13 @@ import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.UUID;
+import java.util.concurrent.Callable;
+import java.util.concurrent.TimeoutException;
+
+import org.killbill.automaton.OperationException;
+import org.killbill.billing.payment.logging.SpyLogger;
+import org.killbill.commons.request.Request;
+import org.killbill.commons.request.RequestData;
import javax.annotation.Nullable;
@@ -36,6 +43,7 @@ import org.killbill.billing.invoice.api.InvoiceApiException;
import org.killbill.billing.invoice.api.InvoiceItem;
import org.killbill.billing.payment.MockRecurringInvoiceItem;
import org.killbill.billing.payment.PaymentTestSuiteWithEmbeddedDB;
+import org.killbill.billing.payment.core.sm.OperationCallbackBase;
import org.killbill.billing.payment.dao.PaymentAttemptModelDao;
import org.killbill.billing.payment.dao.PaymentSqlDao;
import org.killbill.billing.payment.invoice.InvoicePaymentControlPluginApi;
@@ -47,8 +55,10 @@ import org.testng.annotations.BeforeClass;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;
+import com.google.common.base.Optional;
import com.google.common.collect.ImmutableList;
+import static org.killbill.billing.payment.logging.TestLoggingHelper.withSpyLogger;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertNotNull;
import static org.testng.Assert.assertNull;
@@ -833,6 +843,47 @@ public class TestPaymentApi extends PaymentTestSuiteWithEmbeddedDB {
verifyPaymentViaGetPath(pendingRefund4);
}
+ @Test(groups = "slow")
+ public void testCreatePurchaseWithTimeout() throws Exception {
+ final BigDecimal requestedAmount = BigDecimal.TEN;
+ final String paymentExternalKey = "ohhhh";
+ final String transactionExternalKey = "naaahhh";
+
+ final String pluginName = mockPaymentProviderPlugin.PLUGIN_NAME;
+
+ mockPaymentProviderPlugin.makePluginWaitSomeMilliseconds(1100);
+
+ SpyLogger spyLogger = withSpyLogger(OperationCallbackBase.class, new Callable<Void>() {
+
+ @Override
+ public Void call() throws Exception {
+ PaymentApiException thrownException = null;
+
+ try {
+ final Payment payment = paymentApi.createPurchase(account, account.getPaymentMethodId(), null, requestedAmount, Currency.AED, paymentExternalKey, transactionExternalKey,
+ ImmutableList.<PluginProperty>of(), callContext);
+ } catch (PaymentApiException e) {
+ thrownException = e;
+ }
+
+ assertNotNull(thrownException);
+
+ Throwable operationException = thrownException.getCause();
+ assertNotNull(operationException);
+ assertTrue(operationException instanceof OperationException);
+
+ Throwable timeoutException = operationException.getCause();
+ assertNotNull(timeoutException);
+ assertTrue(timeoutException instanceof TimeoutException);
+
+ return null;
+ }
+ });
+
+ assertTrue(spyLogger.contains("Calling plugin.*" + pluginName, Optional.of(SpyLogger.LOG_LEVEL_DEBUG)));
+ assertTrue(spyLogger.contains("TimeoutException.*" + pluginName, Optional.of(SpyLogger.LOG_LEVEL_WARN)));
+ }
+
private void verifyRefund(final Payment refund, final String paymentExternalKey, final String paymentTransactionExternalKey, final String refundTransactionExternalKey, final BigDecimal requestedAmount, final BigDecimal refundAmount, final TransactionStatus transactionStatus) {
Assert.assertEquals(refund.getExternalKey(), paymentExternalKey);
Assert.assertEquals(refund.getTransactions().size(), 2);
diff --git a/payment/src/test/java/org/killbill/billing/payment/core/sm/TestPluginOperation.java b/payment/src/test/java/org/killbill/billing/payment/core/sm/TestPluginOperation.java
index 75e39c3..650bd00 100644
--- a/payment/src/test/java/org/killbill/billing/payment/core/sm/TestPluginOperation.java
+++ b/payment/src/test/java/org/killbill/billing/payment/core/sm/TestPluginOperation.java
@@ -56,6 +56,8 @@ import com.jayway.awaitility.Awaitility;
public class TestPluginOperation extends PaymentTestSuiteNoDB {
+ private final static String PLUGIN_NAME_PLACEHOLDER = "pluginName";
+
private final GlobalLocker locker = new MemoryGlobalLocker();
private final Account account = Mockito.mock(Account.class);
@@ -79,7 +81,7 @@ public class TestPluginOperation extends PaymentTestSuiteNoDB {
final PaymentOperation pluginOperation = getPluginOperation();
try {
- pluginOperation.dispatchWithAccountLockAndTimeout(callback);
+ pluginOperation.dispatchWithAccountLockAndTimeout(PLUGIN_NAME_PLACEHOLDER, callback);
Assert.fail();
} catch (final OperationException e) {
Assert.assertEquals(e.getOperationResult(), OperationResult.EXCEPTION);
@@ -93,7 +95,7 @@ public class TestPluginOperation extends PaymentTestSuiteNoDB {
final PaymentOperation pluginOperation = getPluginOperation();
try {
- pluginOperation.dispatchWithAccountLockAndTimeout(callback);
+ pluginOperation.dispatchWithAccountLockAndTimeout(PLUGIN_NAME_PLACEHOLDER, callback);
Assert.fail();
} catch (final OperationException e) {
Assert.assertEquals(e.getOperationResult(), OperationResult.EXCEPTION);
@@ -152,7 +154,7 @@ public class TestPluginOperation extends PaymentTestSuiteNoDB {
if (shouldFailBecauseOfLockFailure) {
try {
- pluginOperation.dispatchWithAccountLockAndTimeout(callback);
+ pluginOperation.dispatchWithAccountLockAndTimeout(PLUGIN_NAME_PLACEHOLDER, callback);
Assert.fail();
} catch (final OperationException e) {
Assert.assertTrue(e.getCause() instanceof PaymentApiException);
@@ -161,7 +163,7 @@ public class TestPluginOperation extends PaymentTestSuiteNoDB {
}
} else {
try {
- pluginOperation.dispatchWithAccountLockAndTimeout(callback);
+ pluginOperation.dispatchWithAccountLockAndTimeout(PLUGIN_NAME_PLACEHOLDER, callback);
} catch (final OperationException e) {
Assert.fail(e.getMessage());
}
diff --git a/payment/src/test/java/org/killbill/billing/payment/logging/SpyLogger.java b/payment/src/test/java/org/killbill/billing/payment/logging/SpyLogger.java
new file mode 100644
index 0000000..62efbe4
--- /dev/null
+++ b/payment/src/test/java/org/killbill/billing/payment/logging/SpyLogger.java
@@ -0,0 +1,285 @@
+/*
+ * Copyright 2014-2015 Groupon, Inc
+ * Copyright 2014-2015 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.logging;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.regex.Pattern;
+
+import org.slf4j.helpers.FormattingTuple;
+import org.slf4j.helpers.MessageFormatter;
+import org.slf4j.helpers.SubstituteLogger;
+
+import com.google.common.base.Optional;
+
+public class SpyLogger extends SubstituteLogger {
+
+ private List<LogMessage> logMessageList = new ArrayList<LogMessage>();
+
+ public SpyLogger(String loggerName) {
+ super(loggerName);
+ }
+
+ public static final String LOG_LEVEL_TRACE = "TRACE";
+ public static final String LOG_LEVEL_DEBUG = "DEBUG";
+ public static final String LOG_LEVEL_INFO = "INFO";
+ public static final String LOG_LEVEL_WARN = "WARN";
+ public static final String LOG_LEVEL_ERROR = "ERROR";
+
+ @Override
+ public String getName() {
+ return super.getName();
+ }
+
+ @Override
+ public boolean isTraceEnabled() {
+ return super.isTraceEnabled();
+ }
+
+ @Override
+ public void trace(final String msg) {
+ super.trace(msg);
+ save(LOG_LEVEL_TRACE, msg, null);
+ }
+
+ @Override
+ public void trace(final String format, final Object arg) {
+ super.trace(format, arg);
+ formatAndSave(LOG_LEVEL_TRACE, format, arg, null);
+ }
+
+ @Override
+ public void trace(final String format, final Object arg1, final Object arg2) {
+ super.trace(format, arg1, arg2);
+ formatAndSave(LOG_LEVEL_TRACE, format, arg1, arg2);
+ }
+
+ @Override
+ public void trace(final String format, final Object... arguments) {
+ super.trace(format, arguments);
+ formatAndSave(LOG_LEVEL_TRACE, format, arguments);
+
+ }
+
+ @Override
+ public void trace(final String msg, final Throwable t) {
+ super.trace(msg, t);
+ save(LOG_LEVEL_TRACE, msg, t);
+ }
+
+ @Override
+ public boolean isDebugEnabled() {
+ return super.isDebugEnabled();
+ }
+
+ @Override
+ public void debug(final String msg) {
+ super.debug(msg);
+ save(LOG_LEVEL_DEBUG, msg, null);
+ }
+
+ @Override
+ public void debug(final String format, final Object arg) {
+ super.debug(format, arg);
+ formatAndSave(LOG_LEVEL_DEBUG, format, arg, null);
+ }
+
+ @Override
+ public void debug(final String format, final Object arg1, final Object arg2) {
+ super.debug(format, arg1, arg2);
+ formatAndSave(LOG_LEVEL_DEBUG, format, arg1, arg2);
+ }
+
+ @Override
+ public void debug(final String format, final Object... arguments) {
+ super.debug(format, arguments);
+ formatAndSave(LOG_LEVEL_DEBUG, format, arguments);
+ }
+
+ @Override
+ public void debug(final String msg, final Throwable t) {
+ super.debug(msg, t);
+ save(LOG_LEVEL_DEBUG, msg, t);
+ }
+
+ @Override
+ public boolean isInfoEnabled() {
+ return super.isInfoEnabled();
+ }
+
+ @Override
+ public void info(final String msg) {
+ super.info(msg);
+ save(LOG_LEVEL_INFO, msg, null);
+ }
+
+ @Override
+ public void info(final String format, final Object arg) {
+ super.info(format, arg);
+ formatAndSave(LOG_LEVEL_INFO, format, arg, null);
+ }
+
+ @Override
+ public void info(final String format, final Object arg1, final Object arg2) {
+ super.info(format, arg1, arg2);
+ formatAndSave(LOG_LEVEL_INFO, format, arg1, arg2);
+ }
+
+ @Override
+ public void info(final String format, final Object... arguments) {
+ super.info(format, arguments);
+ formatAndSave(LOG_LEVEL_INFO, format, arguments);
+ }
+
+ @Override
+ public void info(final String msg, final Throwable t) {
+ super.info(msg, t);
+ save(LOG_LEVEL_INFO, msg, t);
+ }
+
+ @Override
+ public boolean isWarnEnabled() {
+ return super.isWarnEnabled();
+ }
+
+ @Override
+ public void warn(final String msg) {
+ super.warn(msg);
+ save(LOG_LEVEL_WARN, msg, null);
+ }
+
+ @Override
+ public void warn(final String format, final Object arg) {
+ super.warn(format, arg);
+ formatAndSave(LOG_LEVEL_WARN, format, arg, null);
+ }
+
+ @Override
+ public void warn(final String format, final Object... arguments) {
+ super.warn(format, arguments);
+ formatAndSave(LOG_LEVEL_WARN, format, arguments);
+ }
+
+ @Override
+ public void warn(final String format, final Object arg1, final Object arg2) {
+ super.warn(format, arg1, arg2);
+ formatAndSave(LOG_LEVEL_WARN, format, arg1, arg2);
+ }
+
+ @Override
+ public void warn(final String msg, final Throwable t) {
+ super.warn(msg, t);
+ save(LOG_LEVEL_WARN, msg, t);
+ }
+
+ @Override
+ public boolean isErrorEnabled() {
+ return super.isErrorEnabled();
+ }
+
+ @Override
+ public void error(final String msg) {
+ super.error(msg);
+ save(LOG_LEVEL_ERROR, msg, null);
+ }
+
+ @Override
+ public void error(final String format, final Object arg) {
+ super.error(format, arg);
+ formatAndSave(LOG_LEVEL_ERROR, format, arg, null);
+ }
+
+ @Override
+ public void error(final String format, final Object arg1, final Object arg2) {
+ super.error(format, arg1, arg2);
+ formatAndSave(LOG_LEVEL_ERROR, format, arg1, arg2);
+ }
+
+ @Override
+ public void error(final String format, final Object... arguments) {
+ super.error(format, arguments);
+ formatAndSave(LOG_LEVEL_ERROR, format, arguments);
+ }
+
+ @Override
+ public void error(final String msg, final Throwable t) {
+ super.error(msg, t);
+ save(LOG_LEVEL_ERROR, msg, t);
+ }
+
+ /**
+ * Returns a list with the stored log messages
+ *
+ * @return a list with the stored log messages
+ */
+ public List<LogMessage> getLogMessageList() {
+ return logMessageList;
+ }
+
+ /**
+ * Checks if a certain message has been logged. It has to fulfil the
+ * given regex pattern. If a logLevel is provided, the expected message
+ * also has to have this log level. If no log level has been provided,
+ * a message is just compared to the regex pattern.
+ *
+ * @param regex pattern that the message should follow.
+ * @param logLevel log level that the message should have.
+ * @return true if a message has been found, false if no has been found
+ */
+ public boolean contains(String regex, Optional<String> logLevel) {
+ Pattern pattern = Pattern.compile(regex);
+
+ for (LogMessage logMessage : logMessageList) {
+ final boolean messageMatches = pattern.matcher(logMessage.message).find();
+ final boolean logLevelMatches = logLevel.isPresent() ? logLevel.get().equals(logMessage.logLevel) : true;
+
+ if (messageMatches && logLevelMatches) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private void formatAndSave(String logLevel, String format, Object arg1, Object arg2) {
+ FormattingTuple tp = MessageFormatter.format(format, arg1, arg2);
+ save(logLevel, tp.getMessage(), tp.getThrowable());
+ }
+
+ private void formatAndSave(String logLevel, String format, Object... arguments) {
+ FormattingTuple tp = MessageFormatter.arrayFormat(format, arguments);
+ save(logLevel, tp.getMessage(), tp.getThrowable());
+ }
+
+ private void save(String logLevel, String message, Throwable t) {
+ logMessageList.add(new LogMessage(logLevel, message, t));
+ }
+
+ public class LogMessage {
+
+ public final String logLevel;
+
+ public final String message;
+
+ public final Throwable throwable;
+
+ public LogMessage(final String logLevel, final String message, final Throwable throwable) {
+ this.logLevel = logLevel;
+ this.message = message;
+ this.throwable = throwable;
+ }
+ }
+}
diff --git a/payment/src/test/java/org/killbill/billing/payment/logging/TestLoggingHelper.java b/payment/src/test/java/org/killbill/billing/payment/logging/TestLoggingHelper.java
new file mode 100644
index 0000000..f7f94fe
--- /dev/null
+++ b/payment/src/test/java/org/killbill/billing/payment/logging/TestLoggingHelper.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2014-2015 Groupon, Inc
+ * Copyright 2014-2015 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.logging;
+
+import java.lang.reflect.Field;
+import java.util.Map;
+import java.util.concurrent.Callable;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.slf4j.impl.SimpleLoggerFactory;
+import org.slf4j.impl.StaticLoggerBinder;
+
+public class TestLoggingHelper {
+
+ public static SpyLogger withSpyLogger(final Class loggingClass, final Callable<Void> callable) throws Exception {
+ final Logger regularLogger = LoggerFactory.getLogger(loggingClass);
+ final SpyLogger spyLogger = new SpyLogger(loggingClass.getName());
+
+ try {
+ injectLoggerIntoLoggerFactory(loggingClass, spyLogger);
+ callable.call();
+ } finally {
+ injectLoggerIntoLoggerFactory(loggingClass, regularLogger);
+ }
+ return spyLogger;
+ }
+
+ private static void injectLoggerIntoLoggerFactory(final Class loggingClass, final Logger logger) throws Exception {
+ final SimpleLoggerFactory simpleLoggerFactory = (SimpleLoggerFactory) StaticLoggerBinder.getSingleton().getLoggerFactory();
+ final Field loggerMapField = SimpleLoggerFactory.class.getDeclaredField("loggerMap");
+ loggerMapField.setAccessible(true);
+ final Map loggerMap = (Map) loggerMapField.get(simpleLoggerFactory);
+ loggerMap.put(loggingClass.getName(), logger);
+ }
+}
diff --git a/payment/src/test/java/org/killbill/billing/payment/provider/MockPaymentProviderPlugin.java b/payment/src/test/java/org/killbill/billing/payment/provider/MockPaymentProviderPlugin.java
index f3f94e7..537c603 100644
--- a/payment/src/test/java/org/killbill/billing/payment/provider/MockPaymentProviderPlugin.java
+++ b/payment/src/test/java/org/killbill/billing/payment/provider/MockPaymentProviderPlugin.java
@@ -26,6 +26,7 @@ import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicInteger;
import org.killbill.billing.catalog.api.Currency;
import org.killbill.billing.payment.api.PaymentMethodPlugin;
@@ -66,6 +67,7 @@ public class MockPaymentProviderPlugin implements PaymentPluginApi {
private final AtomicBoolean makeNextInvoiceFailWithError = new AtomicBoolean(false);
private final AtomicBoolean makeNextInvoiceFailWithException = new AtomicBoolean(false);
private final AtomicBoolean makeAllInvoicesFailWithError = new AtomicBoolean(false);
+ private final AtomicInteger makePluginWaitSomeMilliseconds = new AtomicInteger(0);
private final Map<String, InternalPaymentInfo> payments = new ConcurrentHashMap<String, InternalPaymentInfo>();
private final Map<String, List<PaymentTransactionInfoPlugin>> paymentTransactions = new ConcurrentHashMap<String, List<PaymentTransactionInfoPlugin>>();
@@ -189,6 +191,7 @@ public class MockPaymentProviderPlugin implements PaymentPluginApi {
makeNextInvoiceFailWithException.set(false);
makeAllInvoicesFailWithError.set(false);
makeNextInvoiceFailWithError.set(false);
+ makePluginWaitSomeMilliseconds.set(0);
paymentMethods.clear();
payments.clear();
paymentTransactions.clear();
@@ -207,6 +210,10 @@ public class MockPaymentProviderPlugin implements PaymentPluginApi {
makeAllInvoicesFailWithError.set(failure);
}
+ public void makePluginWaitSomeMilliseconds(final int milliseconds) {
+ makePluginWaitSomeMilliseconds.set(milliseconds);
+ }
+
public void updatePaymentTransactions(final UUID paymentId, final List<PaymentTransactionInfoPlugin> newTransactions) {
if (paymentTransactions.containsKey(paymentId.toString())) {
paymentTransactions.put (paymentId.toString(), newTransactions);
@@ -337,6 +344,15 @@ public class MockPaymentProviderPlugin implements PaymentPluginApi {
}
private PaymentTransactionInfoPlugin getPaymentTransactionInfoPluginResult(final UUID kbPaymentId, final UUID kbTransactionId, final TransactionType type, final BigDecimal amount, final Currency currency, final Iterable<PluginProperty> pluginProperties) throws PaymentPluginApiException {
+ if (makePluginWaitSomeMilliseconds.get() > 0) {
+ try {
+ Thread.sleep(makePluginWaitSomeMilliseconds.get());
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ throw new PaymentPluginApiException("An Interruption occurred while the Thread was sleeping.", e);
+ }
+ }
+
if (makeNextInvoiceFailWithException.getAndSet(false)) {
throw new PaymentPluginApiException("", "test error");
}
diff --git a/payment/src/test/resources/payment.properties b/payment/src/test/resources/payment.properties
index 34337a0..7314dfa 100644
--- a/payment/src/test/resources/payment.properties
+++ b/payment/src/test/resources/payment.properties
@@ -1,3 +1,4 @@
org.killbill.payment.failure.retry.start.sec=3600
org.killbill.payment.failure.retry.multiplier=1
org.killbill.payment.failure.retry.max.attempts=3
+org.killbill.payment.plugin.timeout=1s