Java Web


Creative Commons License
This -Java Web- tutorial is licensed under a Creative Commons Attribution-NonCommercial 4.0 International License
Preamble
Headlines
JavaScript Object Notation (JSON) is a data exchange format that has native support in JavaScript. The need for direct and easy processing in Java requires an ad hoc library.
Rule(s)
Scenario(s)
Open Exchange Rates is a professional currency conversion service (paying service with free simplified version). Service consumption is based on JSON as follows…
Example (connection to the service)
/**
* Currency exchange rate has a default base, which is 'USD'. For
* simplification, 'fromCurrency' is not used
*/
public static double convert(String licenseKey, /* String fromCurrency, */ String toCurrency, double amount) throws java.net.MalformedURLException, java.io.IOException {
    java.net.URL url = new java.net.URL("http://openexchangerates.org/api/latest.json" + "?app_id=" + licenseKey); // Subscription to the service is mandatory to get a license key!
    java.net.URLConnection connection = (java.net.URLConnection) url.openConnection();
    javax.json.stream.JsonParserFactory factory = javax.json.Json.createParserFactory(null);
    // Effective connection with the service:
    javax.json.stream.JsonParser parser = factory.createParser(connection.getInputStream());
    (…)
Example (get data from JSON processing)
   (…)
    while (parser.hasNext()) {
        javax.json.stream.JsonParser.Event event = parser.next();
        if (event == javax.json.stream.JsonParser.Event.KEY_NAME && parser.getString().equals(toCurrency)) {
            while (parser.hasNext()) {
                event = parser.next();
                if (event == javax.json.stream.JsonParser.Event.VALUE_NUMBER) {
                    amount *= parser.getBigDecimal().doubleValue();
                    break;
                }
            }
        }
    }
    return amount;
}
Scenario(s)

Carrefour.io is the digital vision of the Carrefour French retailer. Carrefour.io offers the Items the Stores APIs. Queries about items (i.e., common consumer products) return JSON raw data that may require post-processing as in the following example.

A similar scenario is developed here… based on the Async Http Client library. However, please note that, since Java 11, HTTP-based requests are totally asynchronous (further detail here…) using the java.util.concurrent.CompleteableFuture<T> class.

Example (first, connection to the service by means of the OkHttp library)
private void _get_items_(java.time.Duration time_out) { // OkHttp library: http://square.github.io/okhttp/
    assert (!time_out.isNegative() && !time_out.isZero());
    if (_json_query != null && _json_result == null) {
        _get_items_elapsed_time = System.currentTimeMillis();
        okhttp3.OkHttpClient.Builder builder = new okhttp3.OkHttpClient.Builder();
        builder.connectTimeout(time_out);
        builder.readTimeout(time_out);
        // builder.writeTimeout(time_out);
        okhttp3.OkHttpClient client = builder.build();

        okhttp3.Request request = new okhttp3.Request.Builder()
            .url(_url) // ' private final static String _url = "https://api.fr.carrefour.io/v1/openapi/items";'
            .post(okhttp3.RequestBody.create(okhttp3.MediaType.parse("application/json"), _json_query))
            .addHeader("accept", "application/json")
            .addHeader("content-type", "application/json")
            .addHeader("x-ibm-client-id", _x_ibm_client_id)
            .addHeader("x-ibm-client-secret", _x_ibm_client_secret)
            .build();
        try (okhttp3.Response response = client.newCall(request).execute()) {
            java.io.InputStream is = response.body().source().inputStream();
            _result_as_pretty_JSON(is); // Versus '_result_as_raw_JSON(is);'
            // 'close' is guaranteed by try-with-resource(s)
        } catch (java.io.IOException ioe) {
             _json_result = javax.json.Json.createObjectBuilder().add(_ERROR, ioe.getMessage()).build().toString();
            System.err.println(ioe.getClass().getSimpleName() + ": " + ioe.getMessage());
        } finally {
             _get_items_elapsed_time = System.currentTimeMillis() - _get_items_elapsed_time;
        }
    }
}
Example (next, transform raw JSON data into indented format)
private void _result_as_pretty_JSON(java.io.InputStream is) /*throws java.io.IOException*/ {
//        assert (is != null && is.available() == 0); // Official Java doc.: 'The available method for class InputStream always returns 0.'
        java.util.Map<String, Boolean> properties = new java.util.HashMap<>();
        properties.put(javax.json.stream.JsonGenerator.PRETTY_PRINTING, true);
        java.io.StringWriter sw = new java.io.StringWriter();
        try (javax.json.JsonWriter jw = javax.json.Json.createWriterFactory(properties).createWriter(sw)) {
            jw.writeObject(javax.json.Json.createReader(is).readObject());
            // 'close' is guaranteed by try-with-resource(s)
        }
        _json_result = sw.toString();
        _counted_items = -1L; // Error
        try (javax.json.stream.JsonParser parser = javax.json.Json.createParser(new java.io.StringReader(_json_result))) {
            while (parser.hasNext()) {
                // '_LIST' key is associated with a value being an array whose elements are the returned items (by default, only 20 are returned):
                if (parser.next() == javax.json.stream.JsonParser.Event.KEY_NAME && parser.getString().equals(_LIST)) {
                    final javax.json.stream.JsonParser.Event event = parser.next();
                    assert (event == javax.json.stream.JsonParser.Event.START_ARRAY);
                    _counted_items = parser.getArray().size();
                }
            }
        }
    }
