killbill-aplcache

payment: set timeout for control operations in DefaultPaymentGatewayApi This

12/1/2015 11:50:38 AM

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(), ""));
+            }
+        }
+    }
+}