Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

Exploring a Top-Down SOAP Service: Part I

DZone's Guide to

Exploring a Top-Down SOAP Service: Part I

It has been decreed that in-house SOAP services must be developed using a top-down, contract-first approach. What do you do?

· Java Zone
Free Resource

What every Java engineer should know about microservices: Reactive Microservices Architecture.  Brought to you in partnership with Lightbend.

Introduction

In our continuing make-believe story, you wrote some useful code for yourself in Java a year ago because you had a regular need. Soon, you turned it into a reusable component in its own project. (Shape calculator: project shape-calc-jpa-hibernate.) You then wrapped it in a command-line application.

Others in your department saw it, wanted it. Someone later suggested you provide a web application front-end as well, which you delivered.

Your last round with this was having delivered a first-try SOAP service wrapper that you developed using a bottom-up (Java to WSDL) approach.

Just last week, your manager emailed you. It has been decreed that in-house SOAP services must be developed using a top-down, contract-first approach. That means you will need to re-do your service. (This occurred at my previous job.)

It is at this point that we begin this article.

Purpose of Article Series

You can view the other articles in this series here. The list grows as we continue the journey.

There are plenty of good how-to articles on the web covering all of the technologies we've been (and will be) touching during this series, but I wanted to try to integrate their ideas into some comprehensive whole, and do it by simulating what might happen in a real software department for the life of some software system.

There is some "how-to" in these articles, but my hope is you'll find they are more than that. They're not "how-to" in vacuum, but more like real-life. It is messy. There's never enough time. Sometimes good enough is, well, good enough.

I try not to bore you and by just re-hashing everything but instead hope to highlight some interesting points along the way.

I typically work top-down, front-to-back, out-to-in, and test-driven, only adding something when it is required, and purposely attempt to compile, or run, with missing pieces because it's a way to gain understanding, and perhaps to uncover potential pitfalls.

Again, these articles are not intended to be comprehensive. You may have a better way to do this.

Requirement(s)

Within the group of members of your department, it was agreed that the base Shape Calculator component project would not be touched. That is, it has no knowledge of the client or wrapper. For instance, there are no annotations in that project, that would help us in creating our new top-down service.

The other requirements are carried over from when we were exploring a bottom-up, Java-first approach to the SOAP service.

Which SOAP Framework

There are several frameworks available. Here is an article that discusses three. I chose to go with Apache CXF, Version 3.x.

Let's Begin: New Eclipse Project

Note: if there is some detail lacking, it is most likely because it was already covered in previous articles.

We created a Dynamic Web Project and we begin by adding a WSDL folder in the WebContent/WEB-INF folder.

One thing I have not liked about Eclipse (Neon) is the WSDL editor. I prefer to edit the raw WSDL and stay away from the graphical view. Somehow it kepts interfering, so I chose to edit externally (using gVim) and just let Eclipse refresh.

Maven Dependencies

Our immediate need is to add the dependency to the Shape Calculator project, that has been the focal point for the entire article series.

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

You'll need spring-web and context..

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

And of course, we are using CXF for our web service..

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

If you have been following along, you can use your own project, but if not, you can get the code from here.

We Begin Slowly

And really break this down step by step, but then we'll speed up. Just skip whatever is uninteresting.

We Need A WSDL

We want to generate some Java, so let's begin the WSDL.

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<wsdl:definitions 
    xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"
    >
</wsdl:definitions>

Let's Try Initial Run

Here is the project POM (it will probably change):

<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>ws-soap-top-down-p1</groupId>
    <artifactId>ws-soap-top-down-p1</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>war</packaging>

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

    <dependencies>

        <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>shape-calc-jpa-hibernate</groupId>
            <artifactId>shape-calc-jpa-hibernate</artifactId>
            <version>0.0.1-SNAPSHOT</version>
        </dependency>

    </dependencies>

    <build>
        <plugins>
            <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>



    <!-- I made the generate-sources phase be separate so that it is only run 
        when desired, not automatically -->
    <profiles>
        <profile>
            <id>generate-sources</id>
            <activation>
                <activeByDefault>false</activeByDefault>
            </activation>
            <build>
                <plugins>
                    <plugin>
                        <groupId>org.apache.cxf</groupId>
                        <artifactId>cxf-codegen-plugin</artifactId>
                        <version>${cxf.version}</version>
                        <executions>
                            <execution>
                                <id>generate-sources</id>
                                <phase>generate-sources</phase>
                                <configuration>
                                    <encoding>UTF-8</encoding>
                                    <sourceRoot>${basedir}/target/generated-sources/</sourceRoot>
                                    <wsdlOptions>
                                        <wsdlOption>
                                            <wsdl>${basedir}/WebContent/wsdl/ShapeCalcWebService.wsdl</wsdl>
                                            <extraargs>
                                                <extraarg>-impl</extraarg>
                                                <extraarg>-verbose</extraarg>
                                                <extraarg>-validate</extraarg>
                                            </extraargs>
                                            <extendedSoapHeaders>true</extendedSoapHeaders>
                                            <markGenerated>true</markGenerated>
                                        </wsdlOption>
                                    </wsdlOptions>
                                </configuration>
                                <goals>
                                    <goal>wsdl2java</goal>
                                </goals>
                            </execution>
                        </executions>
                    </plugin>
                </plugins>
            </build>
        </profile>
    </profiles>


