Starting July 12th, INTGeoServer services can be accessed through Akka, outside of a J2EE application server. Akka is a toolkit for building highly concurrent, distributed, and resilient applications.
A client code using Akka is known as an actor. For INTGeoServer, we call this actor the sender.
A server code using Akka is also known as an actor. For INTGeoServer, we call this actor the receiver.
For example, to query the serverinfo web service using the classic HTTP protocol, you would typically access this address:
http://myserver.mycompany.com/INTGeoServer/json/serverinfo
In this address, json is the identifier of the servlet being accessed and serverinfo is the name of the web service. In INTGeoServer, the servlet identifier is identical for all services, only the service name changes. In HTTP terminology, the service is uniquely identified by its pathInfo. The pathInfo in this case is /serverinfo
Just like the servlet address is identical for all services, the receiver actor is identical for all services. This is the com.interactive.intgeoapi.server.akka.ReceiverAkkaActor class.
The message sent by the client actor is formatted as a com.fasterxml.jackson.databind.JsonNode and contains the requested service name, preceded by a /
Example where you need a JSON object as output (your program needs to inspect the content of the JSON object):
ActorSystem system = ActorSystem.create("demo");
ActorRef sender = system.actorOf(Props.create(SenderAkkaActor.class));
JSONObjectAkkaRequest akkaRequest = new JSONObjectAkkaRequest("/serverinfo", JsonNodeFactory.instance.objectNode());
sender.tell(akkaRequest, sender);
Example where you only need a JSON string as output (your program is proxying INTGeoServer services and the client is a web browser):
ActorSystem system = ActorSystem.create("demo");
ActorRef sender = system.actorOf(Props.create(SenderAkkaActor.class));
StringAkkaRequest akkaRequest = new StringAkkaRequest("/serverinfo", JsonNodeFactory.instance.objectNode());
sender.tell(akkaRequest, sender);
Both examples capture the same output:
{"name":"INT GeoServer","version":"2.58","build":"$Name$","localTime":{"day":12,"dayOfYear":193,"month":6,"year":2017,"hour":4,"minute":54,"second":57,"millisecond":729}}
A typical implementation of the sender is:
package com.interactive.intgeo.server.akka;
import com.interactive.intgeoapi.server.akka.ReceiverAkkaActor;
import com.interactive.intgeoapi.server.akka.JSONObjectAkkaResponse;
import com.interactive.intgeoapi.server.akka.ByteArrayAkkaResponse;
import com.interactive.intgeoapi.server.akka.JSONObjectAkkaRequest;
import akka.actor.ActorRef;
import akka.actor.Props;
import akka.actor.UntypedActor;
import com.interactive.intgeoapi.server.akka.ByteArrayAkkaRequest;
import com.interactive.intgeoapi.server.akka.StringAkkaRequest;
import com.interactive.intgeoapi.server.akka.StringAkkaResponse;
public class SenderAkkaActor extends UntypedActor {
private final ActorRef receiver;
public SenderAkkaActor() {
receiver = this.getContext().actorOf(Props.create(ReceiverAkkaActor.class));
}
@Override
public void onReceive(Object message) throws Throwable {
if (message instanceof StringAkkaRequest) {
receiver.tell(message, getSelf());
}
if (message instanceof JSONObjectAkkaRequest) {
receiver.tell(message, getSelf());
}
if (message instanceof ByteArrayAkkaRequest) {
receiver.tell(message, getSelf());
}
if (message instanceof StringAkkaResponse) {
StringAkkaResponse response = (StringAkkaResponse) message;
System.out.println(response.getString());
}
if (message instanceof JSONObjectAkkaResponse) {
JSONObjectAkkaResponse response = (JSONObjectAkkaResponse) message;
System.out.println(response.getJSONObject().toString());
}
if (message instanceof ByteArrayAkkaResponse) {
ByteArrayAkkaResponse response = (ByteArrayAkkaResponse) message;
System.out.println(response.getBytes().length + " bytes");
}
}
}
This sample code is for Akka 2.4.
INTGeoServer services have a unified input: a JSON object.
The output depends on the service itself. For example, the serverinfo service outputs a JSON object while the seismicbinaryheader service outputs a binary. For services outputting JSON objects, use the JSONObjectAkkaRequest class. For services outputting binaries, use the ByteArrayAkkaRequest class instead.
Use a StringAkkaResponse instead of a JSONObjectAkkaRequest if you only need the serialized/toString() version of the JSON output. The serialization of the JSON object will be taken care of by the receiver actor, and will tend to be faster for specific web services who don't use a JSONObject internally such as the fastfileslist service.
There are several gotchas that you need to be aware of to use Akka.
The container where your Akka code executes has no knowledge of where your INTGeo configuration files are. There are several services who need to know where the root of the configuration is, for example to retrieve the version number of your INTGeoServer code as specified in the web.xml file.
If you just ran the installer, your root is typically the INTGeoServer directory created by the installer and you need to plug this location using a configuration class such as this one.
public class DefaultAkkaConfig extends AbstractAkkaConfig {
public static String AKKA_CONFIG_ROOT = "C:\\Users\\thierry.danard\\Documents\\MyGit\\IntGeo\\server\\build\\web"; // can be changed
private Map<String, String> parameters = new LinkedHashMap<>();
public DefaultAkkaConfig() {
try {
File fXmlFile = new File(this.getWebInfRealPath() + File.separator + "web.xml");
DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();
DocumentBuilder dBuilder = dbFactory.newDocumentBuilder();
Document doc = dBuilder.parse(fXmlFile);
NodeList paramsList = doc.getElementsByTagName("context-param");
for (int temp = 0; temp < paramsList.getLength(); temp++) {
Node nNode = paramsList.item(temp);
if (nNode.getNodeType() == Node.ELEMENT_NODE) {
Element eElement = (Element) nNode;
NodeList paramNames = eElement.getElementsByTagName("param-name");
Element nameElement = (Element) paramNames.item(0);
NodeList paramValues = eElement.getElementsByTagName("param-value");
Element valueElement = (Element) paramValues.item(0);
parameters.put(nameElement.getTextContent(), valueElement.getTextContent());
}
}
} catch (SAXException ex) {
Logger.getLogger(DefaultAkkaConfig.class.getName()).log(Level.SEVERE, null, ex);
} catch (IOException ex) {
Logger.getLogger(DefaultAkkaConfig.class.getName()).log(Level.SEVERE, null, ex);
} catch (ParserConfigurationException ex) {
Logger.getLogger(DefaultAkkaConfig.class.getName()).log(Level.SEVERE, null, ex);
}
}
@Override
public String getRealPath(String virtualPath) {
if (virtualPath == null) {
return AKKA_CONFIG_ROOT;
}
if (virtualPath.isEmpty()) {
return AKKA_CONFIG_ROOT;
}
if (virtualPath.equals("/")) {
return AKKA_CONFIG_ROOT;
}
return AKKA_CONFIG_ROOT + virtualPath;
}
@Override
public String getInitParameter(String name) {
return parameters.get(name);
}
@Override
public Enumeration<String> getInitParameterNames() {
final Iterator<String> iter = parameters.keySet().iterator();
return new Enumeration<String>() {
@Override
public boolean hasMoreElements() {
return iter.hasNext();
}
@Override
public String nextElement() {
return iter.next();
}
};
}
}
This class is declared in this layer.xml file:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE filesystem PUBLIC "-//NetBeans//DTD Filesystem 1.2//EN" "http://www.netbeans.org/dtds/filesystem-1_2.dtd">
<filesystem>
<folder name="com.interactive.intgeoapi.server.AbstractAkkaConfig">
<file name="com.interactive.intgeo.server.DefaultAkkaConfig.instance" />
</folder>
</filesystem>
By default, INTGeoServer is configured to allow access through Akka if the Akka libraries are present (ex: the com.typesafe.akka.akka-actor_2.11-2.4.14.jar file attached at the bottom of this page)
To disable Akka completely, you can remove the AkkaSupport.jar file from the WEB-INF/lib directory.