Friday, September 21, 2018

Eclipse RAP with Spring Boot

Intro

I like Eclipse :-). I've been using it for many years. I think the IDE is great and it is free! But Eclipse is much more than just the IDE. It is more like an ecosystem of applications, plugins and projects. Anyone can use the Eclipse Framework to create his/her own Eclipse-based application. It is called Eclipse RCP - Rich Client Application. Any RCP is based on the same principles as the Eclipse IDE: the product includes some features, any feature includes some plugins. 


I heard it is possible to include a plugin directly into the product but this is deprecated.
The UI in Eclipse Framework is based on 2 things:
  • The Application model
  • SWT - Standard Widget Toolkit
The Application Model is like a big "Mediator' (remember the pattern?) that handles interactions of many UI components at a high level. It is defined in an XML-like format. It defines windows, parts, commands, handlers, menus etc. 
SWT is a collection of widgets: buttons, labels, dialogs. These widgets are used to create the UI at the lower level than the Application Model. For example the UI within a part (MPart class) is created with SWT.

The Eclipse Framework can also be used to create web applications. It is called Eclipse RAP - Remote Application Platform. The idea is simple and impressive at the same time. The UI is created in Java with SWT widgets. In fact the UI is created with SWT widgets adapted for RAP. Then it is rendered in the browser. There is a special servlet that does the rendering RWTServlet (RWT may stand for Remote Widget Toolkit). Here is the link to the Developer's Guide for RAP.

And finally to the point. We all know that Spring Boot is awesome :-))! It must be the dominant platform for creating web applications. It offers some technologies to create the UI like JSP or Thymeleaf. But JSP is a little old and Thymeleaf could be a little less interactive than RAP. It would be great to make the two work together.

RAP Application with Spring Boot in a WAR file

The first option is to create a WAR with Spring Boot and RAP and deploy it to Tomcat. It is the option described in the Developer's Guide. Here is the link. I copied the important files.
The web.xml:
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://java.sun.com/xml/ns/j2ee"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"
  version="2.4">

  <context-param>
    <param-name>org.eclipse.rap.applicationConfiguration</param-name>
    <param-value>com.example.HelloWorldConfiguration</param-value>
  </context-param>

  <listener>
    <listener-class>org.eclipse.rap.rwt.engine.RWTServletContextListener</listener-class>
  </listener>

  <servlet>
    <servlet-name>rwtServlet</servlet-name>
    <servlet-class>org.eclipse.rap.rwt.engine.RWTServlet</servlet-class>
  </servlet>

  <servlet-mapping>
    <servlet-name>rwtServlet</servlet-name>
    <url-pattern>/hello</url-pattern>
  </servlet-mapping>
</web-app> 

The application configuration:
public class HelloWorldConfiguration implements ApplicationConfiguration {
  public void configure( Application application ) {
    application.addEntryPoint( "/hello", HelloWorld.class, null );
  }
}
And the class:
public class HelloWorld extends AbstractEntryPoint {
  public void createContents( Composite parent ) {
    Label label = new Label( parent, SWT.NONE );
    label.setText( "Hello RAP World" );
  }
}
In order to make it work with the Maven WAR packaging put the web.xml into src/main/webapp/WEB-INF. The entry point in the application configuration must be configured for rwtServlet in web.xml.

For Spring Boot add the following:
@SpringBootApplication
public class SpringApp extends SpringBootServletInitializer {
  public SpringApplicationBuilder configure( SpringApplicationBuilder builder ) {
    return builder.web(WebApplicationType.NONE).sources(<configurations>);
  }
}

In pom.xml set the packaging to war.
<packaging>war</packaging>

There is though one important issue. Eclipse jars are not available in the Maven Central. Maybe it is possible to somehow use the p2 repository but I didn't try that. After all only one jar is necessary. I used the latest available version RAP 3.5. The jar is org.eclipse.rap.rwt_3.5.0.20180613-1038.jar.
I installed it manually to the local Maven repository and used the dependency in the pom.

This was enough to build a WAR and use it in Tomcat 9. No hacks necessary.

RAP Application with Spring Boot in an executable jar

This option looks more convenient. It would be really great to be able to use the RWTServlet inside Spring Boot. Then all the functionality like the executable jar and embedded Tomcat will work out of the box.
First return the main Spring Boot class to normal:

@SpringBootApplication
public class SpringApp {
    public static void main(String[] args) {
        SpringApplication.run(SpringApp.classargs);
    }
}
In fact Spring Boot can add a servlet with a ServletRegistrationBean:

