Habari Client libraries release 2019.06 preview

Habarisoft released the first preview for the upcoming 2019.06 release of its Object Pascal STOMP client libraries for open source message brokers (Apache ActiveMQ, ActiveMQ Artemis, Eclipse OpenMQ and Pivotal RabbitMQ).

Resources

Home page: https://www.habarisoft.com/index.html

Feature matrix: https://www.habarisoft.com/index.html#feature_matrix

FAQ: https://www.habarisoft.com/index.html#faq

Versions

The 2019.06 release preview includes:

The development snapshots are now available for download by registered users.

Upgrade discount

For existing users, upgrade discounts are available.

About Habari Client libraries

habari_logo_2016Habari Client libraries enable Object Pascal applications to take advantage of message broker / message queue technology – which is distributed, loosely coupled, reliable and asynchronous – to build integrated systems, using peer-to-peer and publish-subscribe communication models.

Advertisements

Message exchange example for Delphi and WildFly 16 application server

Red Hat yesterday released the WildFly 16 Final Java EE 8 application server. WildFly uses Apache ActiveMQ Artemis as the the default messaging provider. Artemis supports STOMP, which allows to connect from clients written in many programming languages.

If you need to exchange data between Delphi or Free Pascal programs and applications running on WildFly, you can follow these simple configuration steps and run a demonstration of bidirectional message exchange using the Habari Client for Artemis library from Habarisoft.

Read the full article here: https://habarisoft.wordpress.com/2019/02/28/connect-delphi-and-free-pascal-applications-with-wildfly-16/

Consuming Server-Sent Events (SSE) with Indy TIdHTTP and TIdEventStream

A new Indy HTTP client / JAX-RS server example is now available on GitHub. The server side generates Server-sent events. Server-sent events (SSE) is a technology enabling a browser to receive automatic updates from a server via HTTP connection.

The example code uses TIdHTTP and TIdEventStream to connect to the server, and writes the incoming events to the console window.

indy-sse-jaxrs

Requirements

  • Delphi or Lazarus IDE
  • Indy 10.6.2
  • Java IDE
  • Java EE 8 application server

Client

The SSE client setup in the TIndySSEClient.Create creates a TIdHTTP instance and a TIdEventStream instance.
As recommended, the Accept header is set to 'text/event-stream'. It also sets the Cache-Control header to 'no-store' to prevent proxy servers from caching the result.
Our event handler TIndySSEClient.MyOnWrite is assigned to the TIdEventStreamOnWrite property.

constructor TIndySSEClient.Create;
begin
  inherited Create;

  SSE_URL := URL;

  EventStream := TIdEventStream.Create;
  EventStream.OnWrite := MyOnWrite;

  IdHTTP := TIdHTTP.Create;
  IdHTTP.Request.Accept := 'text/event-stream';
  IdHTTP.Request.CacheControl := 'no-store';
end;

The OnWrite handler decodes the UTF-8 encoded event data and writes it to the console:

procedure TIndySSEClient.MyOnWrite;
begin
  WriteLn('Received ' + IntToStr(Length(ABuffer)) + ' bytes');
  WriteLn;
  WriteLn(IndyTextEncoding_UTF8.GetString(ABuffer));
  ...
end;

Server (main REST method)

The server generates the server-sent event and places a Stock instance in its data part:

  @GET
  @Path("prices")
  @Produces(MediaType.SERVER_SENT_EVENTS)
  public void getStockPrices(@Context SseEventSink sseEventSink) {
    int lastEventId = 1;

    while (running) {
      Stock stock = getNextTransaction();

      System.out.println("Send event ...");
      OutboundSseEvent sseEvent = this.eventBuilder
              .name("stock")
              .id(String.valueOf(lastEventId))
              .mediaType(MediaType.APPLICATION_JSON_TYPE)
              .data(Stock.class, stock)
              .reconnectDelay(3000)
              .comment("price change")
              .build();

      sseEventSink.send(sseEvent);
      lastEventId++;
    }
    sseEventSink.close();
  }

Additional notes

  • this is the first draft of a SSE client, which does not support features such as reconnect
  • the example assumes that every call of MyOnWrite contains exactly one event, teminated by a sequence of two line separators (cr lf / cr / lf) so that no extra code is required to do proper event stream parsing

