killbill-aplcache
payment: set timeout for control operations in DefaultPaymentGatewayApi This …
12/1/2015 11:50:38 AM
Changes
Details
diff --git a/payment/src/main/java/org/killbill/billing/payment/api/DefaultPaymentGatewayApi.java b/payment/src/main/java/org/killbill/billing/payment/api/DefaultPaymentGatewayApi.java
index 6ac3cb8..22fcc42 100644
--- a/payment/src/main/java/org/killbill/billing/payment/api/DefaultPaymentGatewayApi.java
+++ b/payment/src/main/java/org/killbill/billing/payment/api/DefaultPaymentGatewayApi.java
@@ -1,6 +1,6 @@
/*
- * Copyright 2014 Groupon, Inc
- * Copyright 2014 The Billing Project, LLC
+ * 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
@@ -19,6 +19,8 @@ package org.killbill.billing.payment.api;
import java.util.List;
import java.util.UUID;
+import java.util.concurrent.Callable;
+import java.util.concurrent.TimeUnit;
import javax.annotation.Nullable;
import javax.inject.Inject;
@@ -29,28 +31,43 @@ import org.killbill.billing.control.plugin.api.HPPType;
import org.killbill.billing.control.plugin.api.PaymentApiType;
import org.killbill.billing.control.plugin.api.PaymentControlApiException;
import org.killbill.billing.control.plugin.api.PriorPaymentControlResult;
+import org.killbill.billing.payment.core.PaymentExecutors;
import org.killbill.billing.payment.core.PaymentGatewayProcessor;
import org.killbill.billing.payment.core.sm.control.ControlPluginRunner;
+import org.killbill.billing.payment.dispatcher.PluginDispatcher;
+import org.killbill.billing.payment.dispatcher.PluginDispatcher.PluginDispatcherReturnType;
import org.killbill.billing.payment.plugin.api.GatewayNotification;
import org.killbill.billing.payment.plugin.api.HostedPaymentPageFormDescriptor;
import org.killbill.billing.util.callcontext.CallContext;
import org.killbill.billing.util.callcontext.InternalCallContextFactory;
import org.killbill.billing.util.config.PaymentConfig;
+import com.google.common.base.Joiner;
+
+import static org.killbill.billing.payment.dispatcher.PaymentPluginDispatcher.dispatchWithExceptionHandling;
+
public class DefaultPaymentGatewayApi extends DefaultApiBase implements PaymentGatewayApi {
+ private static final Joiner JOINER = Joiner.on(", ");
+
private final PaymentGatewayProcessor paymentGatewayProcessor;
private final ControlPluginRunner controlPluginRunner;
+ private final PluginDispatcher<HostedPaymentPageFormDescriptor> paymentPluginFormDispatcher;
+ private final PluginDispatcher<GatewayNotification> paymentPluginNotificationDispatcher;
private final InternalCallContextFactory internalCallContextFactory;
@Inject
public DefaultPaymentGatewayApi(final PaymentConfig paymentConfig,
final PaymentGatewayProcessor paymentGatewayProcessor,
final ControlPluginRunner controlPluginRunner,
+ final PaymentExecutors executors,
final InternalCallContextFactory internalCallContextFactory) {
super(paymentConfig);
this.paymentGatewayProcessor = paymentGatewayProcessor;
this.controlPluginRunner = controlPluginRunner;
+ final long paymentPluginTimeoutSec = TimeUnit.SECONDS.convert(paymentConfig.getPaymentPluginTimeout().getPeriod(), paymentConfig.getPaymentPluginTimeout().getUnit());
+ this.paymentPluginFormDispatcher = new PluginDispatcher<HostedPaymentPageFormDescriptor>(paymentPluginTimeoutSec, executors);
+ this.paymentPluginNotificationDispatcher = new PluginDispatcher<GatewayNotification>(paymentPluginTimeoutSec, executors);
this.internalCallContextFactory = internalCallContextFactory;
}
@@ -67,8 +84,7 @@ public class DefaultPaymentGatewayApi extends DefaultApiBase implements PaymentG
@Override
public HostedPaymentPageFormDescriptor buildFormDescriptorWithPaymentControl(final Account account, final UUID paymentMethodId, final Iterable<PluginProperty> customFields, final Iterable<PluginProperty> properties, final PaymentOptions paymentOptions, final CallContext callContext) throws PaymentApiException {
-
- return executeWithPaymentControl(account, paymentMethodId, properties, paymentOptions, callContext, new WithPaymentControlCallback<HostedPaymentPageFormDescriptor>() {
+ return executeWithPaymentControl(account, paymentMethodId, properties, paymentOptions, callContext, paymentPluginFormDispatcher, new WithPaymentControlCallback<HostedPaymentPageFormDescriptor>() {
@Override
public HostedPaymentPageFormDescriptor doPaymentGatewayApiOperation(final UUID adjustedPaymentMethodId, final Iterable<PluginProperty> adjustedPluginProperties) throws PaymentApiException {
return buildFormDescriptor(account, adjustedPaymentMethodId, customFields, adjustedPluginProperties, callContext);
@@ -83,7 +99,7 @@ public class DefaultPaymentGatewayApi extends DefaultApiBase implements PaymentG
@Override
public GatewayNotification processNotificationWithPaymentControl(final String notification, final String pluginName, final Iterable<PluginProperty> properties, final PaymentOptions paymentOptions, final CallContext callContext) throws PaymentApiException {
- return executeWithPaymentControl(null, null, properties, paymentOptions, callContext, new WithPaymentControlCallback<GatewayNotification>() {
+ return executeWithPaymentControl(null, null, properties, paymentOptions, callContext, paymentPluginNotificationDispatcher, new WithPaymentControlCallback<GatewayNotification>() {
@Override
public GatewayNotification doPaymentGatewayApiOperation(final UUID adjustedPaymentMethodId, final Iterable<PluginProperty> adjustedPluginProperties) throws PaymentApiException {
if (adjustedPaymentMethodId == null) {
@@ -105,42 +121,49 @@ public class DefaultPaymentGatewayApi extends DefaultApiBase implements PaymentG
final Iterable<PluginProperty> properties,
final PaymentOptions paymentOptions,
final CallContext callContext,
+ final PluginDispatcher<T> pluginDispatcher,
final WithPaymentControlCallback<T> callback) throws PaymentApiException {
-
final List<String> paymentControlPluginNames = toPaymentControlPluginNames(paymentOptions);
if (paymentControlPluginNames.isEmpty()) {
return callback.doPaymentGatewayApiOperation(paymentMethodId, properties);
}
- final PriorPaymentControlResult priorCallResult;
- try {
- priorCallResult = controlPluginRunner.executePluginPriorCalls(account,
- paymentMethodId,
- null, null, null, null,
- PaymentApiType.HPP, null, HPPType.BUILD_FORM_DESCRIPTOR,
- null, null, true, paymentControlPluginNames, properties, callContext);
-
- } catch (final PaymentControlApiException e) {
- throw new PaymentApiException(ErrorCode.PAYMENT_PLUGIN_EXCEPTION, e);
- }
-
- try {
- final T result = callback.doPaymentGatewayApiOperation(priorCallResult.getAdjustedPaymentMethodId(), priorCallResult.getAdjustedPluginProperties());
- controlPluginRunner.executePluginOnSuccessCalls(account,
- paymentMethodId,
- null, null, null, null, null,
- PaymentApiType.HPP, null, HPPType.BUILD_FORM_DESCRIPTOR,
- null, null, null, null, true, paymentControlPluginNames, priorCallResult.getAdjustedPluginProperties(), callContext);
- return result;
- } catch (final PaymentApiException e) {
- controlPluginRunner.executePluginOnFailureCalls(account,
- paymentMethodId,
- null, null, null, null,
- PaymentApiType.HPP, null, HPPType.BUILD_FORM_DESCRIPTOR,
- null, null, true, paymentControlPluginNames, priorCallResult.getAdjustedPluginProperties(), callContext);
-
- throw e;
-
- }
+ final List<String> controlPluginNames = paymentOptions.getPaymentControlPluginNames();
+ return dispatchWithExceptionHandling(account,
+ JOINER.join(controlPluginNames),
+ new Callable<PluginDispatcherReturnType<T>>() {
+ @Override
+ public PluginDispatcherReturnType<T> call() throws Exception {
+ final PriorPaymentControlResult priorCallResult;
+ try {
+ priorCallResult = controlPluginRunner.executePluginPriorCalls(account,
+ paymentMethodId,
+ null, null, null, null,
+ PaymentApiType.HPP, null, HPPType.BUILD_FORM_DESCRIPTOR,
+ null, null, true, paymentControlPluginNames, properties, callContext);
+
+ } catch (final PaymentControlApiException e) {
+ throw new PaymentApiException(ErrorCode.PAYMENT_PLUGIN_EXCEPTION, e);
+ }
+
+ try {
+ final T result = callback.doPaymentGatewayApiOperation(priorCallResult.getAdjustedPaymentMethodId(), priorCallResult.getAdjustedPluginProperties());
+ controlPluginRunner.executePluginOnSuccessCalls(account,
+ paymentMethodId,
+ null, null, null, null, null,
+ PaymentApiType.HPP, null, HPPType.BUILD_FORM_DESCRIPTOR,
+ null, null, null, null, true, paymentControlPluginNames, priorCallResult.getAdjustedPluginProperties(), callContext);
+ return PluginDispatcher.createPluginDispatcherReturnType(result);
+ } catch (final PaymentApiException e) {
+ controlPluginRunner.executePluginOnFailureCalls(account,
+ paymentMethodId,
+ null, null, null, null,
+ PaymentApiType.HPP, null, HPPType.BUILD_FORM_DESCRIPTOR,
+ null, null, true, paymentControlPluginNames, priorCallResult.getAdjustedPluginProperties(), callContext);
+ throw e;
+ }
+ }
+ },
+ pluginDispatcher);
}
}
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 eaf2e5f..1c99c33 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
@@ -50,6 +50,8 @@ import org.killbill.commons.locker.GlobalLocker;
import com.google.common.base.Objects;
+import static org.killbill.billing.payment.dispatcher.PaymentPluginDispatcher.dispatchWithExceptionHandling;
+
// We don't take any lock here because the call needs to be re-entrant
// from the plugin: for example, the BitPay plugin will create the payment during the
// processNotification call, while the PayU plugin will create it during buildFormDescriptor.
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 ba4303e..f09a73f 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
@@ -72,6 +72,7 @@ import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
import com.google.inject.Inject;
+import static org.killbill.billing.payment.dispatcher.PaymentPluginDispatcher.dispatchWithExceptionHandling;
import static org.killbill.billing.util.entity.dao.DefaultPaginationHelper.getEntityPagination;
import static org.killbill.billing.util.entity.dao.DefaultPaginationHelper.getEntityPaginationFromPlugins;
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 78f1d61..dd41668 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
@@ -22,8 +22,6 @@ import java.util.List;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.Callable;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.TimeoutException;
import javax.annotation.Nullable;
@@ -38,7 +36,6 @@ import org.killbill.billing.osgi.api.OSGIServiceRegistration;
import org.killbill.billing.payment.api.PaymentApiException;
import org.killbill.billing.payment.dao.PaymentDao;
import org.killbill.billing.payment.dao.PaymentMethodModelDao;
-import org.killbill.billing.payment.dispatcher.PluginDispatcher;
import org.killbill.billing.payment.dispatcher.PluginDispatcher.PluginDispatcherReturnType;
import org.killbill.billing.payment.plugin.api.PaymentPluginApi;
import org.killbill.billing.tag.TagInternalApi;
@@ -54,12 +51,10 @@ 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;
import com.google.common.base.Function;
-import com.google.common.base.Objects;
import com.google.common.collect.Collections2;
public abstract class ProcessorBase {
@@ -209,36 +204,4 @@ public abstract class ProcessorBase {
}
}
}
-
- 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 {
- 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) {
- 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();
- 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(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/dispatcher/PaymentPluginDispatcher.java b/payment/src/main/java/org/killbill/billing/payment/dispatcher/PaymentPluginDispatcher.java
new file mode 100644
index 0000000..8454892
--- /dev/null
+++ b/payment/src/main/java/org/killbill/billing/payment/dispatcher/PaymentPluginDispatcher.java
@@ -0,0 +1,71 @@
+/*
+ * 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.dispatcher;
+
+import java.util.UUID;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeoutException;
+
+import javax.annotation.Nullable;
+
+import org.killbill.billing.ErrorCode;
+import org.killbill.billing.account.api.Account;
+import org.killbill.billing.payment.api.PaymentApiException;
+import org.killbill.billing.payment.dispatcher.PluginDispatcher.PluginDispatcherReturnType;
+import org.killbill.commons.locker.LockFailedException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.common.base.MoreObjects;
+
+public class PaymentPluginDispatcher {
+
+ private static final Logger log = LoggerFactory.getLogger(PaymentPluginDispatcher.class);
+
+ public static <ReturnType> ReturnType dispatchWithExceptionHandling(@Nullable final Account account, final String pluginNames, final Callable<PluginDispatcherReturnType<ReturnType>> callable, final PluginDispatcher<ReturnType> pluginDispatcher) throws PaymentApiException {
+ final UUID accountId = account != null ? account.getId() : null;
+ final String accountExternalKey = account != null ? account.getExternalKey() : "";
+
+ try {
+ log.debug("Calling plugin(s) {}", pluginNames);
+ final ReturnType result = pluginDispatcher.dispatchWithTimeout(callable);
+ log.debug("Successful plugin(s) call of {} for account {} with result {}", pluginNames, accountId, result);
+ return result;
+ } catch (final TimeoutException e) {
+ final String errorMessage = String.format("TimeoutException while executing the plugin(s) %s", pluginNames);
+ log.warn(errorMessage, e);
+ throw new PaymentApiException(ErrorCode.PAYMENT_PLUGIN_TIMEOUT, accountId, errorMessage);
+ } catch (final InterruptedException e) {
+ Thread.currentThread().interrupt();
+ final String errorMessage = String.format("InterruptedException while executing the following plugin(s): %s", pluginNames);
+ log.warn(errorMessage, e);
+ throw new PaymentApiException(ErrorCode.PAYMENT_INTERNAL_ERROR, MoreObjects.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(format, e);
+ throw new PaymentApiException(ErrorCode.PAYMENT_INTERNAL_ERROR, format);
+ } else {
+ throw new PaymentApiException(e, ErrorCode.PAYMENT_INTERNAL_ERROR, MoreObjects.firstNonNull(e.getMessage(), ""));
+ }
+ }
+ }
+}