VMS Tech Demo 13 - Java RESTful web services

Content:

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

Introduction:

RESTful web services are the most common type of web services today.

Despite being widely used, then many developers do not understand the characteristics of RESTful web services.

Using JSON or lightweight XML is not sufficient for a web service to be a RESTful style web service.

The two fundamen different web srevice styles are:

RPC style is easy to understand. A web service define operations with arguments and return values. This matches functions/methods in programming languages.

RESTful style is different. A web service manage a resource (a collection of items). The only available operations are those defined by the HTTP protocol doing basic CRUD: GET (Read), POST (Create), PUT (Update), DELETE (Delete) and PATCH (Update).

GET reads either an item in a collection or the multiple (possible all) items in a collection, POST creates a new item in the collection, PUT updates an existing item in the collection and DELETE removes an item from the collection.

At first this may seem a little weird, but the model actually works fine in a lot of business applications. But if the model does not work for your problem and you really need RPC style, then you should not try and fake RESTful style but instead go with RPC style like SOAP, XML-RPC or JSON-RPC.

RPC style solutions are covered in:

RESTful web services can be implemented in many languages: Java, C#, Python, PHP etc., but we will look at Java here. On VMS.

JAX-RS:

JAX-RS is the Java API for RESTful web services.

JAX-RS 1.1 was part of Java EE (2009).

There are multiple implementations of JAX-RS. Two of the most common are:

This article will use Jersey.

I have tested with Jersey version 1.2 (JAX-RS API 1.1), because that works with both Java 5 and Java 8.

For standalone server (not Tomcat) an embedded servlet container is needed. I have tested with Jetty 7.6.21, because that works with both Java 5 and Java 8.

You can use other (newer) versions as long as they are 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_1.jar
asm-3_1.jar
config-1_4_3.jar
gson-2_2_4.jar
jackson-core-asl-1_1_1.jar
jaxb-api-2_1.jar
jaxb-impl-2_1_12.jar
jersey-bundle-1_2.jar
jersey-client-1_2.jar
jersey-core-1_2.jar
jersey-json-1_2.jar
jersey-server-1_2.jar
jettison-1_1.jar
jetty-client-7_6_21.jar
jetty-continuation-7_6_21.jar
jetty-http-7_6_21.jar
jetty-io-7_6_21.jar
jetty-security-7_6_21.jar
jetty-server-7_6_21.jar
jetty-servlet-7_6_21.jar
jetty-servlets-7_6_21.jar
jetty-util-7_6_21.jar
jsr311-api-1_1_1.jar
servlet-api-2_5.jar
slf4j-api-1_6_1.jar
slf4j-jdk14-1_6_1.jar
stax-api-1_0-2.jar