Full source code

https://github.com/michaelJustin/indy-sse-jaxrs

References

Single file upload example using Indy TidHTTP and multipart/form-data

A new cross-language example for HTTP and REST is now available on GitHub.

Description

Single file upload example using Delphi client and Java server code and HTTP multipart/form-data

Requirements

  • Delphi 2009 or newer
  • Indy 10.6.2
  • Java JDK 8 or newer
  • Apache Maven
  • WildFly application server

Client

program IndyPostFormData;

{$APPTYPE CONSOLE}

uses
  IdHTTP, IdMultipartFormData, SysUtils;

const
  URL = 'http://localhost:8080/indy-post-formdata-1.0-SNAPSHOT/webresources/generic/pdf';

var
  Indy: TIdHTTP;
  Params: TIdMultiPartFormDataStream;
  Response: string;

begin
  Indy := TIdHTTP.Create;
  Params :=  TIdMultiPartFormDataStream.Create;
  try
    try
      Params.AddFile('file', 'client.pdf');
      Response := Indy.Post(URL, Params);
      WriteLn(Response);
    except
      on E:Exception do
        Writeln(E.Classname, ': ', E.Message);
    end;
  finally
    Params.Free;
    Indy.Free;
  end;
  ReadLn;
end.

 

Server (main REST method)

  @POST
  @Path("/pdf")
  @Consumes({MediaType.MULTIPART_FORM_DATA})
  public Response upload(MultipartFormDataInput input) {
    String UPLOAD_PATH = "c:/tmp/";
    try {
      InputStream fileInputStream = input.getFormDataPart("file", InputStream.class, null);
      String fileName = "test.pdf";

      int read;
      byte[] bytes = new byte[1024];

      try (OutputStream out = new FileOutputStream(new File(UPLOAD_PATH + fileName))) {
        while ((read = fileInputStream.read(bytes)) != -1) {
          out.write(bytes, 0, read);
        }
      }
    } catch (IOException e) {
      throw new WebApplicationException("Error while uploading file. Please try again");
    }
    return Response.ok("Data uploaded successfully").build();
  }

 

Full source code

https://github.com/michaelJustin/indy-post-formdata

Firebird logging support in Jaybird

Jaybird provides logging support as documented on the Jaybird Wiki. The page is subtitled “information outdated” but it works nonetheless.

Here is an example class which shows whether logging is enabled or not, and then opens a connection:

public static final void main(String... args) throws SQLException {

    if ("true".equals(System.getProperty("FBLog4j"))) {
      System.out.println("[*] logging over Log4J");
    } else {
      System.out.println("[*] not logging over Log4J");
    }

    System.out.println("[*] create connection");

    Properties props = new Properties();
    props.setProperty("user", "SYSDBA");
    props.setProperty("password", "masterkey");
    props.setProperty("encoding", "UTF8");

    try (Connection connection = DriverManager.getConnection(
            "jdbc:firebirdsql://localhost:3050/"
            + "C:\\Program Files\\Firebird\\Firebird_2_5"
            + "\\examples\\empbuild\\EMPLOYEE.FDB",
            props)) {

      System.out.println("[*] close connection");
    }
  }

Note that our projects requires Log4j and the Jaybird 2.2.14 driver on the classpath.

We use the log4j.properties configuration in the documentation:

log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=[%c{1},%p] %m%n

log4j.rootCategory=DEBUG, stdout
log4j.category.org.firebirdsql=DEBUG, stdout

If we run the program and include the -DFBLog4j=true JVM property, the output is:

[*] logging over Log4J
[*] create connection
[AbstractJavaGDSImpl,DEBUG] Got socket
[AbstractJavaGDSImpl,DEBUG] Got socket
[AbstractJavaGDSImpl,DEBUG] user.name: ***
[AbstractJavaGDSImpl,DEBUG] user.name: ***
[AbstractJavaGDSImpl,DEBUG] op_connect
[AbstractJavaGDSImpl,DEBUG] op_connect
...

To fix the double output, we can add this line to the configuration:

...
# exclude messages from parent logger:
log4j.additivity.org.firebirdsql=false

