Lecture on Client Side Web Programming -- Python vs. Go vs. Scala Object Oriented Programming

Great papers/books in Computer Sciences -- Awesome project


JEE and Middleware Development

Introduction

The objective of the information on this page is to explain how to build a (Web) application with the JEE platform, able to visualize the weather forecast of a worldwide known city. The main challenge is to post a query on a weather server, to get the answer, to analyse it and to publish it on our local Web server (glasfish5). We mix Java, Python and the Servlets technology inside the JEE 8 platform.

The lecture is accompanied by demos / practical work / implementations of concrete examples... otherwise the lecture will be to much! We start in Section 1 with revision material related to Java.

0- PDF Document used for the Lecture

The main document, in PDF format is available here. I will adapt it according to your skills and our progress.

1- Weather station in pure Java

The codes for the weather station example are available by clicking on the following links: ValMeteoJour.java, EnsValMeteoYanis.java, EnsValMeteo.java, VueStationMeteo.java, StationMeteoConstantes.java. They illustrate, as explained during the lecture, the options with the concepts of inheritance and interface. Compile them as follows:

  $ javac EnsValMeteoYanis.java
  $ javac EnsValMeteo.java
  

As an extension of the previous example, you may have a look to the World Weather Forecast web site, namely the WWIS data interface.

You will get the possibility to build your own graphical interface which will propose to be informed about the weather situation in main cities in the world. You will need to do a curl/wget networking query to parse the XML for the code of cities, then to parse the result. Here is an example of a curl call and its result for the New-York city:

  $ curl http://worldweather.wmo.int/fr/json/278_fr.xml
  {"city":{"lang":"fr","cityName":"New York City, New York","cityLatitude":"40.650000000","cityLongitude":"-73.780000000","cityId":278,"isCapital":false,"stationName":"New York City, New York","tourismURL":"","tourismBoardName":"","isDep":false,"timeZone":"-0400","isDST":"Y","member":{"memId":93,"memName":"Etats-Unis","shortMemName":"","url":"www.nws.noaa.gov\/","orgName":"National Weather Service","logo":"usa_logo.png","ra":4},"forecast":{"issueDate":"2019-04-17 22:40:00","timeZone":"Local","forecastDay":[{"forecastDate":"2019-04-18","wxdesc":"","weather":"pluie fine","minTemp":"9","maxTemp":"17","minTempF":"48","maxTempF":"63","weatherIcon":1502},{"forecastDate":"2019-04-19","wxdesc":"","weather":"Petite averse","minTemp":"14","maxTemp":"21","minTempF":"57","maxTempF":"69","weatherIcon":1201},{"forecastDate":"2019-04-20","wxdesc":"","weather":"Averse de pluie","minTemp":"16","maxTemp":"19","minTempF":"60","maxTempF":"66","weatherIcon":901},{"forecastDate":"2019-04-21","wxdesc":"","weather":"Petite averse","minTemp":"11","maxTemp":"17","minTempF":"52","maxTempF":"63","weatherIcon":1201},{"forecastDate":"2019-04-22","wxdesc":"","weather":"Ensoleill\u00e9","minTemp":"11","maxTemp":"21","minTempF":"52","maxTempF":"70","weatherIcon":2402},{"forecastDate":"2019-04-23","wxdesc":"","weather":"P\u00e9riode ensoleill\u00e9e","minTemp":"12","maxTemp":"18","minTempF":"54","maxTempF":"65","weatherIcon":2201},{"forecastDate":"2019-04-24","wxdesc":"","weather":"Petite averse","minTemp":"10","maxTemp":"16","minTempF":"50","maxTempF":"61","weatherIcon":1201}]},"climate":{"raintype":"Rainfall","raindef":"","rainunit":"inch","datab":1981,"datae":2010,"tempb":"","tempe":"","rdayb":"","rdaye":"","rainfallb":"","rainfalle":"","climatefromclino":"","climateMonth":[{"month":1,"maxTemp":"3.5","minTemp":"-2.8","meanTemp":null,"maxTempF":"38.3","minTempF":"26.9","meanTempF":null,"raindays":"10.4","rainfall":"92.7","climateFromMemDate":"2014-01-06"},{"month":2,"maxTemp":"5.3","minTemp":"-1.7","meanTemp":null,"maxTempF":"41.6","minTempF":"28.9","meanTempF":null,"raindays":"9.2","rainfall":"78.5","climateFromMemDate":"2014-01-06"},{"month":3,"maxTemp":"9.8","minTemp":"1.8","meanTemp":null,"maxTempF":"49.7","minTempF":"35.2","meanTempF":null,"raindays":"10.9","rainfall":"110.7","climateFromMemDate":"2014-01-06"},{"month":4,"maxTemp":"16.2","minTemp":"7.1","meanTemp":null,"maxTempF":"61.2","minTempF":"44.8","meanTempF":null,"raindays":"11.5","rainfall":"114.3","climateFromMemDate":"2014-01-06"},{"month":5,"maxTemp":"21.6","minTemp":"12.2","meanTemp":null,"maxTempF":"70.8","minTempF":"54.0","meanTempF":null,"raindays":"11.1","rainfall":"106.4","climateFromMemDate":"2014-01-06"},{"month":6,"maxTemp":"26.3","minTemp":"17.6","meanTemp":null,"maxTempF":"79.3","minTempF":"63.6","meanTempF":null,"raindays":"11.2","rainfall":"112.0","climateFromMemDate":"2014-01-06"},{"month":7,"maxTemp":"28.9","minTemp":"20.4","meanTemp":null,"maxTempF":"84.1","minTempF":"68.8","meanTempF":null,"raindays":"10.4","rainfall":"116.8","climateFromMemDate":"2014-01-06"},{"month":8,"maxTemp":"28.1","minTemp":"19.9","meanTemp":null,"maxTempF":"82.6","minTempF":"67.8","meanTempF":null,"raindays":"9.5","rainfall":"112.8","climateFromMemDate":"2014-01-06"},{"month":9,"maxTemp":"24.0","minTemp":"16.0","meanTemp":null,"maxTempF":"75.2","minTempF":"60.8","meanTempF":null,"raindays":"8.7","rainfall":"108.7","climateFromMemDate":"2014-01-06"},{"month":10,"maxTemp":"17.7","minTemp":"10.0","meanTemp":null,"maxTempF":"63.8","minTempF":"50.0","meanTempF":null,"raindays":"8.9","rainfall":"111.8","climateFromMemDate":"2014-01-06"},{"month":11,"maxTemp":"12.1","minTemp":"5.3","meanTemp":null,"maxTempF":"53.8","minTempF":"41.6","meanTempF":null,"raindays":"9.6","rainfall":"102.1","climateFromMemDate":"2014-01-06"},{"month":12,"maxTemp":"6.1","minTemp":"0.0","meanTemp":null,"maxTempF":"43.0","minTempF":"32.0","meanTempF":null,"raindays":"10.6","rainfall":"101.6","climateFromMemDate":"2014-01-06"}]}}}macbook-de-christophe:~ christophecerin$ 