</project>

I chose to create a profile (and not include it as part of the normal build) because generating the code is not a continual step. I invoke that profile from Eclipse's Run Configurations.

The results of running generate-sources:

Sep 17, 2016 1:32:51 PM org.apache.cxf.tools.validator.internal.WSDLRefValidator collectValidationPoints

WARNING: WSDL document file:/C:/_dzone-article-sandbox/eclipse-workspace/ws-soap-top-down-p1/WebContent/wsdl/ShapeCalcWebService.wsdl does not define any services

WSDL2Java Warning : can not generate interface class for an WSDL has no PortType : file:/C:/_dzone-article-sandbox/eclipse-workspace/ws-soap-top-down-p1/WebContent/wsdl/ShapeCalcWebService.wsdl

WSDL2Java Warning : can not generate service class for an WSDL has no service : file:/C:/_dzone-article-sandbox/eclipse-workspace/ws-soap-top-down-p1/WebContent/wsdl/ShapeCalcWebService.wsdl

[INFO] ------------------------------------------------------------------------

[INFO] BUILD SUCCESS

[INFO] ------------------------------------------------------------------------

I like that we got a BUILD SUCCESS response. But now let's work on the issue above in bold letters.

More WSDL, More WSDL! Add a Service

We add a service section to our WSDL at the bottom of the file:

    <wsdl:service name="ShapeCalculatorWebService">
    </wsdl:service>

We re-generate:

[INFO] ------------------------------------------------------------------------

[INFO] BUILD FAILURE

[ERROR] Failed to execute goal org.apache.cxf:cxf-codegen-plugin:3.1.7:wsdl2java (generate-sources) on project ws-soap-top-down-p1: Execution generate-sources of goal org.apache.cxf:cxf-codegen-plugin:3.1.7:wsdl2java failed:

[ERROR] Service ShapeCalculatorWebService does not contain any usable ports

Add Port

Re-generating the above results in a "null" error. We are missing an attribute in the <wsdl:port....> . Let's fix:

    <wsdl:service name="ShapeCalculatorWebService">
        <wsdl:port name="ShapeCalculatorWebServicePort" binding="tns:ShapeCalculatorWebServiceServiceSoapBinding">
        </wsdl:port>
    </wsdl:service>

Re-generation results:

[ERROR] Failed to execute goal org.apache.cxf:cxf-codegen-plugin:3.1.7:wsdl2java (generate-sources) on project ws-soap-top-down-p1: Execution generate-sources of goal org.apache.cxf:cxf-codegen-plugin:3.1.7:wsdl2java failed: org.apache.cxf.wsdl11.WSDLRuntimeException: Fail to create wsdl definition file:/C:/_dzone-article-sandbox/eclipse-workspace/ws-soap-top-down-p1/WebContent/wsdl/ShapeCalcWebService.wsdl: WSDLException (at /wsdl:definitions/wsdl:service/wsdl:port): faultCode=UNBOUND_PREFIX: Unable to determine namespace of 'tns:ShapeCalculatorWebServiceServiceSoapBinding'. -> [Help 1]

Add TNS

Add and re-gen.

<wsdl:definitions 
    xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/" 
    xmlns:tns="http://ws.service.shape.calc.eli.com/" 
    >

    <!-- more code here -->