With this fix, the log output becomes:

[*] logging over Log4J
[*] create connection
[AbstractJavaGDSImpl,DEBUG] Got socket
[AbstractJavaGDSImpl,DEBUG] user.name: ***
[AbstractJavaGDSImpl,DEBUG] op_connect
[AbstractJavaGDSImpl,DEBUG] sent
[AbstractJavaGDSImpl,DEBUG] op_accept
[AbstractJavaGDSImpl,DEBUG] received
[AbstractJavaGDSImpl,DEBUG] op_attach
[AbstractJavaGDSImpl,DEBUG] sent
[AbstractJavaGDSImpl,DEBUG] op_response
[AbstractJavaGDSImpl,DEBUG] readStatusVector arg:isc_arg_gds int: 0
[AbstractJavaGDSImpl,DEBUG] received
[AbstractJavaGDSImpl,DEBUG] op_info_database
[AbstractJavaGDSImpl,DEBUG] sent
[AbstractJavaGDSImpl,DEBUG] op_response
[AbstractJavaGDSImpl,DEBUG] readStatusVector arg:isc_arg_gds int: 0
[AbstractJavaGDSImpl,DEBUG] received
[AbstractJavaGDSImpl,DEBUG] parseDatabaseInfo: first 2 bytes are 318 or: 62, 1
[AbstractJavaGDSImpl,DEBUG] isc_info_db_sql_dialect:3
[AbstractJavaGDSImpl,DEBUG] isc_info_firebird_version:WI-V2.5.8.27089 Firebird 2.5-WI-V2.5.8.27089 Firebird 2.5/tcp (***)/P10
[AbstractJavaGDSImpl,DEBUG] isc_info_ods_version:11
[AbstractJavaGDSImpl,DEBUG] isc_info_ods_minor_version:2
[*] close connection
[AbstractJavaGDSImpl,DEBUG] op_detach
[AbstractJavaGDSImpl,DEBUG] sent
[AbstractJavaGDSImpl,DEBUG] op_response
[AbstractJavaGDSImpl,DEBUG] readStatusVector arg:isc_arg_gds int: 0
[AbstractJavaGDSImpl,DEBUG] received
[AbstractJavaGDSImpl,DEBUG] About to invalidate db handle
[AbstractJavaGDSImpl,DEBUG] successfully invalidated db handle

Use case

The log output can become very useful to detect issues with the client application code.

For this example, we remove one line from the code with sets the encoding for the JDBC connection.

Now the log will include a warning because the client does not specifiy an encoding:

    Properties props = new Properties();
    props.setProperty("user", "SYSDBA");
    props.setProperty("password", "masterkey");
    // encoding undefined
    // props.setProperty("encoding", "UTF8");

(to be continued)

[*] logging over Log4J
[*] create connection
[FBManagedConnection,WARN] WARNING: No connection characterset specified (property lc_ctype, encoding, charSet or localEncoding), defaulting to characterset NONE
[AbstractJavaGDSImpl,DEBUG] Got socket
...

Firebird Trace and Audit services via Jaybird

The Jaybird JDBC driver library for the Firebird SQL server includes support for the Firebird “Trace and Audit Services” API. Let’s write a small Java application as a starting point.

Our example will direct all output to System.out, configure and start start a trace session, waits for infinite time, and finally close the trace session.

The full source code is available on GitHub: https://github.com/michaelJustin/firebird-jaybird-trace

The Main class

Here is the main class code. It uses a single-thread ExecutorService to launch our SystemOutTraceNotifier, waits for a keypress, and finally terminates our SystemOutTraceNotifier instance by shutting down the executor.

public class Main {

  private static final String DB = "C:\\Program Files\\Firebird\\Firebird_2_5\\examples\\empbuild\\EMPLOYEE.FDB";

  public static final void main(String... args) throws IOException {

    SystemOutTraceNotifier notifier = new SystemOutTraceNotifier(DB, "SYSDBA", "masterkey");

    ExecutorService executorService = Executors.newSingleThreadExecutor();
    executorService.submit(notifier);

    System.out.println("[*] ... hit any key ...");
    System.in.read();

    executorService.shutdownNow();
    }
}