@Bean
public ServletRegistrationBean<RWTServlet> servletRegistrationBean() {
    ServletRegistrationBean<RWTServlet> rwt = new 
ServletRegistrationBean<RWTServlet>(new RWTServlet(),"/hello""/hello2");
    return rwt;
}

But unfortunately this is not enough. This RWTServlet requires a special context to be bound to the ServletContext. Previously in the WAR example the RWTServletContextListener handled this :-(.

But Spring Boot even in this desperate situation showed was it was worth :-). It can bind custom listeners by declaring them as beans! The default functionality of RWTServletContextListener  required some rework because as it turned out binding this listener from inside Spring Boot had some limitations. This is what I ended up with:

public class RWTSCLSimple extends RWTServletContextListener {

    private ApplicationRunner applicationRunner;

    @Override
    public void contextInitialized( ServletContextEvent event ) {
        ServletContext servletContext = event.getServletContext();
        ApplicationConfiguration configuration = new 
HelloWorldConfiguration();
        applicationRunner = new ApplicationRunner( configuration
servletContext );
        applicationRunner.start();
    }

    @Override
    public void contextDestroyed( ServletContextEvent event ) {
        applicationRunner.stop();
        applicationRunner = null;
    }
}

This class simplifies the initial functionality of RWTServletContextListener and removes some method calls which were "not acceptable for a listener not declared in web.xml" :-)). That error message was very funny.

Now this class must be declared as a bean and that's it:
@Bean
public RWTSCLSimple rwtSCListener() {
    return new RWTSCLSimple();
}

All Spring Boot functionality works out of the box, even server.servlet.context-path. If the reader doesn't mind the little workaround this option looks better than the first one.

Tuesday, September 11, 2018

Java's Process with Linux Shells

Intro

Today's post highlights some challenges of using Java's Process with Linux interactive shells and provides solutions to these challenges. 
The goal is more complex than being able to execute a Linux command and print a result. The idea is to be able to execute the shell in an interactive fashion. To be able to pass a command to the shell and print the output. And while java.lang.Process provides the I/O streams to do that there are some challenges involved. 
In my post I'll describe these challenges and show possible solutions. 

Interactive Output

Problem

The problem is that the process may print some data to the output over irregular intervals. The time it takes for a command to execute is not predefined. And it is preferable that the user be able to see the output in real-time.

Solution

Use a thread to read the output from Process.inputStream. The Thread wakes up every n milliseconds, reads what is available from the input stream and send to the appropriate destination. When the user wants to enter a command enter it immediately and return. 

Reading From Streams

This problem may sound silly, I understand. Java has all those great classes like InputStream, InputStreamReader, Reader etc. What is the problem with reading from the InputStream?
I thought along similar lines when began developing this code. But it turned out some issues were not obvious at all. 
Most of us are used to reading from a file. Practically everyone knows how to read from a File. But the important thing is that the file streams do not block at read. The input stream that is returned from Process.getInputStream() can actually block in this code (in bold):

InputStream is = process.getInputStream();
InputStreamReader isr = new InputStreamReader(is);
char[] isContents = null;
isr.read(isContents, totalCharsRead, isContents.length - totalCharsRead);

One has to use the isr.ready() method to find out if the next read will block like this:

while (isr.ready() && (totalCharsRead < isContents.length)) {
    currCharsRead = isr.read(isContents, totalCharsRead,      
        isContents.length - totalCharsRead);
    totalCharsRead += currCharsRead;
}

It is also important to understand that InputStream.available() may give you more bytes than is actually available! Yes, that is true but the reason for this unknown. If the char array is declared like this:
char[] isContents = new char[is.available()];
one has to keep track of the actual number of chars read by adding the returned value of InputStreamReader.read to some total value and then using this value.
Here is the complete code:

private String readFromInputStream(InputStream is) {
    try {
        if (is.available() > 0) {

            InputStreamReader isr = new InputStreamReader(is);
            char[] isContents = new char[is.available()];
            int currCharsRead = 0;
            int totalCharsRead = 0;
        
            while (isr.ready() && (totalCharsRead < isContents.length)) {
                currCharsRead = isr.read(isContents, totalCharsRead, isContents.length - totalCharsRead);
                totalCharsRead += currCharsRead;
            }
            return new String(isContents, 0, totalCharsRead);
        }
    } catch (IOException e1) {
        LOG.error("", e1);
    }
    return "";
}

Web UI

If a Web UI is necessary then WebSockets is the likely solution. Spring Boot has a good tutorial on how to use the Stomp client.