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

Converting A Web App Into A SOAP Client: Part III

DZone's Guide to

Converting A Web App Into A SOAP Client: Part III

This time we'll explore how to make our runtime-generated WSDL more expressive, thus improving our client's generated code.

· Web Dev Zone ·
Free Resource

Deploying code to production can be filled with uncertainty. Reduce the risks, and deploy earlier and more often. Download this free guide to learn more. Brought to you in partnership with Rollbar.

In Part 2, we explored converting our command-line app into a SOAP client.  In doing so, we noticed some issues, or perhaps changes or improvements that we could make.

One of those issues/improvements could be labeled as the "Package Jumble".

One key step (of course) in converting the command-line app to a SOAP client, is to have a WSDL-to-Java generation of the required classes/plumbing so the app can easily communicate with the SOAP service.

However, in doing the generation, we noticed that all of the request/response types were thrown into the same package as the service client class.

The reason for this is because we chose to work with a bottom-up SOAP service, and apparently we didn't add enough information in the code to generate a sufficiently described/defined WSDL.

Let's use the above as motivation to explore how to improve WSDL-generation in our Java code.

We've worked with the command-line application enough already, so let's do the above using a web application that also needs to be converted to a SOAP client.

You can review the articles related to the web application in parts 1 through 6.

The Web App Conversion to SOAP Client

Image title

So, everywhere in the MVC controller class that you see calculator, it will be replaced with soapClient.

Latest Code

We'll be working with all of the above projects.

You can review the current Web App project articles  parts 1 through 6. The project code is here.

You can review the latest SOAP-REST Service project code here.

The latest (release 2) of the base Shape Calculator project code is here, and you can review all the articles to learn more about it, or about everything else related.

New Projects

You can use your current Web App project if you have it, but it is easier for me (due to linkage to articles) to create a new one.

The same applies to the SOAP/REST Service project...  since we'll be making changes to it (to better define the resulting WSDL).

We'll be copying in the code from each of their respective previous iterations, as needed.

New SOAP Service Project

I created a new Dynamic Web Project in Eclipse, converted it to a Maven project, and copied all the files over from the previous SOAP project,

Image title


built, deployed, and started in the Eclipse Tomcat server. Remember to make sure MySQL is running. You can review a previous article regarding what we did with MySQL.

We check to make sure it looks good...

Image title

And try something simple in SOAPUI (only care about the SOAP service for now).

Image title

And it is ready to be used.

We move on to creating the new Web App project.

New Web App Project

We start with another Dynamic Web Project in Eclipse, etc., and for now, we just copy over one file from the previous Web App, and we copy most of the POM stuff from the Command-Line conversion that we did in Parts 1 and 2 of this article segment. Here is the current project structure and the POM contents:

Image title

<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>1-webapp-soap-rest-client-only</groupId>
  <artifactId>1-webapp-soap-rest-client-only</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>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <cxf.version>3.1.7</cxf.version>
        <!-- ================  WEB SERVICE WSDL URL ========================== --> 
        <web-service-wsdl-url>http://localhost:8080/1-webapp-soap-rest-ws-p1/services/soap/shapecalc?wsdl</web-service-wsdl-url>
    </properties>
    <dependencies>

        <!-- =========== logging dependencies ================= -->
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>jcl-over-slf4j</artifactId>
            <version>1.7.21</version>
        </dependency>

        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-classic</artifactId>
            <version>1.1.7</version>
        </dependency>

    </dependencies>



    <build>

        <plugins>


        <!-- ================  CLEAN ========================== --> 
            <plugin>
                <artifactId>maven-clean-plugin</artifactId>
                <version>3.0.0</version>
                <configuration>
                    <filesets>
                        <fileset>
                            <directory>src/main/java/com/eli/calc/shape/service</directory>
                            <followSymlinks>false</followSymlinks>
                        </fileset>
                    </filesets>
                </configuration>
            </plugin>

        <!-- ================  WSDL2JAVA ========================== --> 
            <!-- Generate Java classes from WSDL during build -->
            <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>

                            <!-- which source folder the generated classes should be placed in 
                                a package -->

                            <sourceRoot>${project.basedir}/src/main/java</sourceRoot>
                            <wsdlOptions>
                                <wsdlOption>

                                    <!-- get the wsdl file from this location -->

                                    <wsdl>${web-service-wsdl-url}</wsdl>
                                    <extraargs>
                                        <extraarg>-client</extraarg>
                                    </extraargs>
                                </wsdlOption>
                            </wsdlOptions>
                        </configuration>
                        <goals>
                            <goal>wsdl2java</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>


        <!-- ================  BUILD ========================== --> 
            <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>
                    <failOnMissingWebXml>false</failOnMissingWebXml>
                </configuration>
            </plugin>

        </plugins>

    </build>

