VMS Java web applications

Content:

  1. Introduction
  2. Context
  3. Example
  4. Setup
  5. Basic
  6. Security
  7. Other languages
  8. Integration

Introduction:

VMS web applications cover lots of options for VMS web applications.

This article will focus on some of the Java based options and provide more details and more variations.

Including some guidance on Tomcat configuration to utilize what Tomcat can provide out of the box.

To maximize understanding the reader should have:

but if not then no panic - most of this stuff especially the Java stuff is very well documented on the internet.

Context:

We will assume there is a need for a VMS web application and that we want something that:

Given that then we eliminate a lot of options:

That leaves two options on VMS:

  1. PHP with DIY MVC
  2. Java with DIY MVC

This article will focus on the Java option.

The Java option does come with some advantages.

Tomcat provide a lot more functionality out of the box than Apache and PHP.

Tomcat can support much higher volume than Apache and PHP on VMS.

It seems like Java and Tomcat are higher priority than PHP at VSI.

Also note that all or some of the code in a Java web application can be written in another JVM language - it does not need to be the Java language just because it is the Java platform.

Don't get me wrong - a Grails solution or a HTML 5 with Spring MVC backend solution are more modern and more advanced than a Java DIY MVC solution, but despite them being able to run on VMS then they are not very VMSish. The typical process for such advanced solutions would be that the VMS developer provide the backend code (Model) and then a specialized web developer will develop the frontend on a PC (the required tool chain is often not available on VMS) and give back a ZIP/WAR/JAR file to deploy on VMS. The simple solution can be made by the VMS developer and can be developed on VMS with EDT or EVE (I wrote most of the code for this article in EVE!).

Example:

We will use an example with a very simple address database.

We will support 3 functions:

In most cases the data will be stored in relational database.

The table looks like:

CREATE TABLE addrinfo (
    id INTEGER NOT NULL,
    name VARCHAR(32),
    address VARCHAR(128),
    town VARCHAR(32),
    PRIMARY KEY(id)
);

Test data:

INSERT INTO addrinfo VALUES (1, 'A A', '1 A Rd', 'A town');
INSERT INTO addrinfo VALUES (2, 'B B', '2 B Rd', 'B town');
INSERT INTO addrinfo VALUES (3, 'C C', '3 C Rd', 'C town');

The example code will focus on the web and Tomcat aspects. Error handling will just be printing the stack trace. In a real application that would need better handling.

Setup:

We need Java and Tomcat installed on the system. They can be downloaded from VSI.

(you can use a standard Tomcat if you are comfortable handling the install manually, but I will recommend to take the VSI kit that can be installed with PROD INSTALL)

I will assume a recent Tomcat that is installed standlone - not an old Tomcat that was integrated with Apache httpd in a weird way.

A quick micro-intro to Tomcat on VMS:

There are two ways to deploy a Java web application to Tomcat:

(if you already know about Java web applications, then you know all about war files, but if not then you can always use the first method)

Tomcat expose the web application as both of:

The web application will contain:

(the trick with [.WEB-INF...] is that Tomcat will never send any file from that subtree to the client)

The minimum [.WEB-INF]web.xml looks like:

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

Remember that jar files on VMS has to be STMLF not FIX512!

All JSP files and [.WEB-INF]web.xml must also be STMLF!

Small note about MVC. MVC is a very common way to structure web applications:

For more info about MVC see here.

Basic:

Let us start with a very basic JSP and Java example.

Files needed:

Files Content
[.WEB-INF.classes.demo]AddrInfo.class
[.WEB-INF.classes.demo]AddrInfoMgr.class
Model classes
show.jsp View JSP
[.WEB-INF.classes.demo]ShowServlet.class
[.WEB-INF.classes.demo]AddServlet.class
[.WEB-INF.classes.demo]RemoveServlet.class
Controller servlets
[.WEB-INF]web.xml config file (minimum for now)

URL is:

http://server:8080/demo/show

which will hit ShowServlet that will get data and forward to show.jsp!

Model:

AddrInfo.java:

package demo;

public class AddrInfo {
    private int id;
    private String name;
    private String address;
    private String town;
    public AddrInfo(int id, String name, String address, String town) {
        this.id = id;
        this.name = name;
        this.address = address;
        this.town = town;
    }
    public int getId() {
        return id;
    }
    public void setId(int id) {
        this.id = id;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public String getAddress() {
        return address;
    }
    public void setAddress(String address) {
        this.address = address;
    }
    public String getTown() {
        return town;
    }
    public void setTown(String town) {
        this.town = town;
    }
}

AddrInfoMgr.java:

package demo;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;

import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.sql.DataSource;

public class AddrInfoMgr {
    private static DataSource ds;
    static {
        try {
            Context initctx = new InitialContext();
            Context envctx  = (Context)initctx.lookup("java:/comp/env");
            ds = (DataSource)envctx.lookup("jdbc/TestDB");
        } catch(NamingException ex) {
            ex.printStackTrace();
        }
    }
    public List<AddrInfo> selectAll() {
        List<AddrInfo> res = new ArrayList<AddrInfo>();
        try {
            Connection con = ds.getConnection();
            PreparedStatement pstmt = con.prepareStatement("SELECT id,name,address,town FROM addrinfo");
            ResultSet rs = pstmt.executeQuery();
            while(rs.next()) {
                int id = rs.getInt(1);
                String name = rs.getString(2);
                String address = rs.getString(3);
                String town = rs.getString(4);
                AddrInfo o = new AddrInfo(id, name, address, town);
                res.add(o);
            }
            rs.close();
            pstmt.close();
            con.close();
        } catch(SQLException ex) {
            ex.printStackTrace();
        }
        return res;
    }
    public void insertOne(AddrInfo o) {
        try {
            Connection con = ds.getConnection();
            PreparedStatement pstmt = con.prepareStatement("INSERT INTO addrinfo VALUES(?,?,?,?)");
            pstmt.setInt(1, o.getId());
            pstmt.setString(2, o.getName());
            pstmt.setString(3, o.getAddress());
            pstmt.setString(4, o.getTown());
            pstmt.executeUpdate();
            pstmt.close();
            con.close();
        } catch(SQLException ex) {
            ex.printStackTrace();
        }
    }
    public void deleteOne(int id) {
        try {
            Connection con = ds.getConnection();
            PreparedStatement pstmt = con.prepareStatement("DELETE FROM addrinfo WHERE id = ?");
            pstmt.setInt(1,  id);
            pstmt.executeUpdate();
            pstmt.close();
            con.close();
        } catch(SQLException ex) {
            ex.printStackTrace();
        }
    }
}

We note that the AddrInfoMgr does not use DriverManager.getConnection to get a database connection, but instead asks Tomcat for a data source identified by the name "jdbc/TestDB".

This means that the web application is using a database connection pool. And that is good for performance. My experiements with Tomcat on VMS for an web application doing only basic CRUD with a remote database server on a fast network show a performance improvement of X6.

To define a MySQL database connection pool with 100 connections add something like this to tomcat$root:[conf]context.xml:

<Context>
    ...
    <Resource name="jdbc/TestDB"
              type="javax.sql.DataSource"
              driverClassName="com.mysql.cj.jdbc.Driver"
              url="jdbc:mysql://arnepc5/Test"
              username="arne"
              password="hemmeligt"
              maxIdle="100"
              maxTotal="100"/>
    ...
</Context>

View:

show.jsp:

<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<html>
<head>
<title>Address Information</title>
</head>
<body>
<h1>Address Information</body>
<h2>Show:</h2>
<table border="1">
<tr>
<th>Id</th>
<th>Name</th>
<th>Address</th>
<th>Town</th>
<th></th>
</tr>
<c:forEach items="${data}" var="o">
    <tr>
        <td><c:out value="${o.id}"/></td>
        <td><c:out value="${o.name}"/></td>
        <td><c:out value="${o.address}"/></td>
        <td><c:out value="${o.town}"/></td>
        <td><a href='remove?id=<c:out value="${o.id}"/>'>Remove</a></td>
    </tr>
</c:forEach>
</table>
<h2>Add:</h2>
<form method="post" action="add">
Id: <input type="text" name="id">
<br>
Name: <input type="text" name="name">
<br>
Address: <input type="text" name="address">
<br>
Town: <input type="text" name="town">
<br>
<input type="submit" value="Add">
</form>
</body>
</html>

The JSP does not use embedded Java code - instead it uses JSTL extensively. For more info on JSTL see here. JSTL may seem a little scary, but in reality you just need to know a handful of tags - here we will only use two c:out and c:forEach.

For JSTL support make sure that jstl.jar and standard.jar are in either tomcat$root:[lib] or [.WEB-INF.lib]. I tend to prefer the first, because having JSTL available for all web apps makes sense to me.

Controller:

ShowServlet.java:

package demo;

import java.io.IOException;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@WebServlet(urlPatterns={"/show"})
public class ShowServlet extends HttpServlet {
    private AddrInfoMgr datamgr = new AddrInfoMgr();
    @Override
    public void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        req.setAttribute("data", datamgr.selectAll());
        req.getRequestDispatcher("show.jsp").forward(req, resp);
    }
}

AddServlet.java:

package demo;

import java.io.IOException;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@WebServlet(urlPatterns={"/add"})
public class AddServlet extends HttpServlet {
    private AddrInfoMgr datamgr = new AddrInfoMgr();
    @Override
    public void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        int id = Integer.parseInt(req.getParameter("id"));
        String name = req.getParameter("name");
        String address = req.getParameter("address");
        String town = req.getParameter("town");
        AddrInfo o = new AddrInfo(id, name, address, town);
        datamgr.insertOne(o);
        resp.sendRedirect("show");
    }
}

RemoveServlet.java:

package demo;

import java.io.IOException;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@WebServlet(urlPatterns={"/remove"})
public class RemoveServlet extends HttpServlet {
    private AddrInfoMgr datamgr = new AddrInfoMgr();
    @Override
    public void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        int id = Integer.parseInt(req.getParameter("id"));
        datamgr.deleteOne(id);
        resp.sendRedirect("show");
    }
}

A little explanation of the mechanics for those not familiar with Java web applications:

Add and remove are relative similar - just note that they use resp.sendRedirect("show") which send a redirect to show back to client. They could also have done a forward, but then the add or remove would have been reapplied in case the user did a refresh!

Directory structure:

Directory TOMCAT$ROOT:[webapps.demo]

show.jsp;1
WEB-INF.DIR;1

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

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

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

demo.DIR;1

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

AddrInfo.class;1
AddrInfoMgr.class;1
AddServlet.class;1
RemoveServlet.class;1
ShowServlet.class;1

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

mysql-connector-j-8_0_33.jar;1

Security:

As soon as we are talking real world production then security becomes critical for web applications.

It is of course possible to hand code all sorts of security mechanisms, but you do not need to do that, because Java EE and Tomcat provide a lot of it out of the box and you just need to enable it.

In fact you should absolutely not hand code these security mechanisms as it is unlikely that your code will be as secure as Tomcat that has been tested extensively for many years.

Authentication and authorization:

A basic security requirement is to require users to login to use the web application.

This is really easy with Java EE.

One simple specify in [.WEB-INF]web.xml that login is required:

