Over a million developers have joined DZone.

The Java Web App Journey... SOAP Services (Part 2)

As our web-enabled Java app continues to mature, delve more into SOAP services and see how to deploy your project as a web service.

· Java Zone

Check out this 8-step guide to see how you can increase your productivity by skipping slow application redeploys and by implementing application profiling, as you code! Brought to you in partnership with ZeroTurnaround.

In part 1, we did most of the setup of our new web service project, and we were able to generate an initial WSDL. Now we want to deploy something (even if minimal) to our Eclipse Tomcat server — so we are jumping right in from where we left off.

Java Config

We completed a web application (it used the Shape Calculator) that we covered over several articles, and here is where we began to discuss doing configuration with Java instead of XML. I will just take what we have already done in the past as a starting point and go from there.

Latest Code

You can obtain the latest web app project code from here.

We aren't really going to do a web application, but some of the files are useful to us in constructing our web service.

And the latest code for the underlying Shape Calculator component project is here.

We will care more about Shape Calculator in a bit, once we have done most of the work constructing the web service.

WebAppInitializer

We copy the WebAppInitializer (within its package) from the above /webapp-p5 project (or whatever you named your own web project if you have been following along with the previous articles), and place it in our new web service in src/main/java.

We need to edit that class.

As soon as we add the class to our project, we get errors. We need to add Spring web:

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-web</artifactId>
    <version>${spring.version}</version>
</dependency>


One of the things we need to do is to change it from initializing a DispatcherServlet, to instead use a CXFServlet.

//System.err.println("\n\n\n\nELI: Web AppInitializer created dispatcher servlet passing root ctx..... \n\n\n\n");
//DispatcherServlet dispatcherServlet = new DispatcherServlet(rootCtx);
System.err.println("\n\n\n\nELI: Web AppInitializer created CXF servlet passing root ctx..... \n\n\n\n");
CXFServlet cxfServlet = new CXFServlet(rootCtx);


Immediately we run into an issue — I forgot to add this to the POM:

<dependency>
    <groupId>org.apache.cxf</groupId>
    <artifactId>cxf-rt-transports-http</artifactId>
    <version>${cxf.version}</version>
</dependency>


What we previously added was just the JAX-WS frontend.

So we run a Maven update and move forward.

Next, for kicks, I changed the following:

//System.err.println("\n\n\n\nELI: Web AppInitializer register dispatcher servlet with servlet context..... \n\n\n\n");
//ServletRegistration.Dynamic registration = servletContext.addServlet("cxfServlet",cxfServlet);
System.err.println("\n\n\n\nELI: Web AppInitializer register CXF servlet with servlet context..... \n\n\n\n");
ServletRegistration.Dynamic registration = servletContext.addServlet("cxfServlet",cxfServlet);


We want a different URL mapping for our web service:

registration.setLoadOnStartup(1);
//registration.addMapping("/");
registration.addMapping("/services/*");


Here is the complete WebAppInitializer. I left in the old code (for those who were following previous articles) in as comments:

package com.eli.calc.shape.config;

import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRegistration;

import org.apache.cxf.transport.servlet.CXFServlet;
import org.springframework.web.WebApplicationInitializer;
import org.springframework.web.context.ContextLoaderListener;
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;


public class WebAppInitializer implements WebApplicationInitializer {

