The pattern for developing REST server object extensions (SOEs) is to create a Java class that implements the mandatory I
and I
interfaces. The I
interface provides init()
and shutdown()
methods that are called when your map service starts and shuts down, while the I
methods handle HTTP requests to your SOE.
Add initialization and business logic to the SOE
The JavaSimpleRESTSOE.java class generated by the rest-soe-archetype
includes methods and interfaces required for a REST SOE. It also contains boilerplate code for implementing sample sub-resources and operations.
Starting from methods listed at the top:
- The
init()
andshutdown()
methods, declared in the IServerObjectExtension interface, are called when SOE is instantiated and destroyed respectively. These methods are common to SOAP and REST SOEs. - The
get
method corresponds to the REST operation by same name. This is the suggested implementation point for this REST operation.Layer Count By Type() - The
get
method corresponds to the root resource (the SOE itself) . Its implementation, as generated by the wizard, includes creating a JSON object with name of SOE in it. This can be customized to add more information to the root resource, such as description.Root Resource() - The
get
method stub is generated to hold business logic for the “layers” sub-resource. Thus, this is the suggested implementation point for this sub-resource.Sub Resourcelayers() - The
invoke
method is an internal method and is used to provide all the internal logic needed to invoke the correct REST operation at runtime. You don't need to modify this method, unless new REST operations are added to the SOE and/or existing ones are removed or modified.REST Operation() - The
handle
method is the entry point into a REST SOE at runtime. This method is called by Server’s REST handler and all information regarding the REST request is made available to this method. This method needs to be modified only if new sub-resources are added or existing ones removed.REST Request() - The
get
method returns the resource hierarchy for your RESTSOE in JSON format. If using the Eclipse IDE wizard to create SOEs, this method will be generated automatically based on the resource hierarchy you specify on the “REST Support” page. This method should be modified only if you wish to modify the resource hierarchy after SOE has been created by the Eclipse wizard.Schema()
Implement SOE initialization logic
The following code snippet includes the init()
and shutdown()
methods of the SOE. The init()
method prepares ArcGIS Enterprise SDK members that the SOE requires at runtime, including a handle to the map service that the SOE extends.
public class SimpleRESTSOE implements IServerObjectExtension, IRESTRequestHandler{
private static final long serialVersionUID = 1L;
private IServerObjectHelper soHelper;
private ILog serverLog;
private IMapLayerInfos layerInfos;
public SimpleRESTSOE()throws Exception{
super();
}
/**
* init() is called once, when the instance of the SOE is created.
*/
public void init(IServerObjectHelper soh)throws IOException, AutomationException{
this.soHelper = soh;
this.serverLog = ServerUtilities.getServerLogger();
IMapServer ms = (IMapServer)this.soHelper.getServerObject();
IMapServerInfo mapServerInfo = ms.getServerInfo(ms.getDefaultMapName());
this.layerInfos = mapServerInfo.getMapLayerInfos();
serverLog.addMessage(3, 200, "Initialized " + this.getClass().getName() +
" SOE.");
}
/**
* shutdown() is called once when map service is shutting down
*/
public void shutdown()throws IOException, AutomationException{
serverLog.addMessage(3, 200, "Shutting down " + this.getClass().getName() +
" SOE.");
this.soHelper = null;
this.serverLog = null;
}
...
}
Define SOE schema
In the following get
method, the SOE schema is defined and the hierarchy of the REST operations and resources is created. From the code, we can see the SOE creates two sub-resources, layers
and serviceproperties
, under the SOE's root, by calling the Server
method. The SOE also creates an operation, get
, under its root, by calling the Server
method.
public String getSchema()throws IOException,AutomationException{
try{
JSONObject javaSimpleRESTSOE = ServerUtilities.createResource("javaSimpleRESTSOE",
"javaSimpleRESTSOE description",false,false);
JSONArray _subResourcesArray = new JSONArray();
_subResourcesArray.put(ServerUtilities.createResource("layers",
"layers in associated map service", false, false));
_subResourcesArray.put(ServerUtilities.createResource("serviceproperties",
"service properties associated map service", false, false));
javaSimpleRESTSOE.put("resources", _subResourcesArray);
JSONArray _OpArray = new JSONArray();
_OpArray.put(ServerUtilities.createOperation("getLayerCountByType",
"type", "json", false));
javaSimpleRESTSOE.put("operations", _OpArray);
return javaSimpleRESTSOE.toString();
}catch(JSONException e){
e.printStackTrace();
}
return null;
}
Handle SOE REST requests
Next, the SOE must handle REST requests by determining whether an operation or resource has been invoked and forwards the request to appropriate methods of the operation or resource. This is achieved by overriding the following handle
method of the IREST
interface.
If operation
is not returned, then the request for a resource is invoked and the SOE will forward this request to the corresponding resource's method via get
to execute its business logic. Otherwise, the request will be forwarded to an operation via invoke
based on the operation
variable.
The business logic for the layer
sub-resource and the get
operation can be found at the next two paragraphs. They both generate the result content in JSON format and then they are returned as a byte array to the handle
method as the SOE's response.
@Override
public byte[] handleRESTRequest(String capabilities, String resourceName,
String operationName, String operationInput, String outputFormat,
String requestProperties, String[] responseProperties)
throws IOException, AutomationException {
try {
Map<String, String> responsePropertiesMap = new HashMap<String, String>();
byte[] response = null;
if (operationName.length() == 0) {
// invoke the REST resource
response = getResource(resourceName, responsePropertiesMap);
} else
{
// invoke the REST operation on specified resource
response = invokeRESTOperation(capabilities, resourceName,
operationName, operationInput, outputFormat,
requestProperties, responsePropertiesMap);
}
//collect response properties that may have changed in subresources or operations
JSONObject responsePropertiesJSON = new JSONObject(responsePropertiesMap);
responseProperties[0] = responsePropertiesJSON.toString();
//return a response
return response;
} catch (Exception e) {
this.serverLog.addMessage(1, 500, e.getMessage());
return ServerUtilities.sendError(500,
"Exception occurred: " + e.getMessage(),
new String[] { "No details specified." }).getBytes("utf-8");
}
}
Implement business logic for the layers
sub-resource
The following code snippet implements the “layers” sub-resource and gathers information about all layers in the associated map service, through use of methods in the com.esri.arcgis.carto.
interface. This information is then poured into a JSONObject and is returned as an array of bytes to the user.
public JSONObject getLayersInfoAsJSON() throws Exception {
JSONObject json = new JSONObject();
int count = this.layerInfos.getCount();
json.put("layerCount", count);
JSONArray layerArray = new JSONArray();
for (int i = 0; i < count; i++) {
IMapLayerInfo layerInfo = layerInfos.getElement(i);
JSONObject layerJSON = new JSONObject();
layerJSON.put("name", layerInfo.getName());
String layerType = layerInfo.getType();
layerJSON.put("type", layerType);
int id = layerInfo.getID();
layerJSON.put("id", id);
layerJSON.put("description", layerInfo.getDescription());
if (layerInfo.isFeatureLayer()) {
IMapServerDataAccess mapServerDataAccess = (IMapServerDataAccess) this.soHelper.getServerObject();
IMapServer ms = (IMapServer)mapServerDataAccess;
FeatureClass fc = new FeatureClass(mapServerDataAccess.getDataSource(ms.getDefaultMapName(), id));
layerJSON.put("featureCount", fc.featureCount(null));
}
layerArray.put(i, layerJSON);
}
json.put("layersInfo", layerArray);
return json;
}
Implement business logic for the get Layer Count By Type
operation.
The following code snippet shows implementation of the “getLayerCountByType” operation. The com.esri.arcgis.carto.
interface provides access to individual layers and other information such as type. This method below merely returns a count of layers of the type specified by the user. Acceptable types in the following code are feature, raster, dataset, or all. This list can easily be extended to include other types as well.
private byte[] getLayerCountByType(JSONObject operationInput, java.util.Map<String, String> responsePropertiesMap)
throws Exception {
String type = operationInput.getString("type");
JSONObject json = new JSONObject();
int count = 0;
if (type != null && !type.isEmpty()) {
String aoType = "";
if (type.equalsIgnoreCase("all")) {
count = layerInfos.getCount();
} else if (type.equalsIgnoreCase("feature")) {
aoType = "Feature Layer";
} else if (type.equalsIgnoreCase("raster")) {
aoType = "Raster Layer";
} else if (type.equalsIgnoreCase("dataset")) {
aoType = "Network Dataset Layer";
}
for (int i = 0; i < layerInfos.getCount(); i++) {
if (layerInfos.getElement(i).getType().equalsIgnoreCase(aoType)) {
count++;
}
}
json.put("count", count);
responsePropertiesMap.put("Content-Type", "application/json");
return json.toString().getBytes();
} else {
throw new Exception(
"Invalid layer type provided. Available types are: \"all\", \"feature\", \"raster\", \"dataset\".");
}
}