In my Camel (2.14.0) application I use Spring Web Services to trigger Camel routes. The artifact is built as an OSGi bundle and deployed in Karaf (3.0.2).
For the first version I configured spring-ws to use the JVM internal web server through the org.springframework.remoting.support.SimpleHttpServerFactoryBean
to expose the web service. This works just fine. But is not very OSGi-ish. So instead I would like to publish the org.springframework.ws.transport.http.MessageDispatcherServlet
as a service to the Karaf whiteboard extender like so:
<bean id="pas-ws-patient-servlet" class="org.springframework.ws.transport.http.MessageDispatcherServlet">
<property name="contextConfigLocation" value="/endpoint-mapping.xml" />
</bean>
<osgi:service ref="pas-ws-patient-servlet" interface="javax.servlet.http.HttpServlet">
<service-properties>
<entry key="alias" value="/${pas.ws.patient.contextroot}" />
</service-properties>
</osgi:service>
Which works like a charm for "regular" servlets. But the MessageDispatcherServlet
wants to build its own WebApplicationContext
and expects to find a bean of type org.springframework.ws.server.EndpointMapping
in that context. Camel provides an implementation of EndpointMapping
that has to be used with its spring-ws component.
The problem I am facing is that the same instance of the endpoint mapping bean must be shared between the OsgiBundleXmlApplicationContext
that creates the Camel context and the application context created by the MessageDispatcherServlet
. Which would be the case if my OsgiBundleXmlApplicationContext
was the parent of the WebApplicationContext
. Though how to set the parent context of the WebApplicationContext
to the "current" context from which I am publishing the servlet as a service eludes me.
Instantiating a WebApplicationContext
from within the OsgiBundleXmlApplicationContext
to pass it to the MessageDispatcherServlet gives me an exception:
java.lang.IllegalArgumentException: Cannot resolve ServletContextResource without ServletContext
Unfortunately the WebServiceMessageReceiver
(which encapsulates the EndpointMapping
) of the MessageDispatcherServlet
is a private member. So I cannot set the mapping bean either in a straight forward way.
Is there a way to create the context hierarchy? Or can a bean instance be shared across contexts in another way?
The solution is actually straight forward and documented in the JavaDoc of FrameworkServlet
, which the MessageDispatcherServlet
extends:
One can set an ApplicationContextInitializer
on the MessageDispatcherServlet. Use the context initializer to set the current application context as the parent of the servlet's application context. For this you have to also implement the ApplicationContextAware
interface to get a hold of the current context (OsgiBundleXmlApplicationContext
in this case). Then register the servlet as a service:
<!-- This bean sets our current context as the parent context of the XmlWebApplicationContext
that the MessageDispatcherServlet insists on creating. -->
<bean id="springWsContextInitializer" class="x.x.x.SpringWsContextInitializer" />
<bean id="spring-ws-servlet" class="org.springframework.ws.transport.http.MessageDispatcherServlet">
<!-- We inherit all beans from the current context, so no need to specify a separate context file. -->
<property name="contextConfigLocation" value="" />
<property name="ContextInitializers" ref="springWsContextInitializer" />
</bean>
<osgi:service ref="spring-ws-servlet" interface="javax.servlet.http.HttpServlet">
<service-properties>
<entry key="alias" value="/${servlet.contextroot}" />
<entry key="servlet-name" value="spring-ws-servlet" />
</service-properties>
</osgi:service>
The context initializer class:
public class SpringWsContextInitializer implements ApplicationContextInitializer<XmlWebApplicationContext>, ApplicationContextAware {
private ApplicationContext applicationContext;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
@Override
public void initialize(XmlWebApplicationContext applicationContext) {
applicationContext.setParent(this.applicationContext);
}
}
To get the same thing to work using blueprint instead of spring-dw I had to change the SpringWsContextInitializer to like so:
public class SpringWsContextInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {
private final EndpointMapping endpointMapping;
public SpringWsContextInitializer(final EndpointMapping endpointMapping) {
this.endpointMapping = endpointMapping;
}
@Override
public void initialize(ConfigurableApplicationContext applicationContext) {
StaticApplicationContext parentContext = new StaticApplicationContext();
parentContext.refresh();
parentContext.getDefaultListableBeanFactory().registerSingleton("endpointMapping", this.endpointMapping);
applicationContext.setParent(parentContext);
}
}
It should be possible to publish the endpoint mapping bean as an OSGi service and then reference the service bean in a context file for the servlet, but the OSGi namespace handler for the OSGi namespace needed on the spring context file could not be resolved.