Let's solve the problem! The Java code to start a curl query is available in this file ProcessBuilderTest.java.

This code downloads the file 278_fr.json, which is a JSON file and it encodes also a Python dictionary. The data file is available.

The Python code that parses the previous dictionary is available with the file 278.py.

The result for the execution of the Python code is:

    MacBook-de-Christophe:Cities christophecerin$ python 278.py
    New York City, New York
    2019-04-27
       Min Temp: 9
       Max Temp: 14
       Forecast: Période ensoleillée
    2019-04-28
       Min Temp: 9
       Max Temp: 14
       Forecast: Pluie
    2019-04-29
       Min Temp: 5
       Max Temp: 14
       Forecast: Ensoleillé
    2019-04-30
       Min Temp: 10
       Max Temp: 18
       Forecast: Petite averse
    2019-05-01
       Min Temp: 11
       Max Temp: 17
       Forecast: Petite averse
    2019-05-02
       Min Temp: 12
       Max Temp: 16
       Forecast: Petite averse
    2019-05-03
       Min Temp: 11
       Max Temp: 16
       Forecast: Petite averse
    MacBook-de-Christophe:Cities christophecerin$
  

2- Java Enterprise Edition (JEE) Installation

For the installation, and if you have multiple distribution of the JDK, as with my installation:

    $ ls /Library/Java/JavaVirtualMachines
    jdk-10.0.1.jdkjdk-12.0.1.jdkjdk1.8.0_181.jdk
    jdk-11.0.1.jdkjdk1.8.0_131.jdkjdk1.8.0_191.jdk
  

you probably need to customize the file glassfish5/glassfish/bin/asadmin of Glassfish5 as follows, here to use JDK 1.8.0_191 which is compatible with JEE 8:

    $ tail -14 asadmin

    # Always use JDK 1.6 or higher
    AS_INSTALL=`dirname "$0"`/../glassfish
    AS_INSTALL_LIB="$AS_INSTALL/lib"
    . "${AS_INSTALL}/config/asenv.conf"
    JAVA=java
    #Depends upon Java from ../config/asenv.conf
    if [ ${AS_JAVA} ]; then
    JAVA=${AS_JAVA}/bin/java
    fi

    # OLD version
    #exec "$JAVA" -jar "$AS_INSTALL_LIB/client/appserver-cli.jar" "$@"

    exec /usr/libexec/java_home -v 1.8.0_191 --exec java -jar "$AS_INSTALL_LIB/client/appserver-cli.jar" "$@"
  

Then you will be able to stop/start the application server:

    MacBook-de-Christophe:~ christophecerin$ glassfish5/glassfish/bin/asadmin start-domain
    Waiting for domain1 to start ....
    Successfully started the domain : domain1
    domain  Location: /Users/christophecerin/glassfish5/glassfish/domains/domain1
    Log File: /Users/christophecerin/glassfish5/glassfish/domains/domain1/logs/server.log
    Admin Port: 4848
    Command start-domain executed successfully.
    MacBook-de-Christophe:~ christophecerin$
  

You are now ready to open a browser with URL as http://localhost:8080/

Other files that can be usefull to read for installing all you need:

Troubleshooting

If you use a special JDK, don't forgot to set environment variable (here for a MacOS system):

    export JAVA_HOME=/Library/Java/JavaVirtualMachines/jdk1.8.0_191.jdk/Contents/Home/
    

If you use the latest version of maven and that the pom.xml file specifies and oldest one, don't forget to modify the dependencies.