</wsdl:definitions>
WSIBP Validator found <{http://ws.service.shape.calc.eli.com/}ShapeCalculatorWebServiceServiceSoapBinding> is NOT a SOAP binding

[ERROR] Caused by {http://ws.service.shape.calc.eli.com/}[binding:ShapeCalculatorWebServiceServiceSoapBinding] not exist.

Add Binding

Keep in mind that if there is a typo in a binding that you did add, you would probably see the same error.

The basic binding section is this:

    <wsdl:binding name="ShapeCalculatorWebServiceSoapBinding" type="tns:ShapeCalculatorWebService">
    </wsdl:binding>

If you have only the empty binding section, you will keep seeing the same error that the binding does not exist.

Let's add more....

    <wsdl:binding name="ShapeCalculatorWebServiceSoapBinding" type="tns:ShapeCalculatorWebService">
        <soap:binding style="document" transport="http://schemas.xmlsoap.org/soap/http"/>
    </wsdl:binding>

And this time, you should see a similar namespace problem like we did with tns. This time, it doesn't know the soap namespace. Let's fix:

<wsdl:definitions 
    xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"
    xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
    xmlns:tns="http://ws.service.shape.calc.eli.com/" 
    >

    <!--  more code here -->

</wsdl:definitions>

And then we get:

[ERROR] Failed to execute............... : Unexpected close tag </wsdl:operation>; expected </wsdl:binding>.

The error:

WSIBP Validator found <{http://ws.service.shape.calc.eli.com/}ShapeCalculatorWebServiceSoapBinding> is NOT a SOAP binding

[ERROR] Caused by {http://ws.service.shape.calc.eli.com/}[binding:ShapeCalculatorWebServiceSoapBinding] not exist.

[ERROR] WSI-BP-1.0 R2209 violation: Unbound PortType elements in Operation 'runAllPendingRequestsNoStopOnError'

Before we continue, let's see what our complete WSDL looks like up to now:

Our current error is due to the following:

Basic Profile Version 2.0, Section 4.4.3 Unbound portType Element Contents.

Now, we have come to a moment in our building of the WSDL; if you don't add (below), you will just continue to get the above cryptic error even if you add other parts to the <binding.....> section.

targetNamespace="http://ws.service.shape.calc.eli.com/" 

We add that to the <wsdl:definitions....>

<?xml version="1.0" encoding="UTF-8"?>
<wsdl:definitions 
    xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/" 
    xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
targetNamespace="http://ws.service.shape.calc.eli.com/" 
    xmlns:tns="http://ws.service.shape.calc.eli.com/" 
    >

    <wsdl:binding name="ShapeCalculatorWebServiceSoapBinding" type="tns:ShapeCalculatorWebService">
        <soap:binding style="document" transport="http://schemas.xmlsoap.org/soap/http"/>
        <wsdl:operation name="runAllPendingRequestsNoStopOnError">
        </wsdl:operation>
    </wsdl:binding>

    <wsdl:service name="ShapeCalculatorWebService">
        <wsdl:port name="ShapeCalculatorWebServicePort" binding="tns:ShapeCalculatorWebServiceSoapBinding">
        </wsdl:port>
    </wsdl:service>

</wsdl:definitions>

We re-generate sources.

[ERROR] Caused by {http://ws.service.shape.calc.eli.com/}[portType:ShapeCalculatorWebService][operation:runAllPendingRequestsNoStopOnError] not exist.

We inform the Binding section of the new operation via the <wsdl:portType...> section.

Add Porttype

        <wsdl:portType name="ShapeCalculatorWebService">
            <wsdl:operation name="runAllPendingRequestsNoStopOnError">
            </wsdl:operation>
        </wsdl:portType>

Let's see our current WSDL before we re-generate:

<?xml version="1.0" encoding="UTF-8"?>
<wsdl:definitions 
    xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/" 
    xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
    targetNamespace="http://ws.service.shape.calc.eli.com/" 
    xmlns:tns="http://ws.service.shape.calc.eli.com/" 
    >

    <wsdl:portType name="ShapeCalculatorWebService">
        <wsdl:operation name="runAllPendingRequestsNoStopOnError">
        </wsdl:operation>
    </wsdl:portType>

    <wsdl:binding name="ShapeCalculatorWebServiceSoapBinding" type="tns:ShapeCalculatorWebService">
        <soap:binding style="document" transport="http://schemas.xmlsoap.org/soap/http"/>
        <wsdl:operation name="runAllPendingRequestsNoStopOnError">
        </wsdl:operation>
    </wsdl:binding>

    <wsdl:service name="ShapeCalculatorWebService">
        <wsdl:port name="ShapeCalculatorWebServicePort" binding="tns:ShapeCalculatorWebServiceSoapBinding">
        </wsdl:port>
    </wsdl:service>

</wsdl:definitions>

We generate-sources and we this time we get:

[ERROR] Invalid WSDL, Operation runAllPendingRequestsNoStopOnError in PortType {http://ws.service.shape.calc.eli.com/}ShapeCalculatorWebService not request-response or one-way

The above is because we have added a <wsdl:portType....> section and identified a <wsdl:operation....> but that operation is not really defined. It is empty.

Let's add some definition that operation:

<wsdl:input name="runAllPendingRequestsNoStopOnError" message="tns:runAllPendingRequestsNoStopOnError">
</wsdl:input>

When we do that, however, we're back to that same cryptic error:

WSIBP Validator found <{http://ws.service.shape.calc.eli.com/}ShapeCalculatorWebServiceSoapBinding> is NOT a SOAP binding

[ERROR] Caused by {http://ws.service.shape.calc.eli.com/}[binding:ShapeCalculatorWebServiceSoapBinding] not exist.

[ERROR] WSI-BP-1.0 R2209 violation: Unbound PortType elements in Operation 'runAllPendingRequestsNoStopOnError'

Back to Binding

Let's go back to the <wsdl:binding...> block, and let's add:

<wsdl:input name="runAllPendingRequestsNoStopOnError">
    <soap:body use="literal"/>
</wsdl:input>

Here now is the latest WSDL:

<?xml version="1.0" encoding="UTF-8"?>
<wsdl:definitions 
    xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/" 
    xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
    targetNamespace="http://ws.service.shape.calc.eli.com/" 
    xmlns:tns="http://ws.service.shape.calc.eli.com/" 
    >

    <wsdl:portType name="ShapeCalculatorWebService">
        <wsdl:operation name="runAllPendingRequestsNoStopOnError">
            <wsdl:input name="runAllPendingRequestsNoStopOnError" message="tns:runAllPendingRequestsNoStopOnError">
            </wsdl:input>
        </wsdl:operation>
    </wsdl:portType>

    <wsdl:binding name="ShapeCalculatorWebServiceSoapBinding" type="tns:ShapeCalculatorWebService">
        <soap:binding style="document" transport="http://schemas.xmlsoap.org/soap/http"/>
        <wsdl:operation name="runAllPendingRequestsNoStopOnError">
            <wsdl:input name="runAllPendingRequestsNoStopOnError">
                <soap:body use="literal"/>
            </wsdl:input>
        </wsdl:operation>
    </wsdl:binding>

    <wsdl:service name="ShapeCalculatorWebService">
        <wsdl:port name="ShapeCalculatorWebServicePort" binding="tns:ShapeCalculatorWebServiceSoapBinding">
        </wsdl:port>
    </wsdl:service>

</wsdl:definitions>

Generate-sources, and we get:

[ERROR] Caused by {http://ws.service.shape.calc.eli.com/}[message:runAllPendingRequestsNoStopOnError] not exist.

Add Message

So, let's add a new block to our WSDL near the top of the fileAdd Message

<wsdl:message name="runAllPendingRequestsNoStopOnError">
</wsdl:message>

Eureka! We have success. 

Sep 21, 2016 1:26:07 AM org.apache.cxf.wsdl11.WSDLServiceBuilder checkForWrapped

INFO: Operation {http://ws.service.shape.calc.eli.com/}runAllPendingRequestsNoStopOnError cannot be unwrapped, input and output messages (if present) must contain only a single part

[INFO] ------------------------------------------------------------------------

[INFO] BUILD SUCCESS

[INFO] ------------------------------------------------------------------------


Image titleWe generated code:

Our Latest WSDL

<?xml version="1.0" encoding="UTF-8"?>
<wsdl:definitions 
    xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/" 
    xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
    xmlns:tns="http://ws.service.shape.calc.eli.com/" 
    targetNamespace="http://ws.service.shape.calc.eli.com/" 
    >

    <wsdl:message name="runAllPendingRequestsNoStopOnError">
    </wsdl:message>

    <wsdl:portType name="ShapeCalculatorWebService">
        <wsdl:operation name="runAllPendingRequestsNoStopOnError">
        <wsdl:input name="runAllPendingRequestsNoStopOnError" message="tns:runAllPendingRequestsNoStopOnError">
            </wsdl:input>
        </wsdl:operation>
    </wsdl:portType>

    <wsdl:binding name="ShapeCalculatorWebServiceSoapBinding" type="tns:ShapeCalculatorWebService">
        <soap:binding style="document" transport="http://schemas.xmlsoap.org/soap/http"/>
        <wsdl:operation name="runAllPendingRequestsNoStopOnError">
            <wsdl:input name="runAllPendingRequestsNoStopOnError">
                <soap:body use="literal"/>
            </wsdl:input>
        </wsdl:operation>
    </wsdl:binding>

    <wsdl:service name="ShapeCalculatorWebService">
        <wsdl:port name="ShapeCalculatorWebServicePort" binding="tns:ShapeCalculatorWebServiceSoapBinding">
        </wsdl:port>
    </wsdl:service>

</wsdl:definitions>

I'm not saying we have completed the WSDL. However, it has enough for us to analyze some of the generated code.

Next Steps

This article has run on quite a bit. In our next article, we will dive a bit into the generated code and we'll look at ways to improve it. That means we will be enhancing our WSDL.

Latest Code

You can get the latest code here and the base project Shape Calculator code

Stay tuned for the next article!

Microservices for Java, explained. Revitalize your legacy systems (and your career) with Reactive Microservices Architecture, a free O'Reilly book. Brought to you in partnership with Lightbend.

Topics:
soap ,wsdl ,maven ,java ,eclipse

Opinions expressed by DZone contributors are their own.

THE DZONE NEWSLETTER

Dev Resources & Solutions Straight to Your Inbox

Thanks for subscribing!

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

X

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

{{ parent.tldr }}

{{ parent.urlSource.name }}