<web-app ...>
    ...
    <security-constraint>
        <web-resource-collection>
            <web-resource-name>Demo</web-resource-name>
            <url-pattern>/*</url-pattern>
        </web-resource-collection>
        <auth-constraint>
            <role-name>users</role-name>
        </auth-constraint>
    </security-constraint>
    ...
    <error-page>
        <error-code>403</error-code>
        <location>/noaccess.jsp</location>
    </error-page>
    ...
    <login-config>
        <auth-method>FORM</auth-method>
        <form-login-config>
            <form-login-page>/login.jsp</form-login-page>
            <form-error-page>/loginfail.jsp</form-error-page>
        </form-login-config>
    </login-config>
    ...
</web-app>

The first section means that all (/*) of this web application is protected and only available for authenticated users that has the role "users" (member of group "users").

The second section means that /noaccess.jsp will be displayed if access is denied.

The third section instructs Tomcat to use form based login (as oppposed to basic login - don't think too much about that as basic login has not been used the last 20 years) with /login.jsp as login form and /loginfail.jsp to be displayed if login fails.

login.jsp:

<html>
<head>
<title>Login</title>
</head>
<body>
<h1>Login</h1>
<h2>Please login:</h2>
<form action="j_security_check" method="POST">
Username: <input type="text" name="j_username">
<br>
Password: <input type="password" name="j_password">
<br>
<input type="submit" value="Login">
</form>
</body>
</html>

loginfail.jsp:

<html>
<head>
<title>Login failure</title>
</head>
<body>
<h1>Login failure</h1>
<h2>Error:</h2>
<p>
Login failed!
</p>
</body>
</html>

noaccess.jsp:

<html>
<head>
<title>No access</title>
</head>
<body>
<h1>No access</h1>
<h2>Error:</h2>
<p>
No access to content!
</p>
</body>
</html>

So the flow is:

  1. user request /show
  2. Tomcat notice that user is not logged in and go to login.jsp
  3. user give username and password to Tomcat (action j_security_check)
  4. Tomcat decide:

The builtin action j_security_check authenticates against an authenticator defined in the tomcat$root:[conf]server.xml file.

Tomcat can use different authenticators:

File based:

This one is really simple.

Modify tomcat$root:[conf]context.xml to have something like:

<Context>
    ...
    <Realm className="org.apache.catalina.realm.MemoryRealm"
           pathname="conf/tomcat-users.xml"/>
    ...
</Context>

tomcat$root:[conf]tomcat-users.xml:

<tomcat-users xmlns="http://tomcat.apache.org/xml"
              xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
              xsi:schemaLocation="http://tomcat.apache.org/xml tomcat-users.xsd"
              version="1.0">
   ...
   <user username="arne" password="hemmeligt" roles="admins,users"/>
   <user username="nobody" password="hemmeligt" roles="users"/>
   <role rolename="admins"/>
   <role rolename="users"/>
</tomcat-users>

Database based:

Defining users and roles (groups) in a flat file does not scale and having password retrievable is not secure.

It is common for web applications to store users in a database. And Tomcat also support that.

Modify tomcat$root:[conf]context.xml to have something like:

<Context>
    ...
    <Realm className="org.apache.catalina.realm.DataSourceRealm"
           dataSourceName="jdbc/TestDB"
           localDataSource="true"
           userTable="webusers"
           userRoleTable="webroles"
           userNameCol="usr"
           userCredCol="pwd"
           roleNameCol="role">
        <CredentialHandler className="org.apache.catalina.realm.MessageDigestCredentialHandler"
                           algorithm="sha-512"
                           iterations="10000"
                           saltLength="32"/>
    </Realm>
    ...
</Context>

And create the two database tables:

CREATE TABLE webusers (
    usr VARCHAR(20) NOT NULL,
    pwd VARCHAR(255)  NOT NULL,
    PRIMARY KEY(usr)
);
CREATE TABLE webroles (
    usr VARCHAR(20) NOT NULL,
    role VARCHAR(20)  NOT NULL,
    PRIMARY KEY(usr,role)
);
INSERT INTO webusers VALUES('arne', '15687a29b0f23277accaf3bcce5fd44191bdba4130413011a899a9258b9e6ca0$10000$cfd93c7fd19936e1a2123d33995ea495e97220505825826fb5a7964a14f4b3de09b252c17884dfeb15dad7ee2b8d7c9658860f9c6820c7dbb97b175f284d6621');
INSERT INTO webusers VALUES('nobody', '758381512ee021edb667d157c0a53f42bbb1c6f3ff3657e25d733b2b02711dac$10000$08af6bb3d815854c10ebdda0ddea375f24197b7ce4b3324aaeb51a4d43c977207f06fdb1dfcc23a4ac7a09ca723428973e0c60978139d1268795dfcbf051bef8');
INSERT INTO webroles VALUES('arne', 'users');
INSERT INTO webroles VALUES('arne', 'admins');
INSERT INTO webroles VALUES('nobody', 'users');

The passwords can be generated with:

import java.security.NoSuchAlgorithmException;

import org.apache.catalina.realm.MessageDigestCredentialHandler;

public class PwdGen1 {
    public static String gen(String algorithm, int iterations, int saltlen, String pwd) throws NoSuchAlgorithmException {
        MessageDigestCredentialHandler md = new MessageDigestCredentialHandler();
        md.setAlgorithm(algorithm);
        md.setIterations(iterations);
        md.setSaltLength(saltlen);
        return md.mutate(pwd);
    }
    public static void main(String[] args) throws Exception {
        System.out.println(gen("sha-512", 10000, 32, "hemmeligt"));
    }
}

But let us say that 10000 rounds of SHA-512 is not sufficient for you. Then use this instead.

<Context>
    ...
    <Realm className="org.apache.catalina.realm.DataSourceRealm"
           dataSourceName="jdbc/TestDB"
           localDataSource="true"
           userTable="webusers"
           userRoleTable="webroles"
           userNameCol="usr"
           userCredCol="pwd"
           roleNameCol="role">
        <CredentialHandler className="org.apache.catalina.realm.SecretKeyCredentialHandler"
                           algorithm="PBKDF2WithHmacSHA512"
                           iterations="200000"
                           saltLength="32"
                           keyLength="256"/>
    </Realm>
    ...
</Context>
CREATE TABLE webusers (
    usr VARCHAR(20) NOT NULL,
    pwd VARCHAR(255)  NOT NULL,
    PRIMARY KEY(usr)
);
CREATE TABLE webroles (
    usr VARCHAR(20) NOT NULL,
    role VARCHAR(20)  NOT NULL,
    PRIMARY KEY(usr,role)
);
INSERT INTO webusers VALUES('arne', 'cf2c2afc87d461fdfbe2d5e5c2b00d12bf9f41b6bc3502626af3ae65a48ba891$200000$1d4901d0f85c7c3b22bf97122c4e2f8c8389896abe63c0f1bb9562a955c6610d');
INSERT INTO webusers VALUES('nobody', '63b64fadee479452a9d8aebfcfebbc81e4704b4cbe0676b6d8db52c91e7ad888$200000$30d8aa401162d45cfc5c15c9fdde62d9cadebdbc6b805007fc60a4eb12088a99');
INSERT INTO webroles VALUES('arne', 'users');
INSERT INTO webroles VALUES('arne', 'admins');
INSERT INTO webroles VALUES('nobody', 'users');

The passwords can be generated with:

import java.security.NoSuchAlgorithmException;

import org.apache.catalina.realm.SecretKeyCredentialHandler;

public class PwdGen2 {
    public static String gen(String algorithm, int iterations, int saltlen, int keylen, String pwd) throws NoSuchAlgorithmException {
        SecretKeyCredentialHandler sk = new SecretKeyCredentialHandler();
        sk.setAlgorithm(algorithm);
        sk.setIterations(iterations);
        sk.setSaltLength(saltlen);
        sk.setKeyLength(keylen);
        return sk.mutate(pwd);
    }
    public static void main(String[] args) throws Exception {
        System.out.println(gen("PBKDF2WithHmacSHA512", 200000, 32, 256, "hemmeligt"));
    }
}

(adjust number iterations, salt length etc. to meet your security requirements)

Custom:

Let us try and authenticate against VMS.

First grab my VMSAUTH library.

Put vmsauth.jar in tomcat$root:[lib] and make VMSAuth_shr pointing to the JNI shareable image a system logical.

Add the following to tomcat$root:[lib]context.xml:

<Context>
    ...
    <Realm className="dk.vajhoej.vms.auth.HttpUafAuthenticator"/>
    ...
</Context>

And that is it!

All authenticated VMS users get the "users" role. Those with high privs (SYSPRV etc.) get the "admins" role.

Prevent brute force attacks:

To prevent brute force password guessing attacks just wrap the Realm in a LockOutRealm.

    <Realm className="org.apache.catalina.realm.LockOutRealm"
           failureCount="3"
           lockOutTime="60">
        <Realm .../>
    </Realm>

Note that LockOutRealm prevents password guessing attacks, but it also opens up for denial of service attacks.

Tomcat is handling all the standard stuff, but it is still possible to access the security information programmatically for more specific behavior.

showx.jsp:

<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<html>
<head>
<title>Address Information</title>
</head>
<body>
<h1>Address Information</body>
<h2>Welcome:</h2>
<p>
Welcome <c:out value="${u}"/> <c:out value="${g}"/>!
</p>
<h2>Show:</h2>
<table border="1">
<tr>
<th>Id</th>
<th>Name</th>
<th>Address</th>
<th>Town</th>
<th></th>
</tr>
<c:forEach items="${data}" var="o">
    <tr>
        <td><c:out value="${o.id}"/></td>
        <td><c:out value="${o.name}"/></td>
        <td><c:out value="${o.address}"/></td>
        <td><c:out value="${o.town}"/></td>
        <td><a href='remove?id=<c:out value="${o.id}"/>'>Remove</a></td>
    </tr>
</c:forEach>
</table>
<h2>Add:</h2>
<form method="post" action="add">
Id: <input type="text" name="id">
<br>
Name: <input type="text" name="name">
<br>
Address: <input type="text" name="address">
<br>
Town: <input type="text" name="town">
<br>
<input type="submit" value="Add">
</form>
</body>
</html>

ShowXServlet.java:

package demo;

import java.io.IOException;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@WebServlet(urlPatterns={"/showx"})
public class ShowXServlet extends HttpServlet {
    private AddrInfoMgr datamgr = new AddrInfoMgr();
    @Override
    public void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        if(req.getRemoteUser() != null) {
            req.setAttribute("u", req.getRemoteUser());
            req.setAttribute("g", String.format("(admins: %s, users: %s)", req.isUserInRole("admins"), req.isUserInRole("users")));
        } else {
            req.setAttribute("u", "unknown");
            req.setAttribute("g", "(no info)");
        }
        req.setAttribute("data", datamgr.selectAll());
        req.getRequestDispatcher("showx.jsp").forward(req,  resp);
    }
}

SSL:

Requiring login is no good if all the traffic is unencrypted plain HTTP.

We need support for HTTPS and a way to force usage of HTTPS.

This is easy with Java EE and Tomcat.

First enable HTTPS in Tomcat with the following fragment in tomcat$root:[conf]server.xml:

<Server ...>
    ...
    <Connector port="8443"
               protocol="org.apache.coyote.http11.Http11NioProtocol"
               maxThreads="150"
               SSLEnabled="true"
               maxParameterCount="1000">
        <SSLHostConfig>
            <Certificate certificateKeystoreFile="conf/selfsigned.jks"
                         type="RSA"
                         certificateKeystorePassword="hemmeligt"/>
        </SSLHostConfig>
    </Connector>
    ...
</Server>

The above use a self signed certificate in the old JKS format created with:

$ keytool -genkey -alias tomcat -keyalg RSA -keystore selfsigned.jks

Any serious usage obviously require a real certificate. Tomcat supports those fine. And getting a real certificate is a known and well documented process.

Second we define that the content of this web application can only be accessed via HTTPS (port 8443) not via HTTP (port 8080) in [.WEB-INF]web.xml:

<web-app ...>
    ...
    <security-constraint>
        ....
        <user-data-constraint>
            <transport-guarantee>CONFIDENTIAL</transport-guarantee>
        </user-data-constraint>
    </security-constraint>
    ...
</web-app>

Now Tomcat will automatically redirect any HTTP request to HTTPS. http://server:8080/demo/show will be redirected to https://server:8443/demo/show - obviously the user can also go directly to https://server:8443/demo/show, but the point is that HTTPS is not optional but mandatory.

Filter:

Sometimes a little extra is needed for security.

One way to implement that is to use a filter. A filter is a piece of code that get executed for all requests for the web application.

Let us see an example of an IP address filter that works with either whitelist or blacklist.

Add filter to [.WEB-INF]web.xml - either with whitelist:

<web-app ...>
    ...
    <filter>
        <filter-name>IPAddressFilter</filter-name>
        <filter-class>demo.IPAddressFilter</filter-class>
        <init-param>
            <param-name>whitelist</param-name>
            <param-value>192.168.0.99</param-value>
        </init-param>    
    </filter>
    <filter-mapping>
        <filter-name>IPAddressFilter</filter-name>
        <url-pattern>*</url-pattern>
    </filter-mapping>
    ...
</web-app>

or blacklist:

<web-app ...>
    ...
    <filter>
        <filter-name>IPAddressFilter</filter-name>
        <filter-class>demo.IPAddressFilter</filter-class>
        <init-param>
            <param-name>blacklist</param-name>
            <param-value>192.168.0.99</param-value>
        </init-param>
    </filter>
    <filter-mapping>
        <filter-name>IPAddressFilter</filter-name>
        <url-pattern>*</url-pattern>
    </filter-mapping>
    ...
</web-app>

IPAddressFilter.java:

package demo;

import java.io.IOException;
import java.util.Arrays;
import java.util.List;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class IPAddressFilter implements Filter {
    private List<String> whitelist;
    private List<String> blacklist;
    @Override
    public void init(FilterConfig conf) throws ServletException {
        String whitestr = conf.getInitParameter("whitelist");
        if(whitestr != null) {
            whitelist = Arrays.asList(whitestr.split(","));
        } else {
            whitelist = null;
        }
        String blackstr = conf.getInitParameter("blacklist");
        if(blackstr != null) {
            blacklist = Arrays.asList(blackstr.split(","));
        } else {
            blacklist = null;
        }
    }
    @Override
    public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException {
        HttpServletRequest httpreq = (HttpServletRequest)req;
        HttpServletResponse httpresp = (HttpServletResponse)resp;
        String thisaddr = req.getRemoteAddr();
        if((whitelist != null && !whitelist.contains(thisaddr)) ||
           (blacklist != null && blacklist.contains(thisaddr))) {
            httpresp.setStatus(HttpServletResponse.SC_FORBIDDEN);
        } else {
            chain.doFilter(httpreq, httpresp);
        }
    }
    @Override
    public void destroy() {
    }
}

Directory structure with all the security enhancements in place:

Directory TOMCAT$ROOT:[webapps.demo]

login.jsp;1
loginfail.jsp;1
noaccess.jsp;1
show.jsp;1
showx.jsp;1
WEB-INF.DIR;1

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

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

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

demo.DIR;1

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

AddrInfo.class;1
AddrInfoMgr.class;1
AddServlet.class;1
IPAddressFilter.class;1
RemoveServlet.class;1
ShowServlet.class;1
ShowXServlet.class;1

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

mysql-connector-j-8_0_33.jar;1

Other languages:

A Java web application does not really need to use Java. Most JVM languages can be used.

And often a different JVM language will require less lines than Java.

Especially JVM script languages require way less lines of code than Java. Sure many of us like static typed languages, but the fact is that for web development dynamic typed languages are dominating. The IT industry does not consider static typing necessary for web frontend code.

Kotlin:

The Kotlin solution is the same as the Java solution just with a sligthly different syntax resulting in fewer lines of code.

Model.kt:

package demo

import java.sql.*

import javax.naming.*
import javax.sql.*

data class AddrInfo(var id: Int, var name: String, var address: String, var town: String)

class AddrInfoMgr {
    companion object JNDI {
        var ds: DataSource
        init {
            val initctx: Context = InitialContext()
            val envctx: Context = initctx.lookup("java:/comp/env") as Context
            ds = envctx.lookup("jdbc/TestDB") as DataSource
        }
    }
    fun selectAll(): List<AddrInfo>  {
        val res: ArrayList<AddrInfo> = ArrayList<AddrInfo>()
        try {
            ds.getConnection().use { con: Connection ->
                con.prepareStatement("SELECT id,name,address,town FROM addrinfo").use { pstmt: PreparedStatement ->
                    pstmt.executeQuery().use { rs: ResultSet ->
                        while(rs.next()) {
                            val id: Int = rs.getInt(1)
                            val name: String = rs.getString(2)
                            val address: String = rs.getString(3)
                            val town: String = rs.getString(4)
                            val o: AddrInfo = AddrInfo(id, name, address, town)
                            res.add(o)
                        }
                    }
                }
            }
        } catch(ex: SQLException) {
            ex.printStackTrace()
        }
        return res
    }
    fun insertOne(o: AddrInfo): Unit {
        try {
            ds.getConnection().use { con: Connection ->
                con.prepareStatement("INSERT INTO addrinfo VALUES(?,?,?,?)").use { pstmt: PreparedStatement ->
                    pstmt.setInt(1, o.id)
                    pstmt.setString(2, o.name)
                    pstmt.setString(3, o.address)
                    pstmt.setString(4, o.town)
                    pstmt.executeUpdate()
                }
            }
        } catch(ex: SQLException) {
            ex.printStackTrace()
        }
    }
    fun deleteOne(id: Int): Unit {
        try {
            ds.getConnection().use { con: Connection ->
                con.prepareStatement("DELETE FROM addrinfo WHERE id = ?").use { pstmt: PreparedStatement ->
                    pstmt.setInt(1,  id)
                    pstmt.executeUpdate()
                }
            }
        } catch(ex: SQLException) {
            ex.printStackTrace()
        }
    }
}

Controller.kt:

package demo

import javax.servlet.*
import javax.servlet.annotation.*
import javax.servlet.http.*

@WebServlet(urlPatterns=["/show"])
class ShowServlet : HttpServlet() {
    val datamgr: AddrInfoMgr = AddrInfoMgr()
    override fun doGet(req: HttpServletRequest, resp: HttpServletResponse): Unit {
        req.setAttribute("data", datamgr.selectAll())
        req.getRequestDispatcher("show.jsp").forward(req,  resp)
    }
}

@WebServlet(urlPatterns=["/add"])
class AddServlet : HttpServlet() {
    val datamgr: AddrInfoMgr = AddrInfoMgr()
    override fun doPost(req: HttpServletRequest, resp: HttpServletResponse): Unit {
        val id: Int = Integer.parseInt(req.getParameter("id"))
        val name: String = req.getParameter("name")
        val address: String = req.getParameter("address")
        val town: String = req.getParameter("town")
        val o: AddrInfo = AddrInfo(id, name, address, town)
        datamgr.insertOne(o)
        resp.sendRedirect("show")
    }
}

@WebServlet(urlPatterns=["/remove"])
class RemoveServlet : HttpServlet() {
    val datamgr: AddrInfoMgr = AddrInfoMgr()
    override fun doGet(req: HttpServletRequest, resp: HttpServletResponse): Unit {
        val id: Int = Integer.parseInt(req.getParameter("id"))
        datamgr.deleteOne(id)
        resp.sendRedirect("show")
    }
}

URL is:

http://server:8080/demok/show

Directory structure with Kotlin:

Directory TOMCAT$ROOT:[webapps.demok]

show.jsp;1
WEB-INF.DIR;1

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

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

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

demo.DIR;1

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

AddrInfo.class;1
AddrInfoMgr$JNDI.class;1
AddrInfoMgr.class;1
AddServlet.class;1
RemoveServlet.class;1
ShowServlet.class;1

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

kotlin-stdlib.jar;1
mysql-connector-j-8_0_33.jar;1

Groovy:

Groovy supports using scripts for servlets. We will use the standard Java code for model.

show.jsp (the only difference compared to the Java version is that URL's end with .groovy):

<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<html>
<head>
<title>Address Information</title>
</head>
<body>
<h1>Address Information</body>
<h2>Show:</h2>
<table border="1">
<tr>
<th>Id</th>
<th>Name</th>
<th>Address</th>
<th>Town</th>
<th></th>
</tr>
<c:forEach items="${data}" var="o">
    <tr>
        <td><c:out value="${o.id}"/></td>
        <td><c:out value="${o.name}"/></td>
        <td><c:out value="${o.address}"/></td>
        <td><c:out value="${o.town}"/></td>
        <td><a href='remove.groovy?id=<c:out value="${o.id}"/>'>Remove</a></td>
    </tr>
</c:forEach>
</table>
<h2>Add:</h2>
<form method="post" action="add.groovy">
Id: <input type="text" name="id">
<br>
Name: <input type="text" name="name">
<br>
Address: <input type="text" name="address">
<br>
Town: <input type="text" name="town">
<br>
<input type="submit" value="Add">
</form>
</body>
</html>

show.groovy:

import demo.*

datamgr = new AddrInfoMgr()
request.setAttribute("data", datamgr.selectAll())
request.getRequestDispatcher("show.jsp").forward(request,  response)

add.groovy:

import demo.*

datamgr = new AddrInfoMgr()
id = Integer.parseInt(request.getParameter("id"))
name = request.getParameter("name")
address = request.getParameter("address")
town = request.getParameter("town")
o = new AddrInfo(id, name, address, town)
datamgr.insertOne(o)
response.sendRedirect("show.groovy")

remove.groovy:

import demo.*

datamgr = new AddrInfoMgr()
id = Integer.parseInt(request.getParameter("id"))
datamgr.deleteOne(id)
response.sendRedirect("show.groovy")

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">
    <servlet>
        <servlet-name>GroovyServlet</servlet-name>
        <servlet-class>groovy.servlet.GroovyServlet</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>GroovyServlet</servlet-name>
        <url-pattern>*.groovy</url-pattern>
    </servlet-mapping>
</web-app>

URL is:

http://server:8080/demog/show.groovy

Directory structure with Groovy:

Directory TOMCAT$ROOT:[webapps.demog]

add.groovy;1
remove.groovy;1
show.groovy;1
show.jsp;1
WEB-INF.DIR;1

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

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

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

demo.DIR;1

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

AddrInfo.class;1
AddrInfoMgr.class;1

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

groovy-4_0_12.jar;1
groovy-json-4_0_12.jar;1
groovy-servlet-4_0_12.jar;1
groovy-xml-4_0_12.jar;1
mysql-connector-j-8_0_33.jar;1

Jython:

Jython supports using scripts for servlets. We will use the standard Java code for model.

show.jsp (the only difference compared to the Java version is that URL's end with .py):

<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<html>
<head>
<title>Address Information</title>
</head>
<body>
<h1>Address Information</body>
<h2>Show:</h2>
<table border="1">
<tr>
<th>Id</th>
<th>Name</th>
<th>Address</th>
<th>Town</th>
<th></th>
</tr>
<c:forEach items="${data}" var="o">
    <tr>
        <td><c:out value="${o.id}"/></td>
        <td><c:out value="${o.name}"/></td>
        <td><c:out value="${o.address}"/></td>
        <td><c:out value="${o.town}"/></td>
        <td><a href='remove.py?id=<c:out value="${o.id}"/>'>Remove</a></td>
    </tr>
</c:forEach>
</table>
<h2>Add:</h2>
<form method="post" action="add.py">
Id: <input type="text" name="id">
<br>
Name: <input type="text" name="name">
<br>
Address: <input type="text" name="address">
<br>
Town: <input type="text" name="town">
<br>
<input type="submit" value="Add">
</form>
</body>
</html>

show.py:

from javax.servlet.http import HttpServlet

from demo import AddrInfoMgr

class show(HttpServlet):
    def __init__(self):
        self.datamgr = AddrInfoMgr()
    def doGet(self, request, response):
        request.setAttribute("data", self.datamgr.selectAll())
        request.getRequestDispatcher("show.jsp").forward(request, response)

add.py:

from javax.servlet.http import HttpServlet

from demo import AddrInfoMgr, AddrInfo

class add(HttpServlet):
    def __init__(self):
        self.datamgr = AddrInfoMgr()
    def doPost(self, request, response):
        id = int(request.getParameter("id"))
        name = request.getParameter("name")
        address = request.getParameter("address")
        town = request.getParameter("town")
        o = AddrInfo(id, name, address, town)
        self.datamgr.insertOne(o)
        response.sendRedirect("show.py")

remove.py:

from javax.servlet.http import HttpServlet

from demo import AddrInfoMgr

class remove(HttpServlet):
    def __init__(self):
        self.datamgr = AddrInfoMgr()
    def doGet(self, request, response):
        id = int(request.getParameter("id"))
        self.datamgr.deleteOne(id)
        response.sendRedirect("show.py")

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">
    <servlet>
        <servlet-name>PyServlet</servlet-name>
        <servlet-class>org.python.util.PyServlet</servlet-class>
        <load-on-startup>1</load-on-startup>
        <init-param>
            <param-name>python.home</param-name>
            <param-value>.</param-value>
        </init-param>
        <init-param>
            <param-name>python.path</param-name>
            <param-value>/tomcat$root/webapps/demop/WEB-INF/classes</param-value>
        </init-param>
    </servlet>
    <servlet-mapping>
        <servlet-name>PyServlet</servlet-name>
        <url-pattern>*.py</url-pattern>
    </servlet-mapping>
</web-app>

URL is:

http://server:8080/demop/show.py

Directory structure with Jython:

add.py;1
remove.py;1
show.jsp;1
show.py;1
WEB-INF.DIR;1

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

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

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

demo.DIR;1

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

AddrInfo.class;1
AddrInfoMgr.class;1

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

jython-standalone-2_7_3.jar;1
mysql-connector-j-8_0_33.jar;1

Note that Jython is Python 2.7 not Python 3.x, which somewhat limits its usability.

PHP:

There exist a PHP implementation for JVM called Quercus.

It is intended to be used as a PHP implementation not just to allow controllers in a Java web app to be written in a script language.

So Quercus can be used in different ways with Tomcat.

PHP as controller script language (only C):

This is similar to the Groovy and Jython example.

show.jsp (the only difference compared to the Java version is that URL's end with .php):

<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<html>
<head>
<title>Address Information</title>
</head>
<body>
<h1>Address Information</body>
<h2>Show:</h2>
<table border="1">
<tr>
<th>Id</th>
<th>Name</th>
<th>Address</th>
<th>Town</th>
<th></th>
</tr>
<c:forEach items="${data}" var="o">
    <tr>
        <td><c:out value="${o.id}"/></td>
        <td><c:out value="${o.name}"/></td>
        <td><c:out value="${o.address}"/></td>
        <td><c:out value="${o.town}"/></td>
        <td><a href='remove.php?id=<c:out value="${o.id}"/>'>Remove</a></td>
    </tr>
</c:forEach>
</table>
<h2>Add:</h2>
<form method="post" action="add.php">
Id: <input type="text" name="id">
<br>
Name: <input type="text" name="name">
<br>
Address: <input type="text" name="address">
<br>
Town: <input type="text" name="town">
<br>
<input type="submit" value="Add">
</form>
</body>
</html>

show.php:

<?php
import demo.AddrInfoMgr;

$request = quercus_servlet_request();
$response = quercus_servlet_response();
$datamgr = new AddrInfoMgr();
$request->setAttribute('data', $datamgr->selectAll());
$response->reset(); // necessary hack
$request->getRequestDispatcher('show.jsp')->forward($request, $response);

?>

(note the reset call - it is required)

add.php:

<?php
import demo.AddrInfo;
import demo.AddrInfoMgr;

$response = quercus_servlet_response();
$datamgr = new AddrInfoMgr();
$id = (int)$_POST['id'];
$name = $_POST['name'];
$address = $_POST['address'];
$town = $_POST['town'];
$o = new AddrInfo($id, $name, $address, $town);
$datamgr->insertOne($o);
$response->sendRedirect('show.php');

?>

(note the use of $_POST['xx'] instead of $request->getParameter('xx') - this is required)

remove.php:

<?php
import demo.AddrInfoMgr;

$response = quercus_servlet_response();
$datamgr = new AddrInfoMgr();
$id = (int)$_GET['id'];
$datamgr->deleteOne($id);
$response->sendRedirect('show.php');

?>

(note the use of $_GET['xx'] instead of $request->getParameter('xx') - this is required)

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">
    <servlet>
        <servlet-name>QuercusServlet</servlet-name>
        <servlet-class>com.caucho.quercus.servlet.QuercusServlet</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>QuercusServlet</servlet-name>
        <url-pattern>*.php</url-pattern>
    </servlet-mapping>
</web-app>

URL is:

http://server:8080/demo_php/show.php

Directory structure:

Directory TOMCAT$ROOT:[webapps.demo_php]

add.php;1
remove.php;1
show.jsp;1
show.php;1
WEB-INF.DIR;1

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

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

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

demo.DIR;1

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

AddrInfo.class;1
AddrInfoMgr.class;1

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

mysql-connector-j-8_0_33.jar;1
quercus-4_0_66.jar;1

PHP for entire frontend (V + C):

realshow.php:

<html>
<head>
<title>Address Information</title>
</head>
<body>
<h1>Address Information</body>
<h2>Show:</h2>
<table border="1">
<tr>
<th>Id</th>
<th>Name</th>
<th>Address</th>
<th>Town</th>
<th></th>
</tr>
<?php
foreach($data as $o) {
?>
    <tr>
        <td><?php echo "{$o->id}";?></td>
        <td><?php echo "{$o->name}";?></td>
        <td><?php echo "{$o->address}";?></td>
        <td><?php echo "{$o->town}";?></td>
        <td><a href='remove.php?id=<?php echo "{$o->id}";?>'>Remove</a></td>
    </tr>
<?php
}
?>
</table>
<h2>Add:</h2>
<form method="post" action="add.php">
Id: <input type="text" name="id">
<br>
Name: <input type="text" name="name">
<br>
Address: <input type="text" name="address">
<br>
Town: <input type="text" name="town">
<br>
<input type="submit" value="Add">
</form>
</body>
</html>

show.php:

<?php
import demo.AddrInfoMgr;

$datamgr = new AddrInfoMgr();
$data = $datamgr->selectAll();
include 'realshow.php';

?>

add.php:

<?php
import demo.AddrInfo;
import demo.AddrInfoMgr;

$datamgr = new AddrInfoMgr();
$id = (int)$_POST['id'];
$name = $_POST['name'];
$address = $_POST['address'];
$town = $_POST['town'];
$o = new AddrInfo($id, $name, $address, $town);
$datamgr->insertOne($o);
header('Location: show.php');

?>

remove.php:

<?php
import demo.AddrInfoMgr;

$datamgr = new AddrInfoMgr();
$id = (int)$_GET['id'];
$datamgr->deleteOne($id);
header('Location: show.php');

?>

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">
    <servlet>
        <servlet-name>QuercusServlet</servlet-name>
        <servlet-class>com.caucho.quercus.servlet.QuercusServlet</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>QuercusServlet</servlet-name>
        <url-pattern>*.php</url-pattern>
    </servlet-mapping>
</web-app>

URL is:

http://server:8080/demo_php2/show.php

Directory structure:

Directory TOMCAT$ROOT:[webapps.demo_php2]

add.php;1
realshow.php;1
remove.php;1
show.php;1
WEB-INF.DIR;1

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

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

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

demo.DIR;1

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

AddrInfo.class;1
AddrInfoMgr.class;1

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

mysql-connector-j-8_0_33.jar;1
quercus-4_0_66.jar;1

PHP everything (M + V + C):

AddrInfo.php:

<?php

class AddrInfo {
    public $id;
    public $name;
    public $address;
    public $town;
    public function __construct($id, $name, $address, $town) {
        $this->id = $id;
        $this->name = $name;
        $this->address = $address;
        $this->town = $town;
    }
}

?>

AddrInfoMgr.php:

<?php

class AddrInfoMgr {
    public function selectAll() {
        $res = array();
        $con = new PDO('java:comp/env/jdbc/TestDB');
        $stmt = $con->prepare('SELECT id,name,address,town FROM addrinfo');
        $stmt->execute(array());
        while($row = $stmt->fetch()) {
            $res[] = new AddrInfo($row['id'], $row['name'], $row['address'], $row['town']);
        }
        return $res;
    }
    public function insertOne($o) {
        $con = new PDO('java:comp/env/jdbc/TestDB');
        $stmt = $con->prepare('INSERT INTO addrinfo VALUES(:id,:name,:address,:town)');
        $stmt->execute(array(':id' => $o->id, ':name' => $o->name, ':address' => $o->address, ':town' => $o->town));
    }
    public function deleteOne($id) {
        $con = new PDO('java:comp/env/jdbc/TestDB');
        $stmt = $con->prepare('DELETE FROM addrinfo WHERE id = :id');
        $stmt->execute(array(':id' => $id));
    }
}

?>

(note that PHP Quercus PDO can fine use the Tomcat database connection pool)

realshow.php:

<html>
<head>
<title>Address Information</title>
</head>
<body>
<h1>Address Information</body>
<h2>Show:</h2>
<table border="1">
<tr>
<th>Id</th>
<th>Name</th>
<th>Address</th>
<th>Town</th>
<th></th>
</tr>
<?php
foreach($data as $o) {
?>
    <tr>
        <td><?php echo "{$o->id}";?></td>
        <td><?php echo "{$o->name}";?></td>
        <td><?php echo "{$o->address}";?></td>
        <td><?php echo "{$o->town}";?></td>
        <td><a href='remove.php?id=<?php echo "{$o->id}";?>'>Remove</a></td>
    </tr>
<?php
}
?>
</table>
<h2>Add:</h2>
<form method="post" action="add.php">
Id: <input type="text" name="id">
<br>
Name: <input type="text" name="name">
<br>
Address: <input type="text" name="address">
<br>
Town: <input type="text" name="town">
<br>
<input type="submit" value="Add">
</form>
</body>
</html>

show.php:

<?php
spl_autoload_register(function ($clznam) {
    include $clznam . '.php';
});

$datamgr = new AddrInfoMgr();
$data = $datamgr->selectAll();
include 'realshow.php';

?>

add.php:

<?php
spl_autoload_register(function ($clznam) {
    include $clznam . '.php';
});

$datamgr = new AddrInfoMgr();
$id = (int)$_POST['id'];
$name = $_POST['name'];
$address = $_POST['address'];
$town = $_POST['town'];
$o = new AddrInfo($id, $name, $address, $town);
$datamgr->insertOne($o);
header('Location: show.php');

?>

remove.php:

<?php
spl_autoload_register(function ($clznam) {
    include $clznam . '.php';
});

$datamgr = new AddrInfoMgr();
$id = (int)$_GET['id'];
$datamgr->deleteOne($id);
header('Location: show.php');

?>

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">
    <servlet>
        <servlet-name>QuercusServlet</servlet-name>
        <servlet-class>com.caucho.quercus.servlet.QuercusServlet</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>QuercusServlet</servlet-name>
        <url-pattern>*.php</url-pattern>
    </servlet-mapping>
</web-app>

URL is:

http://server:8080/demo_php2x/show.php

Directory structure:

Directory TOMCAT$ROOT:[webapps.demo_php2x]

add.php;1
AddrInfo.php;1
AddrInfoMgr.php;1
realshow.php;1
remove.php;1
show.php;1
WEB-INF.DIR;1

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

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

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

demo.DIR;1

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

mysql-connector-j-8_0_33.jar;1
quercus-4_0_66.jar;1

Note that Quercus is PHP 5.x not PHP 7.x/8.x, which somewhat limits its usability.

JSR 223:

JSR 223 is not a JVM language. JSR 223 is a standard for embedding script engine into a Java application. It allows you to embed any JSR 223 compliant script language into a Java application.

A Java web application is a Java application so it is possible to use JSR 223. This makes it possible to do something really cool.

First we need a generic JSR 223 servlet.

JSR223HttpServlet.java:

package demo;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;

import javax.script.ScriptContext;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;
import javax.script.SimpleScriptContext;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class JSR223HttpServlet extends HttpServlet {
    @Override
    public void service(HttpServletRequest request, HttpServletResponse response) throws IOException {
        ScriptEngineManager sem = new ScriptEngineManager();
        ScriptEngine se = sem.getEngineByName(getServletConfig().getInitParameter("lang"));
        ScriptContext ctx = new SimpleScriptContext();
        ctx.setAttribute("request", request, ScriptContext.ENGINE_SCOPE);
        ctx.setAttribute("response", response, ScriptContext.ENGINE_SCOPE);
        String fnm = getServletContext().getRealPath(request.getServletPath());
        String source = new String(Files.readAllBytes(Paths.get(fnm)));
        try {
            se.eval(source, ctx);
        } catch (ScriptException e) {
            e.printStackTrace();
        }
    }
}

And a small change to the view to be able to handle multiple extensions.

show.jsp:

<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<html>
<head>
<title>Address Information</title>
</head>
<body>
<h1>Address Information</body>
<h2>Show:</h2>
<table border="1">
<tr>
<th>Id</th>
<th>Name</th>
<th>Address</th>
<th>Town</th>
<th></th>
</tr>
<c:forEach items="${data}" var="o">
    <tr>
        <td><c:out value="${o.id}"/></td>
        <td><c:out value="${o.name}"/></td>
        <td><c:out value="${o.address}"/></td>
        <td><c:out value="${o.town}"/></td>
        <td><a href='remove.<c:out value="${ext}"/>?id=<c:out value="${o.id}"/>'>Remove</a></td>
    </tr>
</c:forEach>
</table>
<h2>Add:</h2>
<form method='post' action='add.<c:out value="${ext}"/>'>
Id: <input type='text' name='id'>
<br>
Name: <input type='text' name='name'>
<br>
Address: <input type='text' name='address'>
<br>
Town: <input type='text' name='town'>
<br>
<input type='submit' value='Add'>
</form>
</body>
</html>

And now we are ready to implement controllers in lots of different script languages.

show.groovy:

import demo.*

datamgr = new AddrInfoMgr()
request.setAttribute("data", datamgr.selectAll())
request.setAttribute("ext", "groovy")
request.getRequestDispatcher("show.jsp").forward(request, response)

add.groovy:

import demo.*

datamgr = new AddrInfoMgr()
id = Integer.parseInt(request.getParameter("id"))
name = request.getParameter("name")
address = request.getParameter("address")
town = request.getParameter("town")
o = new AddrInfo(id, name, address, town)
datamgr.insertOne(o)
response.sendRedirect("show.groovy")

remove.groovy:

import demo.*

datamgr = new AddrInfoMgr()
id = Integer.parseInt(request.getParameter("id"))
datamgr.deleteOne(id)
response.sendRedirect("show.groovy")

URL is:

http://server:8080/demo_jsr223/show.groovy

show.py:

from demo import AddrInfoMgr

datamgr = AddrInfoMgr()
request.setAttribute("data", datamgr.selectAll())
request.setAttribute("ext", "py")
request.getRequestDispatcher("show.jsp").forward(request, response)

add.py:

from demo import AddrInfoMgr, AddrInfo

datamgr = AddrInfoMgr()
id = int(request.getParameter("id"))
name = request.getParameter("name")
address = request.getParameter("address")
town = request.getParameter("town")
o = AddrInfo(id, name, address, town)
datamgr.insertOne(o)
response.sendRedirect("show.py")

remove.py:

from demo import AddrInfoMgr

datamgr = AddrInfoMgr()
id = int(request.getParameter("id"))
datamgr.deleteOne(id)
response.sendRedirect("show.py")

URL is:

http://server:8080/demo_jsr223/show.py

show.php:

<?php
import demo.AddrInfoMgr;

$datamgr = new AddrInfoMgr();
$request->setAttribute('data', $datamgr->selectAll());
$request->setAttribute('ext', 'php');
$response->reset(); // necessary hack
$request->getRequestDispatcher('show.jsp')->forward($request, $response);

?>

add.php:

<?php
import demo.AddrInfo;
import demo.AddrInfoMgr;

$datamgr = new AddrInfoMgr();
$id = (int)$request->getParameter('id');
$name = $request->getParameter('name');
$address = $request->getParameter('address');
$town = $request->getParameter('town');
$o = new AddrInfo($id, $name, $address, $town);
$datamgr->insertOne($o);
$response->sendRedirect('show.php');

?>

remove.php:

<?php
import demo.AddrInfoMgr;

$datamgr = new AddrInfoMgr();
$id = (int)$request->getParameter('id');
$datamgr->deleteOne($id);
$response->sendRedirect('show.php');

?>

URL is:

http://server:8080/demo_jsr223/show.php

show.js:

var AddrInfoMgr = Java.type("demo.AddrInfoMgr");

var datamgr = new AddrInfoMgr();
request.setAttribute("data", datamgr.selectAll());
request.setAttribute("ext", "js");
request.getRequestDispatcher("show.jsp").forward(request, response);

add.js:

var AddrInfoMgr = Java.type("demo.AddrInfoMgr");
var AddrInfo = Java.type("demo.AddrInfo");

var datamgr = new AddrInfoMgr();
var id = parseInt(request.getParameter("id"), 10);
var name = request.getParameter("name");
var address = request.getParameter("address");
var town = request.getParameter("town");
var o = new AddrInfo(id, name, address, town);
datamgr.insertOne(o);
response.sendRedirect("show.js");

remove.js:

var AddrInfoMgr = Java.type("demo.AddrInfoMgr");

var datamgr = new AddrInfoMgr();
var id = parseInt(request.getParameter("id"), 10);
datamgr.deleteOne(id);
response.sendRedirect("show.js");

URL is:

http://server:8080/demo_jsr223/show.js

show.rb:

java_import 'demo.AddrInfoMgr'

datamgr = AddrInfoMgr.new
request.setAttribute('data', datamgr.selectAll())
request.setAttribute('ext', 'rb')
request.getRequestDispatcher('show.jsp').forward(request, response)

add.rb:

java_import 'demo.AddrInfoMgr'
java_import 'demo.AddrInfo'

datamgr = AddrInfoMgr.new
id = request.getParameter('id').to_i
name = request.getParameter('name')
address = request.getParameter('address')
town = request.getParameter('town')
o = AddrInfo.new(id, name, address, town)
datamgr.insertOne(o)
response.sendRedirect('show.rb')

remove.rb:

java_import 'demo.AddrInfoMgr'

datamgr = AddrInfoMgr.new
id = request.getParameter('id').to_i
datamgr.deleteOne(id)
response.sendRedirect('show.rb')

URL is:

http://server:8080/demo_jsr223/show.rb

show.bsh:

import demo.AddrInfoMgr;

AddrInfoMgr datamgr = new AddrInfoMgr();
request.setAttribute("data", datamgr.selectAll());
request.setAttribute("ext", "bsh");
request.getRequestDispatcher("show.jsp").forward(request, response);

add.bsh:

import demo.AddrInfoMgr;
import demo.AddrInfo;

AddrInfoMgr datamgr = new AddrInfoMgr();
int id = Integer.parseInt(request.getParameter("id"));
String name = request.getParameter("name");
String address = request.getParameter("address");
String town = request.getParameter("town");
AddrInfo o = new AddrInfo(id, name, address, town);
datamgr.insertOne(o);
response.sendRedirect("show.bsh");

remove.bsh:

import demo.AddrInfoMgr;

AddrInfoMgr datamgr = new AddrInfoMgr();
int id = Integer.parseInt(request.getParameter("id"));
datamgr.deleteOne(id);
response.sendRedirect("show.bsh");

URL is:

http://server:8080/demo_jsr223/show.bsh

Now we just need to instantiate multiple servlets for the different languages.

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">
    <servlet>
        <servlet-name>GroovyServlet</servlet-name>
        <servlet-class>demo.JSR223HttpServlet</servlet-class>
        <init-param>
            <param-name>lang</param-name>
            <param-value>groovy</param-value>
        </init-param>
    </servlet>
    <servlet>
        <servlet-name>JythonServlet</servlet-name>
        <servlet-class>demo.JSR223HttpServlet</servlet-class>
        <init-param>
            <param-name>lang</param-name>
            <param-value>python</param-value>
        </init-param>
    </servlet>
    <servlet>
        <servlet-name>QuercusServlet</servlet-name>
        <servlet-class>demo.JSR223HttpServlet</servlet-class>
        <init-param>
            <param-name>lang</param-name>
            <param-value>php</param-value>
        </init-param>
    </servlet>
    <servlet>
        <servlet-name>NashornServlet</servlet-name>
        <servlet-class>demo.JSR223HttpServlet</servlet-class>
        <init-param>
            <param-name>lang</param-name>
            <param-value>javascript</param-value>
        </init-param>
    </servlet>
    <servlet>
        <servlet-name>JRubyServlet</servlet-name>
        <servlet-class>demo.JSR223HttpServlet</servlet-class>
        <init-param>
            <param-name>lang</param-name>
            <param-value>ruby</param-value>
        </init-param>
    </servlet>
    <servlet>
        <servlet-name>BeanShellServlet</servlet-name>
        <servlet-class>demo.JSR223HttpServlet</servlet-class>
        <init-param>
            <param-name>lang</param-name>
            <param-value>beanshell</param-value>
        </init-param>
    </servlet>
    <servlet-mapping>
        <servlet-name>GroovyServlet</servlet-name>
        <url-pattern>*.groovy</url-pattern>
    </servlet-mapping>
    <servlet-mapping>
        <servlet-name>JythonServlet</servlet-name>
        <url-pattern>*.py</url-pattern>
    </servlet-mapping>
    <servlet-mapping>
        <servlet-name>QuercusServlet</servlet-name>
        <url-pattern>*.php</url-pattern>
    </servlet-mapping>
    <servlet-mapping>
        <servlet-name>NashornServlet</servlet-name>
        <url-pattern>*.js</url-pattern>
    </servlet-mapping>
    <servlet-mapping>
        <servlet-name>JRubyServlet</servlet-name>
        <url-pattern>*.rb</url-pattern>
    </servlet-mapping>
    <servlet-mapping>
        <servlet-name>BeanShellServlet</servlet-name>
        <url-pattern>*.bsh</url-pattern>
    </servlet-mapping>
</web-app>

Directory structure:

Directory TOMCAT$ROOT:[webapps.demo_jsr223]

add.bsh;1
add.groovy;1
add.js;1
add.php;1
add.py;1
add.rb;1
remove.bsh;1
remove.groovy;1
remove.js;1
remove.php;1
remove.py;1
remove.rb;1
show.bsh;1
show.groovy;1
show.js;1
show.jsp;1
show.php;1
show.py;1
show.rb;1
WEB-INF.DIR;1

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

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

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

demo.DIR;1

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

AddrInfo.class;1
AddrInfoMgr.class;1
JSR223HttpServlet.class;1

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

bsh-2_1_1.jar;1
groovy-4_0_12.jar;1
groovy-jsr223-4_0_12.jar;1
jruby.jar;1
jython-standalone-2_7_3.jar;1
mysql-connector-j-8_0_33.jar;1
quercus-4_0_66.jar;1
rhino-1_7_14.jar;1

This stuff is pretty impressive, but in the end I am not sure that it will ever make it into a production environment. The options in the previous sections seems to cover most.

Conclusion:

Let us compare lines of code:

Java Kotlin Groovy Jython PHP (C) PHP (V + C) PHP (M + V + C) JSR 223
Model 113 68 (same as Java) (same as Java) (same as Java) (same as Java) 42 (same as Java)
View 40 (same as Java) (same as Java) (same as Java) (same as Java) 43 43 (same as Java)
Controller 63 38 21 39 36 31 36 22-23 (except PHP at 33)

The use of JVM script languages dramatically reduce lines of code in controller layer.

Note that all the features of Tomcat described previously regarding security applies exactly the same no matter whether the language used for servlets are Java, Kotlin, Groovy, Jython or PHP.

Integration:

Database:

Java has multiple API's for database access. The two most widely used API's for RDBMS access are:

JDBC:

JDBC is what is used in previous sections so no need to show that again.

JPA:

Let us implement the model using JPA.

We just need to replace the model classes.

AddrInfo.java:

package demo;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;

@Entity
@Table(name="addrinfo")
public class AddrInfo {
    private int id;
    private String name;
    private String address;
    private String town;
    public AddrInfo() {
        this(0, "", "", "");
    }
    public AddrInfo(int id, String name, String address, String town) {
        this.id = id;
        this.name = name;
        this.address = address;
        this.town = town;
    }
    @Id
    @Column(name="id")
    public int getId() {
        return id;
    }
    public void setId(int id) {
        this.id = id;
    }
    @Column(name="name")
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    @Column(name="address")
    public String getAddress() {
        return address;
    }
    public void setAddress(String address) {
        this.address = address;
    }
    @Column(name="town")
    public String getTown() {
        return town;
    }
    public void setTown(String town) {
        this.town = town;
    }
}

AddrInfoMgr.java:

package demo;

import java.util.List;

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.Persistence;
import javax.persistence.TypedQuery;

public class AddrInfoMgr {
    public List<AddrInfo> selectAll() {
        EntityManagerFactory emf = Persistence.createEntityManagerFactory("demo");
        EntityManager em = emf.createEntityManager();
        TypedQuery<AddrInfo> q = em.createQuery("SELECT o FROM AddrInfo AS o", AddrInfo.class);
        List<AddrInfo> res = q.getResultList();
        em.close();
        return res;
    }
    public void insertOne(AddrInfo o) {
        EntityManagerFactory emf = Persistence.createEntityManagerFactory("demo");
        EntityManager em = emf.createEntityManager();
        em.getTransaction().begin();
        em.persist(o);
        em.getTransaction().commit();
        em.close();
    }
    public void deleteOne(int id) {
        EntityManagerFactory emf = Persistence.createEntityManagerFactory("demo");
        EntityManager em = emf.createEntityManager();
        em.getTransaction().begin();
        em.remove(em.merge(new AddrInfo(id, "", "", "")));
        em.getTransaction().commit();
        em.close();
    }
}

[.WEB-INF.classes.META-INF]persistence.xml:

<persistence xmlns="http://java.sun.com/xml/ns/persistence"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd"
             version="2.0">
   <persistence-unit name="demo">
      <provider>org.hibernate.ejb.HibernatePersistence</provider>
      <non-jta-data-source>java:comp/env/jdbc/TestDB</non-jta-data-source>
      <class>demo.AddrInfo</class>
      <exclude-unlisted-classes/>
      <properties>
        <!-- <property name="hibernate.show_sql" value="true"/> -->
        <property name="hibernate.dialect" value="org.hibernate.dialect.MySQL5Dialect"/>
      </properties>
   </persistence-unit>
</persistence>

URL is:

http://server:8080/demo_jpa/show

Directory structure with JPA:

Directory TOMCAT$ROOT:[webapps.demo_jpa]

show.jsp;1
WEB-INF.DIR;1

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

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

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

demo.DIR;1
META-INF.DIR;1

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

AddrInfo.class;1
AddrInfoMgr.class;1
AddServlet.class;1
RemoveServlet.class;1
ShowServlet.class;1

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

persistence.xml;1

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

antlr-2_7_7.jar;1
byte-buddy-1_12_7.jar;1
classmate-1_5_1.jar;1
hibernate-commons-annotations-5_1_2_Final.jar;1
hibernate-core-5_6_5_Final.jar;1
istack-commons-runtime-3_0_7.jar;1
jandex-2_4_2_Final.jar;1
javax_activation-api-1_2_0.jar;1
javax_persistence-api-2_2.jar;1
jboss-logging-3_4_3_Final.jar;1
jboss-transaction-api_1_2_spec-1_1_1_Final.jar;1
mysql-connector-j-8_0_33.jar;1
stax-ex-1_8.jar;1
txw2-2_3_1.jar;1

From a purist perspective we should have had both a data class with JPA annotations and a clear data class and then read an instance of the first from the database and auto mapped it to an instance of the second. But we chose the simple approach.

My guess is that most VMS developers are more comfortable with plain JDBC than JPA. But the fact is that JPA means less code than plain JDBC.

Index-sequential files:

Traditionally many VMS applications use index-sequential files as a database.

Let us implement the model with data in an index-sequential file.

To do that we will use my ISAM library.

Just put the 3 jar files in [.WEB-INF.lib] and make sure that the logical pointing to the JNI shareable image is a system logical (the provided setup.com make it a process logical).

Pascal code to create the index-sequential file with test data.

program cre(input,output);

type
   addrinfo = packed record
                 id : [key(0),aligned(2)] integer;
                 name : packed array [1..32] of char;
                 address : packed array [1..128] of char;
                 town : packed array [1..32] of char;
              end;
   addrinfodb = file of addrinfo;

var
   o : addrinfo;
   db : addrinfodb;

begin
   open(db, 'demo.isq', unknown, organization := indexed, access_method := keyed);
   o.id := 1;
   o.name := 'A A';
   o.address := '1 A Rd';
   o.town := 'A town';
   db^ := o;
   put(db);
   o.id := 2;
   o.name := 'B B';
   o.address := '2 B Rd';
   o.town := 'B town';
   db^ := o;
   put(db);
   o.id := 3;
   o.name := 'C C';
   o.address := '3 C Rd';
   o.town := 'C town';
   db^ := o;
   put(db);
   close(db);
end.

Now we just need to replace the model classes.

AddrInfo.java:

package demo;

import dk.vajhoej.isam.KeyField;
import dk.vajhoej.record.FieldType;
import dk.vajhoej.record.Struct;
import dk.vajhoej.record.StructField;

@Struct
public class AddrInfo {
    @KeyField(n=0)
    @StructField(n=0, type=FieldType.INT4)
    private int id;
    @StructField(n=1, type=FieldType.FIXSTR, length=32, pad=true, padchar=' ')
    private String name;
    @StructField(n=2, type=FieldType.FIXSTR, length=128, pad=true, padchar=' ')
    private String address;
    @StructField(n=3, type=FieldType.FIXSTR, length=32, pad=true, padchar=' ')
    private String town;
    public AddrInfo() {
        this(0, "", "", "");
    }
    public AddrInfo(int id, String name, String address, String town) {
        this.id = id;
        this.name = name;
        this.address = address;
        this.town = town;
    }
    public int getId() {
        return id;
    }
    public void setId(int id) {
        this.id = id;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public String getAddress() {
        return address;
    }
    public void setAddress(String address) {
        this.address = address;
    }
    public String getTown() {
        return town;
    }
    public void setTown(String town) {
        this.town = town;
    }
}

AddrInfoMgr.java:

package demo;

import java.util.ArrayList;
import java.util.List;

import dk.vajhoej.isam.IsamException;
import dk.vajhoej.isam.IsamResult;
import dk.vajhoej.isam.IsamSource;
import dk.vajhoej.isam.Key0;
import dk.vajhoej.isam.local.LocalIsamSource;
import dk.vajhoej.record.RecordException;

public class AddrInfoMgr {
    private static IsamSource db;
    static {
        try {
            db = new LocalIsamSource("tomcat$root:[webapps.demo_isam.WEB-INF]demo.isq", "dk.vajhoej.vms.rms.IndexSequential", false);
        } catch (IsamException ex) {
            ex.printStackTrace();
        }
    }
    public List<AddrInfo> selectAll() {
        List<AddrInfo> res = new ArrayList<AddrInfo>();
        try {
            synchronized(db) {
                IsamResult<AddrInfo> it = db.readGE(AddrInfo.class, new Key0<Integer>(0));
                while(it.read()) {
                    AddrInfo o = it.current();
                    res.add(o);
                }
            }
        } catch (IsamException ex) {
            ex.printStackTrace();
        } catch (RecordException ex) {
            ex.printStackTrace();
        }
        return res;
    }
    public void insertOne(AddrInfo o) {
        try {
            synchronized(db) {
                db.create(o);
            }
        } catch (IsamException ex) {
            ex.printStackTrace();
        } catch (RecordException ex) {
            ex.printStackTrace();
        }
    }
    public void deleteOne(int id) {
        try {
            synchronized(db) {
                db.delete(AddrInfo.class, new Key0<Integer>(id));
            }
        } catch (IsamException ex) {
            ex.printStackTrace();
        } catch (RecordException ex) {
            ex.printStackTrace();
        }
    }
}

URL is:

http://server:8080/demo_isam/show

Directory structure with ISAM:

Directory TOMCAT$ROOT:[webapps.demo_isam]

show.jsp;1
WEB-INF.DIR;1

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

classes.DIR;1
demo.isq;1
lib.DIR;1
web.xml;1

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

demo.DIR;1

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

AddrInfo.class;1
AddrInfoMgr.class;1
AddServlet.class;1
RemoveServlet.class;1
ShowServlet.class;1

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

isam-vms.jar;1
isam.jar;1
record.jar;1

I have chosen to use a single open file connection to the index-sequential file and synchronize access in the AddrInfoMgr class. RMS does support multiple file connections, but I think this is the safe way.

Message queue:

All the previous is sync "request-process-response". Sometimes processing takes time and async "request-ack and process later" is better.

An easy way to achieve this is using a message queue and an external processor.

(with a full Java EE application server the async processing can fine happen inside the application server but with a web container only like Tomcat then it is much preferrable to do the processing externally)

Now we will make add and remove operations async.

Instead of:

Direct to DB

we send INSERT and DELETE through a message queue for async processing:

Via MQ to DB

To access message queues from servlets we define a queue connection factory on tomcat$root:[conf]context.xml:

<Context>
    ...
    <Resource name="jms/TestMQ"
              auth="Container"
              type="org.apache.activemq.ActiveMQConnectionFactory"
              factory="org.apache.activemq.jndi.JNDIReferenceFactory"
              brokerURL="tcp://localhost:61616"
              useEmbeddedBroker="false"/>
    ...
</Context>

This time we need to both change model classes and controller classes.

AddrInfoMgr.java:

package demo;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;

import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.sql.DataSource;

public class AddrInfoMgr {
    private static DataSource ds;
    static {
        try {
            Context initctx = new InitialContext();
            Context envctx  = (Context)initctx.lookup("java:/comp/env");
            ds = (DataSource)envctx.lookup("jdbc/TestDB");
        } catch(NamingException ex) {
            ex.printStackTrace();
        }
    }
    public List<AddrInfo> selectAll() {
        List<AddrInfo> res = new ArrayList<AddrInfo>();
        try {
            Connection con = ds.getConnection();
            PreparedStatement pstmt = con.prepareStatement("SELECT id,name,address,town FROM addrinfo");
            ResultSet rs = pstmt.executeQuery();
            while(rs.next()) {
                int id = rs.getInt(1);
                String name = rs.getString(2);
                String address = rs.getString(3);
                String town = rs.getString(4);
                AddrInfo o = new AddrInfo(id, name, address, town);
                res.add(o);
            }
            rs.close();
            pstmt.close();
            con.close();
        } catch(SQLException ex) {
            ex.printStackTrace();
        }
        return res;
    }
}

QMgr.java:

package demo;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;

import javax.jms.DeliveryMode;
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.Queue;
import javax.jms.QueueConnection;
import javax.jms.QueueConnectionFactory;
import javax.jms.QueueSender;
import javax.jms.QueueSession;
import javax.jms.Session;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;

public class QMgr {
    private static QueueConnectionFactory qcf;
    static {
        try {
            Context initctx = new InitialContext();
            Context envctx  = (Context)initctx.lookup("java:/comp/env");
            qcf = (QueueConnectionFactory)envctx.lookup("jms/TestMQ");
        } catch(NamingException ex) {
            ex.printStackTrace();
        }
    }
    public void send(String qnm, String payload) {
        try {
            QueueConnection con = qcf.createQueueConnection();
            con.start();
            QueueSession ses = con.createQueueSession(false, Session.AUTO_ACKNOWLEDGE);
            Queue q = ses.createQueue(qnm);
            QueueSender sender = ses.createSender(q);
            sender.setDeliveryMode(DeliveryMode.NON_PERSISTENT);
            Message msg = ses.createTextMessage(payload);
            sender.send(msg);
            sender.close();
            ses.close();
            con.close();
        } catch(JMSException ex) {
            ex.printStackTrace();
        }
    }
}

AddServlet.java:

package demo;

import java.io.IOException;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import com.google.gson.Gson;

@WebServlet(urlPatterns={"/add"})
public class AddServlet extends HttpServlet {
    private QMgr qmgr = new QMgr();
    private Gson gson = new Gson();
    @Override
    public void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        int id = Integer.parseInt(req.getParameter("id"));
        String name = req.getParameter("name");
        String address = req.getParameter("address");
        String town = req.getParameter("town");
        AddrInfo o = new AddrInfo(id, name, address, town);
        qmgr.send("AddQ", gson.toJson(o));
        resp.sendRedirect("show");
    }
}

RemoveServlet.java:

package demo;

import java.io.IOException;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@WebServlet(urlPatterns={"/remove"})
public class RemoveServlet extends HttpServlet {
    private QMgr qmgr = new QMgr();
    @Override
    public void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        int id = Integer.parseInt(req.getParameter("id"));
        qmgr.send("RemoveQ", Integer.toString(id));
        resp.sendRedirect("show");
    }
}

URL is:

http://server:8080/demo_mq/show

Directory structure with MQ:

Directory TOMCAT$ROOT:[webapps.demo_mq]

show.jsp;1
WEB-INF.DIR;1

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

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

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

demo.DIR;1

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

AddrInfo.class;1
AddrInfoMgr.class;1
AddServlet.class;1
QMgr.class;1
RemoveServlet.class;1
ShowServlet.class;1

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

activemq-all-5_16_7.jar;1
gson-2_2_4.jar;1
mysql-connector-j-8_0_33.jar;1

The processors are really out of scope as they are not web, but for completeness here are my test processors implemented in Groovy.

addproc.groovy:

import java.sql.*

import javax.jms.*

import org.apache.activemq.*
import com.google.gson.*

import demo.*

gson = new Gson();
dbcon = DriverManager.getConnection("jdbc:mysql://arnepc5/Test", "arne", "hemmeligt")
pstmt = dbcon.prepareStatement("INSERT INTO addrinfo VALUES(?,?,?,?)")
qcf = new ActiveMQConnectionFactory("tcp://localhost:61616")
qcon = qcf.createQueueConnection()
qcon.start()
ses = qcon.createQueueSession(false, Session.AUTO_ACKNOWLEDGE)
q = ses.createQueue("AddQ")
receiver = ses.createReceiver(q)
while(true) {
    msg = receiver.receive()
    if(msg == null) break
    o = gson.fromJson(msg.text, AddrInfo.class)
    pstmt.setInt(1, o.id)
    pstmt.setString(2, o.name)
    pstmt.setString(3, o.address)
    pstmt.setString(4, o.town)
    pstmt.executeUpdate()
}
receiver.close()
ses.close()
qcon.close()
pstmt.close()
dbcon.close()

removeproc.groovy:

import java.sql.*

import javax.jms.*

import org.apache.activemq.*

dbcon = DriverManager.getConnection("jdbc:mysql://arnepc5/Test", "arne", "hemmeligt")
pstmt = dbcon.prepareStatement("DELETE FROM addrinfo WHERE id = ?")
qcf = new ActiveMQConnectionFactory("tcp://localhost:61616")
qcon = qcf.createQueueConnection()
qcon.start()
ses = qcon.createQueueSession(false, Session.AUTO_ACKNOWLEDGE)
q = ses.createQueue("RemoveQ")
receiver = ses.createReceiver(q)
while(true) {
    msg = receiver.receive()
    if(msg == null) break
    id = Integer.parseInt(msg.text)
    pstmt.setInt(1,  id)
    pstmt.executeUpdate()
}
receiver.close()
ses.close()
qcon.close()
pstmt.close()
dbcon.close()

Note that because this is async, then any add or remove action will not be immediate visible. Instead one has to refresh the show a little later to see the change.

Also these two demo processors does not actually use much time to process a message, but processors in real system may and that is where we need the async aspect.

Native code:

Often a web application need to talk to a legacy applications. Especially on VMS where we have lots of older applications written in Cobol/Basic/Pascal/Fortran.

Sometimes we can just access the applications data source directly - relational databases and index-sequential files are covered in previous sections. Bot other times we need to access native code.

Let us see an example. To simplify the example we will implement the same functionality as in all the previous section just with data coming from native code instead of database / index-sequential file.

To do that we will use my VMSCALL library.

The two jar files vmscall.jar and record.jar must be put in [.WEB-INF.lib] and the logical pointing to the JNI shareable image is a system logical (the provided setup.com make it a process logical).

Let us show a Pascal variant and a C variant. They are not that different on the Java side, but there are some variation in default calling convention.

First the native code and the build commands.

demo.pas:

module demo(input,output);

type
   s32 = packed array [1..32] of char;
   s128 = packed array [1..128] of char;
   addrinfo = packed record
                 id : [key(0),aligned(2)] integer;
                 name : s32;
                 address : s128;
                 town : s32;
              end;

var
   ndb : integer value 0;
   db : array [1..1000] of addrinfo;

[global]
function addrinfo_selectall_n(var n : integer) : integer;

begin
   n := ndb;
   addrinfo_selectall_n := 1;
end;

[global]
function addrinfo_selectall_fetch(ix : integer; var r : addrinfo) : integer;

begin
   r := db[ix];
   addrinfo_selectall_fetch := 1;
end;

[global]
function addrinfo_insertone(id : integer; name : s32; address : s128; town : s32) : integer;

begin
   ndb := ndb + 1;
   db[ndb].id := id;
   db[ndb].name := name;
   db[ndb].address := address;
   db[ndb].town := town;
   addrinfo_insertone := 1;
end;

[global]
function addrinfo_deleteone(id : integer) : integer;

var
   i, j : integer;

begin
   for i := 1 to ndb do begin
      if db[i].id = id then begin
         for j := i to (ndb - 1) do begin
            db[j] := db[j + 1];
         end;
      end;
   end;
   ndb := ndb - 1;
   addrinfo_deleteone := 1;
end;

procedure init;

begin
   addrinfo_insertone(1, 'A A', '1 A Rd', 'A town');
   addrinfo_insertone(2, 'B B', '2 B Rd', 'B town');
   addrinfo_insertone(3, 'C C', '3 C Rd', 'C town');
end;

to begin do init;

end.
$ pas demo
$ link/share=demopshr.exe demo + sys$input/opt
SYMBOL_VECTOR=(ADDRINFO_SELECTALL_N=PROCEDURE, -
               ADDRINFO_SELECTALL_FETCH=PROCEDURE, -
               ADDRINFO_INSERTONE=PROCEDURE, -
               ADDRINFO_DELETEONE=PROCEDURE)
$
$ exit

And then the Model code to access it.

AddrInfo.java:

package demo;

import dk.vajhoej.record.FieldType;
import dk.vajhoej.record.Struct;
import dk.vajhoej.record.StructField;

@Struct
public class AddrInfo {
    @StructField(n=0, type=FieldType.INT4)
    private int id;
    @StructField(n=1, type=FieldType.FIXSTR, length=32, pad=true, padchar=' ')
    private String name;
    @StructField(n=2, type=FieldType.FIXSTR, length=128, pad=true, padchar=' ')
    private String address;
    @StructField(n=3, type=FieldType.FIXSTR, length=32, pad=true, padchar=' ')
    private String town;
    public AddrInfo() {
        this(0, "", "", "");
    }
    public AddrInfo(int id, String name, String address, String town) {
        this.id = id;
        this.name = name;
        this.address = address;
        this.town = town;
    }
    public int getId() {
        return id;
    }
    public void setId(int id) {
        this.id = id;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public String getAddress() {
        return address;
    }
    public void setAddress(String address) {
        this.address = address;
    }
    public String getTown() {
        return town;
    }
    public void setTown(String town) {
        this.town = town;
    }
}

AddrInfoMgr.java:

package demo;

import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.List;

import dk.vajhoej.record.RecordException;

import static dk.vajhoej.vms.call.VMS.*;

public class AddrInfoMgr {
    public List<AddrInfo> selectAll() {
        List<AddrInfo> res = new ArrayList<AddrInfo>();
        try {
            LongWord n = new LongWord();
            int stat = call("demopshr", "ADDRINFO_SELECTALL_N", pass(n).byReference().writeOnly());
            for(int i = 1; i <= n.getValue(); i++) {
                AddrInfo o = new AddrInfo();
                Block<AddrInfo> owrap = new Block<AddrInfo>(o);
                stat = call("demopshr", "ADDRINFO_SELECTALL_FETCH", pass(new LongWord(i)).byReference().readOnly(),
                                                                    pass(owrap).byReference().readWrite());
                o = owrap.getObject(AddrInfo.class);
                res.add(new AddrInfo(o.getId(), o.getName(), o.getAddress(), o.getTown()));
            }
        } catch(RecordException ex) {
            ex.printStackTrace();
        }
        return res;
    }
    public void insertOne(AddrInfo o) {
        try {
            int stat = call("demopshr", "ADDRINFO_INSERTONE", pass(new LongWord(o.getId())).byReference().readOnly(),
                                                              pass(new CharacterString(o.getName(), 32)).byReference().readOnly(),
                                                              pass(new CharacterString(o.getAddress(), 128)).byReference().readOnly(),
                                                              pass(new CharacterString(o.getTown(), 32)).byReference().readOnly());
        } catch(UnsupportedEncodingException ex) {
            ex.printStackTrace();
        }
    }
    public void deleteOne(int id) {
        int stat = call("demopshr", "ADDRINFO_DELETEONE", pass(new LongWord(id)).byReference().readOnly());
    }
}

URL is:

http://server:8080/demo_npas/show

Directory structure with native Pascal:

Directory TOMCAT$ROOT:[webapps.demo_npas]

show.jsp;1
WEB-INF.DIR;1

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

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

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

demo.DIR;1

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

AddrInfo.class;1
AddrInfoMgr.class;1
AddServlet.class;1
RemoveServlet.class;1
ShowServlet.class;1

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

record.jar;1
vmscall.jar;1

First the native code and the build commands.

demo.bas:

function integer addrinfo_selectall_n(integer n)

option type = explicit

record addrinfo
    integer id
    string xname = 32
    string address = 128
    string town = 32
end record
map (glb) integer ndb,  addrinfo db(1000)

n = ndb
addrinfo_selectall_n = 1

end function
!
function integer addrinfo_selectall_fetch(integer ix, addrinfo r)

option type = explicit

record addrinfo
    integer id
    string xname = 32
    string address = 128
    string town = 32
end record
map (glb) integer ndb,  addrinfo db(1000)

r = db(ix)
addrinfo_selectall_fetch = 1

end function
!
function integer addrinfo_insertone(integer id, string xname, string address, string town)

option type = explicit

record addrinfo
    integer id
    string xname = 32
    string address = 128
    string town = 32
end record
map (glb) integer ndb,  addrinfo db(1000)

db(ndb)::id = id
db(ndb)::xname = xname
db(ndb)::address = address
db(ndb)::town = town
ndb = ndb + 1
addrinfo_insertone = 1

end function
!
function integer addrinfo_deleteone(integer id)

option type = explicit

record addrinfo
    integer id
    string xname = 32
    string address = 128
    string town = 32
end record
map (glb) integer ndb,  addrinfo db(1000)
declare integer i, j

for i = 0 to ndb-1
    if db(i)::id = id then
        for j = i to ndb-2
             db(j) = db(j+1)
        next j
    end if
next i
ndb = ndb - 1
addrinfo_deleteone = 1

end function
!
sub init(integer initco by value) !, integer clico by value, integer imginf by value, integer x1 by value, integer x2 by value, integer x3 by value)

option type = explicit

external sub addrinfo_insertone(integer, string, string, string)

call addrinfo_insertone(1, "A A", "1 A Rd", "A town")
call addrinfo_insertone(2, "B B", "2 B Rd", "B town")
call addrinfo_insertone(3, "C C", "3 C Rd", "C town")

end sub

libini.mar:

        .title  libini
        .extrn  lib$initialize
        .psect  lib$initialize long,nopic,con,gbl,noshr,noexe,nowrt
        .address init
        .end

b.com:

$ bas demo
$ macro libini
$ link/share=demobshr.exe demo + libini + sys$input/opt
SYMBOL_VECTOR=(ADDRINFO_SELECTALL_N=PROCEDURE, -
               ADDRINFO_SELECTALL_FETCH=PROCEDURE, -
               ADDRINFO_INSERTONE=PROCEDURE, -
               ADDRINFO_DELETEONE=PROCEDURE)
$
$ exit

And then the Model code to access it.

AddrInfo.java:

package demo;

import dk.vajhoej.record.FieldType;
import dk.vajhoej.record.Struct;
import dk.vajhoej.record.StructField;

@Struct
public class AddrInfo {
    @StructField(n=0, type=FieldType.INT4)
    private int id;
    @StructField(n=1, type=FieldType.FIXSTRNULTERM, length=32)
    private String name;
    @StructField(n=2, type=FieldType.FIXSTRNULTERM, length=128)
    private String address;
    @StructField(n=3, type=FieldType.FIXSTRNULTERM, length=32)
    private String town;
    public AddrInfo() {
        this(0, "", "", "");
    }
    public AddrInfo(int id, String name, String address, String town) {
        this.id = id;
        this.name = name;
        this.address = address;
        this.town = town;
    }
    public int getId() {
        return id;
    }
    public void setId(int id) {
        this.id = id;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public String getAddress() {
        return address;
    }
    public void setAddress(String address) {
        this.address = address;
    }
    public String getTown() {
        return town;
    }
    public void setTown(String town) {
        this.town = town;
    }
}

AddrInfoMgr.java:

package demo;

import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.List;

import dk.vajhoej.record.RecordException;

import static dk.vajhoej.vms.call.VMS.*;

public class AddrInfoMgr {
    public List<AddrInfo> selectAll() {
        List<AddrInfo> res = new ArrayList<AddrInfo>();
        try {
            LongWord n = new LongWord();
            int stat = call("demobshr", "ADDRINFO_SELECTALL_N", pass(n).byReference().writeOnly());
            for(int i = 0; i < n.getValue(); i++) {
                AddrInfo o = new AddrInfo();
                Block<AddrInfo> owrap = new Block<AddrInfo>(o);
                stat = call("demobshr", "ADDRINFO_SELECTALL_FETCH", pass(new LongWord(i)).byReference().readOnly(),
                                                                    pass(owrap).byReference().readWrite());
                o = owrap.getObject(AddrInfo.class);
                res.add(new AddrInfo(o.getId(), o.getName(), o.getAddress(), o.getTown()));
            }
        } catch(RecordException ex) {
            ex.printStackTrace();
        }
        return res;
    }
    public void insertOne(AddrInfo o) {
        try {
            int stat = call("demobshr", "ADDRINFO_INSERTONE", pass(new LongWord(o.getId())).byReference().readOnly(),
                                                              pass(new CharacterString(o.getName())).byDescriptor().readOnly(),
                                                              pass(new CharacterString(o.getAddress())).byDescriptor().readOnly(),
                                                              pass(new CharacterString(o.getTown())).byDescriptor().readOnly());
        } catch(UnsupportedEncodingException ex) {
            ex.printStackTrace();
        }
    }
    public void deleteOne(int id) {
        int stat = call("demobshr", "ADDRINFO_DELETEONE", pass(new LongWord(id)).byReference().readOnly());
    }
}

URL is:

http://server:8080/demo_nbas/show

Directory structure with native Basic:

Directory TOMCAT$ROOT:[webapps.demo_nbas]

show.jsp;1
WEB-INF.DIR;1

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

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

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

demo.DIR;1

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

AddrInfo.class;1
AddrInfoMgr.class;1
AddServlet.class;1
RemoveServlet.class;1
ShowServlet.class;1

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

record.jar;1
vmscall.jar;1

First the native code and the build commands.

demo.c:

#include <string.h>

struct addrinfo
{
    int id;
    char name[32];
    char address[128];
    char town[32];
};

static int ndb;
static struct addrinfo db[1000];

int addrinfo_selectall_n(int *n)
{
    *n = ndb;
    return 1;
}

int addrinfo_selectall_fetch(int ix, struct addrinfo *r)
{
    memcpy(r, &db[ix], sizeof(struct addrinfo));
    return 1;
}

int addrinfo_insertone(int id, char *name, char *address, char *town)
{
    db[ndb].id = id;
    strcpy(db[ndb].name, name);
    strcpy(db[ndb].address, address);
    strcpy(db[ndb].town, town);
    ndb++;
    return 1;
}

int addrinfo_deleteone(int id)
{
    for(int i = 0; i < ndb; i++)
    {
        if(db[i].id == id)
        {
            for(int j = i; j < (ndb - 1); j++)
            {
                memcpy(&db[j], &db[j + 1], sizeof(struct addrinfo));
            }
        }
    }
    ndb--;
    return 1;
}

void init()
{
   addrinfo_insertone(1, "A A", "1 A Rd", "A town");
   addrinfo_insertone(2, "B B", "2 B Rd", "B town");
   addrinfo_insertone(3, "C C", "3 C Rd", "C town");
}

libini.mar:

        .title  libini
        .extrn  lib$initialize
        .psect  lib$initialize long,nopic,con,gbl,noshr,noexe,nowrt
        .address init
        .end

b.com:

$ cc demo
$ macro libini
$ link/share=democshr.exe demo + libini + sys$input/opt
SYMBOL_VECTOR=(ADDRINFO_SELECTALL_N=PROCEDURE, -
               ADDRINFO_SELECTALL_FETCH=PROCEDURE, -
               ADDRINFO_INSERTONE=PROCEDURE, -
               ADDRINFO_DELETEONE=PROCEDURE)
$
$ exit

And then the Model code to access it.

AddrInfo.java:

package demo;

import dk.vajhoej.record.FieldType;
import dk.vajhoej.record.Struct;
import dk.vajhoej.record.StructField;

@Struct
public class AddrInfo {
    @StructField(n=0, type=FieldType.INT4)
    private int id;
    @StructField(n=1, type=FieldType.FIXSTRNULTERM, length=32)
    private String name;
    @StructField(n=2, type=FieldType.FIXSTRNULTERM, length=128)
    private String address;
    @StructField(n=3, type=FieldType.FIXSTRNULTERM, length=32)
    private String town;
    public AddrInfo() {
        this(0, "", "", "");
    }
    public AddrInfo(int id, String name, String address, String town) {
        this.id = id;
        this.name = name;
        this.address = address;
        this.town = town;
    }
    public int getId() {
        return id;
    }
    public void setId(int id) {
        this.id = id;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public String getAddress() {
        return address;
    }
    public void setAddress(String address) {
        this.address = address;
    }
    public String getTown() {
        return town;
    }
    public void setTown(String town) {
        this.town = town;
    }
}

AddrInfoMgr.java:

package demo;

import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.List;

import dk.vajhoej.record.RecordException;

import static dk.vajhoej.vms.call.VMS.*;

public class AddrInfoMgr {
    public List<AddrInfo> selectAll() {
        List<AddrInfo> res = new ArrayList<AddrInfo>();
        try {
            LongWord n = new LongWord();
            int stat = call("democshr", "ADDRINFO_SELECTALL_N", pass(n).byReference().writeOnly());
            for(int i = 0; i < n.getValue(); i++) {
                AddrInfo o = new AddrInfo();
                Block<AddrInfo> owrap = new Block<AddrInfo>(o);
                stat = call("democshr", "ADDRINFO_SELECTALL_FETCH", pass(new LongWord(i)).byValue().readOnly(),
                                                                    pass(owrap).byReference().readWrite());
                o = owrap.getObject(AddrInfo.class);
                res.add(new AddrInfo(o.getId(), o.getName(), o.getAddress(), o.getTown()));
            }
        } catch(RecordException ex) {
            ex.printStackTrace();
        }
        return res;
    }
    public void insertOne(AddrInfo o) {
        try {
            int stat = call("democshr", "ADDRINFO_INSERTONE", pass(new LongWord(o.getId())).byValue().readOnly(),
                                                              pass(new NullTermCharacterString(o.getName())).byReference().readOnly(),
                                                              pass(new NullTermCharacterString(o.getAddress())).byReference().readOnly(),
                                                              pass(new NullTermCharacterString(o.getTown())).byReference().readOnly());
        } catch(UnsupportedEncodingException ex) {
            ex.printStackTrace();
        }
    }
    public void deleteOne(int id) {
        int stat = call("democshr", "ADDRINFO_DELETEONE", pass(new LongWord(id)).byValue().readOnly());
    }
}

URL is:

http://server:8080/demo_nc/show

Directory structure with native C:

Directory TOMCAT$ROOT:[webapps.demo_nc]

show.jsp;1
WEB-INF.DIR;1

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

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

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

demo.DIR;1

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

AddrInfo.class;1
AddrInfoMgr.class;1
AddServlet.class;1
RemoveServlet.class;1
ShowServlet.class;1

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

record.jar;1
vmscall.jar;1

Try to avoid using native code if possible. An error in JVM code will crash the thread (request) - an error in native code will crash the server (Tomcat).

Article history:

Version Date Description
1.0 May 15th 2026 Initial version
1.1 May 16th 2026 Add section with PHP as language and add Basic example under native code
1.2 May 18th 2026 Add section with JSR 223 script languages

Other articles:

See list of all articles here

Comments:

Please send comments to Arne Vajhøj