Java Processes

As was mentioned earlier, Corus allows starting any type of process (provided it interacts with Corus according to the Corus Interoperability Specification). As far as Java processes are concerned, there are a few things to know..

Corus Interoperability Specification in Java

The Corus interop spec implementation in Java is another Sapia project. The implementation consists of a small .jar file named sapia_corus_iop.jar. That library consists of a small XML/HTTP client (as required by the spec).

When executing Java VMs from Corus, that library is “placed" in the application's classpath automatically. The library is made "invisible" to your applications since it is not placed at the level of the system classloader, but at the level of a classloader that is a child of the system classloader, and which is really a sibling of your application's own classloader.

The Java interop implementation detects that it has been started by a Corus server by looking up the system properties; if it finds a value for the corus.process.id property, then it determines that it should poll the corresponding Corus server. In order to do so, it needs the host/port on which the Corus server is listening, which it acquires through the corus.server.host and corus.server.port system properties. Using the values specified through these properties, the Corus interop instance connects to the Corus server and starts the polling process.

Troubleshooting

In order to facilitate troubleshooting, the Java interop client creates the following files under the process directory:

  • stdout.txt
  • stderr.txt

These files hold the output resulting from the redirection of the java.lang.System.out and java.lang.System.err output streams. In addition, when a Corus server starts a process, it logs that process' initial output to the given process' directory, in a file named process.out

In the case of Java processes, this allows tracking errors that have occurred before the JVM was started.

Interacting with Corus from Java

Java applications can interact with Corus to

  • Request a process restart or shutdown.
  • Be notified upon shutdown.
  • Transmit status information.
  • Receive process configuration updates from the Corus server (a functionality dubbed "hot config").

Dependency

In all cases, to interact with Corus from your applications, you will need the sapia_corus_iop_api.jar library in your classpath. This library can be configured as a dependency, as part of your Maven build, as such:

<dependency>
  <groupId>org.sapia</groupId>
  <artifactId>sapia_corus_iop_api</artifactId>
  <version>3.0</version>
</dependency>

The API provided by the library allows you to hook your application to the lifecycle of the JVM, as it's managed by Corus.

Triggering a Restart

An application can request a JVM restart with code such as the following:

import org.sapia.corus.interop.api.InteropLink;
InteropLink.getImpl().restart();

Triggering a Shutdown

An application can request a JVM shutdown as follows:

import org.sapia.corus.interop.api.InteropLink;
InteropLink.getImpl().shutdown();

Registering a Shutdown Listener

You can register a org.sapia.corus.interop.api.ShutdownListener in order to be notified of a JVM shutdown originating from Corus:

import org.sapia.corus.interop.api.InteropLink; 
import org.sapia.corus.interop.api.ShutdownListener; 

public class MyListener implements ShutdownListener {  
  public void onShutdown() {    
    ...  
  } 
} 
	
ShutdownListener listener = new MyListener(); 
InteropLink.getImpl().addShutdownListener(listener);

Note that ShutdownListeners are internally kept as java.lang.ref.SoftReference instances, so it is important that you keep a hard reference on any such listener in your application.

Producing Status Data

