Search code examples
javaweb-servicestomcatcxfjax-ws

How to programmatically publish webservice to tomcat


I would like to publish a webservice programmatically to tomcat.
With e.g JAX-WS or Apache CXF
Similar to Endpoint.publish(...).

//how to tell this tomcat?
Endpoint.publish("http://0.0.0.0:8080/SimpleService", serviceImpl);
//or better something like this:
Endpoint.publish("/SimpleService", serviceImpl);

Without need to use web.xml and/or sun-jaxws.xml (for each service)

Question:
Is there any known way to accomplish it (with JAX-WS or Apache CXF or ...)?

(I know there are similar questions already posted. But none of them really answers my question.)


Solution

  • This is an anwer to my own question.
    I managed to make this work [programmatically publish webservice to tomcat] with help of Apache CXF.

    Here is a simplified working example:

    I subclassed a CXFNonSpringServlet and registered it in web.xml:

    <servlet>
     <servlet-name>MyCXFServlet</servlet-name>
     <display-name>CXF Servlet</display-name>
     <servlet-class>de.test.MyCXFServlet</servlet-class>
     <load-on-startup>2</load-on-startup>
     <async-supported>true</async-supported>
    </servlet>
    
    <servlet-mapping>
     <servlet-name>MyCXFServlet</servlet-name>
     <url-pattern>/soap/*</url-pattern>
    </servlet-mapping>
    

    This is my subclassed CXFNonSpringServlet:

    import java.lang.reflect.Method;
    import java.lang.reflect.Proxy;
    import java.util.HashSet;
    import java.util.Set;
    import javax.jws.WebMethod;
    import javax.servlet.ServletConfig;
    import org.apache.cxf.endpoint.Server;
    import org.apache.cxf.frontend.ServerFactoryBean;
    import org.apache.cxf.service.factory.ReflectionServiceFactoryBean;
    import org.apache.cxf.transport.servlet.CXFNonSpringServlet;
    
    public class MyCXFServlet extends CXFNonSpringServlet
    {
        @Override
        protected void loadBus(ServletConfig sc) 
        {
            super.loadBus(sc);
            publishServices();
        }
    
        private void publishServices()
        {
            Set<Class> serviceInterfaces = new HashSet<>();
            serviceInterfaces.add(de.test.IUserService.class);
            serviceInterfaces.add(de.test.ILoginService.class);
    
            for (Class aSVCInterface : serviceInterfaces)
            {
                final String serviceName = aSVCInterface.getSimpleName();
    
                try
                {
                    ReflectionServiceFactoryBean reflectionFactory = new ReflectionServiceFactoryBean(){
                        @Override
                        protected boolean isValidMethod(Method method)
                        {
                            boolean ret = super.isValidMethod(method);
                            WebMethod wm = method.getAnnotation(WebMethod.class);
                            if (wm != null && wm.exclude())
                                ret = false;
                            return ret;
                        }
    
                        @Override
                        protected String getServiceName() //Override for custom service name
                        {
                            return serviceName;
                        }
    
                    };
                    reflectionFactory.setServiceClass(aSVCInterface);
    
                    Object proxiedServiceObject = Proxy.newProxyInstance(this.getClass().getClassLoader(), new Class[]{aSVCInterface}, new de.test.MyWebServiceInvocationHandler(aSVCInterface));
    
                    ServerFactoryBean factory = new ServerFactoryBean(reflectionFactory);
                    factory.setBus(getBus());
                    factory.setServiceClass(aSVCInterface);
                    factory.setServiceBean(proxiedServiceObject);
                    factory.setAddress("/" + serviceName);
                    Server svr = factory.create();    
                    svr.getEndpoint().getInInterceptors().add(new de.test.MyServiceInterceptor());
                }
                catch (Exception exception)
                {
                    exception.printStackTrace();
                }
            }
        }
    }
    

    The above Servlet will publish 2 simple interfaces as SOAP-WebService.
    The implementation is dynamically (Proxies)

    This is my MyServiceInterceptor:

    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.Proxy;
    import org.apache.cxf.binding.soap.SoapMessage;
    import org.apache.cxf.binding.soap.interceptor.AbstractSoapInterceptor;
    import org.apache.cxf.endpoint.Endpoint;
    import org.apache.cxf.interceptor.Fault;
    import org.apache.cxf.message.Exchange;
    import org.apache.cxf.phase.Phase;
    import org.apache.cxf.service.Service;
    import org.apache.cxf.service.invoker.BeanInvoker;
    import org.apache.cxf.service.invoker.Invoker;
    
    public class MyServiceInterceptor extends AbstractSoapInterceptor
    {
        public MyServiceInterceptor() 
        {
            super(Phase.PRE_INVOKE);
        }
    
        @Override
        public void handleMessage(SoapMessage p_message) throws Fault 
        {
            final Exchange exchange = p_message.getExchange();
            final Endpoint endpoint = exchange.get(Endpoint.class);
            final Service service = endpoint.getService();
            final Invoker invoker = service.getInvoker();
    
            if (invoker instanceof BeanInvoker)
            {
                BeanInvoker bi = (BeanInvoker)invoker;
                Object serviceObj = bi.getServiceObject(null);
                if (Proxy.isProxyClass(serviceObj.getClass()))
                {
                    InvocationHandler ih = Proxy.getInvocationHandler(serviceObj);
                    if (ih instanceof MyWebServiceInvocationHandler)
                    {
                        MyWebServiceInvocationHandler h = (MyWebServiceInvocationHandler)ih;
                        h.setSoapMessage(p_message);
                    }
                }
            }
        }
    }
    

    The MyServiceInterceptor-Class is mainly used to inject the current SOAPMessage to MyWebServiceInvocationHandler.

    My MyWebServiceInvocationHandler (I think, no code needed here) is responsible to invoke the real Service-Method. It just implements InvocationHandler and has a field for the Soap-Message (see MyServiceInterceptor). This is needed to get SOAPMessage-Details (like Header).

    Hope this helps.
    Cheers!