Resource(s)
Streams are revisited from Java 8 in order to support a more transparent processing of data, especially when data sets tend to be “big”. Streams are logically related data that a priori cannot stay in the computer memory as a whole. Streams also entail the idea of parallel processing without, in Java, the explicit use of threads that in essence create complex code. Transparency then comes from facilities from the Java 8 stream API that hides, as much as possible, multithreading.
Scenario(s)

private final static String _POULAIN = "{\"size\": 200,\"queries\": [{\"query\": \"poulain\",\"field\": \"barcodeDescription\"}]}"; as JSON query expects at most 200 items whose ‘brand’ or ‘description’ (according to the API doc., ‘barcodeDescription’ implies search in both ‘brand’ and ‘description’) value field contains the "poulain" literal (search is not case-sensitive) which, for most people in France, matches to a chocolate brand with derivatives such as cocoa powder, etc. Around 100 items are returned.

private final static String _POULAIN_NOISETTE = "{\"queries\": [{\"query\": \"poula\",\"field\": \"barcodeDescription\"},{\"query\": \"noiset\",\"field\": \"barcodeDescription\"}]}"; as JSON query returns less than 10 items due to the "noiset" literal (i.e., looking for chocolate with nuts) must also be present in the ‘brand’ or ‘description’ value field ("oiset" instead of "noiset" retrieves no item). More generally, the Carrefour.io Items API allows (cursory) filters that imply further filtering at local place (i.e., client program).

Queries' post-processing is the possibility of dealing with returned items for further filtering. For queries retrieving big data sets, the use of Java streams aims at accelerating filtering, through parallel processing in particular.

As an illustration, the following example simply gets a stream from raw JSON data (returned items from Carrefour.io Items API).