It is possible for applications to publish status information through Corus. That information will be viewable through the CLI (using the status command, and will also be accessible through the REST API

In order to publish status information, you must register a StatusRequestListener:

import org.sapia.corus.interop.api.InteropLink;
import org.sapia.corus.interop.api.StatusRequestListener;
import org.sapia.corus.interop.api.message.InteropMessageBuilderFactory;
import org.sapia.corus.interop.api.message.StatusMessageCommand.Builder;

public class MyListener implements StatusRequestListener { 
  public void onStatus(Builder statusBuilder, InteropMessageBuilderFactory factory) {
    ...
  } 
} 

StatusRequestListener listener = new MyListener(); 
InteropLink.getImpl().addStatusRequestListener(listener);

Since listeners are kept as SoftRerences, you must keep a hard reference on them in your code.

The listener will be invoked periodically (at the interval at which the process will poll the Corus server). It will be passed a Status instance, to which the listener can then add status information, as follows:

import org.sapia.corus.interop.api.InteropLink;
import org.sapia.corus.interop.api.StatusRequestListener;
import org.sapia.corus.interop.api.message.ContextMessagePart;
import org.sapia.corus.interop.api.message.InteropMessageBuilderFactory;
import org.sapia.corus.interop.api.message.StatusMessageCommand.Builder;

...

  public void onStatus(Builder statusBuilder, InteropMessageBuilderFactory factory) {
    ContextMessagePart.Builder contextBuilder = factory.newContextBuilder().name("org.sapia.corus.sample.jetty");
    contextBuilder
      .param("dispatched", String.valueOf(stats.getDispatched()))
      .param("dispatchedActive", String.valueOf(stats.getDispatchedActive()))
      .param("dispatchedActiveMax", String.valueOf(stats.getDispatchedActiveMax()))
      .param("dispatchedTimeMax", String.valueOf(stats.getDispatchedTimeMax()))
      .param("dispatchedTimeTotal", String.valueOf(stats.getDispatchedTimeTotal()))
      .param("dispatchedTimeMean", String.valueOf(stats.getDispatchedTimeMean()))
      .param("requests", String.valueOf(stats.getRequests()))
      .param("requestsActive", String.valueOf(stats.getRequestsActive()))
      .param("requestsActiveMax", String.valueOf(stats.getRequestsActiveMax()))
      .param("requestsTimeMax", String.valueOf(stats.getRequestTimeMax()))
      .param("requestsTimeMean", String.valueOf(stats.getRequestTimeMean()))
      .param("requestsTimeTotal", String.valueOf(stats.getRequestTimeTotal()))
      .param("suspends", String.valueOf(stats.getSuspends()))
      .param("suspendsActive", String.valueOf(stats.getSuspendsActive()))
      .param("suspendsActiveMax", String.valueOf(stats.getSuspendsActiveMax()));
    statusBuilder.context(contextBuilder.build());        
  }
  
...

In the above code, the listener creates a ContextMessagePart instance, adding status parameters to it. Lastly, the ContextMessagePart is added to the status builder that is passed to the method.

Hot Config

It is possible for applications to be notified of configuration changes that occur on the Corus server side, more precisely: when process properties are updated/added/removed, either through the CLI's conf add command, or through the REST API.

For Java applications, the interop client allows registering a ConfigurationChangeListener for intercepting so-called "configuration events". The interface's signature is as follows:

public interface ConfigurationChangeListener {
  
  public void onConfigurationChange(ConfigurationEventMessageCommand event);

}

A ConfigurationEventMessageCommand encapsulates ParamMessagePart objects, each in fact corresponding to a process property that's been either added or removed. The event also has a getType() method, indicating the type of operation (add, update, delete) that caused the event. The return value of the method can be one of the following:

  • update: indicates that the property has been added to Corus, or updated.
  • delete: indicates that the property has been removed from Corus.

To register your listener, proceed as follows:

ConfigurationChangeListener configListener = new ConfigurationChangeListener() {
  public void onConfigurationChange(ConfigurationEventMessageCommand event) {
    log.debug("Received config change: {}", event.getType());
    
    // get the modified properties as a Map
    Map<String, String> properties = event.toMap()
    
    // or you can also get them as Param objects:
    for(ParamMessagePart p : event.getParams()) {
      if (event.getType().equals(ConfigurationEventMessageCommand.TYPE_UPDATE)) {
        log.debug("Property {} was added/modified", p.getName());
      } else {
        log.debug("Property {} was deleted", p.getName());
      }
    }
  }
}; 
InteropLink.getImpl().add(configListener);

As mentioned earlier and for the sake of insisting: listeners are kept as SoftRerences, so you must keep a hard reference on them in your code.

System Properties Synchronization

For java processes, Corus offers a default implementation of the ConfigurationChangeListener interface that automatically synchronizes the system properties of the JVM from the configuration events received from Corus. This implementation fully supports both update and delete event types and can be easily activated by defining the following system propery of the executed process:

corus.client.configuration.synchronizeSystemProperties=true

When activated, the Corus interop agent that runs within the application process will automatically register a configuration change listener to process the configuration events. If turned on you can always register additionnal custom listeners, but be cautious not to perform any logic that could interfere with the system properties.