</project>

We do a Maven install. And the project structure shows the generated files.

Image title

Namespaces / Packages

Before we copy over the actual classes of the Web App (JSPs, Controller, etc), let's work on improving the generated packages.  If you look at the new SOAP/REST Service project structure image (see higher up) that we just did, there are two other packages that are not seen in what we just generated:  

The  com.eli.calc.shape.service.ws.parms , and The com.eli.calc.shape.service.ws.resp sub-packages.

In what we just generated, all of those types landed in the parent  com.eli.calc.shape.service.wspackage.

Just as we did in Parts 1 and 2, we could leave all this alone and it still works, but we have an excuse to explore defining the WSDL.

So let's see how we might change that to mirror the service project.

Add package-info.java

We add this to the SOAP/REST Service project.

Image title

Re-build, deploy, start. Then do  Maven clean install on the Web App client project.

Image title

And this time, we have what we wanted.

However, if you expand the com.eli.calc.shape.service.ws package, there are still other classes there that "shouldn't" be in that package.  Two of them, in particular, are the ShapeName and CalcType.   If you go to the base Shape Calculator project code, you'll see they belong in the com.eli.calc.shape.model package.

Another Way: Top-Down, WSDL-First

At the start of this article, I mentioned that we have been dealing with a bottom-up SOAP/REST web service.

To just show something different, let's deploy the Top-Down SOAP service that we have previously created.

You can read all about that one by looking at all of the articles labeled "Exploring A Top-Down SOAP Service: Part.." (there are several parts).

Here is the latest code for that project.

Build.Deploy.Start

Image title

So we now have two versions of the SOAP service running.  The latest one (SOAP/REST) is the one we have been working with, and it uses the latest version (rel2) of the Shape Calculator.

The older one is the SOAP service that was created Top-Down.  I want to just temporarily use this one to show how it impacts our Web App's packages.  But then we will return to using the newer service.

Point Web App to SOAP Service

We change the Web App's POM to use this top-down service.

Here is just the   <properties> ... </properties>  section:

    <properties>
        <maven.compiler.target>1.8</maven.compiler.target>
        <maven.compiler.source>1.8</maven.compiler.source>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <cxf.version>3.1.7</cxf.version>
        <!-- ================  WEB SERVICE WSDL URL ========================== --> 
        <!--
        <web-service-wsdl-url>http://localhost:8080/1-webapp-soap-rest-ws-p1/services/soap/shapecalc?wsdl</web-service-wsdl-url>
        -->
        <web-service-wsdl-url>http://localhost:8080/ws-soap-top-down-p8/services/ShapeCalculatorWebService?wsdl</web-service-wsdl-url>
    </properties>

Build Web App

Let's do another Maven clean install:

Image title

And what a difference it made. Look at all the different packages.

Comments

I suppose each method of Web Service creation (code-first; WSDL-first) has its pluses and minses, and you can decide which you prefer (or may have to choose, depending on your situation).

If you are wondering why I didn't go any further trying to improve with the first one (bottom up), it is because one of the original requirements from earlier articles, is that we would not touch the base Shape Calculator project to make any service-related changes.  We would treat it as a 3rd-party project.

That means we can't annotate any of its classes, or add the "package-info.java" to its packages.

Ok..we move on.  I changed the POM WSDL URL property back to using the original bottom-up SOAP/REST service, and did a Maven clean install.

(There's a "clean" section in the POM - it's probably missing some filesets.. doing a clean after having run against the top-down service may result in old packages remaining.)

We Move On To Complete The Web App Client

We need to copy some dependencies from the original web app...

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

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

        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>jstl</artifactId>
            <version>1.2</version>
        </dependency>

And these.

Image title

And these, too.

Image title

POM:

