VMS Tech Demo 10 - Java SOAP web services

Content:

  1. Introduction
  2. JAX-WS
  3. Standalone server
  4. Tomcat
  5. Tomcat with Axis2
  6. Client
  7. Client in JVM script languages
  8. Conclusion

Introduction:

SOAP web services are not in fashion today. But they still work and quite often RPC style is actually more desirable than REST style.

SOAP is known to be very heavy and very complex. But a good SOAP toolkit can hide all that complexity from the application developer.

This article is going to take the simple approach. That includes doing "code first" (aka "Java first") and not "contract first" (aka "WSDL first").

JAX-WS makes it reasonable simple to do SOAP for Java developers.

JAX-WS:

JAX-WS is the second generation Java API for SOAP web services.

It was part of Java SE from version 6 to version 10 and part of Java EE from version 5.

For VMS that mean:

Version Standalone server Servlet container like Tomcat Full application server like JBoss
Java 5 / VMS Alpha need JAX-WS jar files (*) need JAX-WS jar files (*) need JAX-WS jar files (because the application server is too old)
Java 8 / VMS Itanium all set need JAX-WS jar files (because servlet support is misisng in Java SE) should be all set
Java 8 on / VMS x86-64 all set (*) need JAX-WS jar files (because servlet support is misisng in Java SE) (*) should be all set
future Java 17 or 21 / VMS x86-64 need JAX-WS jar files need JAX-WS jar files should be all set

*) config that I have actually tested

I have tested with JAX-WS kit version 2.2.5, because that works with both Java 5 and Java 8. You can use another version as long as it is supported by the Java version available. Java 5 on Alpha is 20 years old and Java 8 on Itanium/x86-64 is 10 years old.

The specific files I have tested with are:

activation-1_1.jar;1
gmbal-api-only-3_1_0-b001.jar;1
ha-api-3_1_8.jar;1
http-20070405.jar;1
istack-commons-runtime-2_2_1.jar;1
javax_annotation-3_1_1-b06.jar;1
jaxb-api-2_2_3.jar;1
jaxb-impl-2_2_4-1.jar;1
jaxb-xjc-2_2_4-1.jar;1
jaxws-api-2_2_5.jar;1
jaxws-rt-2_2_5.jar;1
jaxws-tools-2_2_5.jar;1
jsr181-api-1_0-MR1.jar;1
junit-3_8.jar;1
management-api-3_0_0-b012.jar;1
mimepull-1_6.jar;1
policy-2_2_2.jar;1
resolver-20050927.jar;1
saaj-api-1_3_3.jar;1
saaj-impl-1_3_10.jar;1
stax-api-1_0-2.jar;1
stax-api-1_0_1.jar;1
stax-ex-1_4.jar;1
stax2-api-3_1_1.jar;1
streambuffer-1_2.jar;1
txw2-20090102.jar;1
woodstox-core-asl-4_1_1.jar;1
wstx-asl-3_2_3.jar;1

(note that I converted file names to be ODS-2 friendly aka underscore instead of period)

Nothing in the Java code below or the referenced jar files are VMS specific. The exact same things runs on Linux and Windows. It is just the setup that are VMS specific. But that setup is important, because there are a gazillion tools and tutorials available for Linux and Windows - not much for VMS.

Standalone server:

Running the web service in a standalone server makes sense when:

SOAP web services can appear crazy complex. SOAP, WSDL, XML schemas etc.. But the reality is that creating a SOAP web service in Java with JAX-WS is very simple.

Process:

  1. put @WebService annotation on class you want to expose as a web service
  2. **optionally** put @WebMethod annotatation on the methods in the class
  3. publish it in some Java code by calling the Endpoint.publish method

That is not so hard. :-)

Let us see an example.

Data.java:

package test.server;

public class Data {
    private int ival;
    private String sval;
    public Data() {
        this(0, "");
    }
    public Data(int ival, String sval) {
        super();
        this.ival = ival;
        this.sval = sval;
    }
    public int getIval() {
        return ival;
    }
    public void setIval(int ival) {
        this.ival = ival;
    }
    public String getSval() {
        return sval;
    }
    public void setSval(String sval) {
        this.sval = sval;
    }
}