    @Override
    public void onStartup(ServletContext servletContext) throws ServletException {

        System.err.println("\n\n\n\nELI: Web AppInitializer onStartup() \n\n\n\n");

        AnnotationConfigWebApplicationContext rootCtx = new AnnotationConfigWebApplicationContext();
        rootCtx.register(WebServiceContext.class);

        System.err.println("\n\n\n\nELI: Web AppInitializer root context loader listener.. \n\n\n\n");
        ContextLoaderListener loaderListener = new ContextLoaderListener(rootCtx);

        System.err.println("\n\n\n\nELI: Web AppInitializer add root context loader listener to servletcontext..... \n\n\n\n");
        servletContext.addListener(loaderListener);

        //System.err.println("\n\n\n\nELI: Web AppInitializer created dispatcher servlet passing root ctx..... \n\n\n\n");
        //DispatcherServlet dispatcherServlet = new DispatcherServlet(rootCtx);
        System.err.println("\n\n\n\nELI: Web AppInitializer dispatcherServlet CXF servlet ..... \n\n\n\n");
        CXFServlet dispatcherServlet = new CXFServlet();

        //System.err.println("\n\n\n\nELI: Web AppInitializer register dispatcher servlet with servlet context..... \n\n\n\n");
        //ServletRegistration.Dynamic registration = servletContext.addServlet("cxfServlet",cxfServlet);
        System.err.println("\n\n\n\nELI: Web AppInitializer register CXF servlet with servlet context..... \n\n\n\n");
        ServletRegistration.Dynamic registration = servletContext.addServlet("cxfServlet",dispatcherServlet);

        registration.setLoadOnStartup(1);
        //registration.addMapping("/");
        registration.addMapping("/services/*");

        System.err.println("\n\n\n\nELI: Web AppInitializer onStartup() done\n\n\n\n");

    }

}


Notice in the code, it references a WebServiceContext.class.

This is similar to our old WebConfig from our previous web application. However, it quickly becomes not very similar at all. The WebConfig took care of initializing Spring MVC beans, whereas we want CXF-related beans.

So, we don't need the @EnableWebMvc. However, we do need a @Configuration.

And we'll need the following:

    @Bean
    public SpringBus springBus() {
        return new SpringBus();
    }

    @Bean
    public Endpoint endpoint() {
        EndpointImpl endpoint = new EndpointImpl(springBus(), new ShapeCalculatorWebServiceImpl());
        endpoint.publish("http://localhost:8080/web-service-soap-bottom-up-p1/services/ws/ShapeCalculatorWebService");
        return endpoint;
    }


Note: We will come back to this later, but I want to point out that the above snippet is seen in many examples on the Web. However, it may cause a problem for us later. We'll leave it as-is for now — specifically, the "new ShapeCalculatorWebServiceImpl()".

The complete WebServiceContext now looks like this:

package com.eli.calc.shape.config;

import javax.xml.ws.Endpoint;

import org.apache.cxf.bus.spring.SpringBus;
import org.apache.cxf.jaxws.EndpointImpl;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

import com.eli.calc.shape.service.ws.impl.ShapeCalculatorWebServiceImpl;

@Configuration
public class WebServiceContext {

    @Bean
    public SpringBus springBus() {
        return new SpringBus();
    }

    @Bean
    public Endpoint endpoint() {
        EndpointImpl endpoint = new EndpointImpl(springBus(), new ShapeCalculatorWebServiceImpl());
        endpoint.publish("http://localhost:8080/web-service-soap-bottom-up-p1/services/ws/ShapeCalculatorWebService");
        return endpoint;
    }
}


One of the better articles I have seen explaining the how-and-why of Java Config with CXF and Spring is found here, and I will just let it speak for itself.

When I attempted a "Maven install," there was an error of a missing javax.servlet package.

We have to add another POM dependency:

<dependency>
    <groupId>javax.servlet</groupId>
    <artifactId>javax.servlet-api</artifactId>
    <version>3.1.0</version>
</dependency>


This time, "Maven install" worked.