<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>1-webapp-soap-rest-client-only</groupId>
  <artifactId>1-webapp-soap-rest-client-only</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>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <spring.version>4.3.2.RELEASE</spring.version>
        <cxf.version>3.1.7</cxf.version>
        <!-- ================  WEB SERVICE WSDL URL ========================== --> 
        <!--
        <web-service-wsdl-url>http://localhost:8080/ws-soap-top-down-p8/services/ShapeCalculatorWebService?wsdl</web-service-wsdl-url>
        -->
        <web-service-wsdl-url>http://localhost:8080/1-webapp-soap-rest-ws-p1/services/soap/shapecalc?wsdl</web-service-wsdl-url>
    </properties>

    <dependencies>

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

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

        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>jstl</artifactId>
            <version>1.2</version>
        </dependency>

        <!-- =========== logging dependencies ================= -->
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>jcl-over-slf4j</artifactId>
            <version>1.7.21</version>
        </dependency>

        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-classic</artifactId>
            <version>1.1.7</version>
        </dependency>

    </dependencies>



    <build>

        <plugins>


        <!-- ================  CLEAN ========================== --> 
            <plugin>
                <artifactId>maven-clean-plugin</artifactId>
                <version>3.0.0</version>
                <configuration>
                    <filesets>
                        <fileset>
                            <directory>src/main/java/com/eli/calc/shape/service</directory>
                            <followSymlinks>false</followSymlinks>
                        </fileset>
                    </filesets>
                </configuration>
            </plugin>

        <!-- ================  WSDL2JAVA ========================== --> 
            <!-- Generate Java classes from WSDL during build -->
            <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>

                            <!-- which source folder the generated classes should be placed in 
                                a package -->

                            <sourceRoot>${project.basedir}/src/main/java</sourceRoot>
                            <wsdlOptions>
                                <wsdlOption>

                                    <!-- get the wsdl file from this location -->

                                    <wsdl>${web-service-wsdl-url}</wsdl>
                                    <extraargs>
                                        <extraarg>-client</extraarg>
                                    </extraargs>
                                </wsdlOption>
                            </wsdlOptions>
                        </configuration>
                        <goals>
                            <goal>wsdl2java</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>


        <!-- ================  BUILD ========================== --> 
            <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>
                    <failOnMissingWebXml>false</failOnMissingWebXml>
                </configuration>
            </plugin>

        </plugins>

    </build>

</project>

Build

Doing a Maven update and build. 

Image title

Some of these can be taken care of easily.

Image title

Remove those imports, then "Organize Imports".

Image title

That changes the package names of those same classes. Remember that we copied this class from a web app that had direct access to the Shape Calculator.

Try another build. This time, we get:

Image title

And this is good.  Remember, at the start of the article, we were going to replace all tightly-coupled code ("calculator") with code invoking the SOAP/REST service ("soapClient").

RequestResponseController Conversion

This is before:

Image title

And this is after:

Image title

Remember from Parts 1 and 2 of these articles that this ShapeCalculatorWebService_ShapeCalculatorWebServiceImplPort_Client class was generated.

Now these remain:

Image title

And it is because the old calculator operations contained the word All in them, but the new, generated class operations do not.

However, that's not the only issue.   At this point, we have imported the "wrong" ShapeCalculatorWebService_ShapeCalculatorWebServiceImplPort_Client class into our Controller.

Need Correct Generated Class

Again, go back and review Parts 1 and 2 of these articles...  originally, when we were converting over the Command-Line app into a SOAP client, we took a similarly generated *_Client class, but we converted the template code into public methods and added some of our own logic, and we also moved it from the generated  com.eli.calc.shape.service.ws  package, into the maincom.eli.calc.shape.apps  package.

Sooo....instead of using the latest, generated class from our current Web App conversion project, let's copy this "upgraded/enhanced" version from the Command-Line App conversion project.

The question becomes, then... "where do we put this class".   It isn't a controller...  it really does go best where's its unenhanced, parallel-universe half now resides: in the com.eli.calc.shape.service.ws  package.

BUT - anything put in that package will go poof! on the next Maven clean.   Yeah, we could add an exclusion to the POM not to remove a particular file but. I HATE that idea. Yuck.

How about we do this instead.

Image title

I am not going to go over every step in excruciating detail - you can view and compare the code yourself.

Another change is that we no longer need the custom Listener from the original Web App, because this SOAP client version does not manage either the calculator component, nor a DataSource. So let's remove the entire com.eli.calc.shape.listeners.impl  package.

Image title

Removing that will cause an error in the WebAppInitializer.

WebAppInitializer Changes

Image title

We Need To Initialize The soapClient With WSDL URL

There are many ways to do the following.  This is just one way.  Probably not even the best way. But it gets the job done for now, and we can always refactor if we have to later.

I am more interested in that we cover as many apropos technologies than making sure every little step is done to the nth correct degree.

Feel free to improve on this.

WebConfig Changes

In order to initialize the soapClient that will be used by the MVC RequestResultController, we create a  @Bean.

    @Bean
    ShapeCalculatorWebService_ShapeCalculatorWebServiceImplPort_Client soapClient(
                             @Value("${ws.wsdl.url}") String urlString) {

        URL wsdlURL = ShapeCalculatorWebServiceImplService.WSDL_LOCATION;

        if (null!=urlString && !urlString.isEmpty()) { 
            File wsdlFile = new File(urlString);
            try {
                if (wsdlFile.exists()) {
                    wsdlURL = wsdlFile.toURI().toURL();
                } else {
                    wsdlURL = new URL(urlString);
                }
            } catch (MalformedURLException e) {
                logger.error(e.getMessage(),e);
                throw new RuntimeException(e);
            }
        }

        return new ShapeCalculatorWebService_ShapeCalculatorWebServiceImplPort_Client(
                wsdlURL);
    }