SystemOutTraceNotifier

This class creates and configures an instance of FBTraceMonitor.

  public SystemOutTraceNotifier(String database, String user, String pass) {
    traceManager = new FBTraceManager(GDSType.getType("PURE_JAVA"));
    traceManager.setHost("localhost");
    traceManager.setDatabase(database);
    traceManager.setUser(user);
    traceManager.setPassword(pass);
  }

The main method sets the logger output stream to System.out and starts the trace session.
The FBTraceManager instance will write all trace output to the output stream, while our  method simply waits ‘forever’, or until it gets interrupted by invoking the terminateNow() method of our executor service (see above).

    public Void call() throws Exception {
        try {
            traceManager.setLogger(System.out);

            System.out.println("[*] Start trace session " + sessionName);
            traceManager.startTraceSession(sessionName, TRACE_CFG);

            try {
                Thread.sleep(Long.MAX_VALUE);
            } catch (InterruptedException ex) {
                System.out.println("[*] Trace was interrupted");
                Thread.currentThread().interrupt();
            }

            Integer sessionId = traceManager.getSessionId(sessionName);
            if (sessionId != null) {
                System.out.println("[*] Trace is stopping");
                traceManager.stopTraceSession(sessionId);
            } else {
                System.out.println("[*] Could not stop (session id is null)");
            }
        } catch (SQLException ex) {
            ex.printStackTrace(System.out);
        }
        return null;
    }

Note that we must stop the trace session. Otherwise the trace manager will not terminate its own worker thread, and our program will still run even after the call() method terminated.

A problem arises if we try to end our trace before anything has been written to the trace. In this case, the FBTraceManager has not yet recorded the session id of our trace session, and traceManager.getSessionId(sessionName) will return null. As traceManager.stopTraceSession(null) will cause an error and the trace manager will not terminate its worker thread.

Configuration of the trace session

The configuration string for the trace session:

private static final String TRACE_CFG = "database>\n"
            + "	enabled                true\n"
            + "	log_statement_finish   true\n"
            + "	print_plan             true\n"
            + "	time_threshold         0\n"
            + "/database>";

Output

To test the program we launch a Firebird client, connect to the example employee database, and issue the SQL statement “select * from customer”.

[*] ... hit any key ...
[*] Start trace session 49413da1-9a4b-44e5-bf58-540d729124da
Start trace
Trace session ID 5 started
2019-02-08T10:16:58.2480 (5792:00000000003CD9E8) TRACE_INIT
	SESSION_5 58ad9a02-b306-48d0-8afc-fd6b3d86a449
	

2019-02-08T10:16:58.2490 (5792:00000000003CD9E8) EXECUTE_STATEMENT_FINISH
	C:\PROGRAM FILES\FIREBIRD\FIREBIRD_2_5\EXAMPLES\EMPBUILD\EMPLOYEE.FDB (ATT_26, SYSDBA:NONE, NONE, XNET:T11176)
	C:\Program Files\Firebird\Firebird_2_5\bin\isql.exe:2728
		(TRA_814, CONCURRENCY | WAIT | READ_WRITE)

Statement 105:
-------------------------------------------------------------------------------
select * from customer
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
PLAN (CUSTOMER NATURAL)
15 records fetched
      0 ms, 33 fetch(es)
...

Important notice

From https://firebirdsql.org/rlsnotesh/rnfb25-trace.html#rnfb25-trace-plugin:

This Trace API exists in Firebird 2.5 and is in use. However, since it will be changed in forthcoming sub-releases, it is not officially published and must be regarded as unstable.

Source code

The code for this article will be available in a GitHub repository soon.

JAX-WS SOAP server testing with Delphi and Thorntail

Update: this project is now available on Github

In the previous blog post, I have shown Delphi code examples using Indy TIdHTTP as a client for a basic REST server written using the JAX-RS API, running in Thorntail.

This article shows how a JAX-WS SOAP WebService can be exposed using Thorntail and consumed with Delphi.

For brevity, we will adopt a code-first approach, and use JAX-WS annotations to expose our service under SOAP format.

JAX-WS WebService – the Java EE server

package com.example;