Example
try (javax.json.stream.JsonParser parser = javax.json.Json.createParser(new java.io.StringReader(_json_result))) {
    while (parser.hasNext()) {
        if (parser.next() == javax.json.stream.JsonParser.Event.START_ARRAY) { // The JSON parser points to a JSON array
            java.util.stream.Stream<javax.json.JsonValue> stream = parser.getArrayStream();
…
Rule(s)
Example
public void check_format() {
    try (javax.json.stream.JsonParser parser = javax.json.Json.createParser(new java.io.StringReader(_json_result))) {
        while (parser.hasNext()) {
            // '_LIST' key is associated with a value being an array whose elements are the returned items (by default, only 20 are returned):
            if (parser.next() == javax.json.stream.JsonParser.Event.KEY_NAME && parser.getString().equals(_LIST)) {
                final javax.json.stream.JsonParser.Event event = parser.next();
                assert (event == javax.json.stream.JsonParser.Event.START_ARRAY);

                java.util.stream.Stream<javax.json.JsonValue> stream = parser.getArrayStream();
// Check that all members are 'javax.json.JsonValue.ValueType.OBJECT':
                assert (stream
                    .allMatch(json_value -> json_value.getValueType() == javax.json.JsonValue.ValueType.OBJECT));               
// Check (based on map-reduce principle) that all members are 'javax.json.JsonValue.ValueType.OBJECT':
                java.util.Optional<Boolean> optional = stream
                    .map(json_value -> json_value.getValueType() == javax.json.JsonValue.ValueType.OBJECT)
                    .reduce((Boolean a, Boolean b) -> a & b);
                assert (optional.isPresent() && optional.get());
            }
        }
    }
}
Rule(s)
Example
stream
    .filter(json_value -> json_value.getValueType() == javax.json.JsonValue.ValueType.OBJECT)
    .peek(json_value -> System.out.println("Filtered value: " + json_value.asJsonObject().toString()))
    .forEach(new java.util.function.Consumer<javax.json.JsonValue>() {
        @Override
        public void accept(javax.json.JsonValue json_value) {
            _search_items_(json_value.asJsonObject(), words);
        }
    });
Resource(s)
See also
Eugen Baeldung Web site has a dedicated section on Java streams here
Streams work together with lambda expressions for code leanness.
Scenario(s)

java.util.function.Function<T, R> (a Java functional interface) is the type of the unique parameter of the map method for streams. This method returns a stream of R elements from T elements. Examples below without, and later with, lambda expressions show two equivalent forms of instantiating java.util.function.Function<T, R>.

Example (without lambda expression)
final javax.json.stream.JsonParser.Event event = parser.next();
assert (event == javax.json.stream.JsonParser.Event.START_ARRAY); // The JSON parser points to a JSON array
// For test only:
// parser.getArrayStream().forEach(System.out::println); // Stream is no longer usable afterwards!
java.util.stream.Stream<javax.json.JsonObject> stream = parser.getArrayStream()
    .map(new java.util.function.Function<javax.json.JsonValue, javax.json.JsonObject>() {
        @Override
        public javax.json.JsonObject apply(javax.json.JsonValue json_value) {
            // '(javax.json.JsonObject) json_value' <=> 'json_value.asJsonObject()'
            return (javax.json.JsonObject) json_value; // Be sure that array elements are actually JSON objects!
        }
    });
Rule(s)
Example (with lambda expression)
java.util.stream.Stream<javax.json.JsonObject> stream = parser.getArrayStream()
// '(javax.json.JsonObject) json_value' <=> 'json_value.asJsonObject()'
    .map(json_value -> (javax.json.JsonObject) json_value); // Be sure that array elements are actually JSON objects!
Resource(s)
Streams make parallelism eventually transparent.
Rule(s)
Example (stream is made parallel from the parallel Boolean variable at line 1)
java.util.stream.Stream<javax.json.JsonValue> stream = parallel ? parser.getArrayStream().parallel() : parser.getArrayStream();
assert (stream.isParallel() || !parallel);
stream
// '(javax.json.JsonObject) json_value' <=> 'json_value.asJsonObject()':
    .map(json_value -> (javax.json.JsonObject) json_value) // Be sure that array elements are actually JSON objects!
    .forEach(new java.util.function.Consumer<javax.json.JsonObject>() {
        @Override
        public void accept(javax.json.JsonObject json_object) {
            _search_items_(json_object, words);
        }
    });
Rule(s)
Example (trySplit)
private void _max_splitting(java.util.Set<java.util.Spliterator<javax.json.JsonObject>> split_iterators, java.util.Spliterator<javax.json.JsonObject> split_iterator) {
    // Line for test only:
    long split_iterator_estimateSize = split_iterator.estimateSize();
    java.util.Spliterator<javax.json.JsonObject> splitting = split_iterator.trySplit();
    if (splitting == null) {
        split_iterators.add(split_iterator);
    } else {
        // Line for test only:
        assert (split_iterator_estimateSize >= split_iterator.estimateSize() + splitting.estimateSize());
        _max_splitting(split_iterators, split_iterator);
        _max_splitting(split_iterators, splitting);
    }
}
Rule(s)
Example
java.util.List<javax.json.JsonObject> list = parser.getArrayStream()
    .map(json_value -> json_value.asJsonObject())
    .collect(java.util.stream.Collectors.toList()); // Reduction principle
java.util.Spliterator<javax.json.JsonObject> root_split_iterator = list.spliterator();
assert (root_split_iterator.characteristics() == (java.util.Spliterator.ORDERED | java.util.Spliterator.SIZED | java.util.Spliterator.SUBSIZED));
// Alternative:   
// java.util.Spliterator<javax.json.JsonObject> root_split_iterator = java.util.Spliterators.spliterator(list, (java.util.Spliterator.IMMUTABLE | java.util.Spliterator.NONNULL | java.util.Spliterator.ORDERED | java.util.Spliterator.SIZED | java.util.Spliterator.SUBSIZED));

java.util.Set<java.util.Spliterator<javax.json.JsonObject>> split_iterators = new java.util.HashSet<>();
_max_splitting(split_iterators, root_split_iterator); // Versus '_min_splitting(split_iterators, root_split_iterator);'
split_iterators.forEach((split_iterator) -> {
    split_iterator.forEachRemaining(json_object -> _search_items_(json_object, words));
    // Alternative:                        
    // boolean result;
    // do {
        // result = split_iterator.tryAdvance(json_object -> _search_items_(json_object, words));
    // } while (result);
});
Streams performance
Scenario(s)

The retrieval of 9.500 (requested size) items among (around) 20.000 items matching the query is followed by the application of the same (local) filter. This filtering is called 500 times with the same method in order to determine an average elapsed time. The No stream method filtering is realized with the help of filter_items while filter_items_ supports both the Sequential stream and Parallel stream methods ('parallel' argument equals to false or true). The java.util.Spliterator method is supported by filter_items__.

Measures
Method ⤳ No stream Sequential stream Parallel stream java.util.Spliterator
Elapsed time ≈ 250 ms ≈ 250 ms ≈ 250 ms ≈ 275 ms
Comments Common (end-to-end) JSON parsing Stream of JSON values (i.e., javax.json.JsonValue) from JSON parser: parser.getArrayStream() Stream of JSON values (i.e., javax.json.JsonValue) is switched to “parallel” as follows: parser.getArrayStream().parallel() Variations on trySplit) for minimum versus maximum splitting and characteristics provoke no effect on performance!

Surprisingly, the java.util.Spliterator method has a slight overhead while others lead to the same performance!
The consumption of Web services has been reviewed in Java 11 based on an asynchronous mode (here…). Unsurprisingly, synchronization facilities come from Java 8 parallelism framework.
Rule(s)
Example
private void _get_items__(java.time.Duration time_out) { // https://dzone.com/articles/java-11-standardized-http-client-api
    assert (!time_out.isNegative() && !time_out.isZero());
    if (_json_query != null && _json_result == null) {
        _get_items_elapsed_time = System.currentTimeMillis();

        var client = java.net.http.HttpClient.newBuilder().build();

        var request = java.net.http.HttpRequest.newBuilder(java.net.URI.create(_url))
            .POST(java.net.http.HttpRequest.BodyPublishers.ofString(_json_query))
            .headers("accept", "application/json",
                "content-type", "application/json; charset=UTF-8",
                "x-ibm-client-id", _x_ibm_client_id,
                "x-ibm-client-secret", _x_ibm_client_secret)
            .timeout(time_out)
            .build();

        java.util.concurrent.CompletableFuture<java.net.http.HttpResponse<java.io.InputStream>> response = client.sendAsync(request, java.net.http.HttpResponse.BodyHandlers.ofInputStream());
        try {
            // 'get' waits for result:
            var is = response.get().body();
            _result_as_pretty_JSON(is); // Versus '_result_as_raw_JSON(is);'
        } catch (java.util.concurrent.ExecutionException | InterruptedException ee_ie) {
            _json_result = javax.json.Json.createObjectBuilder().add(_ERROR, ee_ie.getMessage()).build().toString();
            System.err.println(ee_ie.getClass().getSimpleName() + ": " + ee_ie.getMessage());
        } finally {
            _get_items_elapsed_time = System.currentTimeMillis() - _get_items_elapsed_time;
        }
    }
}
WebSockets is a full-duplex technology in Internet programming. One may write a Java WebSockets server from scratch (here…) or reuse libraries like Java-Websocket, Jetty, Tyrus (Java reference implementation) or Vert.x.
Scenario(s)
A minimal JavaScript program connects to a WebSockets server. This lightweight server is written in Java and obeys the WebSockets protocol and API (here…).
Example (access to the WebSockets server)
window.onload = () => {
    // Tested with Tyrus 1.15 WebSockets Java library
    let service = new WebSocket("ws://localhost:1963/FranckBarbier/WebSockets_illustration");
    service.onmessage = (event) => {
        console.log("Message from Java: " + event.data);
    };
    service.onopen = () => {
        console.log("service.onopen...");
        let response = window.confirm(service.url + " just opened... Say 'Hi!'?");
        if (response)
            service.send(JSON.stringify({Response: "Hi!"}));
    };
    service.onclose = (event/*:CloseEvent*/) => {
        console.log("service.onclose... " + event.code);
        window.alert("Bye! See you later...");
// '1011': the server is terminating the connection because it encountered an unexpected condition that prevented it from fulfilling the request.
    };
    service.onerror = () => {
        window.alert("service.onerror...");
    };
};
Example (WebSockets server creation and start)
public class WebSockets_illustration {

    /**
     * Danger : il faut que le constructeur de 'My_ServerEndpoint' soit bien
     * accessible par le serveur WebSockets. Ne pas oublier 'static'!
     */
    @javax.websocket.server.ServerEndpoint(value = "/WebSockets_illustration")
    public static class My_ServerEndpoint {

        @javax.websocket.OnClose
        public void onClose(javax.websocket.Session session, javax.websocket.CloseReason close_reason) {
            System.out.println("onClose: " + close_reason.getReasonPhrase());
        }

        @javax.websocket.OnError
        public void onError(javax.websocket.Session session, Throwable throwable) {
            System.out.println("onError: " + throwable.getMessage());
        }

        @javax.websocket.OnMessage
        public void onMessage(javax.websocket.Session session, String message) {
            System.out.println("Message from JavaScript: " + message);
        }

        @javax.websocket.OnOpen
        public void onOpen(javax.websocket.Session session, javax.websocket.EndpointConfig ec) throws java.io.IOException {
            System.out.println("OnOpen... " + ec.getUserProperties().get("Author"));
            session.getBasicRemote().sendText("{Handshaking: \"Yes\"}");
        }
    }

    public static void main(String[] args) {

        java.util.Map<String, Object> user_properties = new java.util.HashMap();
        user_properties.put("Author", "FranckBarbier");

        org.glassfish.tyrus.server.Server server = new org.glassfish.tyrus.server.Server("localhost", 1963, "/FranckBarbier", user_properties /* or 'null' */, My_ServerEndpoint.class);
        try {
            server.start();
            // The Web page is launched from Java:
            java.awt.Desktop.getDesktop().browse(java.nio.file.FileSystems.getDefault().getPath("web" + java.io.File.separatorChar + "index.html").toUri());

            java.io.BufferedReader reader = new java.io.BufferedReader(new java.io.InputStreamReader(System.in));
            System.out.println("Please press a key to stop the server...");
            reader.readLine();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            server.stop();
        }
    }
}
Resource(s)