web.application.properties

And one more step to do with the initialization. We need a property value for the (above)  ${ws.wsdl.url} .

Image title

Deployment

Again, this is just one way to do this.   I chose to separate out this web app client to run on a different server from the web service(s).

Image title

Image title

Test #1: Basic Operation

Let's see what a browser shows when we point it to the server running the new web app.

Image title

Ok, that looks good. But will it connect to the other Tomcat server, running the SOAP/REST service? (It should, since it had to as part of Spring initialization).

Test #2: Connect To SOAP Service

Let's do a "Request Calculation".

Image title

And that looks good.

Test #3: Shutdown MySQL

This means the backend SOAP service should return an error reponse to our new web app, so let's see how it handles it...  We will attempt another Calculation  Request after we shut down MySQL..

Image title

We verify:

Image title

And we do the request.

Image title

And that works. We could make that look better, but it's sufficient for our purposes.

Test #4: Shutdown SOAP Service

Image title

It took a while because there are two services on the first Tomcat, and I had unceremoniously shut down their datasource.

Now let's try another Calculation Request.

Image title

And looks good.

Test #5: The SOAP Service Has New ShapeName

The scenario is that there has been a change to the back-end service, but the front-end web app is not aware of the change.

We did something similar in the past two articles.  It involves adding a new ShapeName to the base ShapeCalculator project and rebuilding it as well as the SOAP/Rest service project. That also means we shut down the Tomcat server and re-started it.

However, we will leave the Tomcat server running the Web App up. Then we'll see how it handles a change in the backend service.

Image title

So, the above is the new shape we are adding.   After the updated SOAP/REST service is running again, let's try out the Web App.

It seems to work, however,  just as we did before in articles Parts 1 and 2, we use SOAPUI to stand in as our "updated" client app.

We grab the WSDL.

Image title

And queue a new calculation via SOAPUI.

Image title

Image title

And (above) is the proof we have a new pending request with a new shape.

Now we try to get the list of pending requests via the Web App.

Image title

That warning is a result of utilizing the SOAP service's newer operations.

Image title

Added a new private method to the RequestResponseController.

    private String checkIfThisAppIsOutdated() {

        // this app's generated classes may need re-generation if the service
        // has new items...

        String obsoleteMessage = "";

        if (ShapeName.values().length != soapClient.getShapeNames().size()){

            obsoleteMessage += shapeNamesObsoleteMessage;
        }

        if (CalcType.values().length != soapClient.getCalcTypes().size()) {

            obsoleteMessage += calcTypesObsoleteMessage;
        }

        return obsoleteMessage;
    }

Invoke it from the various JSP handlers.

Image title

And finally, the JSPs.

Image title

Results of More Testing For Failure

We will not go over the code for these.  I just wanted to give an idea. There are many ways to do something.   You can review the code (listed at the end of this article). 

Possible errors can be:

  • The data in the database does not match what the SOAP service can handle. Perhaps two different versions of the service were running.
  • The data types (i.e. ShapeName) on the SOAP service side does not match with the Web app types.... that means the Web app needs to be rebuilt.
  • The database or connection to it is down
  • The SOAP service itself is down
  • etc.

Here are some screenshots of some of those examples:

Image title

Image title

And the last one:

Image title

Final Comments

We have completed this next release of the Web App (converted to SOAP client).  There's much that could be improved, or added.  One obvious improvement would be performance / responsiveness.

However, in our make-believe story, your boss has told you that you need to move on.  Hand what you have over to the other departments, and they can take it from here.

We have other things to do.

Latest Code

You can review all of the latest code for this Web App conversion project, here.

And here is the code for the same Web App, prior to conversion.

Here is the latest release of the SOAP/REST service.

And here is the latest release of the Shape Calculator project.

What Is Next?

We are going to re-trace our steps, but this time, we will be exploring having the Web App communicate with the REST side of the service.

We are very close to one of our more important goals - securing web services.

See you next time!

Deploying code to production can be filled with uncertainty. Reduce the risks, and deploy earlier and more often. Download this free guide to learn more. Brought to you in partnership with Rollbar.

Topics:
web service clients ,web service definition ,wsdl ,cxf ,web app ,mvc ,spring ,soap ,rest ,j2ee

Opinions expressed by DZone contributors are their own.

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

{{ parent.tldr }}

{{ parent.urlSource.name }}