arcgissamples\soe\POIFinderSOE.java
/* Copyright 2010 ESRI * * All rights reserved under the copyright laws of the United States * and applicable international laws, treaties, and conventions. * * You may freely redistribute and use this sample code, with or * without modification, provided you include the original copyright * notice and use restrictions. * * See the use restrictions. * */ package arcgissamples.soe; import java.io.IOException; import java.net.UnknownHostException; import java.util.HashMap; import java.util.Iterator; import java.util.Set; import com.esri.arcgis.carto.IFeatureLayer; import com.esri.arcgis.carto.Map; import com.esri.arcgis.carto.MapServer; import com.esri.arcgis.geodatabase.FeatureClass; import com.esri.arcgis.geodatabase.FeatureCursor; import com.esri.arcgis.geodatabase.IFeature; import com.esri.arcgis.geodatabase.QueryFilter; import com.esri.arcgis.interop.AutomationException; import com.esri.arcgis.interop.extn.ArcGISExtension; import com.esri.arcgis.interop.extn.ServerObjectExtProperties; import com.esri.arcgis.server.IServerObjectExtension; import com.esri.arcgis.server.IServerObjectHelper; import com.esri.arcgis.system.ILog; import com.esri.arcgis.system.IRESTRequestHandler; import com.esri.arcgis.system.ServerUtilities; import static com.esri.arcgis.system.ServerUtilities.*; import com.esri.arcgis.server.json.*; @ArcGISExtension @ServerObjectExtProperties( displayName = "Points of Interest Finder SOE", description = "Finds points of interest such as gas stations and restaurants", defaultSOAPCapabilities = { "Find Gas Stations", "Find Restaurants" }, allSOAPCapabilities = { "Find Gas Stations", "Find Restaurants" }, supportsMSD = true ) public class POIFinderSOE implements IServerObjectExtension, IRESTRequestHandler { private static final long serialVersionUID = 1L; private IServerObjectHelper soHelper; private ILog serverLog; private MapServer mapServer; private FeatureClass poiFC; public POIFinderSOE() throws Exception { super(); } /**************************************************************************************************************************** * IServerObjectExtension methods: This is a mandatory interface that must be supported by all SOEs. This interface * is used by the Server Object to manage the lifetime of the SOE and includes two methods: init() and shutdown(). * The Server Object cocreates the SOE and calls the init() method handing it a back reference to the Server Object * via the Server Object Helper argument. The Server Object Helper implements a weak reference on the Server Object. * The extension can keep a strong reference on the Server Object Helper (for example, in a member variable) but * should not keep a strong reference on the Server Object. The log entries are merely informative and completely * optional. ****************************************************************************************************************************/ /** * init() is called once, when the instance of the SOE is created. */ public void init(IServerObjectHelper soh) throws IOException, AutomationException { /* * An SOE should get the Server Object from the Server Object Helper in order to make any method calls on the * Server Object and release the reference after making the method calls. */ this.soHelper = soh; this.serverLog = getServerLogger(); // get the Server Object (SO) this SOE is associated with this.mapServer = (MapServer) soHelper.getServerObject(); // get the Map object from SO Map map = (Map) this.mapServer.getMap("Layers"); // Get index of layer containing bus stations int poiLayerIndex = getLayerIndex(map, "PointsOfInterest"); // contruct a feature class for bus stops IFeatureLayer poiFL = (IFeatureLayer) mapServer.getLayer("", poiLayerIndex); this.poiFC = new FeatureClass(poiFL.getFeatureClass()); } /** * shutdown() is called once when the Server Object's context is being shut down and is about to go away. */ public void shutdown() throws IOException, AutomationException { /* * The SOE should release its reference on the Server Object Helper. */ this.poiFC = null; this.mapServer = null; this.soHelper = null; this.serverLog = null; } /**************************************************************************************************************************** * IRESTRequestHandler methods: This interface indicates that SOE supports REST. It exposes two methods: handleRESTRequest() * and getSchema(). ****************************************************************************************************************************/ /** * Handles REST request */ public byte[] handleRESTRequest(String capabilities, String resourceName, String operationName, String operationInput, String outputFormat, String requestProperties, String[] responseProperties) throws IOException, AutomationException { /* * This method handles REST requests by determining whether an operation * or resource has been invoked and then forwards the request to * appropriate methods. */ try { // if no operationName is specified send description of specified // resource if (operationName.length() == 0) { return getResource(resourceName); } else // invoke REST operation on specified resource { return invokeRESTOperation(capabilities, resourceName, operationName, operationInput, outputFormat, requestProperties, responseProperties); } } catch (Exception e) { return ServerUtilities.sendError(0, "Exception occurred: " + e.getMessage(), null).getBytes("utf-8"); } } /** * Returns schema of the REST resource */ public String getSchema() throws IOException, AutomationException { try { JSONObject _ServerObjectExt = createResource("POIFinderSOE", "Finds points of interesr such as restaurants and gas stations", false); JSONArray _ServerObjectExt_OpArray = new JSONArray(); _ServerObjectExt_OpArray.put(createOperation("findGasStationByName", "gasStationName", "json")); _ServerObjectExt_OpArray.put(createOperation("findRestaurantByName", "restaurantName", "json")); _ServerObjectExt.put("operations", _ServerObjectExt_OpArray); JSONArray _ServerObjectExt_SubResourceArray = new JSONArray(); JSONObject _GasStations = createResource("GasStations", "Gas Stations", true); _ServerObjectExt_SubResourceArray.put(_GasStations); JSONObject _Restaurants = createResource("Restaurants", "Restaurants", true); _ServerObjectExt_SubResourceArray.put(_Restaurants); _ServerObjectExt.put("resources", _ServerObjectExt_SubResourceArray); return _ServerObjectExt.toString(); } catch (JSONException e) { e.printStackTrace(); } return null; } /**************************************************************************************************************************** * SOE Util methods. ****************************************************************************************************************************/ /** * Invokes specified REST operation on specified REST resource * @param capabilitiesList * @param resourceName * @param operationName * @param operationInput * @param outputFormat * @param requestProperties * @param responseProperties * @return */ private byte[] invokeRESTOperation(String capabilitiesList, String resourceName, String operationName, String operationInput, String outputFormat, String requestProperties, String[] responseProperties) throws Exception { byte[] operationOutput = null; JSONObject operationInputAsJSON = new JSONObject(operationInput); //parse request properties and create a map java.util.Map<String, String> requestPropertiesMap = new HashMap<String, String>(); if (requestProperties != null && requestProperties.length() > 0) { JSONObject requestPropertiesJSON = new JSONObject(requestProperties); Iterator<String> jsonKeys = requestPropertiesJSON.keys(); while (jsonKeys.hasNext()) { String key = jsonKeys.next(); requestPropertiesMap.put(key, requestPropertiesJSON.getString(key)); } } //create a Map to hold response properties java.util.Map<String, String> responsePropertiesMap = new HashMap<String, String>(); if (resourceName.equalsIgnoreCase("") || resourceName.length() == 0) { if (operationName.equalsIgnoreCase("findGasStationByName")) { operationOutput = findGasStationByName(operationInputAsJSON); } else if (operationName.equalsIgnoreCase("findRestaurantByName")) { operationOutput = findRestaurantByName(operationInputAsJSON); } else { operationOutput = sendError(0, "Operation " + "\"" + operationName + "\" not supported on sub-resource " + resourceName + ".", new String[]{"no details", " specified"}).getBytes(); } } else if(resourceName.equalsIgnoreCase("GasStations/[0-9]+")) { int id = Integer.valueOf(resourceName.substring(resourceName.lastIndexOf("/") + 1, resourceName.length())).intValue(); operationOutput = getGasStation(id).toString().getBytes(); } else if(resourceName.equalsIgnoreCase("Restaurants/[0-9]+")) { int id = Integer.valueOf(resourceName.substring(resourceName.lastIndexOf("/") + 1, resourceName.length())).intValue(); operationOutput = getRestaurant(id).toString().getBytes(); } else //if non existent sub-resource specified, report error { operationOutput = sendError(0, "No sub-resource by name \"" + resourceName + "\" found.", new String[]{""}).getBytes(); } //convert response properties to String array if (!responsePropertiesMap.isEmpty()) { Set<String> keys = responsePropertiesMap.keySet(); Iterator<String> keysIterator = keys.iterator(); int i = 0; while (keysIterator.hasNext()) { String key = keysIterator.next(); String value = responsePropertiesMap.get(key); responseProperties[i] = key + "=" + value; i++; } } return operationOutput; } /** * Returns description of resource specified by resourceName * @param resourceName * @return byte[] */ private byte[] getResource(String resourceName) { if(resourceName.equalsIgnoreCase("") || resourceName.length() == 0) { return getRootResourceDescription().toString().getBytes(); } else if(resourceName.matches("GasStations/[0-9]+")) { int id = Integer.valueOf(resourceName.substring(resourceName.lastIndexOf("/") + 1, resourceName.length())).intValue(); return getGasStation(id).toString().getBytes(); } else if(resourceName.matches("Restaurants/[0-9]+")) { int id = Integer.valueOf(resourceName.substring(resourceName.lastIndexOf("/") + 1, resourceName.length())).intValue(); return getRestaurant(id).toString().getBytes(); } return null; } /** * Returns description of the root resource * @return description as a JSONObject */ private JSONObject getRootResourceDescription() { try { JSONObject rootResource = new JSONObject(); rootResource.put("Name", "POI Finder SOE"); rootResource.put("Description", "Gas Stations and Restaurants in Portland using REST SOE"); rootResource.put("GasStations", getGasStationCollection()); rootResource.put("Restaurants", getRestaurantCollection()); return rootResource; } catch (Exception e) { e.printStackTrace(); } return null; } /** * Retrieves Gas Station info using specified id * @param id * @return */ private JSONObject getGasStation(int id) { try { if(id >= 1 || id < this.poiFC.featureCount(null)) { String whereClause = "\"OBJECTID\" = " + id + " AND \"Type\" = 1"; QueryFilter dirQueryFilter = new QueryFilter(); dirQueryFilter.setWhereClause(whereClause); FeatureCursor featureCursor = new FeatureCursor(this.poiFC.search(dirQueryFilter, false)); IFeature feature = featureCursor.nextFeature(); if(feature != null) { JSONObject busRouteJSON = new JSONObject(); busRouteJSON.put("id", feature.getValue(0)); busRouteJSON.put("name", feature.getValue(2)); busRouteJSON.put("address", feature.getValue(3)); busRouteJSON.put("city", feature.getValue(4)); busRouteJSON.put("state", feature.getValue(5)); busRouteJSON.put("zip", feature.getValue(6)); return busRouteJSON; } else { JSONObject errorObject = new JSONObject(); errorObject.put("ErrorMessage", "ERROR: No gas station feature with id = " + id + " found."); return errorObject; } } else { JSONObject errorObject = new JSONObject(); errorObject.put("ErrorMessage", "ERROR: Invalid Id provided."); return errorObject; } } catch (JSONException e) { e.printStackTrace(); } catch (AutomationException e) { e.printStackTrace(); } catch (UnknownHostException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } return null; } /** * Retrieves Restaurant info using specified id * @param id * @return */ private JSONObject getRestaurant(int id) { try { if(id >= 1 || id < this.poiFC.featureCount(null)) { String whereClause = "\"OBJECTID\" = " + id + " AND \"Type\" = 4"; QueryFilter dirQueryFilter = new QueryFilter(); dirQueryFilter.setWhereClause(whereClause); FeatureCursor featureCursor = new FeatureCursor(this.poiFC.search(dirQueryFilter, false)); IFeature feature = featureCursor.nextFeature(); if(feature != null) { JSONObject busRouteJSON = new JSONObject(); busRouteJSON.put("id", feature.getValue(0)); busRouteJSON.put("name", feature.getValue(2)); busRouteJSON.put("address", feature.getValue(3)); busRouteJSON.put("city", feature.getValue(4)); busRouteJSON.put("state", feature.getValue(5)); busRouteJSON.put("zip", feature.getValue(6)); return busRouteJSON; } else { JSONObject errorObject = new JSONObject(); errorObject.put("ErrorMessage", "ERROR: No restaurant feature with id = " + id + " found."); return errorObject; } } else { JSONObject errorObject = new JSONObject(); errorObject.put("ErrorMessage", "ERROR: Invalid Id provided."); return errorObject; } } catch (JSONException e) { e.printStackTrace(); } catch (AutomationException e) { e.printStackTrace(); } catch (UnknownHostException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } return null; } /** * Finds gas station by specified name * @param gasStationName * @return */ private byte[] findGasStationByName(JSONObject operationInput) throws Exception { JSONArray array = null; String gasStationName = operationInput.getString("gasStationName"); try { String whereClause = "\"Name\" LIKE '%" + gasStationName + "%' AND \"Type\" = 1"; array = findPOIs(whereClause); if(array.length() == 0) { JSONObject errorObject = new JSONObject(); errorObject.put("Warning", "No gas station that contains \"" + gasStationName + "\" in it's name found."); array.put(errorObject); } } catch (JSONException e) { e.printStackTrace(); } return array.toString().getBytes("utf-8"); } /** * Finds restaurant by specified name * @param restaurantName * @return */ private byte[] findRestaurantByName(JSONObject operationInput) throws Exception { JSONArray array = null; String restaurantName = operationInput.getString("restaurantName"); try { String whereClause = "\"Name\" LIKE '%" + restaurantName + "%' AND \"Type\" = 4"; array = findPOIs(whereClause); if(array.length() == 0) { JSONObject errorObject = new JSONObject(); errorObject.put("Warning", "No restaurant that contains \"" + restaurantName + "\" in it's name found."); array.put(errorObject); } } catch (JSONException e) { e.printStackTrace(); } return array.toString().getBytes("utf-8"); } /** * Find points of interest using specified where class * @param whereClause * @return */ private JSONArray findPOIs(String whereClause) { JSONArray array = null; try { QueryFilter dirQueryFilter = new QueryFilter(); dirQueryFilter.setWhereClause(whereClause); FeatureCursor featureCursor = new FeatureCursor(this.poiFC.search(dirQueryFilter, false)); IFeature feature = featureCursor.nextFeature(); array = new JSONArray(); while(feature != null) { JSONObject json = new JSONObject(); json.put("id", feature.getValue(0)); json.put("name", feature.getValue(2)); json.put("address", feature.getValue(3)); json.put("city", feature.getValue(4)); json.put("state", feature.getValue(5)); json.put("zip", feature.getValue(6)); array.put(json); feature = featureCursor.nextFeature(); } } catch (JSONException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } return array; } /** * Returns Gas Station collection * @return */ private JSONArray getGasStationCollection() { JSONArray gasStationsArray = new JSONArray(); for(int i = 1; i < 41; i+=5) { gasStationsArray.put(getGasStation(i)); } return gasStationsArray; } /** * Returns Restaurant collection * @return */ private JSONArray getRestaurantCollection() { JSONArray restaurantArray = new JSONArray(); for(int i = 86; i < 137; i+=5) { restaurantArray.put(getRestaurant(i)); } return restaurantArray; } /**************************************************************************************************************************** * General Util methods. ****************************************************************************************************************************/ /** * Creates a REST resource based on specified name, description and collection flag * @param name * @param description * @param isCollection * @return REST resource as a JSONObject */ private JSONObject createResource(String name, String description, boolean isCollection) { try { JSONObject json = new JSONObject(); if (name.length() > 0 && name != null) { json.put("name", name); } else { throw new Exception("Resource must have a valid name."); } json.put("description", description); json.put("isCollection", isCollection); return json; } catch (Exception e) { e.printStackTrace(); } return null; } /** * Creates an operation that can be called on a REST resource, using specified name, parameter list and * output formats list * @param operationName * @param parameterList * @param supportedOutputFormatsList * @return Operation as a JSONObject */ private JSONObject createOperation(String operationName, String parameterList, String supportedOutputFormatsList) { try { JSONObject operation = new JSONObject(); if (operationName.length() > 0 && operationName != null) { operation.put("name", operationName); } else { throw new Exception("Operation must have a valid name."); } // parameters if (parameterList.length() > 0 && parameterList != null) { JSONArray operationParamArray = new JSONArray(); String[] parameters = parameterList.split(","); for (String parameter : parameters) { operationParamArray.put(parameter.trim()); } operation.put("parameters", operationParamArray); } else { throw new Exception( "Operation must have parameters. If your operation does not requires params, then please convert it to a sub-resource."); } // supported Output formats if (supportedOutputFormatsList.length() > 0 && supportedOutputFormatsList != null) { JSONArray outputFormatsArray = new JSONArray(); String[] outputFormats = supportedOutputFormatsList.split(","); for (String outputFormat : outputFormats) { outputFormatsArray.put(outputFormat.trim()); } operation.put("supportedOutputFormats", outputFormatsArray); } else { throw new Exception("Operation must have supported output formats specified"); } return operation; } catch (Exception e) { e.printStackTrace(); } return null; } /** * Returns a detailed error thats constructed based on specified code, message and details array. * @param code * @param message * @param details * @return error String */ private String sendError(int code, String message, String[] details) { /* * An json error is sent back to client in the following structure.{ "error": { "code": 400, "message": * "Cannot perform query. Invalid query parameters.", "details": ["'time' param is invalid"] }} */ try { JSONObject errorObject = new JSONObject(); JSONObject error = new JSONObject(); error.put("code", code); error.put("message", message); if (details != null) { JSONArray detailsArray = new JSONArray(); for (String detail : details) { detailsArray.put(detail); } error.put("details", detailsArray); } errorObject.put("warning", error); return errorObject.toString(); } catch (JSONException e) { e.printStackTrace(); } return null; } /** * Retrieves ID of a layer * * @param mapServer * @param layerName * @return */ private int getLayerIndex(Map map, String layerName) throws IOException, AutomationException { int layerID = -1; for (int i = 0; i < map.getLayerCount(); i++) { String name = map.getLayer(i).getName(); if (layerName.equalsIgnoreCase(name)) { layerID = i; break; } } if (layerID < 0) { serverLog.addMessage(4, 8000, "Could not find layer " + layerName + " in " + map.getName()); } return layerID; } }