killbill-uncached

Add new filter to attach a ThreadLocal RequestId (and provide

8/19/2015 7:53:20 PM

Details

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 f8c2d67..5a23cab 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
@@ -41,6 +41,7 @@ import org.killbill.billing.payment.api.TransactionStatus;
 import org.killbill.billing.payment.dao.PaymentDao;
 import org.killbill.billing.payment.dao.PaymentMethodModelDao;
 import org.killbill.billing.payment.dao.PaymentTransactionModelDao;
+import org.killbill.billing.payment.dispatcher.CallableWithRequestData;
 import org.killbill.billing.payment.dispatcher.PluginDispatcher;
 import org.killbill.billing.payment.dispatcher.PluginDispatcher.PluginDispatcherReturnType;
 import org.killbill.billing.payment.plugin.api.PaymentPluginApi;
@@ -56,6 +57,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;
 
diff --git a/payment/src/main/java/org/killbill/billing/payment/dispatcher/CallableWithRequestData.java b/payment/src/main/java/org/killbill/billing/payment/dispatcher/CallableWithRequestData.java
new file mode 100644
index 0000000..e78d8ad
--- /dev/null
+++ b/payment/src/main/java/org/killbill/billing/payment/dispatcher/CallableWithRequestData.java
@@ -0,0 +1,44 @@
+/*
+ * 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.concurrent.Callable;
+
+import org.killbill.commons.request.Request;
+import org.killbill.commons.request.RequestData;
+
+public class CallableWithRequestData<T> implements Callable<T> {
+
+    private final RequestData requestData;
+    private final Callable<T> delegate;
+
+    public CallableWithRequestData(final RequestData requestData, final Callable<T> delegate) {
+        this.requestData = requestData;
+        this.delegate = delegate;
+    }
+
+    @Override
+    public T call() throws Exception {
+        try {
+            Request.setPerThreadRequestData(requestData);
+            return delegate.call();
+        } finally {
+            Request.resetPerThreadRequestData();
+        }
+    }
+}
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 a60eff6..f8b6ca5 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
@@ -27,6 +27,7 @@ import java.util.concurrent.TimeoutException;
 
 import org.killbill.commons.profiling.Profiling;
 import org.killbill.commons.profiling.ProfilingData;
+import org.killbill.commons.request.Request;
 
 public class PluginDispatcher<ReturnType> {
 
@@ -48,7 +49,10 @@ public class PluginDispatcher<ReturnType> {
     public ReturnType dispatchWithTimeout(final Callable<PluginDispatcherReturnType<ReturnType>> task, final long timeout, final TimeUnit unit)
             throws TimeoutException, ExecutionException, InterruptedException {
 
-        final Future<PluginDispatcherReturnType<ReturnType>> future = executor.submit(task);
+        // Wrap existing callable to keep the original requestId
+        final Callable<PluginDispatcherReturnType<ReturnType>> callableWithRequestData = new CallableWithRequestData(Request.getPerThreadRequestData(), task);
+
+        final Future<PluginDispatcherReturnType<ReturnType>> future = executor.submit(callableWithRequestData);
         final PluginDispatcherReturnType<ReturnType> pluginDispatcherResult = future.get(timeout, unit);
 
         if (pluginDispatcherResult instanceof WithProfilingPluginDispatcherReturnType) {
diff --git a/payment/src/main/java/org/killbill/billing/payment/glue/PaymentModule.java b/payment/src/main/java/org/killbill/billing/payment/glue/PaymentModule.java
index 0c0b0a7..bd95ba4 100644
--- a/payment/src/main/java/org/killbill/billing/payment/glue/PaymentModule.java
+++ b/payment/src/main/java/org/killbill/billing/payment/glue/PaymentModule.java
@@ -136,8 +136,10 @@ public class PaymentModule extends KillBillModule {
 
     protected void installProcessors(final PaymentConfig paymentConfig) {
 
-        final ExecutorService pluginExecutorService = new WithProfilingThreadPoolExecutor(paymentConfig.getPaymentPluginThreadNb(), paymentConfig.getPaymentPluginThreadNb(),
-                                                                                          0L, TimeUnit.MILLISECONDS,
+        final ExecutorService pluginExecutorService = new WithProfilingThreadPoolExecutor(paymentConfig.getPaymentPluginThreadNb(),
+                                                                                          paymentConfig.getPaymentPluginThreadNb(),
+                                                                                          0L,
+                                                                                          TimeUnit.MILLISECONDS,
                                                                                           new LinkedBlockingQueue<Runnable>(),
                                                                                           new ThreadFactory() {
 
diff --git a/payment/src/test/java/org/killbill/billing/payment/dispatcher/TestPluginDispatcher.java b/payment/src/test/java/org/killbill/billing/payment/dispatcher/TestPluginDispatcher.java
index d226d73..aef5eec 100644
--- a/payment/src/test/java/org/killbill/billing/payment/dispatcher/TestPluginDispatcher.java
+++ b/payment/src/test/java/org/killbill/billing/payment/dispatcher/TestPluginDispatcher.java
@@ -26,6 +26,8 @@ import org.killbill.billing.ErrorCode;
 import org.killbill.billing.payment.PaymentTestSuiteNoDB;
 import org.killbill.billing.payment.api.PaymentApiException;
 import org.killbill.billing.payment.dispatcher.PluginDispatcher.PluginDispatcherReturnType;
+import org.killbill.commons.request.Request;
+import org.killbill.commons.request.RequestData;
 import org.testng.Assert;
 import org.testng.annotations.Test;
 
@@ -33,6 +35,8 @@ public class TestPluginDispatcher extends PaymentTestSuiteNoDB {
 
     private final PluginDispatcher<Void> voidPluginDispatcher = new PluginDispatcher<Void>(10, Executors.newSingleThreadExecutor());
 
+    private final PluginDispatcher<String> stringPluginDispatcher = new PluginDispatcher<String>(1, Executors.newSingleThreadExecutor());
+
     @Test(groups = "fast")
     public void testDispatchWithTimeout() throws TimeoutException, PaymentApiException {
         boolean gotIt = false;
@@ -106,4 +110,25 @@ public class TestPluginDispatcher extends PaymentTestSuiteNoDB {
         }
         Assert.assertTrue(gotIt);
     }
+
+
+    @Test(groups = "fast")
+    public void testDispatchWithRequestData() throws TimeoutException, PaymentApiException, ExecutionException, InterruptedException {
+
+        final String requestId = "vive la vie et les coquillettes";
+
+        final Callable<PluginDispatcherReturnType<String>> delegate = new Callable<PluginDispatcherReturnType<String>>() {
+            @Override
+            public PluginDispatcherReturnType<String> call() throws Exception {
+                return PluginDispatcher.<String>createPluginDispatcherReturnType(Request.getPerThreadRequestData().getRequestId());
+            }
+        };
+
+        final CallableWithRequestData<PluginDispatcherReturnType<String>> callable = new CallableWithRequestData<PluginDispatcherReturnType<String>>(new RequestData(requestId),
+                                                                                                                                                        delegate);
+
+        final String actualRequestId = stringPluginDispatcher.dispatchWithTimeout(callable, 100, TimeUnit.MILLISECONDS);
+        Assert.assertEquals(actualRequestId, requestId);
+    }
+
 }

pom.xml 2(+1 -1)

diff --git a/pom.xml b/pom.xml
index 6b6b458..8deb6da 100644
--- a/pom.xml
+++ b/pom.xml
@@ -21,7 +21,7 @@
     <parent>
         <artifactId>killbill-oss-parent</artifactId>
         <groupId>org.kill-bill.billing</groupId>
-        <version>0.37</version>
+        <version>0.38-SNAPSHOT</version>
     </parent>
     <artifactId>killbill</artifactId>
     <version>0.15.3-SNAPSHOT</version>
diff --git a/profiles/killbill/src/main/java/org/killbill/billing/server/filters/RequestIdFilter.java b/profiles/killbill/src/main/java/org/killbill/billing/server/filters/RequestIdFilter.java
new file mode 100644
index 0000000..3cfe141
--- /dev/null
+++ b/profiles/killbill/src/main/java/org/killbill/billing/server/filters/RequestIdFilter.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.server.filters;
+
+import java.util.List;
+
+import org.killbill.billing.util.UUIDs;
+import org.killbill.commons.request.Request;
+import org.killbill.commons.request.RequestData;
+
+import com.google.inject.Singleton;
+import com.sun.jersey.spi.container.ContainerRequest;
+import com.sun.jersey.spi.container.ContainerRequestFilter;
+import com.sun.jersey.spi.container.ContainerResponse;
+import com.sun.jersey.spi.container.ContainerResponseFilter;
+
+@Singleton
+public class RequestIdFilter implements ContainerRequestFilter, ContainerResponseFilter {
+
+
+    private static final String REQUEST_ID_HEADER_REQ = "X-Killbill-Request-Id-Req";
+
+    @Override
+    public ContainerRequest filter(final ContainerRequest request) {
+        final List<String> requestIdHeaderRequests = request.getRequestHeader(REQUEST_ID_HEADER_REQ);
+        final String requestId = (requestIdHeaderRequests == null || requestIdHeaderRequests.isEmpty()) ? UUIDs.randomUUID().toString() : requestIdHeaderRequests.get(0);
+        Request.setPerThreadRequestData(new RequestData(requestId));
+        return request;
+    }
+
+    @Override
+    public ContainerResponse filter(final ContainerRequest request, final ContainerResponse response) {
+        Request.resetPerThreadRequestData();
+        return response;
+    }
+}
diff --git a/profiles/killbill/src/main/java/org/killbill/billing/server/listeners/KillbillGuiceListener.java b/profiles/killbill/src/main/java/org/killbill/billing/server/listeners/KillbillGuiceListener.java
index 6e506da..bca802a 100644
--- a/profiles/killbill/src/main/java/org/killbill/billing/server/listeners/KillbillGuiceListener.java
+++ b/profiles/killbill/src/main/java/org/killbill/billing/server/listeners/KillbillGuiceListener.java
@@ -28,6 +28,7 @@ import org.killbill.billing.jaxrs.util.KillbillEventHandler;
 import org.killbill.billing.platform.api.KillbillConfigSource;
 import org.killbill.billing.platform.config.DefaultKillbillConfigSource;
 import org.killbill.billing.server.filters.ProfilingContainerResponseFilter;
+import org.killbill.billing.server.filters.RequestIdFilter;
 import org.killbill.billing.server.filters.ResponseCorsFilter;
 import org.killbill.billing.server.modules.KillbillServerModule;
 import org.killbill.billing.server.security.TenantFilter;
@@ -73,6 +74,7 @@ public class KillbillGuiceListener extends KillbillPlatformGuiceListener {
         // The logging filter is still incompatible with the GZIP filter
         //builder.addJerseyFilter(GZIPContentEncodingFilter.class.getName());
         builder.addJerseyFilter(ProfilingContainerResponseFilter.class.getName());
+        builder.addJerseyFilter(RequestIdFilter.class.getName());
 
         // Broader, to support the "Try it out!" feature
         //builder.addFilter("/" + SWAGGER_PATH + "*", ResponseCorsFilter.class);