Test.java:

package test.server;

import java.util.ArrayList;
import javax.jws.WebMethod;
import javax.jws.WebService;

@WebService
public class Test {
    @WebMethod
    public int getInt() {
        return 123;
    }
    @WebMethod
    public String getString() {
        return "ABC";
    }
    @WebMethod
    public Data getData() {
        return new Data(123, "ABC");
    }
    @WebMethod
    public ArrayList<Integer> getListOfInts() {
        ArrayList<Integer> res = new ArrayList<Integer>();
        res.add(123);
        res.add(456);
        return res;
    }
    @WebMethod
    public ArrayList<String> getListOfStrings() {
        ArrayList<String> res = new ArrayList<String>();
        res.add("ABC");
        res.add("DEF");
        return res;
    }
    @WebMethod
    public ArrayList<Data> getListOfData() {
        ArrayList<Data> res = new ArrayList<Data>();
        res.add(new Data(123, "ABC"));
        res.add(new Data(456, "DEF"));
        return res;
    }
    @WebMethod
    public int add(int a, int b) {
        return a + b;
    }
    @WebMethod
    public String concat(String a, String b) {
        return a + b;
    }
    @WebMethod
    public Data modify(Data o) {
        return new Data(o.getIval() + 1, o.getSval() + "X");
    }
}

TestServer.java:

package test.server;

import javax.xml.ws.Endpoint;

public class TestServer {
    public static void main(String[] args) {
        Endpoint.publish("http://localhost:8080/test/Test", new Test());
    }
}

This auto generate the WSDL and expose it at the URL:

http://localhost:8080/test/Test?wsdl

If there is a need to tweak the SOAP binding then it is done with the @SOAPBinding annotation. Possible attributes are:

But when that need arise then it starts getting complicated.

Java 5 (VMS Alpha):

cp.com:

$ jaxwslibloc = "DISK2:[ARNE.jaxws.lib]" ! change location
$ cp = f$parse("[]") - ".;"
$ loop:
$    jar = f$search("''jaxwslibloc'*.jar")
$    if jar .eqs. "" then goto endloop
$    cp = cp + "," + (jar - ";1")
$    goto loop
$ endloop:
$ define/nolog java$classpath 'cp'
$ exit

server.com:

$ @cp
$ set def [.test.server]
$ javac *.java
$ set def [--]
$ java "test.server.TestServer"
$ exit

Java 8 (VMS Itanium and VMS x86-64):

server.com:

$ set def [.test.server]
$ javac *.java
$ set def [--]
$ java -cp . "test.server.TestServer"
$ exit

Tomcat:

Running the web service in Tomcat makes sense when:

When deploying in Tomcat the web service class and data classes are the same as for standalone server, but the publishing is done via web application configuration instead of in Java code.

Data.java:

package test.server;

public class Data {
    private int ival;
    private String sval;
    public Data() {
        this(0, "");
    }
    public Data(int ival, String sval) {
        super();
        this.ival = ival;
        this.sval = sval;
    }
    public int getIval() {
        return ival;
    }
    public void setIval(int ival) {
        this.ival = ival;
    }
    public String getSval() {
        return sval;
    }
    public void setSval(String sval) {
        this.sval = sval;
    }
}

Test.java:

package test.server;

import java.util.ArrayList;
import javax.jws.WebMethod;
import javax.jws.WebService;

@WebService
public class Test {
    @WebMethod
    public int getInt() {
        return 123;
    }
    @WebMethod
    public String getString() {
        return "ABC";
    }
    @WebMethod
    public Data getData() {
        return new Data(123, "ABC");
    }
    @WebMethod
    public ArrayList<Integer> getListOfInts() {
        ArrayList<Integer> res = new ArrayList<Integer>();
        res.add(123);
        res.add(456);
        return res;
    }
    @WebMethod
    public ArrayList<String> getListOfStrings() {
        ArrayList<String> res = new ArrayList<String>();
        res.add("ABC");
        res.add("DEF");
        return res;
    }
    @WebMethod
    public ArrayList<Data> getListOfData() {
        ArrayList<Data> res = new ArrayList<Data>();
        res.add(new Data(123, "ABC"));
        res.add(new Data(456, "DEF"));
        return res;
    }
    @WebMethod
    public int add(int a, int b) {
        return a + b;
    }
    @WebMethod
    public String concat(String a, String b) {
        return a + b;
    }
    @WebMethod
    public Data modify(Data o) {
        return new Data(o.getIval() + 1, o.getSval() + "X");
    }
}