import javax.jws.WebService;
import javax.jws.WebMethod;

@WebService(
   portName="helloPort",
   serviceName="helloService",
   targetNamespace = "http://hello.example.com/")
public class Hello {
    private final String message = "Hello, ";

    @WebMethod
    public String sayHello(String name) {
        return message + name + ".";
    }
}

That’s the standard JAX-WS web service code for a simple SOAP endpoint which has one method, sayHello, that takes a String argument and returns a String response.

After building the server with Maven, we launch it from the command line:

>java -jar target/soap-endpoint-thorntail.jar

The server will log the startup and deployment of our web service application:

2019-01-28 09:24:42,517 INFO  [org.jboss.as.server] (main) WFLYSRV0010: Deployed
 "soap-endpoint.war" (runtime-name : "soap-endpoint.war")

To check that the web service is working, we can invoke its WSDL at the endpoint address:

http://localhost:8080/helloService?WSDL

Alternatively, we can use SoapUI to run a first test request:

soapui

JAX-WS WebService – the Delphi client

First we create a new command line application.

Then we use the WSDL importer to generate a Delphi binding. This will create a unit ”helloService”, and add it to the project.

importer

Now wee need only to insert a call to the web service in the Delphi code:

program helloWorldClient;

{$APPTYPE CONSOLE}

uses
  helloService in 'helloService.pas',
  SysUtils, ActiveX;

var
  HelloServer: Hello;
  Response: string;

begin
  ActiveX.CoInitialize(nil);
  try
     HelloServer := GetHello;
     Response := HelloServer.sayHello('Delphi');
     WriteLn(Response);
  except
    on E:Exception do
      Writeln(E.Classname, ': ', E.Message);
  end;

  ReadLn;
  CoUninitialize;
end.

Note for older Delphi versions: if we run the Delphi code unmodified, our client will show an exception:

