killbill-aplcache
Changes
osgi/pom.xml 51(+51 -0)
Details
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
index 9fee49d..651eed8 100644
--- a/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/PluginResource.java
+++ b/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/PluginResource.java
@@ -18,10 +18,14 @@ package com.ning.billing.jaxrs.resources;
import java.io.IOException;
+import javax.servlet.ServletConfig;
+import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletRequestWrapper;
import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.HttpServletResponseWrapper;
import javax.ws.rs.DELETE;
import javax.ws.rs.GET;
import javax.ws.rs.HEAD;
@@ -29,7 +33,6 @@ 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;
@@ -43,7 +46,7 @@ import com.google.inject.Singleton;
import com.google.inject.name.Named;
@Singleton
-@Path(JaxrsResource.PLUGINS_PATH)
+@Path(JaxrsResource.PLUGINS_PATH + "{subResources:.*}")
public class PluginResource extends JaxRsResourceBase {
private final HttpServlet osgiServlet;
@@ -60,65 +63,97 @@ public class PluginResource extends JaxRsResourceBase {
}
@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);
+ public Response doDELETE(@javax.ws.rs.core.Context final HttpServletRequest request,
+ @javax.ws.rs.core.Context final HttpServletResponse response,
+ @javax.ws.rs.core.Context final ServletContext servletContext,
+ @javax.ws.rs.core.Context final ServletConfig servletConfig) throws ServletException, IOException {
+ return serviceViaOSGIPlugin(request, response, servletContext, servletConfig);
}
@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);
+ public Response doGET(@javax.ws.rs.core.Context final HttpServletRequest request,
+ @javax.ws.rs.core.Context final HttpServletResponse response,
+ @javax.ws.rs.core.Context final ServletContext servletContext,
+ @javax.ws.rs.core.Context final ServletConfig servletConfig) throws ServletException, IOException {
+ return serviceViaOSGIPlugin(request, response, servletContext, servletConfig);
}
@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);
+ public Response doOPTIONS(@javax.ws.rs.core.Context final HttpServletRequest request,
+ @javax.ws.rs.core.Context final HttpServletResponse response,
+ @javax.ws.rs.core.Context final ServletContext servletContext,
+ @javax.ws.rs.core.Context final ServletConfig servletConfig) throws ServletException, IOException {
+ return serviceViaOSGIPlugin(request, response, servletContext, servletConfig);
}
@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);
+ public Response doPOST(@javax.ws.rs.core.Context final HttpServletRequest request,
+ @javax.ws.rs.core.Context final HttpServletResponse response,
+ @javax.ws.rs.core.Context final ServletContext servletContext,
+ @javax.ws.rs.core.Context final ServletConfig servletConfig) throws ServletException, IOException {
+ return serviceViaOSGIPlugin(request, response, servletContext, servletConfig);
}
@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);
+ public Response doPUT(@javax.ws.rs.core.Context final HttpServletRequest request,
+ @javax.ws.rs.core.Context final HttpServletResponse response,
+ @javax.ws.rs.core.Context final ServletContext servletContext,
+ @javax.ws.rs.core.Context final ServletConfig servletConfig) throws ServletException, IOException {
+ return serviceViaOSGIPlugin(request, response, servletContext, servletConfig);
}
@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);
+ public Response doHEAD(@javax.ws.rs.core.Context final HttpServletRequest request,
+ @javax.ws.rs.core.Context final HttpServletResponse response,
+ @javax.ws.rs.core.Context final ServletContext servletContext,
+ @javax.ws.rs.core.Context final ServletConfig servletConfig) throws ServletException, IOException {
+ prepareOSGIRequest(request, servletContext, servletConfig);
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);
+ private Response serviceViaOSGIPlugin(final HttpServletRequest request, final HttpServletResponse response,
+ final ServletContext servletContext, final ServletConfig servletConfig) throws ServletException, IOException {
+ prepareOSGIRequest(request, servletContext, servletConfig);
+ osgiServlet.service(new OSGIServletRequestWrapper(request), new OSGIServletResponseWrapper(response));
return Response.status(response.getStatus()).build();
}
- private void prepareOSGIRequest(final String pluginName, final HttpServletRequest request) {
- request.setAttribute("killbill.osgi.pluginName", pluginName);
+ private void prepareOSGIRequest(final HttpServletRequest request, final ServletContext servletContext, final ServletConfig servletConfig) {
+ request.setAttribute("killbill.osgi.servletContext", servletContext);
+ request.setAttribute("killbill.osgi.servletConfig", servletConfig);
+ }
+
+ // Request wrapper to hide the /plugins prefix to OSGI bundles
+ private static final class OSGIServletRequestWrapper extends HttpServletRequestWrapper {
+
+ public OSGIServletRequestWrapper(final HttpServletRequest request) {
+ super(request);
+ }
+
+ @Override
+ public String getPathInfo() {
+ return super.getPathInfo().replace(JaxrsResource.PLUGINS_PATH, "");
+ }
+
+ @Override
+ public String getContextPath() {
+ return JaxrsResource.PLUGINS_PATH;
+ }
+
+ @Override
+ public String getServletPath() {
+ return super.getServletPath().replace(JaxrsResource.PLUGINS_PATH, "");
+ }
+ }
+
+ private static final class OSGIServletResponseWrapper extends HttpServletResponseWrapper {
+
+ public OSGIServletResponseWrapper(final HttpServletResponse response) {
+ super(response);
+ }
}
}
osgi/pom.xml 51(+51 -0)
diff --git a/osgi/pom.xml b/osgi/pom.xml
index 76f1bef..e9386f8 100644
--- a/osgi/pom.xml
+++ b/osgi/pom.xml
@@ -32,6 +32,57 @@
<artifactId>org.apache.felix.framework</artifactId>
</dependency>
<dependency>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>org.apache.felix.configadmin</artifactId>
+ <version>1.6.0</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>org.apache.felix.log</artifactId>
+ <version>1.0.1</version>
+ </dependency>
+
+ <!-- TODO PIERRE Dependencies for the Felix Web Console.
+ In 4.0.1-SNAPSHOT, these are provided by the bundle,
+ see https://issues.apache.org/jira/browse/FELIX-3778.
+ -->
+ <dependency>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>org.apache.felix.webconsole</artifactId>
+ <version>4.0.0</version>
+ </dependency>
+ <dependency>
+ <groupId>commons-fileupload</groupId>
+ <artifactId>commons-fileupload</artifactId>
+ <version>1.2.1</version>
+ </dependency>
+ <dependency>
+ <groupId>commons-io</groupId>
+ <artifactId>commons-io</artifactId>
+ <version>1.4</version>
+ </dependency>
+ <dependency>
+ <groupId>org.json</groupId>
+ <artifactId>json</artifactId>
+ <version>20070829</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>org.apache.felix.shell</artifactId>
+ <version>1.0.0</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>org.osgi.service.obr</artifactId>
+ <version>1.0.2</version>
+ </dependency>
+ <dependency>
+ <groupId>org.osgi</groupId>
+ <artifactId>org.osgi.compendium</artifactId>
+ <version>4.3.1</version>
+ </dependency>
+
+ <dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
</dependency>
diff --git a/osgi/src/main/java/com/ning/billing/osgi/DefaultOSGIService.java b/osgi/src/main/java/com/ning/billing/osgi/DefaultOSGIService.java
index 49d1289..068a91b 100644
--- a/osgi/src/main/java/com/ning/billing/osgi/DefaultOSGIService.java
+++ b/osgi/src/main/java/com/ning/billing/osgi/DefaultOSGIService.java
@@ -24,13 +24,14 @@ import java.util.Map;
import javax.inject.Inject;
+import org.apache.felix.cm.impl.ConfigurationManager;
import org.apache.felix.framework.Felix;
import org.apache.felix.framework.util.FelixConstants;
+import org.apache.felix.webconsole.internal.OsgiManagerActivator;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleActivator;
import org.osgi.framework.BundleContext;
import org.osgi.framework.BundleException;
-import org.osgi.framework.InvalidSyntaxException;
import org.osgi.framework.ServiceReference;
import org.osgi.framework.launch.Framework;
import org.slf4j.Logger;
@@ -39,9 +40,7 @@ import org.slf4j.osgi.logservice.impl.Activator;
import com.ning.billing.lifecycle.LifecycleHandlerType;
import com.ning.billing.lifecycle.LifecycleHandlerType.LifecycleLevel;
-import com.ning.billing.osgi.api.OSGIPluginProperties;
import com.ning.billing.osgi.api.OSGIService;
-import com.ning.billing.osgi.api.OSGIServiceRegistration;
import com.ning.billing.osgi.api.config.PluginConfigServiceApi;
import com.ning.billing.osgi.api.config.PluginJavaConfig;
import com.ning.billing.osgi.api.config.PluginRubyConfig;
@@ -52,7 +51,6 @@ import com.ning.billing.payment.plugin.api.PaymentPluginApi;
import com.ning.billing.util.config.OSGIConfig;
import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableMap;
public class DefaultOSGIService implements OSGIService {
@@ -60,7 +58,6 @@ public class DefaultOSGIService implements OSGIService {
private static final Logger logger = LoggerFactory.getLogger(DefaultOSGIService.class);
-
private final OSGIConfig osgiConfig;
private final PluginFinder pluginFinder;
private final PluginConfigServiceApi pluginConfigServiceApi;
@@ -115,7 +112,6 @@ public class DefaultOSGIService implements OSGIService {
public void startFramework() {
}
-
@LifecycleHandlerType(LifecycleLevel.STOP_SERVICE)
public void stop() {
try {
@@ -186,8 +182,19 @@ public class DefaultOSGIService implements OSGIService {
final Map<Object, Object> felixConfig = new HashMap<Object, Object>();
felixConfig.putAll(config);
- // Install default bundles: killbill and slf4j ones
- felixConfig.put(FelixConstants.SYSTEMBUNDLE_ACTIVATORS_PROP, ImmutableList.<BundleActivator>of(killbillActivator, new Activator()));
+ // Install default bundles
+ // TODO PIERRE Should the Felix Web Console (and its dependencies) be rather installed at deploy time?
+ felixConfig.put(FelixConstants.SYSTEMBUNDLE_ACTIVATORS_PROP,
+ ImmutableList.<BundleActivator>of(killbillActivator,
+ // SLF4J LogService
+ new Activator(),
+ // Felix Web Console
+ new OsgiManagerActivator(),
+ // Felix Log Service (installed for the Web Console)
+ // TODO PIERRE Does it conflict with the SLF4J one?
+ new org.apache.felix.log.Activator(),
+ // Felix Configuration Admin Service (installed for the Web Console)
+ new ConfigurationManager()));
final Framework felix = new Felix(felixConfig);
felix.init();
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 701e8dc..6b9ff07 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,9 +16,11 @@
package com.ning.billing.osgi.glue;
+import javax.servlet.Servlet;
import javax.servlet.http.HttpServlet;
import javax.sql.DataSource;
+import org.osgi.service.http.HttpService;
import org.skife.config.ConfigurationObjectFactory;
import com.ning.billing.osgi.DefaultOSGIKillbill;
@@ -28,6 +30,7 @@ import com.ning.billing.osgi.api.OSGIKillbill;
import com.ning.billing.osgi.api.OSGIService;
import com.ning.billing.osgi.api.OSGIServiceRegistration;
import com.ning.billing.osgi.api.config.PluginConfigServiceApi;
+import com.ning.billing.osgi.http.DefaultHttpService;
import com.ning.billing.osgi.http.DefaultServletRouter;
import com.ning.billing.osgi.http.OSGIServlet;
import com.ning.billing.osgi.pluginconf.DefaultPluginConfigServiceApi;
@@ -51,14 +54,19 @@ public class DefaultOSGIModule extends AbstractModule {
}
protected void installOSGIServlet() {
- bind(new TypeLiteral<OSGIServiceRegistration<HttpServlet>>() {}).to(DefaultServletRouter.class).asEagerSingleton();
+ bind(new TypeLiteral<OSGIServiceRegistration<Servlet>>() {}).to(DefaultServletRouter.class).asEagerSingleton();
bind(HttpServlet.class).annotatedWith(Names.named(OSGI_NAMED)).to(OSGIServlet.class).asEagerSingleton();
}
+ protected void installHttpService() {
+ bind(HttpService.class).to(DefaultHttpService.class).asEagerSingleton();
+ }
+
@Override
protected void configure() {
installConfig();
installOSGIServlet();
+ installHttpService();
bind(OSGIService.class).to(DefaultOSGIService.class).asEagerSingleton();
diff --git a/osgi/src/main/java/com/ning/billing/osgi/http/DefaultHttpContext.java b/osgi/src/main/java/com/ning/billing/osgi/http/DefaultHttpContext.java
new file mode 100644
index 0000000..9ded549
--- /dev/null
+++ b/osgi/src/main/java/com/ning/billing/osgi/http/DefaultHttpContext.java
@@ -0,0 +1,45 @@
+/*
+ * 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.http;
+
+import java.io.IOException;
+import java.net.URL;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.osgi.service.http.HttpContext;
+
+public class DefaultHttpContext implements HttpContext {
+
+ @Override
+ public boolean handleSecurity(final HttpServletRequest request, final HttpServletResponse response) throws IOException {
+ // Security should have already been handled by Shiro
+ return true;
+ }
+
+ @Override
+ public URL getResource(final String name) {
+ // Maybe it's in our classpath?
+ return DefaultHttpContext.class.getClassLoader().getResource(name);
+ }
+
+ @Override
+ public String getMimeType(final String name) {
+ return null;
+ }
+}
diff --git a/osgi/src/main/java/com/ning/billing/osgi/http/DefaultHttpService.java b/osgi/src/main/java/com/ning/billing/osgi/http/DefaultHttpService.java
new file mode 100644
index 0000000..a5d606c
--- /dev/null
+++ b/osgi/src/main/java/com/ning/billing/osgi/http/DefaultHttpService.java
@@ -0,0 +1,71 @@
+/*
+ * 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.http;
+
+import java.util.Dictionary;
+import java.util.Hashtable;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+import javax.servlet.Servlet;
+import javax.servlet.ServletException;
+
+import org.osgi.service.http.HttpContext;
+import org.osgi.service.http.HttpService;
+import org.osgi.service.http.NamespaceException;
+
+@Singleton
+public class DefaultHttpService implements HttpService {
+
+ private final DefaultServletRouter servletRouter;
+
+ @Inject
+ public DefaultHttpService(final DefaultServletRouter servletRouter) {
+ this.servletRouter = servletRouter;
+ }
+
+ @Override
+ public void registerServlet(final String alias, final Servlet servlet, final Dictionary initparams, final HttpContext httpContext) throws ServletException, NamespaceException {
+ if (alias == null) {
+ throw new IllegalArgumentException("Invalid alias (null)");
+ } else if (servlet == null) {
+ throw new IllegalArgumentException("Invalid servlet (null)");
+ }
+
+ servletRouter.registerService(alias, servlet);
+ }
+
+ @Override
+ public void registerResources(final String alias, final String name, final HttpContext httpContext) throws NamespaceException {
+ final Servlet staticServlet = new StaticServlet(httpContext);
+ try {
+ registerServlet(alias, staticServlet, new Hashtable(), httpContext);
+ } catch (ServletException e) {
+ throw new IllegalArgumentException(e);
+ }
+ }
+
+ @Override
+ public void unregister(final String alias) {
+ servletRouter.unregisterService(alias);
+ }
+
+ @Override
+ public HttpContext createDefaultHttpContext() {
+ return new DefaultHttpContext();
+ }
+}
diff --git a/osgi/src/main/java/com/ning/billing/osgi/http/DefaultServletRouter.java b/osgi/src/main/java/com/ning/billing/osgi/http/DefaultServletRouter.java
index fc36943..8196e1b 100644
--- a/osgi/src/main/java/com/ning/billing/osgi/http/DefaultServletRouter.java
+++ b/osgi/src/main/java/com/ning/billing/osgi/http/DefaultServletRouter.java
@@ -21,28 +21,37 @@ import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import javax.inject.Singleton;
-import javax.servlet.http.HttpServlet;
+import javax.servlet.Servlet;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
import com.ning.billing.osgi.api.OSGIServiceRegistration;
@Singleton
-public class DefaultServletRouter implements OSGIServiceRegistration<HttpServlet> {
+public class DefaultServletRouter implements OSGIServiceRegistration<Servlet> {
+
+ private static final Logger logger = LoggerFactory.getLogger(DefaultServletRouter.class);
- private final Map<String, HttpServlet> pluginServlets = new ConcurrentHashMap<String, HttpServlet>();
+ // Internal Servlet routing table: map of plugin prefixes to servlet instances.
+ // A plugin prefix can be foo, foo/bar, foo/bar/baz, ... and is mounted on /plugins/<pluginPrefix>
+ private final Map<String, Servlet> pluginServlets = new ConcurrentHashMap<String, Servlet>();
@Override
- public void registerService(final String pluginName, final HttpServlet httpServlet) {
- pluginServlets.put(pluginName, httpServlet);
+ public void registerService(final String pathPrefix, final Servlet httpServlet) {
+ logger.info("Registering OSGI servlet at " + pathPrefix);
+ pluginServlets.put(pathPrefix, httpServlet);
}
@Override
- public void unregisterService(final String pluginName) {
- pluginServlets.remove(pluginName);
+ public void unregisterService(final String pathPrefix) {
+ logger.info("Unregistering OSGI servlet at " + pathPrefix);
+ pluginServlets.remove(pathPrefix);
}
@Override
- public HttpServlet getServiceForPluginName(final String pluginName) {
- return pluginServlets.get(pluginName);
+ public Servlet getServiceForPluginName(final String pathPrefix) {
+ return getServletForPathPrefix(pathPrefix);
}
@Override
@@ -51,7 +60,23 @@ public class DefaultServletRouter implements OSGIServiceRegistration<HttpServlet
}
@Override
- public Class<HttpServlet> getServiceType() {
- return HttpServlet.class;
+ public Class<Servlet> getServiceType() {
+ return Servlet.class;
+ }
+
+ // TODO PIERRE Naive implementation - we should rather switch to e.g. heap tree
+ public String getPluginPrefixForPath(final String pathPrefix) {
+ String bestMatch = null;
+ for (final String potentialMatch : pluginServlets.keySet()) {
+ if (pathPrefix.startsWith(potentialMatch) && (bestMatch == null || bestMatch.length() < potentialMatch.length())) {
+ bestMatch = potentialMatch;
+ }
+ }
+ return bestMatch;
+ }
+
+ private Servlet getServletForPathPrefix(final String pathPrefix) {
+ final String bestMatch = getPluginPrefixForPath(pathPrefix);
+ return bestMatch == null ? null : pluginServlets.get(bestMatch);
}
}
diff --git a/osgi/src/main/java/com/ning/billing/osgi/http/OSGIServlet.java b/osgi/src/main/java/com/ning/billing/osgi/http/OSGIServlet.java
index 182d142..a9af086 100644
--- a/osgi/src/main/java/com/ning/billing/osgi/http/OSGIServlet.java
+++ b/osgi/src/main/java/com/ning/billing/osgi/http/OSGIServlet.java
@@ -17,21 +17,26 @@
package com.ning.billing.osgi.http;
import java.io.IOException;
+import java.util.Vector;
import javax.inject.Inject;
import javax.inject.Singleton;
+import javax.servlet.Servlet;
+import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletRequestWrapper;
import javax.servlet.http.HttpServletResponse;
-import com.ning.billing.osgi.api.OSGIServiceRegistration;
-
@Singleton
public class OSGIServlet extends HttpServlet {
+ private final Vector<Servlet> initializedServlets = new Vector<Servlet>();
+ private final Object servletsMonitor = new Object();
+
@Inject
- private OSGIServiceRegistration<HttpServlet> servletRouter;
+ private DefaultServletRouter servletRouter;
@Override
protected void doGet(final HttpServletRequest req, final HttpServletResponse resp) throws ServletException, IOException {
@@ -64,16 +69,57 @@ public class OSGIServlet extends HttpServlet {
}
private void serviceViaPlugin(final HttpServletRequest req, final HttpServletResponse resp) throws ServletException, IOException {
- final HttpServlet pluginServlet = getPluginServlet(req);
+ // requestPath is the full path minus the JAX-RS prefix (/plugins)
+ final String requestPath = req.getServletPath() + req.getPathInfo();
+
+ final Servlet pluginServlet = getPluginServlet(requestPath);
if (pluginServlet != null) {
- pluginServlet.service(req, resp);
+ initializeServletIfNeeded(req, pluginServlet);
+ final OSGIServletRequestWrapper requestWrapper = new OSGIServletRequestWrapper(req, servletRouter.getPluginPrefixForPath(requestPath));
+ pluginServlet.service(requestWrapper, resp);
} else {
resp.sendError(404);
}
}
- private HttpServlet getPluginServlet(final HttpServletRequest req) {
- final String pluginName = (String) req.getAttribute("killbill.osgi.pluginName");
+ // Request wrapper to hide the plugin prefix to OSGI servlets (the plugin prefix serves as a servlet path)
+ private static final class OSGIServletRequestWrapper extends HttpServletRequestWrapper {
+
+ private final String pluginPrefix;
+
+ public OSGIServletRequestWrapper(final HttpServletRequest request, final String pluginPrefix) {
+ super(request);
+ this.pluginPrefix = pluginPrefix;
+ }
+
+ @Override
+ public String getPathInfo() {
+ return super.getPathInfo().replace(pluginPrefix, "");
+ }
+
+ @Override
+ public String getContextPath() {
+ return super.getContextPath() + pluginPrefix;
+ }
+ }
+
+ // Hack to bridge the gap between the web container and the OSGI servlets
+ private void initializeServletIfNeeded(final HttpServletRequest req, final Servlet pluginServlet) throws ServletException {
+ if (!initializedServlets.contains(pluginServlet)) {
+ synchronized (servletsMonitor) {
+ if (!initializedServlets.contains(pluginServlet)) {
+ final ServletConfig servletConfig = (ServletConfig) req.getAttribute("killbill.osgi.servletConfig");
+ if (servletConfig != null) {
+ // TODO PIERRE The servlet will never be destroyed!
+ pluginServlet.init(servletConfig);
+ initializedServlets.add(pluginServlet);
+ }
+ }
+ }
+ }
+ }
+
+ private Servlet getPluginServlet(final String pluginName) {
if (pluginName != null) {
return servletRouter.getServiceForPluginName(pluginName);
} else {
diff --git a/osgi/src/main/java/com/ning/billing/osgi/http/StaticServlet.java b/osgi/src/main/java/com/ning/billing/osgi/http/StaticServlet.java
new file mode 100644
index 0000000..9f8d5ac
--- /dev/null
+++ b/osgi/src/main/java/com/ning/billing/osgi/http/StaticServlet.java
@@ -0,0 +1,86 @@
+/*
+ * 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.http;
+
+import java.io.IOException;
+import java.net.URL;
+
+import javax.servlet.RequestDispatcher;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletRequestWrapper;
+import javax.servlet.http.HttpServletResponse;
+
+import org.osgi.service.http.HttpContext;
+
+import com.google.common.io.Resources;
+
+// Simple servlet to serve OSGI resources
+public class StaticServlet extends HttpServlet {
+
+ private final HttpContext httpContext;
+
+ public StaticServlet(final HttpContext httpContext) {
+ this.httpContext = httpContext;
+ }
+
+ @Override
+ protected void doGet(final HttpServletRequest req, final HttpServletResponse resp) throws ServletException, IOException {
+ final URL url = findResourceURL(req);
+ if (url != null) {
+ Resources.copy(url, resp.getOutputStream());
+ resp.setStatus(200);
+ return;
+ }
+
+ // If we can't find it, the container might
+ final RequestDispatcher rd = getServletContext().getNamedDispatcher("default");
+ final HttpServletRequest wrapped = new HttpServletRequestWrapper(req) {
+ public String getServletPath() { return ""; }
+ };
+ rd.forward(wrapped, resp);
+ }
+
+ // TODO PIERRE HUGE HACK
+ // We don't really know at this point the resource path to look for
+ // e.g. if the request is for /plugins/foo/bar/baz/qux.css, should
+ // we look for /qux.css? /baz/qux.css? /bar/baz/qux.css? /foo/bar/baz/qux.css?
+ private URL findResourceURL(final HttpServletRequest request) {
+ final String url = request.getRequestURI();
+ for (int i = 0; i < url.lastIndexOf('/'); i++) {
+ final int idx = url.indexOf('/', i);
+ if (idx > -1) {
+ final String resourceName = url.substring(idx);
+ final URL match = findResourceURL(resourceName);
+ if (match != null) {
+ return match;
+ }
+ }
+ }
+ return null;
+ }
+
+ private URL findResourceURL(final String resourceName) {
+ URL url = httpContext.getResource(resourceName);
+ if (url == null) {
+ // Look into the OSGI bundle JAR
+ url = httpContext.getClass().getResource(resourceName);
+ }
+ return url;
+ }
+}
diff --git a/osgi/src/main/java/com/ning/billing/osgi/KillbillActivator.java b/osgi/src/main/java/com/ning/billing/osgi/KillbillActivator.java
index 33b320f..51b84d6 100644
--- a/osgi/src/main/java/com/ning/billing/osgi/KillbillActivator.java
+++ b/osgi/src/main/java/com/ning/billing/osgi/KillbillActivator.java
@@ -19,7 +19,7 @@ package com.ning.billing.osgi;
import java.util.List;
import javax.inject.Inject;
-import javax.servlet.http.HttpServlet;
+import javax.servlet.Servlet;
import org.osgi.framework.BundleActivator;
import org.osgi.framework.BundleContext;
@@ -27,6 +27,7 @@ import org.osgi.framework.ServiceEvent;
import org.osgi.framework.ServiceListener;
import org.osgi.framework.ServiceReference;
import org.osgi.framework.ServiceRegistration;
+import org.osgi.service.http.HttpService;
import com.ning.billing.osgi.api.OSGIKillbill;
import com.ning.billing.osgi.api.OSGIPluginProperties;
@@ -38,6 +39,7 @@ import com.google.common.collect.ImmutableList;
public class KillbillActivator implements BundleActivator, ServiceListener {
private final OSGIKillbill osgiKillbill;
+ private final HttpService defaultHttpService;
private final List<OSGIServiceRegistration> allRegistrationHandlers;
private volatile ServiceRegistration osgiKillbillRegistration;
@@ -46,9 +48,11 @@ public class KillbillActivator implements BundleActivator, ServiceListener {
@Inject
public KillbillActivator(final OSGIKillbill osgiKillbill,
- final OSGIServiceRegistration<HttpServlet> servletRouter,
+ final HttpService defaultHttpService,
+ final OSGIServiceRegistration<Servlet> servletRouter,
final OSGIServiceRegistration<PaymentPluginApi> paymentProviderPluginRegistry) {
this.osgiKillbill = osgiKillbill;
+ this.defaultHttpService = defaultHttpService;
this.allRegistrationHandlers = ImmutableList.<OSGIServiceRegistration>of(servletRouter, paymentProviderPluginRegistry);
}
@@ -117,9 +121,10 @@ public class KillbillActivator implements BundleActivator, ServiceListener {
return true;
}
-
private void registerServices(final BundleContext context) {
osgiKillbillRegistration = context.registerService(OSGIKillbill.class.getName(), osgiKillbill, null);
+
+ context.registerService(HttpService.class.getName(), defaultHttpService, null);
}
private void unregisterServices() {