A Servlet Filter can be implemented by creating a Class
that implements javax.servlet.Filter
In this spike the logic that was on the WebContextServlet.java, has been moved to a WebContextFilter.java.
This way we were able to rely on the urlPatterns of the Filter, that are more flexible than the Servlet allows. And other positive outcome was the fact that this Filter implementation is very similar to Pentaho Server PentahoWebContextFilter.java, enabling a future story to unify this two separate implementations into one.
Issue to solve:
- When a
ResourceMappingand aFilterare registered in different bundles and we try to filter a request that matches the mappingalias, that filter will not be executed even if the request matches itsurlPatterns.
In a clean Karaf the same issue was detected.
By testing different bundle combinations, we were able to conclude that
for a Filter to execute without being blocked by a ResourceMapping both
need to be on the same Http Context.
Calls to the cxf services are also blocking Filters.
This most probably is related with cxf having its own Http Context..
But cxf service has a filter implementation of its own that solves this problem.
Since Jetty runs inside Karaf, we couldn’t find where the web.xml is located, we found that jetty.xml is inside /karaf/etc/ but only contains server configuration information. To try to figure out how the filters are used in Karaf/Jetty, we did some tests and found out that the most restrictive filter wins, for example, given two filters to webcontext.js, and a filter with alias of "/{project.artifactId}/{project.version}/webcontext.js" and another filter with alias of "/{project.artifactId}/{project.version}" the first filter wins, because it is more restrictive.
What we need to garantee is that the Filter and the ResourceMapping have
the same Http Context.
To achieve the best options seems to be:
- Sharing the http context between bundles.
- Having a
WebContextFilterthat gets registered in the bundles that need this functionality, solving the problem of having different Http Contexts and from the spike experience this looks to make implementation simpler.
We have some classes that we can use to implement the filter. In this example I
implemented from ContainerRequestFilter to filter incoming requests:
@PreMatching
public class CxfFilter implements ContainerRequestFilter {
@Override
public void filter( ContainerRequestContext containerRequestContext ) throws IOException {
// do something here...
}
}And to use the filter, register it in the blueprint.xml file:
<bean id="cxfFilter" class="example.namespace.CxfFilter"/>
<jaxrs:server address="/det/core/data" id="dsService">
<jaxrs:serviceBeans>
<!-- endpoints here -->
</jaxrs:serviceBeans>
<!-- register the filter bean as a provider -->
<jaxrs:providers>
<ref component-id="cxfFilter"/>
</jaxrs:providers>
</jaxrs:server>Code required to setup two bundles that share the Http Context between them, using BundleActivators.
This solution was tested in both karaf 4.x and in our own karaf (3.x).
// Bundle A - PrimaryActivator.java implements BundleActivator
@Override
public void start( BundleContext bundleContext ) throws Exception {
// 1. Get the WebContainer service
WebContainer service = getWebContainerService( bundleContext );
// 2. Create a shared http context (SharedWebContainerContext)
this.sharedHttpContext = service.getDefaultSharedHttpContext();
// 3. Register it has a service (to find and use it in other bundles)
registerSharedHttpContext( bundleContext );
// 4. Register a ResourceMapping with the sharedHttpContext
registerResourceMapping( service );
// 5. Register a Servlet with the sharedHttpContext
registerMyServlet( service );
// 6. Register your in the sharedHttpContext
sharedHttpContext.registerBundle( bundleContext.getBundle() );
}// Bundle B - SecondaryActivator.java implements BundleActivator
@Override
public void start( BundleContext bundleContext ) throws Exception {
// 1. Get the WebContainer service
WebContainer service = getWebContainerService( bundleContext );
// 2. Get sharedHttpContext instance (Created in bundle A)
this.sharedHttpContext = getSharedHttpContext( bundleContext );
// 3. Register a filter with the sharedHttpContext
registerMyFilter( service );
// 4. Register your in the sharedHttpContext
this.sharedHttpContext.registerBundle( bundleContext.getBundle() );
}// Used in Bundle A & B
WebContainer getWebContainerService( BundleContext bundleContext ) {
ServiceReference<WebContainer> webContainerRef = null;
boolean started = false;
while (!started) {
webContainerRef = bundleContext.getServiceReference( WebContainer.class );
started = webContainerRef != null;
}
return bundleContext.getService( webContainerRef );
}
// Used in Bundle A
void registerSharedHttpContext( BundleContext bundleContext ) {
Dictionary<String, Object> params = new Hashtable<>();
params.put( "httpContext.id", "sharedHttpContextID" );
bundleContext.registerService(
HttpContext.class, this.sharedHttpContext, initParamsHttpContext
);
}
// Used in Bundle B
SharedWebContainerContext getSharedHttpContext( BundleContext bundleContext ) {
Collection<ServiceReference<HttpContext>> serviceReferences =
bundleContext.getServiceReferences(
HttpContext.class, "(httpContext.id=sharedHttpContextID)"
);
if ( serviceReferences.size() > 1 ) { // error: should only exist one
return null;
}
ServiceReference<HttpContext> httpContextRef = serviceReferences.iterator().next();
return (SharedWebContainerContext) bundleContext.getService( httpContextRef );
}
// Used in Bundle A
void registerResourceMapping( WebContainer service ) {
String resAlias = "/path/to/resources";
String resPath = "/folder";
service.registerResources( resAlias, resPath, this.sharedHttpContext );
}
// Used in Bundle A
void registerMyServlet( WebContainer service ) {
String[] servletAlias = new String[] { "/*" };
service.registerServlet(
new MyServlet();, servletAlias, null, this.sharedHttpContext
);
}
// Used in Bundle B
void registerMyFilter( WebContainer service ) {
String urlPattern = new String[] { "*/helloWorld.js" };
service.registerFilter(
new MyFilter(), urlPattern, null, null, this.sharedHttpContext
);
}<service id="webContextWildcard" interface="javax.servlet.Servlet"
ref="WebContext">
<service-properties>
<entry key="alias" value="/*"/>
</service-properties>
</service><service interface="javax.servlet.Servlet" id="webContextRootPath"
ref="WebContext">
<service-properties>
<entry key="alias" value="/webcontext.js"/>
</service-properties>
</service><service interface="javax.servlet.Servlet" id="webContextBundleSpecific"
ref="WebContext">
<service-properties>
<entry key="alias" value="/{project.artifactId}/{project.version}/webcontext.js"/>
</service-properties>
</service>