0

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?

Ralf
  • 6,735
  • 3
  • 16
  • 32
  • any reason for not using blueprint to configure this? – Achim Nierbeck Jan 21 '15 at 19:29
  • @AchimNierbeck Not anymore, I think. I am using spring-dm because setting up spring-ws with the JDK webserver and/or with endpoint interceptors requires the use of the [spring-ws](http://www.springframework.org/schema/web-services) namespace, which is [not supported](http://qnalist.com/questions/5374905/bundle-using-camel-spring-ws-failes-to-deploy-in-karaf) by blueprint. How is deploying through blueprint going to solve the bean-visibility problem though? The spring-ws servlet insists on creating its own application context. Is there an OSGi/blueprint version of the servlet available? – Ralf Jan 22 '15 at 07:31
  • well, camel is optimized to run with blueprint in osgi environments, no need to use spring there. regarding webservices, use cxf directly it works like a charm – Achim Nierbeck Jan 22 '15 at 07:41

1 Answers1

0

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.

Ralf
  • 6,735
  • 3
  • 16
  • 32