WEB-INF/web.xml:

<web-app xmlns="http://java.sun.com/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
         version="3.0">
    <listener>
        <listener-class>com.sun.xml.ws.transport.http.servlet.WSServletContextListener</listener-class>
    </listener>
    <servlet>
        <servlet-name>JAXWSServlet</servlet-name>
        <servlet-class>com.sun.xml.ws.transport.http.servlet.WSServlet</servlet-class>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>JAXWSServlet</servlet-name>
        <url-pattern>/*</url-pattern>
    </servlet-mapping>
</web-app>

WEB-INF/sun-jaxws.xml:

<endpoints xmlns="http://java.sun.com/xml/ns/jax-ws/ri/runtime" version="2.0">
  <endpoint name="TestService" implementation="test.server.Test" url-pattern="/Test"/>
</endpoints>

This auto generate the WSDL and if the web app is "test" then expose it at the URL:

http://localhost:8080/test/Test?wsdl

As usual the web application can either be packaged and deployed as war file - or deployed directly into directory tree.

Directory structure on Tomcat 8.5:

Directory TOMCAT$ROOT:[webapps.test]

WEB-INF.DIR;1

Total of 1 file.

Directory TOMCAT$ROOT:[webapps.test.WEB-INF]

classes.DIR;1       lib.DIR;1           sun-jaxws.xml;1     web.xml;1

Total of 4 files.

Directory TOMCAT$ROOT:[webapps.test.WEB-INF.classes]

test.DIR;1

Total of 1 file.

Directory TOMCAT$ROOT:[webapps.test.WEB-INF.classes.test]

server.DIR;1

Total of 1 file.

Directory TOMCAT$ROOT:[webapps.test.WEB-INF.classes.test.server]

Data.class;1        Test.class;1

Total of 2 files.

Directory TOMCAT$ROOT:[webapps.test.WEB-INF.lib]

activation-1_1.jar;1                    gmbal-api-only-3_1_0-b001.jar;1
ha-api-3_1_8.jar;1  http-20070405.jar;1 istack-commons-runtime-2_2_1.jar;1
javax_annotation-3_1_1-b06.jar;1        jaxb-api-2_2_3.jar;1
jaxb-impl-2_2_4-1.jar;1                 jaxb-xjc-2_2_4-1.jar;1
jaxws-api-2_2_5.jar;1                   jaxws-rt-2_2_5.jar;1
jaxws-tools-2_2_5.jar;1                 jsr181-api-1_0-MR1.jar;1
junit-3_8.jar;1     management-api-3_0_0-b012.jar;1         mimepull-1_6.jar;1
policy-2_2_2.jar;1  resolver-20050927.jar;1                 saaj-api-1_3_3.jar;1
saaj-impl-1_3_10.jar;1                  stax-api-1_0-2.jar;1
stax-api-1_0_1.jar;1                    stax-ex-1_4.jar;1   stax2-api-3_1_1.jar;1
streambuffer-1_2.jar;1                  txw2-20090102.jar;1 woodstox-core-asl-4_1_1.jar;1
wstx-asl-3_2_3.jar;1

Total of 28 files.

Grand total of 6 directories, 37 files.

Tomcat with Axis2:

Apache Axis2 is an advanced Java web service framework.

Apache Axis2 is available for VMS Itanium and VMS x86-64.

If you really are into Java web services, then definitely take a look at Apache Axis2. But it is way more complex than what is presented here and therefore considered out of scope.

Client:

To create a client do:

  1. generate stub code from WSDL URL
  2. write client code that
    1. instantiate service factory
    2. use getPort method to get reference
    3. call methods on that reference

TestClient.java:

package test.client;

import java.util.List;

import test.client.gen.Data;
import test.client.gen.Test;
import test.client.gen.TestService;

public class TestClient {
    public static void main(String[] args) {
        TestService factory = new TestService();
        Test soap = factory.getPort(Test.class);
        System.out.println(soap.getInt());
        System.out.println(soap.getString());
        Data o = soap.getData();
        System.out.printf("(%d,%s)\n", o.getIval(), o.getSval());
        System.out.println(soap.getListOfInts());
        System.out.println(soap.getListOfStrings());
        List<Data> all = soap.getListOfData();
        System.out.print("[");
        for(Data one : all) {
            System.out.printf("(%d,%s)", one.getIval(), one.getSval());
        }
        System.out.println("]");
        System.out.println(soap.add(123, 456));
        System.out.println(soap.concat("ABC", "DEF"));
        o = new Data();
        o.setIval(123);
        o.setSval("ABC");
        System.out.printf("(%d,%s)\n", o.getIval(), o.getSval());
        o = soap.modify(o);        
        System.out.printf("(%d,%s)\n", o.getIval(), o.getSval());
    }
}

Java 5 (VMS Alpha):

cp.com:

$ jaxwslibloc = "DISK2:[ARNE.jaxws.lib]" ! change location
$ cp = f$parse("[]") - ".;"
$ loop:
$    jar = f$search("''jaxwslibloc'*.jar")
$    if jar .eqs. "" then goto endloop
$    cp = cp + "," + (jar - ";1")
$    goto loop
$ endloop:
$ define/nolog java$classpath 'cp'
$ exit

client.com:

$ @cp
$ java "com.sun.tools.ws.WsImport" "-keep" "-p" "test.client.gen" "http://localhost:8080/test/Test?wsdl"
$ set def [.test.client]
$ javac TestClient.java
$ set def [--]
$ java "test.client.TestClient"
$ exit

Java 8 (VMS Itanium and VMS x86-64):

client.com:

$ wsimport -keep -p test.client.gen http://localhost:8080/test/Test?wsdl
$ set def [.test.client]
$ javac -cp ../.. TestClient.java
$ set def [--]
$ java -cp . "test.client.TestClient"
$ exit

Client in JVM script languages:

Groovy:

TestClient.groovy:

import test.client.gen.*

factory = new TestService()
soap = factory.getPort(Test.class)
println(soap.getInt())
println(soap.getString())
o = soap.getData()
println("(${o.ival},${o.sval})")
println(soap.getListOfInts())
println(soap.getListOfStrings())
all = soap.getListOfData()
print("[")
for(one in all) {
    print("(${one.ival},${one.sval})")
}
println("]")
println(soap.add(123, 456))
println(soap.concat("ABC", "DEF"))
o = new Data(ival: 123, sval: "ABC")
println("(${o.ival},${o.sval})")
o = soap.modify(o)        
println("(${o.ival},${o.sval})")

Jython (JVM Python):

test_client.py:

from test.client.gen import *

factory = TestService()
soap = factory.getPort(Test)
print(soap.getInt())
print(soap.getString())
o = soap.getData()
print('(%d,%s)' % (o.ival,o.sval))
print(soap.getListOfInts())
print(soap.getListOfStrings())
all = soap.getListOfData()
s = '['
for one in all:
    s = s + ('(%d,%s)' % (one.ival,one.sval))
s = s + ']'
print(s)
print(soap.add(123, 456))
print(soap.concat('ABC', 'DEF'))
o = Data()
o.ival = 123
o.sval = 'ABC'
print('(%d,%s)' % (o.ival,o.sval))
o = soap.modify(o)        
print('(%d,%s)' % (o.ival,o.sval))

Conclusion:

This is easy! :-)

Does it perform? It does cost some CPU power to convert to and from the heavy SOAP XML. But it is not that bad. You should expect something like 5000-15000 requests per minute with both client and server running on the system. More if it is only server.

Article history:

Version Date Description
1.0 January 18th 2024 Initial version

Other articles:

See list of all articles here

Comments:

Please send comments to Arne Vajhøj