springapache-camelspring-wsapache-karafspring-dm

How to set OsgiBundleXmlApplicationContext as parent of WebApplicationContext


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?


Solution

  • 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.