Our complete, updated POM is now this:

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>web-service-soap-bottom-up-p1</groupId>
    <artifactId>web-service-soap-bottom-up-p1</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>war</packaging>

    <properties>
        <maven.compiler.target>1.8</maven.compiler.target>
        <maven.compiler.source>1.8</maven.compiler.source>
        <spring.version>4.3.2.RELEASE</spring.version>
        <cxf.version>3.1.7</cxf.version>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

    <dependencies>

        <dependency>
            <groupId>shape-calc-jpa-hibernate</groupId>
            <artifactId>shape-calc-jpa-hibernate</artifactId>
            <version>0.0.1-SNAPSHOT</version>
        </dependency>

        <dependency>
            <groupId>org.apache.cxf</groupId>
            <artifactId>cxf-rt-frontend-jaxws</artifactId>
            <version>${cxf.version}</version>
        </dependency>

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-web</artifactId>
            <version>${spring.version}</version>
        </dependency>

        <dependency>
            <groupId>org.apache.cxf</groupId>
            <artifactId>cxf-rt-transports-http</artifactId>
            <version>${cxf.version}</version>
        </dependency>

        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>javax.servlet-api</artifactId>
            <version>3.1.0</version>
        </dependency>

    </dependencies>


    <build>
        <pluginManagement>
            <plugins>
                <plugin>
                    <groupId>org.eclipse.m2e</groupId>
                    <artifactId>lifecycle-mapping</artifactId>
                    <version>1.0.0</version>
                    <configuration>
                        <lifecycleMappingMetadata>
                            <pluginExecutions>
                                <pluginExecution>
                                    <pluginExecutionFilter>
                                        <groupId>org.apache.cxf</groupId>
                                        <artifactId>cxf-java2ws-plugin</artifactId>
                                        <versionRange>[${cxf.version},)</versionRange>
                                        <goals>
                                            <goal>java2ws</goal>
                                        </goals>
                                    </pluginExecutionFilter>
                                    <action>
                                        <execute />
                                    </action>
                                </pluginExecution>
                            </pluginExecutions>
                        </lifecycleMappingMetadata>
                    </configuration>
                </plugin>
            </plugins>
        </pluginManagement>


        <plugins>

            <plugin>
                <groupId>org.apache.cxf</groupId>
                <artifactId>cxf-java2ws-plugin</artifactId>
                <version>${cxf.version}</version>
                <executions>
                    <execution>
                        <id>process-classes</id>
                        <phase>process-classes</phase>
                        <configuration>
                            <className>com.eli.calc.shape.service.ws.ShapeCalculatorWebService</className>
                            <serviceName>ShapeCalculatorWebService</serviceName>
                            <address>http://localhost:8080/web-service-soap-bottom-up/services/ws/ShapeCalculatorWebService</address>
                            <genWsdl>true</genWsdl>
                            <genWrapperbean>false</genWrapperbean>
                            <verbose>true</verbose>
                            <argline>
                                -createxsdimports -s ${project.build.directory}/generated/src -classdir ${project.build.outputDirectory}
                            </argline>
                            <soap12>true</soap12>
                        </configuration>
                        <goals>
                            <goal>java2ws</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>

            <plugin>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.5.1</version>
                <configuration>
                    <source>${maven.compiler.source}</source>
                    <target>${maven.compiler.target}</target>
                </configuration>
            </plugin>

            <plugin>
                <artifactId>maven-war-plugin</artifactId>
                <version>2.6</version>
                <configuration>
                    <warSourceDirectory>WebContent</warSourceDirectory>
                    <failOnMissingWebXml>false</failOnMissingWebXml>
                </configuration>
            </plugin>

        </plugins>

    </build>

</project>


Set up/Run a MySQL Database

If you need to, please review the previous articles where I detail what I did to use MySQL and run the database from Cygwin terminals. There's no need to rehash all that again here.

You'll need it to be up before attempting to deploy and start our web service, as it is dependent on the Shape Calculator component project:

<dependency>
    <groupId>shape-calc-jpa-hibernate</groupId>
    <artifactId>shape-calc-jpa-hibernate</artifactId>
    <version>0.0.1-SNAPSHOT</version>
</dependency>


And that child project is expecting a DataSource up.

Build/Deploy/Start Web Service

Right away, during startup, our Eclipse console indicates errors. They are all related to the WebServiceConfig class.

Error

Cannot find any registered HttpDestinationFactory from the Bus. 

Cause

If you look up through this article, where WebServiceConfig is listed, you'll notice a line of code:

endpoint.publish("http://localhost:8080/web-service-soap-bottom-up-p1/services/ws/ShapeCalculatorWebService");

There are articles on the web suggesting the above. But there's a problem with it — it is an absolute path. We need a relative path.

Solution

Any of the following seem to work to get us past this first error:

this:

endpoint.publish("/web-service-soap-bottom-up-p1/services/ws/ShapeCalculatorWebService");

or this:

endpoint.publish("/ShapeCalculatorWebService");

or this:

endpoint.publish("ShapeCalculatorWebService");