ERemotableException: Unmarshalling Error: unerwartetes Element (URI:"http://hell
o.example.com/", lokal:"arg0"). Erwartete Elemente sind

The fix on the Delphi side is shown below. The last parameter of RegisterInvokeOptions must be changed from ioDocument to ioHasNamespace.

initialization
  InvRegistry.RegisterInterface(TypeInfo(Hello), 'http://hello.example.com/', '');
  InvRegistry.RegisterDefaultSOAPAction(TypeInfo(Hello), '');
  InvRegistry.RegisterInvokeOptions(TypeInfo(Hello), ioHasNamespace);

And with this change, the client receives the expected response:

Hello, Delphi.

 

Conclusion

Thorntail, which is designed for usage in microservice architectures, saves much of the time which is usually needed for creating, deploying, and testing Java EE components (such as JAX-RS and JAX-WS web services). Building a Java EE compliant web service server with Thorntail requires only a basic Java IDE. The resulting server is packaged as a single jar file and can be run instantly from the command line, or can be packaged as a war file ready for deployment in any Java EE compatible application server.

References

Unmarshalling Error

Example code based on Thorntail docs https://docs.thorntail.io/2.3.0.Final/#_basics

JAX-WS Java API for XML Web Services

JAX-RS REST service testing with Indy TidHTTP and Thorntail

Update: this project is now available on Github

Thorntail (https://thorntail.io/) is an open source project which creates stand-alone Java EE application servers that can be launched with a simple java -jar command.

Thorntail can be a very handy tool for quick prototyping and integration testing of Delphi web service client code, as it requires no installation of a heavy-weight Java EE application server enviroment.

Example 1: HTTP GET Test

JAX-RS web service code / TIdHTTP client code


package com.example.rest;

import javax.ws.rs.Path;
import javax.ws.rs.core.Response;
import javax.ws.rs.GET;
import javax.ws.rs.Produces;

@Path("/hello")
public class HelloWorldEndpoint {

  @GET
  @Produces("text/plain")
  public Response doGet() {
    return Response.ok("Hello from Thorntail!").build();
  }
}

That’s the standard JAX-RS web service code for a simple endpoint which processes GET requests. The boilerplate code which defines the root context is:

package com.example.rest;

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

@ApplicationPath("/rest")
public class RestApplication extends Application {
}

The web service client code, written with Delphi and the Indy TIdHTTP client class:

program IndyClient;

{$APPTYPE CONSOLE}

uses
  IdHTTP,
  SysUtils;

procedure Main;
var
  IdHTTP: TIdHTTP;
  Response: string;
begin
  IdHTTP := TIdHTTP.Create;
  try
    Response := IdHTTP.Get('http://localhost:8080/rest/hello');
    WriteLn(Response);
  finally
    IdHTTP.Free;
  end;
end;

begin
  try
    Main;
  except
    on E:Exception do
      Writeln(E.Classname, ': ', E.Message);
  end;
  ReadLn;
end.

After building the server with Maven, we launch it from the command line:

>java -jar target/restful-endpoint-thorntail.jar

The server will log the startup and deployment of our web service application:

2019-01-23 15:45:58,037 INFO [org.jboss.as.server] (main) WFLYSRV0010: Deployed
"restful-endpoint.war" (runtime-name : "restful-endpoint.war")

And we are ready to test our Delphi client.
Typically for GET requests, we can also launch a web browser and navigate to https://localhost:8080/rest/hello to test it.
In the next example, we move one step further and add a POST handler to the web service, and change the Delphi code to send a HTTP POST request with an JSON payload.

Example 2: HTTP POST Test

JAX-RS web service code / TIdHTTP client code


package com.example.rest;

import javax.ws.rs.Path;
import javax.ws.rs.core.Response;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Produces;

@Path("/hello")
public class HelloWorldEndpoint {

  @GET
  @Produces("text/plain")
  public Response doGet() {
    return Response.ok("Hello from Thorntail!").build();
  }

  @POST
  @Produces("text/plain")
  public Response doPost() {
    return Response.ok("Hello from Thorntail POST!").build();
  }
}

And the corresponding Delphi test code is:

program IndyClient;

{$APPTYPE CONSOLE}

uses
  IdHTTP,
  SysUtils, Classes;

procedure Main;
var
  IdHTTP: TIdHTTP;
  RequestBody: TStream;
  Response: string;
begin
  IdHTTP := TIdHTTP.Create;
  RequestBody := TStringStream.Create('{"answer":42}',
    TEncoding.UTF8);
  try
    Response := IdHTTP.Post('http://localhost:8080/rest/hello',
      RequestBody);
    WriteLn(Response);
  finally
    RequestBody.Free;
    IdHTTP.Free;
  end;
end;

begin
  try
    Main;
  except
    on E:Exception do
      Writeln(E.Classname, ': ', E.Message);
  end;
  ReadLn;
end.

Example 3: HTTP POST with media type application/json

In this example, we remove the GET handler to the server code (so that only HTTP POST requests are allowed now), and add a restriction that the POST request must use the media type “application/json”:

JAX-RS web service code / TIdHTTP client code

@Path("/hello")
public class HelloWorldEndpoint {

  @POST
  @Produces("text/plain")
  @Consumes("application/json")
  public Response doPost(String body) {
    return Response.ok("Hello from Thorntail POST!\n" + body).build();
  }
}

The server code now also prints the body of the POST request so we can verify it has been received.

If we run the Delphi code unmodified, the server will refuse the POST request (because it is lacking the mediatype) and our client will show an exception:

EIdHTTPProtocolException: HTTP/1.1 415 Unsupported Media Type

The fix on the Delphi side is shown below, in line 6, the content type header is added to the request.

  IdHTTP := TIdHTTP.Create;
  try
    RequestBody := TStringStream.Create('{"answer":42}',
      TEncoding.UTF8);
    try
      IdHTTP.Request.ContentType := 'application/json';
      Response := IdHTTP.Post('http://localhost:8080/rest/hello',
        RequestBody);
      WriteLn(Response);
    finally
      RequestBody.Free;
    end;
  finally
    IdHTTP.Free;
  end;

And with this change, the client receives the expected response:

Hello from Thorntail POST!
{"answer":42}

Example 4: HTTP POST with media type and response type application/json

In this example, the Delphi client sends another request header. Now the media type “application/json” is not only specified for the request content, but also required for the server response, by setting the HTTP Accept header (in line 7).

TIdHTTP client code

  IdHTTP := TIdHTTP.Create;
  try
    RequestBody := TStringStream.Create('{"answer":42}',
      TEncoding.UTF8);
    try
      IdHTTP.Request.ContentType := 'application/json';
      IdHTTP.Request.Accept := 'application/json';
      Response := IdHTTP.Post('http://localhost:8080/rest/hello',
        RequestBody);
      WriteLn(Response);
    finally
      RequestBody.Free;
    end;
  finally
    IdHTTP.Free;
  end;

If we run this client code with the same server as in the previous example, there will be a new exception with HTTP code 406:

EIdHTTPProtocolException: HTTP/1.1 406 Not Acceptable

Since our Delphi client used the Accept header to ask for a response with media type “application/json”, the server rejects the request because the POST handler responds with media type “text/plain”, which does not match the client request.

So we update the server to match the change on the client side:

JAX-RS web service code / TIdHTTP client code

@Path("/hello")
public class HelloWorldEndpoint {

  @POST
  @Produces("application/json")
  @Consumes("application/json")
  public Response doPost(String body) {
    return Response.ok(body).build();
  }

}

Note that the server code also only returns the request body (which must be JSON).

Now the client and server code use matching media types both for request and for the accepted response, and the client shows the expected server response:

{"answer":42}

Conclusion

Thorntail, which is designed for usage in microservice architectures, saves much of the time which is usually needed for creating, deploying, and testing Java EE components (such as JAX-RS web services). Building a Java EE compliant web service server with Thorntail requires only a basic Java IDE. The resulting server is packaged as a single jar file and can be run instantly from the command line, or can be packaged as a war file ready for deployment in any Java EE compatible application server.

References

Example code based on Thorntail docs https://docs.thorntail.io/2.3.0.Final/#_basics

JAX-RS https://en.wikipedia.org/wiki/Java_API_for_RESTful_Web_Services

Related articles

Indy 10.6 HTTPS POST example with JSON body

Apache NetBeans IDE 10.0 released

Apache NetBeans (incubating) 10 is the Apache NetBeans incubator release of the features provided by Apache NetBeans (incubating) 9.0. The features driving the Apache NetBeans 10 release are JDK 11 and PHP, though JavaScript and Groovy features are also included in Apache NetBeans for the first time.

Roadmap: Apache NetBeans Release Roadmap

New and noteworthy: https://cwiki.apache.org/confluence/display/NETBEANS/Apache+NetBeans+10.0+New+and+Noteworthy

Download: https://netbeans.apache.org/download/nb100/nb100.html#_downloading

Getting started: https://netbeans.apache.org/help/getting-started.html

First milestone of ScroogeXHTML for the Java™ platform 8.0

Version 8.0.MS1 of the RTF (Rich Text) to HTML5 and XHTML converter library ScroogeXHTML for the Java™ platform is now available for registered users.

New features in 8.0.MS1

  • compiles with Oracle JDK 8 and Oracle OpenJDK 11
  • JUnit tests pass with Oracle JDK 8 and Oracle OpenJDK 11

Other changes in 8.0.MS1:

  • moved package to com.scroogexhtml
  • fixed Spotbugs warnings and JavaDoc errors
  • removed deprecated methods getISO8601DateTime, getStyleSheetLink, setCompatibleDefaults, useListTable, metaDate

Download link

https://www.habarisoft.com/scroogexhtml_j/download/snapshot/ScroogeXHTML-jar-8.0.MS1.install.jar (only for registered users)

API documentation

https://www.habarisoft.com/scroogexhtml_j/8.0.MS1/docs/api/index.html (note: JavaDoc generated with JDK 11 includes a search feature and no HTML frames anymore)

About

ScroogeXHTML for the Java platform is a library which can convert a subset of the Rich Text Format (RTF) standard to HTML5 and XHTML, as a standalone document, or as a fragment which can be used in other documents. It supports RTF features such as hyperlinks, field results, simple tables, picture data extraction, and embeds PNG and JPG pictures as data URI. It is easy to use, and requires no external runtime libraries except the SLF4J logging facade.

Other platforms

ScroogeXHTML is also available for Delphi 2009+ and Free Pascal.

scrooge_portrait_logo_2016