Share this page to your:
Mastodon

I needed to build a Java SOAP web service and, although I’ve done it before, it has been a little while and I wanted to use the latest approach. There are, it seems, a lot of options, some of them out of date or superseded by others, and some of them using technologies that don’t play quite as well with the other things I want to do. For example if you want to work with straight JAX-WS there is good information here. There is also some information on mixing JAX-WS and Spring, but not quite what I wanted.

My wish list was:

  • Build with Maven, for example not Ant, and not Gradle, because that’s what I normally do. I do think Maven is way better than Ant (almost always) and Gradle may be marvelous but I don’t want to learn it today.
  • Spring 4.2.5 because it is the latest.
  • Use @config and servlet 3.x which means no web.xml because I like to avoid xml when I can. I accept there are times when it is easier to use xml though.
  • logging handled by logback, because that’s my favourite. More specifically I like slf4j and logback takes full advantage of slf4j.

The nearest thing I found was this helpful tutorial which goes through everything very well and the examples work etc. Brian has done a good job, and I’m looking at his other blog entries. The only problem with this one is that it was written 3 years ago and uses Spring xml and Servlet 2.5, with a web.xml file.

So I did a little massaging of Brian’s code an came up with a revised version of his demo. It is functionally equivalent and you can follow Brian’s instructions for most of it. But there are some bits that are different.

  • Different versions of Spring (4.2.5) and spring-ws-core (2.2.4).
  • Added javax.servlet.api 3.1.0 to maven dependencies.
  • Added logback-classic and jcl-over-slf4j to maven dependencies
  • Moved the target directory for the JAXB generated files to target/generated/xjc just because that makes things tidier.
  • Replaced the Spring configuration file with SpringWebConfig.java and replaced web.xml with WebInitialiser.java

So apart from deleting a couple of now redundant xml files they were all changes to pom.xml except for the last point. The last point replaces the two xml files with two Java files. Let’s take a look at them:

public class WebInitialiser extends AbstractAnnotationConfigDispatcherServletInitializer {

    @Override
    public void onStartup(ServletContext container) {
        // Creates the root application context
        WebApplicationContext appContext = createServletApplicationContext();

        // Register and map the dispatcher servlet
        MessageDispatcherServlet messageDispatcherServlet = new MessageDispatcherServlet(appContext);
        messageDispatcherServlet.setTransformSchemaLocations(true);
        ServletRegistration.Dynamic dispatcher = container.addServlet("dispatcher", messageDispatcherServlet);
        dispatcher.setLoadOnStartup(1);
        dispatcher.addMapping("/endpoints/*");
        dispatcher.addMapping("*.wsdl");
    }

    @Override
    protected Class<?>[] getServletConfigClasses() {
        return new Class[] { SpringWebConfig.class };
    }

    @Override
    protected String[] getServletMappings() {
        return new String[] { "/" };
    }

    @Override
    protected Class<?>[] getRootConfigClasses() {
        return null;
    }
}

Spring finds the class automatically and runs the onLoad method in lieu of a web.xml file. So the code here replicates the web.xml declarations. The only things it has to do are create the Spring application context and set up the dispatcher servlet. That means it has to specify the configuration classes (in getServletConfigClasses()) and the MessageDispatcherServlet.

The configuration class is SpringWebConfig.java

@Configuration
@EnableWs
@ComponentScan({ "com.blog.samples.services" })
public class SpringWebConfig extends WsConfigurerAdapter {
    
    @Autowired
    private ServletContext context;
    
    // The bean name here has to match the PortTypeName
    @Bean(name="AccountDetailsService")
    public DefaultWsdl11Definition getWsdl() throws IOException {
        DefaultWsdl11Definition ret = new DefaultWsdl11Definition();
        ret.setSchemaCollection(xsdSchema());
        ret.setPortTypeName("AccountDetailsService");
        ret.setServiceName("AccountDetailsServices");
        ret.setLocationUri("/endpoints");
        return ret;
    }
    
    /**
     * This bean defines the schema for the wsdl generation. There are actually two schema files and this one imports the second one.
     * It seems to do the resolution at bean definition time rather than on request because there is only one http request logged.
     * Note that using classpath fails to resolve the imported xsd and generates a bad wsdl.
     * 
     * @return CommonsXsdSchemaCollection
     */
    @Bean
    public CommonsXsdSchemaCollection xsdSchema() {
        CommonsXsdSchemaCollection ret = new CommonsXsdSchemaCollection();
        ret.setInline(true);
        ret.setXsds(new Resource[]{new ServletContextResource(context,"schemas/AccountDetailsServiceOperations.xsd")});
        return ret;
    }   
}

This one gave me the most bother because I did not initially notice two things: first that the bean name of the wsdl bean is important and second that the xsd resource has to be a ServletContextResource and not a ClassPathResource. I had initially moved the xsd files from src/main/webapp/schemas (where Brian had put them) to src/main/resources because it made more sense to me. But I was assuming the xsd files need to be on the classpath and this is not the case. I’m still a bit vague on how this really works but there are two xsd files here, the one named imports the other one. The resolution of the import seems to happen at bean initialisation time, not when the wsdl is requested.  The main thing, for me anyway, is that it does work. The alternatives: using ClassPathResource and/or using SimpleXsdSchema instead of CommonsXsdSchema did not work. The results of those varied between producing a blank wsdl to a wsdl that looked okay initially but the import ws not actually resolved. The latter meant that when you try to develop a client using the wsdl it is thrown out with errors.

Previous Post Next Post