killbill-aplcache

Merge branch 'new-paymentpluginapi' of github.com:killbill/killbill

2/7/2013 12:37:17 AM

Details

diff --git a/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/JaxrsResource.java b/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/JaxrsResource.java
index 08813a4..71b62fa 100644
--- a/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/JaxrsResource.java
+++ b/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/JaxrsResource.java
@@ -130,5 +130,9 @@ public interface JaxrsResource {
     public static final String EXPORT = "export";
     public static final String EXPORT_PATH = PREFIX + "/" + EXPORT;
 
+    public static final String PLUGINS = "plugins";
+    // No PREFIX here!
+    public static final String PLUGINS_PATH = "/" + PLUGINS;
+
     public static final String CBA_REBALANCING = "cbaRebalancing";
 }
diff --git a/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/PluginResource.java b/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/PluginResource.java
new file mode 100644
index 0000000..9fee49d
--- /dev/null
+++ b/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/PluginResource.java
@@ -0,0 +1,124 @@
+/*
+ * Copyright 2010-2013 Ning, Inc.
+ *
+ * Ning 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 com.ning.billing.jaxrs.resources;
+
+import java.io.IOException;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.ws.rs.DELETE;
+import javax.ws.rs.GET;
+import javax.ws.rs.HEAD;
+import javax.ws.rs.OPTIONS;
+import javax.ws.rs.POST;
+import javax.ws.rs.PUT;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.core.Response;
+
+import com.ning.billing.jaxrs.util.Context;
+import com.ning.billing.jaxrs.util.JaxrsUriBuilder;
+import com.ning.billing.util.api.AuditUserApi;
+import com.ning.billing.util.api.CustomFieldUserApi;
+import com.ning.billing.util.api.TagUserApi;
+
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+import com.google.inject.name.Named;
+
+@Singleton
+@Path(JaxrsResource.PLUGINS_PATH)
+public class PluginResource extends JaxRsResourceBase {
+
+    private final HttpServlet osgiServlet;
+
+    @Inject
+    public PluginResource(@Named("osgi") final HttpServlet osgiServlet,
+                          final JaxrsUriBuilder uriBuilder,
+                          final TagUserApi tagUserApi,
+                          final CustomFieldUserApi customFieldUserApi,
+                          final AuditUserApi auditUserApi,
+                          final Context context) {
+        super(uriBuilder, tagUserApi, customFieldUserApi, auditUserApi, context);
+        this.osgiServlet = osgiServlet;
+    }
+
+    @DELETE
+    @Path("/{pluginName:" + STRING_PATTERN + "}/{rest:.+}")
+    public Response doDELETE(@PathParam("pluginName") final String pluginName,
+                             @javax.ws.rs.core.Context final HttpServletRequest request,
+                             @javax.ws.rs.core.Context final HttpServletResponse response) throws ServletException, IOException {
+        return serviceViaOSGIPlugin(pluginName, request, response);
+    }
+
+    @GET
+    @Path("/{pluginName:" + STRING_PATTERN + "}/{rest:.+}")
+    public Response doGET(@PathParam("pluginName") final String pluginName,
+                          @javax.ws.rs.core.Context final HttpServletRequest request,
+                          @javax.ws.rs.core.Context final HttpServletResponse response) throws ServletException, IOException {
+        return serviceViaOSGIPlugin(pluginName, request, response);
+    }
+
+    @OPTIONS
+    @Path("/{pluginName:" + STRING_PATTERN + "}/{rest:.+}")
+    public Response doOPTIONS(@PathParam("pluginName") final String pluginName,
+                              @javax.ws.rs.core.Context final HttpServletRequest request,
+                              @javax.ws.rs.core.Context final HttpServletResponse response) throws ServletException, IOException {
+        return serviceViaOSGIPlugin(pluginName, request, response);
+    }
+
+    @POST
+    @Path("/{pluginName:" + STRING_PATTERN + "}/{rest:.+}")
+    public Response doPOST(@PathParam("pluginName") final String pluginName,
+                           @javax.ws.rs.core.Context final HttpServletRequest request,
+                           @javax.ws.rs.core.Context final HttpServletResponse response) throws ServletException, IOException {
+        return serviceViaOSGIPlugin(pluginName, request, response);
+    }
+
+    @PUT
+    @Path("/{pluginName:" + STRING_PATTERN + "}/{rest:.+}")
+    public Response doPUT(@PathParam("pluginName") final String pluginName,
+                          @javax.ws.rs.core.Context final HttpServletRequest request,
+                          @javax.ws.rs.core.Context final HttpServletResponse response) throws ServletException, IOException {
+        return serviceViaOSGIPlugin(pluginName, request, response);
+    }
+
+    @HEAD
+    @Path("/{pluginName:" + STRING_PATTERN + "}/{rest:.+}")
+    public Response doHEAD(@PathParam("pluginName") final String pluginName,
+                           @javax.ws.rs.core.Context final HttpServletRequest request,
+                           @javax.ws.rs.core.Context final HttpServletResponse response) throws ServletException, IOException {
+        prepareOSGIRequest(pluginName, request);
+        osgiServlet.service(request, response);
+
+        // Make sure to return 204
+        return Response.noContent().build();
+    }
+
+    private Response serviceViaOSGIPlugin(final String pluginName, final HttpServletRequest request, final HttpServletResponse response) throws ServletException, IOException {
+        prepareOSGIRequest(pluginName, request);
+        osgiServlet.service(request, response);
+
+        return Response.status(response.getStatus()).build();
+    }
+
+    private void prepareOSGIRequest(final String pluginName, final HttpServletRequest request) {
+        request.setAttribute("killbill.osgi.pluginName", pluginName);
+    }
+}

osgi/pom.xml 4(+4 -0)

diff --git a/osgi/pom.xml b/osgi/pom.xml
index b21699c..f3303b1 100644
--- a/osgi/pom.xml
+++ b/osgi/pom.xml
@@ -49,6 +49,10 @@
             <scope>provided</scope>
         </dependency>
         <dependency>
+            <groupId>javax.servlet</groupId>
+            <artifactId>javax.servlet-api</artifactId>
+        </dependency>
+        <dependency>
             <groupId>org.skife.config</groupId>
             <artifactId>config-magic</artifactId>
         </dependency>
diff --git a/osgi/src/main/java/com/ning/billing/osgi/glue/DefaultOSGIModule.java b/osgi/src/main/java/com/ning/billing/osgi/glue/DefaultOSGIModule.java
index 0655cf9..b2c3408 100644
--- a/osgi/src/main/java/com/ning/billing/osgi/glue/DefaultOSGIModule.java
+++ b/osgi/src/main/java/com/ning/billing/osgi/glue/DefaultOSGIModule.java
@@ -16,12 +16,14 @@
 
 package com.ning.billing.osgi.glue;
 
+import javax.servlet.http.HttpServlet;
 import javax.sql.DataSource;
 
 import org.skife.config.ConfigurationObjectFactory;
 
 import com.ning.billing.osgi.DefaultOSGIKillbill;
 import com.ning.billing.osgi.KillbillActivator;
+import com.ning.billing.osgi.OSGIServlet;
 import com.ning.billing.osgi.api.OSGIKillbill;
 import com.ning.billing.osgi.api.config.PluginConfigServiceApi;
 import com.ning.billing.osgi.pluginconf.DefaultPluginConfigServiceApi;
@@ -43,9 +45,15 @@ public class DefaultOSGIModule extends AbstractModule {
         bind(OSGIDataSourceConfig.class).toInstance(osgiDataSourceConfig);
     }
 
+    protected void installOSGIServlet() {
+        bind(HttpServlet.class).annotatedWith(Names.named(OSGI_NAMED)).to(OSGIServlet.class).asEagerSingleton();
+    }
+
     @Override
     protected void configure() {
         installConfig();
+        installOSGIServlet();
+
         bind(KillbillActivator.class).asEagerSingleton();
         bind(PluginFinder.class).asEagerSingleton();
         bind(PluginConfigServiceApi.class).to(DefaultPluginConfigServiceApi.class).asEagerSingleton();
diff --git a/osgi/src/main/java/com/ning/billing/osgi/OSGIServlet.java b/osgi/src/main/java/com/ning/billing/osgi/OSGIServlet.java
new file mode 100644
index 0000000..d8420a9
--- /dev/null
+++ b/osgi/src/main/java/com/ning/billing/osgi/OSGIServlet.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright 2010-2013 Ning, Inc.
+ *
+ * Ning 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 com.ning.billing.osgi;
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+public class OSGIServlet extends HttpServlet {
+
+    private final Map<String, HttpServlet> pluginServlets = new HashMap<String, HttpServlet>();
+
+    public void registerResource(final String pluginName, final HttpServlet httpServlet) {
+        pluginServlets.put(pluginName, httpServlet);
+    }
+
+    @Override
+    protected void doGet(final HttpServletRequest req, final HttpServletResponse resp) throws ServletException, IOException {
+        serviceViaPlugin(req, resp);
+    }
+
+    @Override
+    protected void doHead(final HttpServletRequest req, final HttpServletResponse resp) throws ServletException, IOException {
+        serviceViaPlugin(req, resp);
+    }
+
+    @Override
+    protected void doPost(final HttpServletRequest req, final HttpServletResponse resp) throws ServletException, IOException {
+        serviceViaPlugin(req, resp);
+    }
+
+    @Override
+    protected void doPut(final HttpServletRequest req, final HttpServletResponse resp) throws ServletException, IOException {
+        serviceViaPlugin(req, resp);
+    }
+
+    @Override
+    protected void doDelete(final HttpServletRequest req, final HttpServletResponse resp) throws ServletException, IOException {
+        serviceViaPlugin(req, resp);
+    }
+
+    @Override
+    protected void doOptions(final HttpServletRequest req, final HttpServletResponse resp) throws ServletException, IOException {
+        serviceViaPlugin(req, resp);
+    }
+
+    private void serviceViaPlugin(final HttpServletRequest req, final HttpServletResponse resp) throws ServletException, IOException {
+        final HttpServlet pluginServlet = getPluginServlet(req);
+        if (pluginServlet != null) {
+            pluginServlet.service(req, resp);
+        } else {
+            resp.sendError(404);
+        }
+    }
+
+    private HttpServlet getPluginServlet(final HttpServletRequest req) {
+        final String pluginName = (String) req.getAttribute("killbill.osgi.pluginName");
+        if (pluginName != null) {
+            return pluginServlets.get(pluginName);
+        } else {
+            return null;
+        }
+    }
+}
diff --git a/server/src/main/java/com/ning/billing/server/listeners/KillbillGuiceListener.java b/server/src/main/java/com/ning/billing/server/listeners/KillbillGuiceListener.java
index f8c1567..aaf22b7 100644
--- a/server/src/main/java/com/ning/billing/server/listeners/KillbillGuiceListener.java
+++ b/server/src/main/java/com/ning/billing/server/listeners/KillbillGuiceListener.java
@@ -37,6 +37,7 @@ import com.ning.billing.util.svcsapi.bus.InternalBus;
 import com.ning.jetty.base.modules.ServerModuleBuilder;
 import com.ning.jetty.core.listeners.SetupServer;
 
+import com.google.common.annotations.VisibleForTesting;
 import com.google.inject.Injector;
 import com.google.inject.Module;
 import net.sf.ehcache.CacheManager;
@@ -47,6 +48,7 @@ public class KillbillGuiceListener extends SetupServer {
     public static final Logger logger = LoggerFactory.getLogger(KillbillGuiceListener.class);
     public static final String KILLBILL_MULTITENANT_PROPERTY = "killbill.server.multitenant";
 
+    private Injector injector;
     private DefaultLifecycle killbillLifecycle;
     private BusService killbillBusService;
     private KillbillEventHandler killbilleventHandler;
@@ -86,12 +88,15 @@ public class KillbillGuiceListener extends SetupServer {
         super.contextInitialized(event);
 
         logger.info("KillbillLifecycleListener : contextInitialized");
-        final Injector theInjector = injector(event);
-        killbillLifecycle = theInjector.getInstance(DefaultLifecycle.class);
-        killbillBusService = theInjector.getInstance(BusService.class);
-        killbilleventHandler = theInjector.getInstance(KillbillEventHandler.class);
 
-        registerMBeansForCache(theInjector.getInstance(CacheManager.class));
+        injector = injector(event);
+        event.getServletContext().setAttribute(Injector.class.getName(), injector);
+
+        killbillLifecycle = injector.getInstance(DefaultLifecycle.class);
+        killbillBusService = injector.getInstance(BusService.class);
+        killbilleventHandler = injector.getInstance(KillbillEventHandler.class);
+
+        registerMBeansForCache(injector.getInstance(CacheManager.class));
 
         /*
                 ObjectMapper mapper = theInjector.getInstance(ObjectMapper.class);
@@ -137,4 +142,9 @@ public class KillbillGuiceListener extends SetupServer {
         // Complete shutdown sequence
         killbillLifecycle.fireShutdownSequencePostEventUnRegistration();
     }
+
+    @VisibleForTesting
+    public Injector getInstantiatedInjector() {
+        return injector;
+    }
 }
diff --git a/server/src/test/java/com/ning/billing/jaxrs/KillbillClient.java b/server/src/test/java/com/ning/billing/jaxrs/KillbillClient.java
index e4ee470..94cb566 100644
--- a/server/src/test/java/com/ning/billing/jaxrs/KillbillClient.java
+++ b/server/src/test/java/com/ning/billing/jaxrs/KillbillClient.java
@@ -812,6 +812,58 @@ public abstract class KillbillClient extends ServerTestSuiteWithEmbeddedDB {
     }
 
     //
+    // PLUGINS
+    //
+
+    protected Response pluginGET(final String uri) throws Exception {
+        return pluginGET(uri, DEFAULT_EMPTY_QUERY);
+    }
+
+    protected Response pluginGET(final String uri, final Map<String, String> queryParams) throws Exception {
+        return doGet(JaxrsResource.PLUGINS_PATH + "/" + uri, queryParams, DEFAULT_HTTP_TIMEOUT_SEC);
+    }
+
+    protected Response pluginHEAD(final String uri) throws Exception {
+        return pluginHEAD(uri, DEFAULT_EMPTY_QUERY);
+    }
+
+    protected Response pluginHEAD(final String uri, final Map<String, String> queryParams) throws Exception {
+        return doHead(JaxrsResource.PLUGINS_PATH + "/" + uri, queryParams, DEFAULT_HTTP_TIMEOUT_SEC);
+    }
+
+    protected Response pluginPOST(final String uri, @Nullable final String body) throws Exception {
+        return pluginPOST(uri, body, DEFAULT_EMPTY_QUERY);
+    }
+
+    protected Response pluginPOST(final String uri, @Nullable final String body, final Map<String, String> queryParams) throws Exception {
+        return doPost(JaxrsResource.PLUGINS_PATH + "/" + uri, body, queryParams, DEFAULT_HTTP_TIMEOUT_SEC);
+    }
+
+    protected Response pluginPUT(final String uri, @Nullable final String body) throws Exception {
+        return pluginPUT(uri, body, DEFAULT_EMPTY_QUERY);
+    }
+
+    protected Response pluginPUT(final String uri, @Nullable final String body, final Map<String, String> queryParams) throws Exception {
+        return doPut(JaxrsResource.PLUGINS_PATH + "/" + uri, body, queryParams, DEFAULT_HTTP_TIMEOUT_SEC);
+    }
+
+    protected Response pluginDELETE(final String uri) throws Exception {
+        return pluginDELETE(uri, DEFAULT_EMPTY_QUERY);
+    }
+
+    protected Response pluginDELETE(final String uri, final Map<String, String> queryParams) throws Exception {
+        return doDelete(JaxrsResource.PLUGINS_PATH + "/" + uri, queryParams, DEFAULT_HTTP_TIMEOUT_SEC);
+    }
+
+    protected Response pluginOPTIONS(final String uri) throws Exception {
+        return pluginOPTIONS(uri, DEFAULT_EMPTY_QUERY);
+    }
+
+    protected Response pluginOPTIONS(final String uri, final Map<String, String> queryParams) throws Exception {
+        return doOptions(JaxrsResource.PLUGINS_PATH + "/" + uri, queryParams, DEFAULT_HTTP_TIMEOUT_SEC);
+    }
+
+    //
     // HTTP CLIENT HELPERS
     //
     protected Response doPost(final String uri, @Nullable final String body, final Map<String, String> queryParams, final int timeoutSec) {
@@ -846,11 +898,31 @@ public abstract class KillbillClient extends ServerTestSuiteWithEmbeddedDB {
         return doGetWithUrl(url, queryParams, timeoutSec);
     }
 
+    protected Response doHead(final String uri, final Map<String, String> queryParams, final int timeoutSec) {
+        final String url = String.format("http://%s:%d%s", config.getServerHost(), config.getServerPort(), uri);
+        return doHeadWithUrl(url, queryParams, timeoutSec);
+    }
+
+    protected Response doOptions(final String uri, final Map<String, String> queryParams, final int timeoutSec) {
+        final String url = String.format("http://%s:%d%s", config.getServerHost(), config.getServerPort(), uri);
+        return doOptionsWithUrl(url, queryParams, timeoutSec);
+    }
+
     protected Response doGetWithUrl(final String url, final Map<String, String> queryParams, final int timeoutSec) {
         final BoundRequestBuilder builder = getBuilderWithHeaderAndQuery("GET", url, queryParams);
         return executeAndWait(builder, timeoutSec, false);
     }
 
+    protected Response doHeadWithUrl(final String url, final Map<String, String> queryParams, final int timeoutSec) {
+        final BoundRequestBuilder builder = getBuilderWithHeaderAndQuery("HEAD", url, queryParams);
+        return executeAndWait(builder, timeoutSec, false);
+    }
+
+    protected Response doOptionsWithUrl(final String url, final Map<String, String> queryParams, final int timeoutSec) {
+        final BoundRequestBuilder builder = getBuilderWithHeaderAndQuery("OPTIONS", url, queryParams);
+        return executeAndWait(builder, timeoutSec, false);
+    }
+
     private Response executeAndWait(final BoundRequestBuilder builder, final int timeoutSec, final boolean addContextHeader) {
 
         if (addContextHeader) {
@@ -890,6 +962,10 @@ public abstract class KillbillClient extends ServerTestSuiteWithEmbeddedDB {
             builder = httpClient.preparePut(url);
         } else if (verb.equals("DELETE")) {
             builder = httpClient.prepareDelete(url);
+        } else if (verb.equals("HEAD")) {
+            builder = httpClient.prepareHead(url);
+        } else if (verb.equals("OPTIONS")) {
+            builder = httpClient.prepareOptions(url);
         } else {
             Assert.fail("Unknown verb " + verb);
         }
diff --git a/server/src/test/java/com/ning/billing/jaxrs/TestJaxrsBase.java b/server/src/test/java/com/ning/billing/jaxrs/TestJaxrsBase.java
index 25e1f10..15df041 100644
--- a/server/src/test/java/com/ning/billing/jaxrs/TestJaxrsBase.java
+++ b/server/src/test/java/com/ning/billing/jaxrs/TestJaxrsBase.java
@@ -23,6 +23,10 @@ import java.util.HashMap;
 import java.util.Iterator;
 import java.util.Map;
 
+import javax.inject.Inject;
+import javax.inject.Named;
+import javax.servlet.http.HttpServlet;
+
 import org.eclipse.jetty.servlet.FilterHolder;
 import org.joda.time.LocalDate;
 import org.skife.config.ConfigurationObjectFactory;
@@ -47,12 +51,14 @@ import com.ning.billing.invoice.api.InvoiceNotifier;
 import com.ning.billing.invoice.glue.DefaultInvoiceModule;
 import com.ning.billing.invoice.notification.NullInvoiceNotifier;
 import com.ning.billing.junction.glue.DefaultJunctionModule;
+import com.ning.billing.osgi.glue.DefaultOSGIModule;
 import com.ning.billing.overdue.glue.DefaultOverdueModule;
 import com.ning.billing.payment.glue.PaymentModule;
 import com.ning.billing.payment.provider.MockPaymentProviderPluginModule;
 import com.ning.billing.server.listeners.KillbillGuiceListener;
 import com.ning.billing.server.modules.KillbillServerModule;
 import com.ning.billing.tenant.glue.TenantModule;
+import com.ning.billing.usage.glue.UsageModule;
 import com.ning.billing.util.clock.Clock;
 import com.ning.billing.util.clock.ClockMock;
 import com.ning.billing.util.config.PaymentConfig;
@@ -87,6 +93,10 @@ public class TestJaxrsBase extends KillbillClient {
 
     protected static final int DEFAULT_HTTP_TIMEOUT_SEC = 6000; // 5;
 
+    @Inject
+    @Named("osgi")
+    protected HttpServlet osgiServlet;
+
     protected static TestKillbillGuiceListener listener;
 
     private final DBTestingHelper helper = KillbillTestSuiteWithEmbeddedDB.getDBTestingHelper();
@@ -188,6 +198,8 @@ public class TestJaxrsBase extends KillbillClient {
             install(new DefaultOverdueModule());
             install(new TenantModule());
             install(new ExportModule());
+            install(new DefaultOSGIModule());
+            install(new UsageModule());
             installClock();
         }
 
@@ -248,6 +260,8 @@ public class TestJaxrsBase extends KillbillClient {
         server.configure(config, getListeners(), getFilters());
 
         server.start();
+
+        listener.getInstantiatedInjector().injectMembers(this);
     }
 
     protected Iterable<EventListener> getListeners() {
diff --git a/server/src/test/java/com/ning/billing/jaxrs/TestPlugin.java b/server/src/test/java/com/ning/billing/jaxrs/TestPlugin.java
new file mode 100644
index 0000000..f8da0a7
--- /dev/null
+++ b/server/src/test/java/com/ning/billing/jaxrs/TestPlugin.java
@@ -0,0 +1,216 @@
+/*
+ * Copyright 2010-2013 Ning, Inc.
+ *
+ * Ning 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 com.ning.billing.jaxrs;
+
+import java.io.IOException;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import javax.annotation.Nullable;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.testng.Assert;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import com.ning.billing.jaxrs.resources.JaxrsResource;
+import com.ning.billing.osgi.OSGIServlet;
+import com.ning.http.client.Response;
+
+public class TestPlugin extends TestJaxrsBase {
+
+    private static final String TEST_PLUGIN_NAME = "test-osgi";
+
+    private static final byte[] TEST_PLUGIN_RESPONSE_BYTES = new byte[]{0xC, 0x0, 0xF, 0xF, 0xE, 0xE};
+
+    private static final String TEST_PLUGIN_VALID_GET_PATH = "setGETMarkerToTrue";
+    private static final String TEST_PLUGIN_VALID_HEAD_PATH = "setHEADMarkerToTrue";
+    private static final String TEST_PLUGIN_VALID_POST_PATH = "setPOSTMarkerToTrue";
+    private static final String TEST_PLUGIN_VALID_PUT_PATH = "setPUTMarkerToTrue";
+    private static final String TEST_PLUGIN_VALID_DELETE_PATH = "setDELETEMarkerToTrue";
+    private static final String TEST_PLUGIN_VALID_OPTIONS_PATH = "setOPTIONSMarkerToTrue";
+
+    private final AtomicBoolean requestGETMarker = new AtomicBoolean(false);
+    private final AtomicBoolean requestHEADMarker = new AtomicBoolean(false);
+    private final AtomicBoolean requestPOSTMarker = new AtomicBoolean(false);
+    private final AtomicBoolean requestPUTMarker = new AtomicBoolean(false);
+    private final AtomicBoolean requestDELETEMarker = new AtomicBoolean(false);
+    private final AtomicBoolean requestOPTIONSMarker = new AtomicBoolean(false);
+
+    @BeforeMethod(groups = "slow")
+    public void setUp() throws Exception {
+        setupOSGIPlugin();
+        resetAllMarkers();
+    }
+
+    @Test(groups = "slow")
+    public void testPassRequestsToUnknownPlugin() throws Exception {
+        final String uri = "pluginDoesNotExist/something";
+        Response response;
+
+        // We don't test the output here as it is some Jetty specific HTML blurb
+
+        response = pluginGET(uri);
+        testAndResetAllMarkers(response, 404, null, false, false, false, false, false, false);
+
+        response = pluginHEAD(uri);
+        testAndResetAllMarkers(response, 404, null, false, false, false, false, false, false);
+
+        response = pluginPOST(uri, null);
+        testAndResetAllMarkers(response, 404, null, false, false, false, false, false, false);
+
+        response = pluginPUT(uri, null);
+        testAndResetAllMarkers(response, 404, null, false, false, false, false, false, false);
+
+        response = pluginDELETE(uri);
+        testAndResetAllMarkers(response, 404, null, false, false, false, false, false, false);
+
+        response = pluginOPTIONS(uri);
+        testAndResetAllMarkers(response, 404, null, false, false, false, false, false, false);
+    }
+
+    @Test(groups = "slow")
+    public void testPassRequestsToKnownPluginButWrongPath() throws Exception {
+        final String uri = TEST_PLUGIN_NAME + "/somethingSomething";
+        Response response;
+
+        response = pluginGET(uri);
+        testAndResetAllMarkers(response, 200, new byte[]{}, false, false, false, false, false, false);
+
+        response = pluginHEAD(uri);
+        testAndResetAllMarkers(response, 204, new byte[]{}, false, false, false, false, false, false);
+
+        response = pluginPOST(uri, null);
+        testAndResetAllMarkers(response, 200, new byte[]{}, false, false, false, false, false, false);
+
+        response = pluginPUT(uri, null);
+        testAndResetAllMarkers(response, 200, new byte[]{}, false, false, false, false, false, false);
+
+        response = pluginDELETE(uri);
+        testAndResetAllMarkers(response, 200, new byte[]{}, false, false, false, false, false, false);
+
+        response = pluginOPTIONS(uri);
+        testAndResetAllMarkers(response, 200, new byte[]{}, false, false, false, false, false, false);
+    }
+
+    @Test(groups = "slow")
+    public void testPassRequestsToKnownPluginAndKnownPath() throws Exception {
+        Response response;
+
+        response = pluginGET(TEST_PLUGIN_NAME + "/" + TEST_PLUGIN_VALID_GET_PATH);
+        testAndResetAllMarkers(response, 230, TEST_PLUGIN_RESPONSE_BYTES, true, false, false, false, false, false);
+
+        response = pluginHEAD(TEST_PLUGIN_NAME + "/" + TEST_PLUGIN_VALID_HEAD_PATH);
+        testAndResetAllMarkers(response, 204, new byte[]{}, false, true, false, false, false, false);
+
+        response = pluginPOST(TEST_PLUGIN_NAME + "/" + TEST_PLUGIN_VALID_POST_PATH, null);
+        testAndResetAllMarkers(response, 230, TEST_PLUGIN_RESPONSE_BYTES, false, false, true, false, false, false);
+
+        response = pluginPUT(TEST_PLUGIN_NAME + "/" + TEST_PLUGIN_VALID_PUT_PATH, null);
+        testAndResetAllMarkers(response, 230, TEST_PLUGIN_RESPONSE_BYTES, false, false, false, true, false, false);
+
+        response = pluginDELETE(TEST_PLUGIN_NAME + "/" + TEST_PLUGIN_VALID_DELETE_PATH);
+        testAndResetAllMarkers(response, 230, TEST_PLUGIN_RESPONSE_BYTES, false, false, false, false, true, false);
+
+        response = pluginOPTIONS(TEST_PLUGIN_NAME + "/" + TEST_PLUGIN_VALID_OPTIONS_PATH);
+        testAndResetAllMarkers(response, 230, TEST_PLUGIN_RESPONSE_BYTES, false, false, false, false, false, true);
+    }
+
+    private void testAndResetAllMarkers(final Response response, final int responseCode, @Nullable final byte[] responseBytes, final boolean get, final boolean head,
+                                        final boolean post, final boolean put, final boolean delete, final boolean options) throws IOException {
+        Assert.assertEquals(response.getStatusCode(), responseCode);
+        if (responseBytes != null) {
+            Assert.assertEquals(response.getResponseBodyAsBytes(), responseBytes);
+        }
+
+        Assert.assertEquals(requestGETMarker.get(), get);
+        Assert.assertEquals(requestHEADMarker.get(), head);
+        Assert.assertEquals(requestPOSTMarker.get(), post);
+        Assert.assertEquals(requestPUTMarker.get(), put);
+        Assert.assertEquals(requestDELETEMarker.get(), delete);
+        Assert.assertEquals(requestOPTIONSMarker.get(), options);
+
+        resetAllMarkers();
+    }
+
+    private void resetAllMarkers() {
+        requestGETMarker.set(false);
+        requestHEADMarker.set(false);
+        requestPOSTMarker.set(false);
+        requestPUTMarker.set(false);
+        requestDELETEMarker.set(false);
+        requestOPTIONSMarker.set(false);
+    }
+
+    private void setupOSGIPlugin() {
+        ((OSGIServlet) osgiServlet).registerResource(TEST_PLUGIN_NAME, new HttpServlet() {
+            @Override
+            protected void doGet(final HttpServletRequest req, final HttpServletResponse resp) throws ServletException, IOException {
+                if ((JaxrsResource.PLUGINS_PATH + "/" + TEST_PLUGIN_NAME + "/" + TEST_PLUGIN_VALID_GET_PATH).equals(req.getPathInfo())) {
+                    requestGETMarker.set(true);
+                    resp.getOutputStream().write(TEST_PLUGIN_RESPONSE_BYTES);
+                    resp.setStatus(230);
+                }
+            }
+
+            @Override
+            protected void doHead(final HttpServletRequest req, final HttpServletResponse resp) throws ServletException, IOException {
+                if ((JaxrsResource.PLUGINS_PATH + "/" + TEST_PLUGIN_NAME + "/" + TEST_PLUGIN_VALID_HEAD_PATH).equals(req.getPathInfo())) {
+                    requestHEADMarker.set(true);
+                }
+            }
+
+            @Override
+            protected void doPost(final HttpServletRequest req, final HttpServletResponse resp) throws ServletException, IOException {
+                if ((JaxrsResource.PLUGINS_PATH + "/" + TEST_PLUGIN_NAME + "/" + TEST_PLUGIN_VALID_POST_PATH).equals(req.getPathInfo())) {
+                    requestPOSTMarker.set(true);
+                    resp.getOutputStream().write(TEST_PLUGIN_RESPONSE_BYTES);
+                    resp.setStatus(230);
+                }
+            }
+
+            @Override
+            protected void doPut(final HttpServletRequest req, final HttpServletResponse resp) throws ServletException, IOException {
+                if ((JaxrsResource.PLUGINS_PATH + "/" + TEST_PLUGIN_NAME + "/" + TEST_PLUGIN_VALID_PUT_PATH).equals(req.getPathInfo())) {
+                    requestPUTMarker.set(true);
+                    resp.getOutputStream().write(TEST_PLUGIN_RESPONSE_BYTES);
+                    resp.setStatus(230);
+                }
+            }
+
+            @Override
+            protected void doDelete(final HttpServletRequest req, final HttpServletResponse resp) throws ServletException, IOException {
+                if ((JaxrsResource.PLUGINS_PATH + "/" + TEST_PLUGIN_NAME + "/" + TEST_PLUGIN_VALID_DELETE_PATH).equals(req.getPathInfo())) {
+                    requestDELETEMarker.set(true);
+                    resp.getOutputStream().write(TEST_PLUGIN_RESPONSE_BYTES);
+                    resp.setStatus(230);
+                }
+            }
+
+            @Override
+            protected void doOptions(final HttpServletRequest req, final HttpServletResponse resp) throws ServletException, IOException {
+                if ((JaxrsResource.PLUGINS_PATH + "/" + TEST_PLUGIN_NAME + "/" + TEST_PLUGIN_VALID_OPTIONS_PATH).equals(req.getPathInfo())) {
+                    requestOPTIONSMarker.set(true);
+                    resp.getOutputStream().write(TEST_PLUGIN_RESPONSE_BYTES);
+                    resp.setStatus(230);
+                }
+            }
+        });
+    }
+}