(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.

JAX-RS is annotation based:

The examples will use JSON only as JSON is way more common than XML today for RESTful web services.

The business logic will be simulated by a DataModel class that simulates the basic CRUD operations.

For more JAX-RS examples see the articles Web Service - RESTful and Web Service - Standalone.

Standalone server:

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

Let us see an example.

package server;

import javax.xml.bind.annotation.XmlRootElement;

@XmlRootElement
public class Data {
    private int iv;
    private double xv;
    private String sv;
    public Data() {
        this(0, 0.0, "");
    }
    public Data(int iv, double xv, String sv) {
        super();
        this.iv = iv;
        this.xv = xv;
        this.sv = sv;
    }
    public int getIv() {
        return iv;
    }
    public void setIv(int iv) {
        this.iv = iv;
    }
    public double getXv() {
        return xv;
    }
    public void setXv(double xv) {
        this.xv = xv;
    }
    public String getSv() {
        return sv;
    }
    public void setSv(String sv) {
        this.sv = sv;
    }
}
package server;

import java.util.List;

import javax.servlet.http.HttpServletResponse;
import javax.ws.rs.Consumes;
import javax.ws.rs.DefaultValue;
import javax.ws.rs.DELETE;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.POST;
import javax.ws.rs.PUT;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;

@Path("/data")
public class DataService {
    @Context 
    private HttpServletResponse response;
    @GET
    @Produces({MediaType.APPLICATION_JSON})
    @Path("/{v}")
    public Data getOne(@PathParam("v") int v) {
        return DataModel.getInstance().getOne(v);
    }
    @GET
    @Produces({MediaType.APPLICATION_JSON})
    @Path("")
    public List<Data> getSome(@DefaultValue("-1") @QueryParam("start") int start, @DefaultValue("-1") @QueryParam("finish") int finish) {
        if(start == -1 && finish == -1) {
            return DataModel.getInstance().getAll();
        } else {
            return DataModel.getInstance().getSome(start, finish);
        }
    }
    @POST
    @Consumes({MediaType.APPLICATION_JSON})
    @Produces({MediaType.APPLICATION_JSON})
    @Path("")
    public Data add(Data o) {
        DataModel.getInstance().add(o);
        return o;
    }
    @PUT
    @Consumes({MediaType.APPLICATION_JSON})
    @Path("/{v}")
    public void update(@PathParam("v") int v, Data o) {
        if(v == o.getIv()) {
            DataModel.getInstance().update(o);
        }
    }
    @DELETE
    @Path("/{v}")
    public void remove(@PathParam("v") int v) {
        DataModel.getInstance().remove(v);
    }
}
package server;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;

public class DataModel {
    private static DataModel instance;
    private Map<Integer,Data> data = null;
    private DataModel() {
        data = new TreeMap<Integer,Data>();
        data.put(1, new Data(1, 1.1, "Text #1"));
        data.put(2, new Data(2, 2.2, "Text #2"));
        data.put(3, new Data(3, 3.3, "Text #3"));
    }
    public static synchronized DataModel getInstance() {
        if(instance == null) {
            instance = new DataModel();
        }
        return instance;
    }
    public synchronized Data getOne(int v) {
        return data.get(v);
    }
    public synchronized List<Data> getAll() {
        List<Data> res = new ArrayList<Data>();
        for(Data d : data.values()) {
            res.add(d);
        }
        return res;
    }
    public synchronized List<Data> getSome(int start, int finish) {
        List<Data> res = new ArrayList<Data>();
        for(Data d : data.values()) {
            if(start <= d.getIv() & d.getIv() <= finish) {
                res.add(d);
            }
        }
        return res;
    }
    public synchronized void add(Data d) {
        data.put(d.getIv(), d);
    }
    public synchronized void update(Data d) {
        data.put(d.getIv(), d);
    }
    public synchronized void remove(int v) {
        data.remove(v);
    }
}
package server;

import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.servlet.ServletHolder;

import com.sun.jersey.spi.container.servlet.ServletContainer;

public class ServerMain {
    private static final int PORT = 8080;
    private static final String CONTEXT = "/";
    private static final String API = "/api/*";
    public static void main(String[] args) throws Exception {
        Server server = new Server(PORT);
        ServletContextHandler ctx = new ServletContextHandler(ServletContextHandler.NO_SESSIONS);
        ctx.setContextPath(CONTEXT);
        server.setHandler(ctx);
        ServletHolder srvlet = ctx.addServlet(ServletContainer.class, API);
        srvlet.setInitOrder(1);
        srvlet.setInitParameter("com.sun.jersey.config.property.packages", "server");
        srvlet.setInitParameter("com.sun.jersey.api.json.POJOMappingFeature", "true");
        server.start();
        server.join();
    }
}
$ jaxrslibloc = "DISK2:[ARNE.jaxrs]" ! change location
$ cp = f$parse("[]") - ".;"
$ loop:
$    jar = f$search("''jaxrslibloc'*.jar")
$    if jar .eqs. "" then goto endloop
$    cp = cp + "," + (jar - ";1")
$    goto loop
$ endloop:
$ define/nolog java$classpath 'cp'
$ exit
$ @cp
$ set def [.server]
$ javac *.java
$ set def [-]
$ java "server.ServerMain"

The service is available at URL:

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.

package server;

import javax.xml.bind.annotation.XmlRootElement;

@XmlRootElement
public class Data {
    private int iv;
    private double xv;
    private String sv;
    public Data() {
        this(0, 0.0, "");
    }
    public Data(int iv, double xv, String sv) {
        super();
        this.iv = iv;
        this.xv = xv;
        this.sv = sv;
    }
    public int getIv() {
        return iv;
    }
    public void setIv(int iv) {
        this.iv = iv;
    }
    public double getXv() {
        return xv;
    }
    public void setXv(double xv) {
        this.xv = xv;
    }
    public String getSv() {
        return sv;
    }
    public void setSv(String sv) {
        this.sv = sv;
    }
}
package server;

import java.util.List;

import javax.servlet.http.HttpServletResponse;
import javax.ws.rs.Consumes;
import javax.ws.rs.DefaultValue;
import javax.ws.rs.DELETE;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.POST;
import javax.ws.rs.PUT;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;

@Path("/data")
public class DataService {
    @Context 
    private HttpServletResponse response;
    @GET
    @Produces({MediaType.APPLICATION_JSON})
    @Path("/{v}")
    public Data getOne(@PathParam("v") int v) {
        return DataModel.getInstance().getOne(v);
    }
    @GET
    @Produces({MediaType.APPLICATION_JSON})
    @Path("")
    public List<Data> getSome(@DefaultValue("-1") @QueryParam("start") int start, @DefaultValue("-1") @QueryParam("finish") int finish) {
        if(start == -1 && finish == -1) {
            return DataModel.getInstance().getAll();
        } else {
            return DataModel.getInstance().getSome(start, finish);
        }
    }
    @POST
    @Consumes({MediaType.APPLICATION_JSON})
    @Produces({MediaType.APPLICATION_JSON})
    @Path("")
    public Data add(Data o) {
        DataModel.getInstance().add(o);
        return o;
    }
    @PUT
    @Consumes({MediaType.APPLICATION_JSON})
    @Path("/{v}")
    public void update(@PathParam("v") int v, Data o) {
        if(v == o.getIv()) {
            DataModel.getInstance().update(o);
        }
    }
    @DELETE
    @Path("/{v}")
    public void remove(@PathParam("v") int v) {
        DataModel.getInstance().remove(v);
    }
}
package server;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;

public class DataModel {
    private static DataModel instance;
    private Map<Integer,Data> data = null;
    private DataModel() {
        data = new TreeMap<Integer,Data>();
        data.put(1, new Data(1, 1.1, "Text #1"));
        data.put(2, new Data(2, 2.2, "Text #2"));
        data.put(3, new Data(3, 3.3, "Text #3"));
    }
    public static synchronized DataModel getInstance() {
        if(instance == null) {
            instance = new DataModel();
        }
        return instance;
    }
    public synchronized Data getOne(int v) {
        return data.get(v);
    }
    public synchronized List<Data> getAll() {
        List<Data> res = new ArrayList<Data>();
        for(Data d : data.values()) {
            res.add(d);
        }
        return res;
    }
    public synchronized List<Data> getSome(int start, int finish) {
        List<Data> res = new ArrayList<Data>();
        for(Data d : data.values()) {
            if(start <= d.getIv() & d.getIv() <= finish) {
                res.add(d);
            }
        }
        return res;
    }
    public synchronized void add(Data d) {
        data.put(d.getIv(), d);
    }
    public synchronized void update(Data d) {
        data.put(d.getIv(), d);
    }
    public synchronized void remove(int v) {
        data.remove(v);
    }
}
package server;

import java.util.HashSet;
import java.util.Set;

import javax.ws.rs.ApplicationPath;
import javax.ws.rs.core.Application;

@ApplicationPath("/api")
public class LoadServices extends Application {
    @Override
    public Set<Class<?>> getClasses() {
        Set<Class<?>> res = new HashSet<Class<?>>();
        res.add(DataService.class);
        return res;
    }
}

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">
</web-app>

The service is available at URL (if deployed in rest.war):

Directory structure on Tomcat 8.5:

Directory TOMCAT$ROOT:[webapps.rest]

WEB-INF.DIR;1

Total of 1 file.

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

classes.DIR;1       lib.DIR;1           web.xml;2           web.xml;1

Total of 4 files.

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

server.DIR;1

Total of 1 file.

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

Data.class;8        DataModel.class;8   DataService.class;8 LoadServices.class;8
ServerMain.class;8

Total of 5 files.

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

activation-1_1_1.jar;1                  asm-3_1.jar;1       config-1_4_3.jar;1
jackson-core-asl-1_1_1.jar;1            jaxb-api-2_1.jar;1  jaxb-impl-2_1_12.jar;1
jersey-bundle-1_2.jar;1                 jersey-client-1_2.jar;1
jersey-core-1_2.jar;1                   jersey-json-1_2.jar;1
jersey-server-1_2.jar;1                 jettison-1_1.jar;1  jsr311-api-1_1_1.jar;1
stax-api-1_0-2.jar;1

Total of 14 files.

Grand total of 5 directories, 25 files.

Client:

There are several possible approaches to client:

To keep it simple we will use standard Java HttpURLConnection.

For JSON library we will use Google GSON library.

package client;

import javax.xml.bind.annotation.XmlRootElement;

@XmlRootElement
public class Data {
    private int iv;
    private double xv;
    private String sv;
    public Data() {
        this(0, 0.0, "");
    }
    public Data(int iv, double xv, String sv) {
        super();
        this.iv = iv;
        this.xv = xv;
        this.sv = sv;
    }
    public int getIv() {
        return iv;
    }
    public void setIv(int iv) {
        this.iv = iv;
    }
    public double getXv() {
        return xv;
    }
    public void setXv(double xv) {
        this.xv = xv;
    }
    public String getSv() {
        return sv;
    }
    public void setSv(String sv) {
        this.sv = sv;
    }
    @Override
    public String toString() {
        return String.format("(%d,%f,%s)",  iv, xv, sv);
    }
}
package client;

import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import java.lang.reflect.Type;

import com.google.gson.reflect.TypeToken;
import com.google.gson.Gson;

public class ClientMain {
    private static String interact(String method, String urlstr, String typ, String body) {
        try {
            HttpURLConnection con = (HttpURLConnection)(new URL(urlstr)).openConnection();
            con.setRequestMethod(method);
            con.addRequestProperty("accept",  typ);
            if(body != null) {
                con.addRequestProperty("content-type", typ);
                con.setDoOutput(true);
                con.getOutputStream().write(body.getBytes());
            }
            StringBuilder sb = new StringBuilder();
            con.connect();
            if(con.getResponseCode() / 100 == 2) {
                InputStream is = con.getInputStream();
                byte[] b = new byte[1000];
                int n;
                while((n = is.read(b)) >= 0) {
                    sb.append(new String(b,0,n));
                }
                is.close();
            } else {
                System.out.printf("Error: %d %s\n", con.getResponseCode(), con.getResponseMessage());
            }
            con.disconnect();
            return sb.toString();
        } catch (MalformedURLException e) {
            e.printStackTrace();
            return null;
        } catch (IOException e) {
            e.printStackTrace();
            return null;
        }
    }
    private static void testGetOne(String urlstr, int v) {
        String response = interact("GET", urlstr + "/" + v, "application/json", null);
        Gson gson = new Gson();
        Data d = gson.fromJson(response, Data.class);
        System.out.println(d);
    }
    private static void testGetAll(String urlstr) {
        String response = interact("GET", urlstr, "application/json", null);
        Gson gson = new Gson();
        Type t = new TypeToken<TreeMap<String,ArrayList<Data>>>(){}.getType();
        Map<String,List<Data>> result = gson.fromJson(response, t);
        System.out.println(result.get("data"));
    }
    private static void testGetSome(String urlstr, int start, int finish) {
        String response = interact("GET", urlstr + "?start=" + start + "&finish=" + finish, "application/json", null);
        Gson gson = new Gson();
        Type t = new TypeToken<TreeMap<String,ArrayList<Data>>>(){}.getType();
        Map<String,List<Data>> result = gson.fromJson(response, t);
        System.out.println(result.get("data"));
    }
    private static void testAdd(String urlstr, Data d1) {
        Gson  gson = new Gson();
        String request = gson.toJson(d1);
        String response = interact("POST", urlstr + "/", "application/json", request);
        Data d2 = gson.fromJson(response, Data.class);
        System.out.println(d2);
    }
    private static void testUpdate(String urlstr, int v) {
        String response = interact("GET", urlstr + "/" + v, "application/json", null);
        Gson gson = new Gson();
        Data d = gson.fromJson(response, Data.class);
        d.setXv(d.getXv() + 0.01);
        d.setSv(d.getSv() + " - updated");
        String request = gson.toJson(d);
        interact("PUT", urlstr + "/" + v, "application/json", request);
    }
    private static void testRemove(String urlstr, int v) {
        interact("DELETE", urlstr + "/" + v, "application/json", null);
    }
    private static void test(String urlstr) {
        testGetOne(urlstr, 2);
        testGetAll(urlstr);
        testGetSome(urlstr, 2, 3);
        testAdd(urlstr, new Data(4, 4.4, "Text #4"));
        testGetAll(urlstr);
        testGetSome(urlstr, 2, 3);
        testUpdate(urlstr, 2);
        testGetAll(urlstr);
        testRemove(urlstr, 4);
        testGetAll(urlstr);
    }
    public static void main(String[] args) {
        test("http://localhost:8080/api/data");
        //test("http://localhost:8080/rest/api/data");
    }
}
$ set def [.client]
$ javac -cp .:/disk2/arne/jaxrs/gson-2_2_4.jar:/disk2/arne/jaxrs/jaxb-api-2_1.jar *.java
$ set def [-]
$ java -cp .:/disk2/arne/jaxrs/gson-2_2_4.jar:/disk2/arne/jaxrs/jaxb-api-2_1.jar "client.ClientMain"

Client in JVM script languages:

Groovy:

Groovy is as always just like Java just with less code.

import com.google.gson.reflect.*
import com.google.gson.*

class Data {
    int iv;
    double xv;
    String sv;
    @Override
    String toString() {
        return String.format("(%d,%f,%s)", iv, xv, sv)
    }
}

def interact(method, urlstr, typ, body) {
    con = (new URL(urlstr)).openConnection()
    con.setRequestMethod(method)
    con.addRequestProperty("accept",  typ)
    if(body != null) {
        con.addRequestProperty("content-type", typ)
        con.setDoOutput(true)
        con.outputStream.write(body.bytes)
    }
    sb = new StringBuilder()
    con.connect()
    if(con.responseCode.intdiv(100) == 2) {
        is = con.inputStream
        b = new byte[1000]
        while((n = is.read(b)) >= 0) {
            sb.append(new String(b, 0, n))
        }
        is.close()
    } else {
        println("Error: ${con.responseCode} ${con.responseMessage}")
    }
    con.disconnect()
    return sb.toString()
}

def testGetOne(urlstr, v) {
    response = interact("GET", urlstr + "/" + v, "application/json", null)
    gson = new Gson()
    d = gson.fromJson(response, Data.class)
    println(d)
}

def testGetAll(urlstr) {
    response = interact("GET", urlstr, "application/json", null)
    gson = new Gson()
    t = new TypeToken<TreeMap<String,ArrayList<Data>>>(){}.getType()
    result = gson.fromJson(response, t)
    println(result.get("data"))
}

def testGetSome(urlstr, start, finish) {
    response = interact("GET", urlstr + "?start=" + start + "&finish=" + finish, "application/json", null)
    gson = new Gson()
    t = new TypeToken<TreeMap<String,ArrayList<Data>>>(){}.getType()
    result = gson.fromJson(response, t)
    println(result.get("data"))
}

def testAdd(urlstr, d1) {
    gson = new Gson()
    request = gson.toJson(d1)
    response = interact("POST", urlstr + "/", "application/json", request)
    d2 = gson.fromJson(response, Data.class)
    println(d2)
}

def testUpdate(urlstr, v) {
    response = interact("GET", urlstr + "/" + v, "application/json", null)
    gson = new Gson()
    d = gson.fromJson(response, Data.class)
    d.xv = d.xv + 0.01
    d.sv = d.sv + " - updated"
    request = gson.toJson(d)
    interact("PUT", urlstr + "/" + v, "application/json", request)
}

def testRemove(urlstr, v) {
    interact("DELETE", urlstr + "/" + v, "application/json", null)
}

def test(urlstr) {
    testGetOne(urlstr, 2)
    testGetAll(urlstr)
    testGetSome(urlstr, 2, 3)
    testAdd(urlstr, new Data(iv: 4, xv: 4.4, sv: "Text #4"))
    testGetAll(urlstr)
    testGetSome(urlstr, 2, 3)
    testUpdate(urlstr, 2)
    testGetAll(urlstr)
    testRemove(urlstr, 4)
    testGetAll(urlstr)
}

test("http://localhost:8080/api/data")
//test("http://localhost:8080/rest/api/data")
$ groovy_cp = "/disk2/arne/jaxrs/gson-2_2_4.jar:/disk2/arne/jaxrs/jaxb-api-2_1.jar"
$ groovy client.groovy

Jython (JVM Python):

Python is a powerful language, but in this case it require some Java glue code to make everything work.

package client;

import javax.xml.bind.annotation.XmlRootElement;

@XmlRootElement
public class Data {
    private int iv;
    private double xv;
    private String sv;
    public Data() {
        this(0, 0.0, "");
    }
    public Data(int iv, double xv, String sv) {
        super();
        this.iv = iv;
        this.xv = xv;
        this.sv = sv;
    }
    public int getIv() {
        return iv;
    }
    public void setIv(int iv) {
        this.iv = iv;
    }
    public double getXv() {
        return xv;
    }
    public void setXv(double xv) {
        this.xv = xv;
    }
    public String getSv() {
        return sv;
    }
    public void setSv(String sv) {
        this.sv = sv;
    }
    @Override
    public String toString() {
        return String.format("(%d,%f,%s)",  iv, xv, sv);
    }
}
package client;

import java.util.ArrayList;
import java.util.TreeMap;
import java.lang.reflect.Type;

import com.google.gson.reflect.TypeToken;

public class DataList {
    public static Type getType() {
        return new TypeToken<TreeMap<String,ArrayList<Data>>>(){}.getType();
    }
}
from java.net import URL

from org.python.core.util import FileUtil

from com.google.gson.reflect import TypeToken
from com.google.gson import Gson

from client import Data, DataList

def interact(method, urlstr, typ, body):
    con = (URL(urlstr)).openConnection()
    con.setRequestMethod(method)
    con.addRequestProperty('accept',  typ)
    if body != None:
        con.addRequestProperty('content-type', typ)
        con.setDoOutput(True)
        fout = FileUtil.wrap(con.outputStream)
        fout.write(body)
        fout.flush()
    sb = ''
    con.connect()
    if con.responseCode // 100 == 2:
        fin = FileUtil.wrap(con.inputStream)
        for line in fin:
            sb = sb + line
        fin.close()
    else:
        print('Error: %d %s' % (con.responseCode, con.responseMessage))
    con.disconnect()
    return sb

def testGetOne(urlstr, v):
    response = interact('GET', urlstr + '/' + str(v), 'application/json', None)
    gson = Gson()
    d = gson.fromJson(response, Data)
    print(d)

def testGetAll(urlstr):
    response = interact('GET', urlstr, 'application/json', None)
    gson = Gson()
    result = gson.fromJson(response, DataList.getType())
    print(result.get('data'))

def testGetSome(urlstr, start, finish):
    response = interact('GET', urlstr + '?start=' + str(start) + '&finish=' + str(finish), 'application/json', None)
    gson = Gson()
    result = gson.fromJson(response, DataList.getType())
    print(result.get('data'))

def testAdd(urlstr, d1):
    gson = Gson()
    request = gson.toJson(d1)
    response = interact('POST', urlstr + '/', 'application/json', request)
    d2 = gson.fromJson(response, Data)
    print(d2)

def testUpdate(urlstr, v):
    response = interact('GET', urlstr + '/' + str(v), 'application/json', None)
    gson = Gson()
    d = gson.fromJson(response, Data)
    d.xv = d.xv + 0.01
    d.sv = d.sv + ' - updated'
    request = gson.toJson(d)
    interact('PUT', urlstr + '/' + str(v), 'application/json', request)

def testRemove(urlstr, v):
    interact('DELETE', urlstr + '/' + str(v), 'application/json', None)

def test(urlstr):
    testGetOne(urlstr, 2)
    testGetAll(urlstr)
    testGetSome(urlstr, 2, 3)
    testAdd(urlstr, Data(4, 4.4, 'Text #4'))
    testGetAll(urlstr)
    testGetSome(urlstr, 2, 3)
    testUpdate(urlstr, 2)
    testGetAll(urlstr)
    testRemove(urlstr, 4)
    testGetAll(urlstr)

test('http://localhost:8080/api/data')
#test('http://localhost:8080/rest/api/data')
$ define/nolog jython_libs "/disk2/arne/jaxrs/gson-2_2_4.jar:/disk2/arne/jaxrs/jaxb-api-2_1.jar"
$ jython xclient.py

Conclusion:

Exposing RESTful web services in Java is as easy on VMS as on any other platform.

Consuming RESTful web services in a JVM languages on VMS may require knowing a few tricks as on any other platform.

Article history:

Version Date Description
1.0 February 18th 2024 Initial version

Other articles:

See list of all articles here

Comments:

Please send comments to Arne Vajhøj