Implement the REST SOE's interfaces

The pattern for developing REST server object extensions (SOEs) is to create a Java class that implements the mandatory IServerObjectExtension and IRestRequestHandler interfaces. The IServerObjectExtension interface provides init() and shutdown() methods that are called when your map service starts and shuts down, while the IRestRequestHandler 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.

d921098b 9395 4adb 9ff8 9f0d1c42711910

Starting from methods listed at the top:

  • The init() and shutdown() 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 getLayerCountByType() method corresponds to the REST operation by same name. This is the suggested implementation point for this REST operation.
  • The getRootResource() 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.
  • The getSubResourcelayers() method stub is generated to hold business logic for the “layers” sub-resource. Thus, this is the suggested implementation point for this sub-resource.
  • The invokeRESTOperation() 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.
  • The handleRESTRequest() 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.
  • The getSchema() 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.

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.

                                    
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
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 getSchema() 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 ServerUtilities.createResource() method. The SOE also creates an operation, getLayerCountByType, under its root, by calling the ServerUtilities.createOperation() method.

                       
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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 handleRESTRequest() method of the IRESTRequestHandler interface.

If operationName 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 getResource() to execute its business logic. Otherwise, the request will be forwarded to an operation via invokeRESTOperation based on the operationName variable.

The business logic for the layer sub-resource and the getLayerCountByType 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 handleRESTRequest() method as the SOE's response.

                                 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
@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.IMapLayerInfos interface. This information is then poured into a JSONObject and is returned as an array of bytes to the user.

                           
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
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 getLayerCountByType operation.

The following code snippet shows implementation of the “getLayerCountByType” operation. The com.esri.arcgis.carto.IMapLayerInfos 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.

                                   
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
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\".");
    }
}

Your browser is no longer supported. Please upgrade your browser for the best experience. See our browser deprecation post for more details.

;