(That last entry, without the "/", is also a problem, but for a different reason that we'll get to.)

I am going to change the code to that last entry for now.

package com.eli.calc.shape.config;

import javax.xml.ws.Endpoint;

import org.apache.cxf.Bus;
import org.apache.cxf.bus.spring.SpringBus;
import org.apache.cxf.jaxws.EndpointImpl;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.ImportResource;

import com.eli.calc.shape.service.ws.impl.ShapeCalculatorWebServiceImpl;

@Configuration
public class WebServiceContext {

    @Bean
    public SpringBus springBus() {
        System.err.println("\n\n\n\nELI: WebServiceConfig springBus \n\n\n\n");
        return new SpringBus();
    }

    @Bean
    public Endpoint endpoint() {
        System.err.println("\n\n\n\nELI: WebServiceConfig endpoint \n\n\n\n");
        EndpointImpl endpoint = new EndpointImpl(springBus(), new ShapeCalculatorWebServiceImpl());

        //NOT WORK NOT WORK NOT WORK
        //must be a relative path
        //endpoint.publish("http://localhost:8080/web-service-soap-bottom-up-p1/services/ws/ShapeCalculatorWebService");

        //WORKS
        //endpoint.publish("/web-service-soap-bottom-up-p1/services/ws/ShapeCalculatorWebService");

        //WORKS
        //endpoint.publish("/ShapeCalculatorWebService");

        endpoint.publish("ShapeCalculatorWebService");

        return endpoint;
    }
}


Error

No bean named 'cxf' is defined.

Cause

We are missing a directive in the WebServiceConfig.

Solution #1

// add this at class-level
@ImportResource({"classpath:META-INF/cxf/cxf.xml"})


Solution #2

    //@Bean
    @Bean(name = Bus.DEFAULT_BUS_ID) //this is REQUIRED or you get "No bean named 'cxf' is defined"
    public SpringBus springBus() {
        System.err.println("\n\n\n\nELI: WebServiceConfig springBus \n\n\n\n");
        return new SpringBus();
    }

You just need one of them.

This time, we get a clean Tomcat start log in our Eclipse console.

We try the URL in a browser, and we have this:

Image title

Error

If you click on the WSDL URL, you get this error:

Image title

I point this out because I've seen articles suggesting we do what has in fact caused the problem.

Cause

A bad endpoint.

    @Bean
    public Endpoint endpoint() {
        System.err.println("\n\n\n\nELI: WebServiceConfig endpoint \n\n\n\n");
        EndpointImpl endpoint = new EndpointImpl(springBus(), new ShapeCalculatorWebServiceImpl());

        //missing slash
        endpoint.publish("ShapeCalculatorWebService");

        return endpoint;
    }


Solution

Add "/":

    @Bean
    public Endpoint endpoint() {
        System.err.println("\n\n\n\nELI: WebServiceConfig endpoint \n\n\n\n");
        EndpointImpl endpoint = new EndpointImpl(springBus(), new ShapeCalculatorWebServiceImpl());

        endpoint.publish("/ShapeCalculatorWebService");

        return endpoint;
    }


And now it works:

Image title

Initial WSDL Test

I downloaded SOAPUI, and we're going to see how well our WSDL does. 

Grab the URL from the browser, plop it in, hit <OK>.

Image title

Obviously, we need to do some work. If you recall, our initial wrapper classes do not have much in them:

Image title

To Be Continued...

We will be diving into one of the Calculator operations and go back and forth from Java to WSDL and SOAPUI.

I apologize for not having a link to the latest code.  I will provide that in part 3, coming up.

Stay tuned for the next article!

The Java Zone is brought to you in partnership with ZeroTurnaround. Check out this 8-step guide to see how you can increase your productivity by skipping slow application redeploys and by implementing application profiling, as you code!

Topics:
soap ,web services ,spring ,java

Opinions expressed by DZone contributors are their own.

The best of DZone straight to your inbox.

SEE AN EXAMPLE
Please provide a valid email address.

Thanks for subscribing!

Awesome! Check your inbox to verify your email so you can start receiving the latest in tech news and resources.
Subscribe

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}