For instance, if you try to install some projects coming with glassfish distribution as samples, modify the pom.xml with the correct version of maven (here 3.6.1 and not 2.1.1 in the original distribution. We need a pom.mxl file as follows:

    <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
      <modelVersion>4.0.0</modelVersion>
      <parent>
	<artifactId>servlet-samples</artifactId>
	<groupId>org.glassfish-samples</groupId>
	<version>5.0-SNAPSHOT</version>
      </parent>

      <groupId>org.glassfish-samples</groupId>
      <artifactId>non-blocking-io-read-war</artifactId>
      <version>5.0-SNAPSHOT</version>
      <packaging>war</packaging>

      <name>non-blocking-io-read-war</name>

      <dependencies>
	<dependency>
	  <groupId>javax</groupId>
	  <artifactId>javaee-api</artifactId>
	</dependency>
      </dependencies>

      <build>
	<finalName>non-blocking-io-read-war</finalName>
	<plugins>
	  <plugin>
	    <artifactId>maven-compiler-plugin</artifactId>
	    <version>3.6.1</version>
	  </plugin>
	  <plugin>
	    <artifactId>maven-war-plugin</artifactId>
	    <configuration>
	      <failOnMissingWebXml>false</failOnMissingWebXml>
	    </configuration>
	  </plugin>
	  <plugin>
	    <artifactId>maven-dependency-plugin</artifactId>
	    <version>3.6</version>
	  </plugin>
	</plugins>
      </build>

   </project>
  

If your observe the following message, either follow the indication (change the port number) or shutdown the server (a new one will be lanch):

    $ mvn cargo:run
    [INFO] Scanning for projects...
    [INFO]
    [INFO] -----------< org.glassfish-samples:non-blocking-io-read-war >-----------
      [INFO] Building non-blocking-io-read-war 5.0-SNAPSHOT
      [INFO] --------------------------------[ war ]---------------------------------
      [INFO]
      [INFO] --- cargo-maven2-plugin:1.4.0:run (default-cli) @ non-blocking-io-read-war ---
      [INFO] [en2.ContainerRunMojo] Resolved container artifact org.codehaus.cargo:cargo-core-container-glassfish:jar:1.4.0 for container glassfish4x
      [INFO] [talledLocalContainer] Port for cargo-domain (4848) is in use. Try a different port number.
      [INFO] [talledLocalContainer] CLI130: Could not create domain, cargo-domain
      [INFO] [talledLocalContainer] Command create-domain failed.
      [INFO] ------------------------------------------------------------------------
      [INFO] BUILD FAILURE
      [INFO] ------------------------------------------------------------------------
      [INFO] Total time:  2.215 s
      [INFO] Finished at: 2019-04-28T15:39:09+08:00
      [INFO] ------------------------------------------------------------------------
      [ERROR] Failed to execute goal org.codehaus.cargo:cargo-maven2-plugin:1.4.0:run (default-cli) on project non-blocking-io-read-war: Execution default-cli of goal org.codehaus.cargo:cargo-maven2-plugin:1.4.0:run failed: Failed to create a GlassFish 4.x standalone configuration: GlassFish admin command failed: asadmin exited 1 -> [Help 1]
      [ERROR]
      [ERROR] To see the full stack trace of the errors, re-run Maven with the -e switch.
      [ERROR] Re-run Maven using the -X switch to enable full debug logging.
      [ERROR]
      [ERROR] For more information about the errors and possible solutions, please read the following articles:
      [ERROR] [Help 1] http://cwiki.apache.org/confluence/display/MAVEN/PluginExecutionException
  

In my case, I decided to shutdown the server, then to install, then to run the application:

    $ ~/glassfish5/bin/asadmin stop-domain
    $ mvn install
    $ mvn cargo:run
    [INFO] Scanning for projects...
    [INFO]
    [INFO] -----------< org.glassfish-samples:non-blocking-io-read-war >-----------
      [INFO] Building non-blocking-io-read-war 5.0-SNAPSHOT
      [INFO] --------------------------------[ war ]---------------------------------
      [INFO]
      [INFO] --- cargo-maven2-plugin:1.4.0:run (default-cli) @ non-blocking-io-read-war ---
      [INFO] [en2.ContainerRunMojo] Resolved container artifact org.codehaus.cargo:cargo-core-container-glassfish:jar:1.4.0 for container glassfish4x
      [INFO] [talledLocalContainer] Using port 4848 for Admin.
      [INFO] [talledLocalContainer] Using port 8080 for HTTP Instance.
      [INFO] [talledLocalContainer] Using port 7676 for JMS.
      [INFO] [talledLocalContainer] Using port 3700 for IIOP.
      [INFO] [talledLocalContainer] Using port 8181 for HTTP_SSL.
      [INFO] [talledLocalContainer] Using port 3820 for IIOP_SSL.
      [INFO] [talledLocalContainer] Using port 3920 for IIOP_MUTUALAUTH.
      [INFO] [talledLocalContainer] Using port 8686 for JMX_ADMIN.
      [INFO] [talledLocalContainer] Using port 6666 for OSGI_SHELL.
      [INFO] [talledLocalContainer] Using port 9009 for JAVA_DEBUGGER.
      [INFO] [talledLocalContainer] Distinguished Name of the self-signed X.509 Server Certificate is:
      [INFO] [talledLocalContainer] [CN=MacBook-de-Christophe.local,OU=GlassFish,O=Oracle Corporation,L=Santa Clara,ST=California,C=US]
      [INFO] [talledLocalContainer] Distinguished Name of the self-signed X.509 Server Certificate is:
      [INFO] [talledLocalContainer] [CN=MacBook-de-Christophe.local-instance,OU=GlassFish,O=Oracle Corporation,L=Santa Clara,ST=California,C=US]
      [INFO] [talledLocalContainer] Domain cargo-domain created.
      [INFO] [talledLocalContainer] Domain cargo-domain admin port is 4848.
      [INFO] [talledLocalContainer] Domain cargo-domain admin user is "admin".
      [INFO] [talledLocalContainer] Command create-domain executed successfully.
      [INFO] [talledLocalContainer] GlassFish 4.x starting...
      [INFO] [talledLocalContainer] Java HotSpot(TM) 64-Bit Server VM warning: ignoring option PermSize=48m; support was removed in 8.0
      [INFO] [talledLocalContainer] Java HotSpot(TM) 64-Bit Server VM warning: ignoring option MaxPermSize=128m; support was removed in 8.0
      [INFO] [talledLocalContainer] Waiting for cargo-domain to start ......
      [INFO] [talledLocalContainer] Successfully started the domain : cargo-domain
      [INFO] [talledLocalContainer] domain  Location: /Users/christophecerin/glassfish5/samples/servlet/non-blocking-io-read-war/target/cargo/installs/glassfish/glassfish/domains/cargo-domain
      [INFO] [talledLocalContainer] Log File: /Users/christophecerin/glassfish5/samples/servlet/non-blocking-io-read-war/target/cargo/installs/glassfish/glassfish/domains/cargo-domain/logs/server.log
      [INFO] [talledLocalContainer] Admin Port: 4848
      [INFO] [talledLocalContainer] Command start-domain executed successfully.
      [INFO] [talledLocalContainer] Application deployed with name non-blocking-io-read-war.
      [INFO] [talledLocalContainer] Command deploy executed successfully.
      [INFO] [talledLocalContainer] Application deployed with name cargocpc.
      [INFO] [talledLocalContainer] Command deploy executed successfully.
      [INFO] [talledLocalContainer] GlassFish 4.x started on port [8080]
      [INFO] Press Ctrl-C to stop the container...
  

Last but not least, the previous Web application is now available at URL http://localhost:8080/non-blocking-io-read-war. Enjoy!

3- Servlet and filtering for the Weather Station (JEE)

Servlets are the Java programs that runs on the Java-enabled web server or application server. They are used to handle the request obtained from the web server, process the request, produce the response, then send response back to the web server.
Properties of Servlets :

Execution of Servlets :
Execution of Servlets involves the six basic steps:

  1. The clients send the request to the web server.
  2. The web server receives the request.
  3. The web server passes the request to the corresponding servlet.
  4. The servlet processes the request and generate the response in the form of output.
  5. The servlet send the response back to the web server.
  6. The web server sends the response back to the client and the client browser displays it on the screen.

Servlet Architecture

The following diagram shows the servlet architecture:

Advantages of a Java Servlet

The Servlet Container

Servlet container, also known as Servlet engine is an integrated set of objects that provide run time environment for Java Servlet components.

In simple words, it is a system that manages Java Servlet components on top of the Web server to handle the Web client requests.

Description of the sample application

This sample application that simulates a Weather Station demonstrates (a) how to use the non-blocking I/O functionality in servlets (b) how to parse the arguments of the URL (c) how to do a curl query and how to parse and analyze the respond.

The code is available online HERE.

In this sample application, the client (ClientTest) sends two parts of data to the server (ServerTest). The server registers a ReadListener, whose methods are called when events related to the input stream occur. The implementation of ReadListener in this application collects data and echoes it back in reverse order.

The server executes the reverse operation, in a non blocking manner. This means that the server is not blocked until the end of the conversation with the client.

Then, we proceed with the execution of a code that examine the parameters on the URL line. The client prints the name and values of the parameters. This is a concrete example demonstrating the analysis of parameters i.e. how to communicate from the external world with the client's program.

At last, the client executes the curl query in order to download the information about the city, given in a parameter (city code), on the URL line.

Notice that all the useful work is accomplished in the client. We could also balance the work by making the server executing the Internet query. This is just a choice!

Client

In ClientTest, the client initiates an HTTP connection to the server and writes two parts of data with a two second pause between them, which simulates blocking from the client side.

  @WebServlet(name = "ClientTest", urlPatterns = {"/"})
  public class ClientTest extends HttpServlet {
  OutputStream output = null;
  InputStream input = null;

  protected void processRequest(HttpServletRequest request, HttpServletResponse response)
  throws ServletException, IOException {
  ...
  String urlPath = "http://"
  + request.getServerName()
  + ":" + request.getLocalPort() //default http port is 8080
  + request.getContextPath()
  + "/ServerTest";

  URL url = new URL(urlPath);

  HttpURLConnection conn = (HttpURLConnection) url.openConnection();
  ...
  conn.connect();
  ...
  output = conn.getOutputStream();
  String firstPart = "Hello";
  writeData(output, firstPart);
  Thread.sleep(2000);
  ...
  // Sending the second part
  input = conn.getInputStream();
  printEchoData(out, input);
  }

  protected void writeData(OutputStream output, String data) throws IOException {
  if (data != null && !data.equals("") && output != null) {
  output.write(data.getBytes());
  output.flush();
  }
  }

  protected void printEchoData(PrintWriter out, InputStream input) throws IOException {
  while (input.available() > 0 && input != null && out != null) {
  out.print((char) input.read());
  }
  out.println("</br>");
  }
  }

The servlet uses OutputStream and InputStream to write and read data, and Thread.sleep() to pause the thread for two seconds to simulate I/O blocking. You can send larger blocks of data instead of "Hello World".

Then the servlet executes the code to analyse the URL parameters:

  @WebServlet(name = "ClientTest", urlPatterns = {"/*"})
  public class ClientTest extends HttpServlet {

  protected void processRequest(HttpServletRequest request, HttpServletResponse response)
  throws ServletException, IOException {
  response.setContentType("text/html;charset=UTF-8");

  ...

  // Start parsing of parameters
  Enumeration paramNames = request.getParameterNames();
  out.print("<h1> Parameter Analysis...</h1>");
  out.println("<table border=1 cellpadding=5 cellspacing=5>");
    //out.println("<table>");
      out.println("<tr> <th>Parameter Name</th>" +
	"<th>Parameter Value</th></tr>");
      while(paramNames.hasMoreElements())
      {
      String paramName = (String)paramNames.nextElement();
      out.print("<tr><td>" + paramName + "\n<td>");
	  String[] paramValues = request.getParameterValues(paramName);
	  if (paramValues.length == 1)
	  {
	  String paramValue = paramValues[0];
	  if (paramValue.length() == 0)
	  out.println("No Value");
	  else
	  out.println(paramValue);
	  }
	  else
	  {
	    out.println("<ul>");
	    for(int i=0; i<paramValues.length; i++)
	       {
                 out.println("<li>" + paramValues[i] + "</li>");
	       }
	    out.println("</ul>");
	  }
	 }
	 out.println("</table>");
	 out.flush();
	 ...
	 ...

Then the client execute the querry to the Internet in order to get the wheather of the city specified as an argument of the URL. In the first implementation, We store the answer into a file in the /tmp/your_user_name/Cities/ directory. The code is as follows:

  ProcessBuilder pb = new ProcessBuilder(
         "/usr/bin/curl",
         "-s",
         "http://worldweather.wmo.int/fr/json/" + city + "_fr.xml");

  pb.directory(new File("/tmp/your_user_name/Cities"));
  pb.redirectErrorStream(true);
  Process p = pb.start();
  InputStream is = p.getInputStream();

  FileOutputStream outputStream = new FileOutputStream(
          "/tmp/your_user_name/Cities/" + city + "_fr.json");

  BufferedInputStream bis = new BufferedInputStream(is);
  byte[] bytes = new byte[100];
  int numberByteReaded;
  while ((numberByteReaded = bis.read(bytes, 0, 100)) != -1) {
         outputStream.write(bytes, 0, numberByteReaded);
         Arrays.fill(bytes, (byte) 0);
  }
  outputStream.flush();
  outputStream.close();

In the second implementation, we transfer the answer of the Python code which analyze the answer, to the code that transforms an InputStream parameter to a String one. The code for transforming the types is String theString = IOUtils.toString(iss, "UTF-8");. The final portion of code is:

  ProcessBuilder pbb = new ProcessBuilder(
      "python",
      "/Users/christophecerin/glassfish5/samples/servlet/weather-station-war/src/main/java/org/glassfish/servlet/weather_station_war/parse.py",
      "/tmp/your_user_name/Cities/"+city+"_fr.json");
  pbb.redirectErrorStream(true);
  Process pp = pbb.start();
  InputStream iss = pp.getInputStream();
  String theString = IOUtils.toString(iss, "UTF-8");

Server

In ServerTest, the server receives the request, starts the asynchronous processing of the request, and registers a ReadListener

  @WebServlet(name="ServerTest", urlPatterns={"/ServerTest"}, asyncSupported = true)
  public class ServerTest extends HttpServlet {

  protected void processRequest(HttpServletRequest request, HttpServletResponse response)
  throws ServletException, IOException {
  ...
  final AsyncContext context = request.startAsync();
  final ServletInputStream input = request.getInputStream();
  final ServletOutputStream output = response.getOutputStream();
  input.setReadListener(new ServerReadListenerImpl(input, output, context));
  }
  ...
  }

@WebServlet(..., asyncSupported = true) is an annotation that specifies the servlet name, its URL, and enables asynchronous processing. The setReadListener() method registers a read listener for the input stream.

Note: Non-blocking I/O only works with asynchronous request processing in servlets and filters.

Implementation of the Read Listener

ReadListenerImpl.java is the implementation of the ReadListener interface:

  public class ReadListenerImpl implements ReadListener {

  public ServerReadListenerImpl() {
  ...
  }

  @Override
  public void onDataAvailable() {
  ...
  while (input.isReady() && !input.isFinished()) {
  sb.append((char) input.read()); // Use StringBuilder to append chars together
  }
  }

  @Override
  public void onAllDataRead() {
  ...
  output.print("Echo the reverse String from server: " + sb.reverse().toString() + "<br>");
  output.flush();
  ...
  }

  @Override
  public void onError(Throwable t) {
  ...
  System.out.println("--> onError");
			}
			

The onDataAvailable() method is invoked when data is available to be read from the input request stream. The container subsequently invokes the read() method if and only if isReady() returns true. The onAllDataRead() method is invoked when all the data from the request has been read. The onError(Throwable t) method is invoked if there is any error or exceptions occurs while processing the request. The isReady() method returns true if the underlying data stream is not blocked. At this point, the container invokes the onDataAvailable() method.

You can customize the constructor to handle different parameters. Usually, the parameters are ServletInputStream, ServletOutputStream, or AsyncContext. This sample uses all of them to implement the ReadListener interface.

Key Features

This sample application demonstrates the following key features:

To compile and to start the execution, do the following sequence of actions:

  MacBook-de-Christophe:weather-station-war christophecerin$ mvn clean
  [INFO] Scanning for projects...
  [INFO]
  [INFO] -------------< org.glassfish-samples:weather-station-war >--------------
    [INFO] Building weather-station-war 5.0-SNAPSHOT
    [INFO] --------------------------------[ war ]---------------------------------
    [INFO]
    [INFO] --- maven-clean-plugin:2.3:clean (default-clean) @ weather-station-war ---
    [INFO] Deleting file set: /Users/christophecerin/glassfish5/samples/servlet/weather-station-war/target (included: [**], excluded: [])
    [INFO] ------------------------------------------------------------------------
    [INFO] BUILD SUCCESS
    [INFO] ------------------------------------------------------------------------
    [INFO] Total time:  0.471 s
    [INFO] Finished at: 2019-05-02T09:18:27+08:00
    [INFO] ------------------------------------------------------------------------
    MacBook-de-Christophe:weather-station-war christophecerin$ mvn install
    [INFO] Scanning for projects...
    [INFO]
    [INFO] -------------< org.glassfish-samples:weather-station-war >--------------
      [INFO] Building weather-station-war 5.0-SNAPSHOT
      [INFO] --------------------------------[ war ]---------------------------------
      [INFO]
      [INFO] --- maven-resources-plugin:2.6:resources (default-resources) @ weather-station-war ---
      [INFO] Using 'UTF-8' encoding to copy filtered resources.
      [INFO] Copying 3 resources
      [INFO]
      [INFO] --- maven-compiler-plugin:3.6.1:compile (default-compile) @ weather-station-war ---
      [INFO] Changes detected - recompiling the module!
      [INFO] Compiling 3 source files to /Users/christophecerin/glassfish5/samples/servlet/weather-station-war/target/classes
      [INFO]
      [INFO] --- maven-resources-plugin:2.6:testResources (default-testResources) @ weather-station-war ---
      [INFO] Using 'UTF-8' encoding to copy filtered resources.
      [INFO] skip non existing resourceDirectory /Users/christophecerin/glassfish5/samples/servlet/weather-station-war/src/test/resources
      [INFO]
      [INFO] --- maven-compiler-plugin:3.6.1:testCompile (default-testCompile) @ weather-station-war ---
      [INFO] No sources to compile
      [INFO]
      [INFO] --- maven-surefire-plugin:2.12.4:test (default-test) @ weather-station-war ---
      [INFO] No tests to run.
      [INFO]
      [INFO] --- maven-war-plugin:2.2:war (default-war) @ weather-station-war ---
      [INFO] Packaging webapp
      [INFO] Assembling webapp [weather-station-war] in [/Users/christophecerin/glassfish5/samples/servlet/weather-station-war/target/weather-station-war]
      [INFO] Processing war project
      [INFO] Copying webapp resources [/Users/christophecerin/glassfish5/samples/servlet/weather-station-war/src/main/webapp]
      [INFO] Webapp assembled in [58 msecs]
      [INFO] Building war: /Users/christophecerin/glassfish5/samples/servlet/weather-station-war/target/weather-station-war.war
      [INFO]
      [INFO] --- maven-install-plugin:2.4:install (default-install) @ weather-station-war ---
      [INFO] Installing /Users/christophecerin/glassfish5/samples/servlet/weather-station-war/target/weather-station-war.war to /Users/christophecerin/.m2/repository/org/glassfish-samples/weather-station-war/5.0-SNAPSHOT/weather-station-war-5.0-SNAPSHOT.war
      [INFO] Installing /Users/christophecerin/glassfish5/samples/servlet/weather-station-war/pom.xml to /Users/christophecerin/.m2/repository/org/glassfish-samples/weather-station-war/5.0-SNAPSHOT/weather-station-war-5.0-SNAPSHOT.pom
      [INFO] ------------------------------------------------------------------------
      [INFO] BUILD SUCCESS
      [INFO] ------------------------------------------------------------------------
      [INFO] Total time:  2.420 s
      [INFO] Finished at: 2019-05-02T09:18:34+08:00
      [INFO] ------------------------------------------------------------------------
      MacBook-de-Christophe:weather-station-war christophecerin$ mvn cargo:run
      [INFO] Scanning for projects...
      [INFO]
      [INFO] -------------< org.glassfish-samples:weather-station-war >--------------
	[INFO] Building weather-station-war 5.0-SNAPSHOT
	[INFO] --------------------------------[ war ]---------------------------------
	[INFO]
	[INFO] --- cargo-maven2-plugin:1.4.0:run (default-cli) @ weather-station-war ---
	[INFO] [en2.ContainerRunMojo] Resolved container artifact org.codehaus.cargo:cargo-core-container-glassfish:jar:1.4.0 for container glassfish4x
	[INFO] [talledLocalContainer] Using port 4848 for Admin.
	[INFO] [talledLocalContainer] Using port 8080 for HTTP Instance.
	[INFO] [talledLocalContainer] Using port 7676 for JMS.
	[INFO] [talledLocalContainer] Using port 3700 for IIOP.
	[INFO] [talledLocalContainer] Using port 8181 for HTTP_SSL.
	[INFO] [talledLocalContainer] Using port 3820 for IIOP_SSL.
	[INFO] [talledLocalContainer] Using port 3920 for IIOP_MUTUALAUTH.
	[INFO] [talledLocalContainer] Using port 8686 for JMX_ADMIN.
	[INFO] [talledLocalContainer] Using port 6666 for OSGI_SHELL.
	[INFO] [talledLocalContainer] Using port 9009 for JAVA_DEBUGGER.
	[INFO] [talledLocalContainer] Distinguished Name of the self-signed X.509 Server Certificate is:
	[INFO] [talledLocalContainer] [CN=Campusnetwork.net.hziee.edu.cn,OU=GlassFish,O=Oracle Corporation,L=Santa Clara,ST=California,C=US]
	[INFO] [talledLocalContainer] Distinguished Name of the self-signed X.509 Server Certificate is:
	[INFO] [talledLocalContainer] [CN=Campusnetwork.net.hziee.edu.cn-instance,OU=GlassFish,O=Oracle Corporation,L=Santa Clara,ST=California,C=US]
	[INFO] [talledLocalContainer] Domain cargo-domain created.
	[INFO] [talledLocalContainer] Domain cargo-domain admin port is 4848.
	[INFO] [talledLocalContainer] Domain cargo-domain admin user is "admin".
	[INFO] [talledLocalContainer] Command create-domain executed successfully.
	[INFO] [talledLocalContainer] GlassFish 4.x starting...
	[INFO] [talledLocalContainer] Java HotSpot(TM) 64-Bit Server VM warning: ignoring option PermSize=48m; support was removed in 8.0
	[INFO] [talledLocalContainer] Java HotSpot(TM) 64-Bit Server VM warning: ignoring option MaxPermSize=128m; support was removed in 8.0
	[INFO] [talledLocalContainer] Waiting for cargo-domain to start ......
	[INFO] [talledLocalContainer] Successfully started the domain : cargo-domain
	[INFO] [talledLocalContainer] domain  Location: /Users/christophecerin/glassfish5/samples/servlet/weather-station-war/target/cargo/installs/glassfish/glassfish/domains/cargo-domain
	[INFO] [talledLocalContainer] Log File: /Users/christophecerin/glassfish5/samples/servlet/weather-station-war/target/cargo/installs/glassfish/glassfish/domains/cargo-domain/logs/server.log
	[INFO] [talledLocalContainer] Admin Port: 4848
	[INFO] [talledLocalContainer] Command start-domain executed successfully.
	[INFO] [talledLocalContainer] Application deployed with name weather-station-war.
	[INFO] [talledLocalContainer] Command deploy executed successfully.
	[INFO] [talledLocalContainer] Application deployed with name cargocpc.
	[INFO] [talledLocalContainer] Command deploy executed successfully.
	[INFO] [talledLocalContainer] GlassFish 4.x started on port [8080]
	[INFO] Press Ctrl-C to stop the container...

It remains to explore the weather, here for Clermont-Ferrand in France, with a query like http://localhost:8080/weather-station-war/?City=1052.

4- JavaMail (JEE)

A good introduction to Java mail is available online at this address or from the main repository.

You probably need to get the following two jar files javax.mail.jar, activation.jar in order to be able to complie and to execute the samples. You can download the java mail jar file as well as sample examples from https://github.com/javaee/javamail/releases and the activation jar file from https://www.oracle.com/technetwork/java/jaf11-139815.html.

This is an example on how to compile and send a message with a sample coming with the java mail examples:

cerin@ankara:~/public_html/HDU$ javac -cp ./javax.mail.jar:./activation.jar:.  msgsendsample.java
cerin@ankara:~/public_html/HDU$ java -cp ./javax.mail.jar:./activation.jar:.  msgsendsample christophe.cerin@orange.fr christophe.cerin@lipn.univ-paris13.fr mail.lipn.univ-paris13.fr true

DEBUG: JavaMail version 1.6.2
DEBUG: successfully loaded resource: /META-INF/javamail.default.address.map
DEBUG: setDebug: JavaMail version 1.6.2
DEBUG: getProvider() returning javax.mail.Provider[TRANSPORT,smtp,com.sun.mail.smtp.SMTPTransport,Oracle]
DEBUG SMTP: useEhlo true, useAuth false
DEBUG SMTP: trying to connect to host "mail.lipn.univ-paris13.fr", port 25, isSSL false
220 mail.lipn.univ-paris13.fr ESMTP Postfix;
DEBUG SMTP: connected to host "mail.lipn.univ-paris13.fr", port: 25
EHLO luxembourg.lipn.univ-paris13.fr
250-mail.lipn.univ-paris13.fr
250-PIPELINING
250-SIZE 50000000
250-ETRN
250-STARTTLS
250-ENHANCEDSTATUSCODES
250-8BITMIME
250 DSN
DEBUG SMTP: Found extension "PIPELINING", arg ""
DEBUG SMTP: Found extension "SIZE", arg "50000000"
DEBUG SMTP: Found extension "ETRN", arg ""
DEBUG SMTP: Found extension "STARTTLS", arg ""
DEBUG SMTP: Found extension "ENHANCEDSTATUSCODES", arg ""
DEBUG SMTP: Found extension "8BITMIME", arg ""
DEBUG SMTP: Found extension "DSN", arg ""
DEBUG SMTP: use8bit false
MAIL FROM:
250 2.1.0 Ok
RCPT TO:
250 2.1.5 Ok
DEBUG SMTP: Verified Addresses
DEBUG SMTP:   christophe.cerin@orange.fr
DATA
354 End data with .
Date: Fri, 3 May 2019 02:10:29 +0200 (CEST)
From: christophe.cerin@lipn.univ-paris13.fr
To: christophe.cerin@orange.fr
Message-ID: <943968903.0.1556842229376@luxembourg.lipn.univ-paris13.fr>
Subject: JavaMail APIs Test
MIME-Version: 1.0
Content-Type: text/plain; charset=us-ascii
Content-Transfer-Encoding: 7bit

This is a message body.
Here's the second line.
.
250 2.0.0 Ok: queued as 7813E261407
DEBUG SMTP: message successfully delivered to mail server
QUIT
221 2.0.0 Bye
cerin@ankara:~/public_html/HDU$ ps -aux | grep -i postfix
root      2608  0.0  0.0  30536  3816 ?        Ss   avril08   0:06 /usr/lib/postfix/master
postfix   2612  0.0  0.0  32540  3896 ?        S    avril08   0:02 qmgr -l -t unix -u
postfix  19036  0.0  0.0  32388  3440 ?        S    01:57   0:00 pickup -l -t unix -u -c
cerin    19820  0.0  0.0  15484  2260 pts/7    S+   02:11   0:00 grep -i postfix
cerin@ankara:~/public_html/HDU$ 

Special note: the machine that executes the previous java command should be configured as a relay. You will probably need to run Postfix on your machine. To start a Postfix service on an Unix machine do as follows:

MacBook-de-Christophe:javamail-samples christophecerin$ sudo postfix start
Password:
postfix/postfix-script: starting the Postfix mail system
MacBook-de-Christophe:javamail-samples christophecerin$ java -cp ../javax.mail.jar:../activation.jar:. msgsendsample christophe.cerin@orange.fr christophe.cerin@lipn.univ-paris13.fr mail.lipn.univ-paris13.fr true

DEBUG: JavaMail version 1.6.2
DEBUG: successfully loaded resource: /META-INF/javamail.default.address.map
DEBUG: setDebug: JavaMail version 1.6.2
DEBUG: getProvider() returning javax.mail.Provider[TRANSPORT,smtp,com.sun.mail.smtp.SMTPTransport,Oracle]
DEBUG SMTP: useEhlo true, useAuth false
DEBUG SMTP: trying to connect to host "mail.lipn.univ-paris13.fr", port 25, isSSL false
220 mail.lipn.univ-paris13.fr ESMTP Postfix;
DEBUG SMTP: connected to host "mail.lipn.univ-paris13.fr", port: 25
EHLO 192.168.125.115
250-mail.lipn.univ-paris13.fr
250-PIPELINING
250-SIZE 50000000
250-ETRN
250-STARTTLS
250-ENHANCEDSTATUSCODES
250-8BITMIME
250 DSN
DEBUG SMTP: Found extension "PIPELINING", arg ""
DEBUG SMTP: Found extension "SIZE", arg "50000000"
DEBUG SMTP: Found extension "ETRN", arg ""
DEBUG SMTP: Found extension "STARTTLS", arg ""
DEBUG SMTP: Found extension "ENHANCEDSTATUSCODES", arg ""
DEBUG SMTP: Found extension "8BITMIME", arg ""
DEBUG SMTP: Found extension "DSN", arg ""
DEBUG SMTP: use8bit false
MAIL FROM:
250 2.1.0 Ok
RCPT TO:
404 4.5.2 <192.168.125.115>: Helo command rejected: need fully-qualified hostname
DEBUG SMTP: Valid Unsent Addresses
DEBUG SMTP:   christophe.cerin@orange.fr
DEBUG SMTP: Sending failed because of invalid destination addresses
RSET
250 2.0.0 Ok
DEBUG SMTP: MessagingException while sending, THROW:
javax.mail.SendFailedException: Invalid Addresses;
nested exception is:
com.sun.mail.smtp.SMTPAddressFailedException: 404 4.5.2 <192.168.125.115>: Helo command rejected: need fully-qualified hostname

at com.sun.mail.smtp.SMTPTransport.rcptTo(SMTPTransport.java:2079)
at com.sun.mail.smtp.SMTPTransport.sendMessage(SMTPTransport.java:1301)
at javax.mail.Transport.send0(Transport.java:255)
at javax.mail.Transport.send(Transport.java:124)
at msgsendsample.main(msgsendsample.java:86)
Caused by: com.sun.mail.smtp.SMTPAddressFailedException: 404 4.5.2 <192.168.125.115>: Helo command rejected: need fully-qualified hostname

at com.sun.mail.smtp.SMTPTransport.rcptTo(SMTPTransport.java:1979)
... 4 more
QUIT
221 2.0.0 Bye

--Exception handling in msgsendsample.java
javax.mail.SendFailedException: Invalid Addresses;
nested exception is:
com.sun.mail.smtp.SMTPAddressFailedException: 404 4.5.2 <192.168.125.115>: Helo command rejected: need fully-qualified hostname

at com.sun.mail.smtp.SMTPTransport.rcptTo(SMTPTransport.java:2079)
at com.sun.mail.smtp.SMTPTransport.sendMessage(SMTPTransport.java:1301)
at javax.mail.Transport.send0(Transport.java:255)
at javax.mail.Transport.send(Transport.java:124)
at msgsendsample.main(msgsendsample.java:86)
Caused by: com.sun.mail.smtp.SMTPAddressFailedException: 404 4.5.2 <192.168.125.115>: Helo command rejected: need fully-qualified hostname

at com.sun.mail.smtp.SMTPTransport.rcptTo(SMTPTransport.java:1979)
... 4 more

** Invalid Addresses
** ValidUnsent Addresses
christophe.cerin@orange.fr


MacBook-de-Christophe:javamail-samples christophecerin$ java -cp ../javax.mail.jar:../activation.jar:. msgsendsample christophe.cerin@orange.fr christophe.cerin@lipn.univ-paris13.fr localhost true

DEBUG: JavaMail version 1.6.2
DEBUG: successfully loaded resource: /META-INF/javamail.default.address.map
DEBUG: setDebug: JavaMail version 1.6.2
DEBUG: getProvider() returning javax.mail.Provider[TRANSPORT,smtp,com.sun.mail.smtp.SMTPTransport,Oracle]
DEBUG SMTP: useEhlo true, useAuth false
DEBUG SMTP: trying to connect to host "localhost", port 25, isSSL false
220 MacBook-de-Christophe.local ESMTP Postfix
DEBUG SMTP: connected to host "localhost", port: 25
EHLO 192.168.125.115
250-MacBook-de-Christophe.local
250-PIPELINING
250-SIZE 10485760
250-VRFY
250-ETRN
250-ENHANCEDSTATUSCODES
250-8BITMIME
250-DSN
250 SMTPUTF8
DEBUG SMTP: Found extension "PIPELINING", arg ""
DEBUG SMTP: Found extension "SIZE", arg "10485760"
DEBUG SMTP: Found extension "VRFY", arg ""
DEBUG SMTP: Found extension "ETRN", arg ""
DEBUG SMTP: Found extension "ENHANCEDSTATUSCODES", arg ""
DEBUG SMTP: Found extension "8BITMIME", arg ""
DEBUG SMTP: Found extension "DSN", arg ""
DEBUG SMTP: Found extension "SMTPUTF8", arg ""
DEBUG SMTP: use8bit false
MAIL FROM:
250 2.1.0 Ok
RCPT TO:
250 2.1.5 Ok
DEBUG SMTP: Verified Addresses
DEBUG SMTP:   christophe.cerin@orange.fr
DATA
354 End data with .
Date: Fri, 3 May 2019 08:39:58 +0800 (CST)
From: christophe.cerin@lipn.univ-paris13.fr
To: christophe.cerin@orange.fr
Message-ID: <2075203460.0.1556843998941@[192.168.125.115]>
Subject: JavaMail APIs Test
MIME-Version: 1.0
Content-Type: text/plain; charset=us-ascii
Content-Transfer-Encoding: 7bit

This is a message body.
Here's the second line.
.
250 2.0.0 Ok: queued as 19E2B2017F5F8F
DEBUG SMTP: message successfully delivered to mail server
QUIT
221 2.0.0 Bye
MacBook-de-Christophe:javamail-samples christophecerin$

Special special note: to configure your Postfix sender (on a Mac) please have a look to https://benjaminrojas.net/configuring-postfix-to-send-mail-from-mac-os-x-mountain-lion/. In this tutorial the local postfix program will contact Google mail and, after an authentication with Gmail, with your Google login and password, will send your emails. In that way, people will received your emails according to your Google email address. This method does not act as a plain mail server. Google mail is used as a relay. Your local machine has not been configured as a mail server by itself!


Christophe Cérin
christophe.cerin@univ-paris13.fr
April-May, 2019