Developing Scenarios  

Developing an Application Web Service


  1. Project Description
  2. Concepts
  3. Design
  4. Requirements
  5. Implementation
  6. Additional Resources

This walkthrough is for developers who need to build and deploy a Java Technology application Web service incorporating geocoding and spatial query functionality using the ArcGIS Server API. It describes the process of building, deploying, and consuming the Toxic_Site_Locator_Search_Application_Web_Service sample, which is part of the ArcGIS developer samples.

<ARCGISHOME>\java\samples\webadf.ags_application_web_service

Rather than walk through this scenario, you can get the completed Web service from the samples installation location. The sample is installed as part of the ArcGIS developer samples.

Project Description

The purpose of this scenario is to create a Java Technology Web service using the Eclipse IDE that uses ArcObjects to locate all of the toxic waste sites within a specified distance of a specified address and return that number as an integer.

This Web service is intended to be called by other programs. An example of such a client program is another JSP web application. The Eclipse IDE will also generate an example of how a client application would consume this Web service.

Concepts

A Web service is a set of related application functions that can be programmatically invoked over the Internet. The function can be one that solves a particular application problem, as in this example, a Web service that finds all of the toxic waste sites within a certain distance of an address, or performs some other type of GIS function. Web services can be implemented using the Web service framework of your Web server such as the AXIS toolkit for Apache.

When using a framework, such as AXIS, to create and consume your application Web services, you need to use native or application-defined types as both arguments and return values from your Web methods. Clients of the Web service will not be ArcObjects applications, and as such, your Web service should not expect ArcObjects types as arguments and should not directly return ArcObjects types.

Any development language that can use standard HTTP to invoke methods can consume this Web service. The Web service consumer can get the methods and types exposed by the Web service through its Web Service Description Language (WSDL). As you walk through this scenario, you will see where special attributes need to be added to your methods and classes such that they can be expressed in WSDL and serialized as XML.

Design

This Web service is designed to make stateless use of the GIS server. It uses ArcObjects on the server to locate an address and query a feature class. To support this application, you need to add a pooled geocode server object to your ArcGIS Server using ArcCatalog.

One key aspect of designing your application is whether it is stateful or stateless. You can make either stateful or stateless use of a server object running within the GIS server. A stateless application makes read-only use of a server object, meaning the application does not make changes to the server object or any of its associated objects. A stateful application makes read_write use of a server object where the application does make changes to the server object or its related objects.

Both coarse-grained calls to remote ArcObjects, such as the methods on the MapServer and GeocodeServer, as well as fine-grained calls to remote

Web services are, by definition, stateless applications.

ArcObjects, such as those for creating new geometries, are exposed through the ArcGIS Server API and can be used in your Web service. The Web service will connect to the GIS server and use an instance of the geocode server object to locate the address supplied to the Web method from the calling application. To then buffer the resultant point and use that buffered geometry to query toxic waste sites, you will use the geocode server's server context. Since the geodatabase has been designed such that the address locator is stored in the same geodatabase as the feature class containing the toxic waste sites, you can use the fine-grained objects associated with the geocode server to get a reference to that workspace.

Requirements

The requirements for working through this scenario are that you have ArcGIS Server and ArcGIS Desktop installed and running. The machine on which you develop this Web service must have ArcGIS Server for the Java Platform installed. Additionally, you must have the Eclipse IDE with the WTP 1.0 plugins installed.

You must have a geocode server object configured and running on your ArcGIS Server that uses the Portland_shp.loc locator installed with the samples. In ArcCatalog, create a connection to your GIS server and use the Add Server Object command to create a new server object with the following properties:

The Add Server Object wizard

ArcCatalog is used for managing your spatial data holdings, defining your geographic data schemas, and managing your ArcGIS Server. Once you have created your PortlandGC server object, open its properties using ArcCatalog to verify it is correctly configured.

Accept the defaults for the remainder of the configuration properties.

After creating the server object, start it and right-click to verify that it is correctly configured and that the geocoding properties are displayed.

The following Java API ArcObjects packages will be used in this example:

The development environment does not require any ArcGIS licensing; however, connecting to a server and using a geocoding server object does require that the GIS server is licensed to run ArcObjects in the server. None of the packages used requires an extension license.

The IDE used in this example is Eclipse with the Web Tools Project 1.0 plugins, and all IDE specific steps will assume that this is the IDE you are using. Furthermore, it is assumed that you have already set up your IDE for working with Dynamic Web projects using the instructions found in the document ArcGIS Server Applications from Templates.

Implementation

All code written in this example is in Java.

Creating a new application web service using an Eclipse Dynamic Web project template.

  1. Start Eclipse.
  2. Click File, click New, then click Project... .

  3. In the New Project dialog, expand the Web node and select "Dynamic Web Project".

  4. Click "Next". This will open the New Dynamic Web Project dialog.
  5. In the New Dynamic Web Project dialog, type "ToxicLocations" as the name of the new project .

  6. Click "Finish". This will create a new blank Dynamic Web Project.

Adding references to ESRI packages to your project

To program using ArcGIS Server, you need to add references to the ESRI packages that contain proxies to the ArcObjects components that the Web service will use. These assemblies were installed when you installed ArcGIS Server for the Java Platform.

  1. In the Project Explorer, expand the Dynamic Web Projects node, then the ToxicLocations node, then the WebContent node, followed by the WEB-INF node. Right click on the "lib" folder and select "Import" from the drop-down menu.

    The Project Explorer

  2. In the Import dialog, choose "File System" and click "Next".

    The Import dialog box

  3. Click "Next" to open the part-two import dialog box.

    The part-two Import dialog box

  4. Click the "From directory:" "Browse" button and navigate to the <install location>\java\lib directory.
  5. Select the arcobjects.jar file and select "Finish" to complete the import.

Creating the ToxicSiteLocator Class

    The point of this Web service is to expose a method that is accessible via HTTP-based SOAP requests and that returns all of the toxic site locations within a specified distance of a specified address. These types of methods must be declared as public.

    Before creating the new method, you must first create a class to contain it.

  1. To create the ToxicSiteLocations class, expand the Dynamic Web Projects --> ToxicLocations node in the Project explorer, then right-click the JavaSource node and choose New --> Class from the drop-down menu.

    The Project Explorer

  2. In the New Java Class window enter "ToxicSiteLocator" in the "Name:" text field, enter "toxiclocation" in the "Package:" text field, accept the rest of the default options, and select "Finish" to create your new class.

    The New Java Class dialog

  3. You now have a new java class called "ToxicSiteLocator". Add the following import statements to add references to the ArcGIS Server Java API packages that will be used in this scenario.
    • import com.esri.arcgis.geodatabase.*;
    • import com.esri.arcgis.geometry.*;
    • import com.esri.arcgis.location.*;
    • import com.esri.arcgis.server.*;
    • import com.esri.arcgis.system.*;
  4. Your code should now appear as follows:
    	package toxiclocation;
    
    	import com.esri.arcgis.geodatabase.*;
    	import com.esri.arcgis.geometry.*;
    	import com.esri.arcgis.location.*;
    	import com.esri.arcgis.server.*;
    	import com.esri.arcgis.system.*;
    
    	public class ToxicSiteLocator {
    	}

Creating the Web service method

Now, you will create a method called findToxicLocations that takes as arguments an address, ZIP Code, and search distance and returns an integer representing the number of toxic sites found.

This method will ultimately open a cursor on a feature class in a geodatabase.

  1. Add the following class variables that will be used for connecting to the ArcGIS Server to the ToxicSiteLocator class. These variables should be set to the host, domain, account and password for connecting to the ArcGIS Server.
        private String m_host = "yourHost";
    	private String m_domain = "yourDomain";
    	private String m_username = "yourAGSUSER";
    	private String m_password = "yourAGSUSERPassword";
      
  2. Add the web method definition for finding toxic locations.
    	public int findToxicLocations(String address, String zipCode,
    		double distance) throws Exception {
    	}
      

    Your code should now look like the following:

    	package toxiclocation;
    
    	import com.esri.arcgis.geodatabase.*;
    	import com.esri.arcgis.geometry.*;
    	import com.esri.arcgis.location.*;
    	import com.esri.arcgis.server.*;
    	import com.esri.arcgis.system.*;
    
    	public class ToxicSiteLocator {
    		private String m_host = "yourHost";
    		private String m_domain = "yourDomain";
    		private String m_username = "yourAGSUSER";
    		private String m_password = "yourAGSUSERPassword";
    
    		public int findToxicLocations(String address, String zipCode,
    				double distance) throws Exception {
    		}
    

Adding local variable definitions

First add the definitions for all the local variables that will be used in this method. They will be discussed fully below.

	int numToxicLocations = 0;
	IServerContext serverContext = null;
	IServerConnection serverConnection = new ServerConnection();
	ServerInitializer serverInitializer = null;
	IServerObjectManager som = null;
	IServerObject serverObject = null;
	IGeocodeServer geocodeServer = null;
	IPropertySet addressPropertySet = null;
	IPropertySet resultsPropertySet = null;
	IPoint geocodedPoint = null;
	ISegmentCollection segmentCollection = null;
	IGeometry geometry = null;
	IGeocodeServerObjects geocodeServerObjects = null;
	IReferenceDataTables referenceTables = null;
	IEnumReferenceDataTable referenceDataTableEnum = null;
	IReferenceDataTable referenceTable = null;
	IDatasetName datasetName = null;
	IName workspaceName = null;
	IFeatureWorkspace featureWorkspace = null;
	IFeatureClass featureClass = null;
	ISpatialFilter spatialFilter = null;
	IFeatureCursor featureCursor = null;

	  

Validating input parameters

The first thing the Web method will do is verify the provided parameters are valid and, if not, return null.

Type the following code:

	// Verify input values
	if (address == null || zipCode == null || distance == 0.0) {
		return 0;
	}

Connecting to the GIS server

This Web service makes use of objects in the GIS server, so you will add code to connect to the GIS server and get the IServerObjectManager interface.

  1. Add the following lines of code:
    	try {
    		serverInitializer = new ServerInitializer();
    		serverInitializer.initializeServer(m_domain, m_username, m_password);
    		serverConnection.connect(m_host);
    		som = serverConnection.getServerObjectManager();
    

    The Web service will make use of the PortlandGC server object that you created with ArcCatalog. You get a server object by asking the server for a server context containing the object.

  2. Add the following line of code to get the server object's context:
    		serverContext = som.createServerContext("PortlandGC", "GeocodeServer");
    

    It's the responsibility of the developer to release the server object's context when finished using it. It's important to ensure that your method calls releaseContext() on the server context when the method no longer needs the server object or any other objects running in the context. It's also important that you ensure the context is released in the event of an error. So, the remainder of the code will run within a try/catch block. If an error occurs in the code in the try block, the code in the catch block will be executed. So, you will add the call to release the context at the end of the try block and in the catch block.

  3. Add the following code to your Web service:

    	  serverContext.releaseContext();
          return numToxicLocations;
        }
        catch (Exception e) {
          serverContext.releaseContext();
          e.printStackTrace();
          return 0;
        }
    

    Your findToxicLocations() method should now look like the following:

    public int findToxicLocations(String address, String zipCode,
    		  double distance) throws Exception {
    
    	  int numToxicLocations = 0;
    	  IServerContext serverContext = null;
    	  IServerConnection serverConnection = new ServerConnection();
    	  ServerInitializer serverInitializer = null;
    	  IServerObjectManager som = null;
    	  IServerObject serverObject = null;
    	  IGeocodeServer geocodeServer = null;
    	  IPropertySet addressPropertySet = null;
    	  IPropertySet resultsPropertySet = null;
    	  IPoint geocodedPoint = null;
    	  ISegmentCollection segmentCollection = null;
    	  IGeometry geometry = null;
    	  IGeocodeServerObjects geocodeServerObjects = null;
    	  IReferenceDataTables referenceTables = null;
    	  IEnumReferenceDataTable referenceDataTableEnum = null;
    	  IReferenceDataTable referenceTable = null;
    	  IDatasetName datasetName = null;
    	  IName workspaceName = null;
    	  IFeatureWorkspace featureWorkspace = null;
    	  IFeatureClass featureClass = null;
    	  ISpatialFilter spatialFilter = null;
    	  IFeatureCursor featureCursor = null;
    
    	  // Verify input values
    	  if (address == null || zipCode == null || distance == 0.0) {
    		  return 0;
          }
    
        try {
          serverInitializer = new ServerInitializer();
          serverInitializer.initializeServer(m_domain, m_username, m_password);
          serverConnection.connect(m_host);
          som = serverConnection.getServerObjectManager();
          serverContext = som.createServerContext("PortlandGC", "GeocodeServer");
    
          serverContext.releaseContext();
          return numToxicLocations;
        }
        catch (Exception e) {
          serverContext.releaseContext();
          e.printStackTrace();
          return 0;
        }
    }
    

Geocoding the input address

Now that you have connected to the GIS server and have a context containing the geocode server object, you will add the code to use the server object to geocode the input address and store the resulting point as gcPoint.

Add the following lines of code to your try block:

      serverObject = new IServerObjectProxy(serverContext.getServerObject());
      geocodeServer = new IGeocodeServerProxy(serverObject);

      addressPropertySet = new IPropertySetProxy(serverContext.createObject(PropertySet.
          getClsid()));
      addressPropertySet.setProperty("Street", address);
      addressPropertySet.setProperty("Zone", zipCode);

      // locate the address
      resultsPropertySet = geocodeServer.geocodeAddress(addressPropertySet, null);
      geocodedPoint = new IPointProxy(resultsPropertySet.getProperty("Shape"));

A PropertySet is a generic class that is used to hold a set of any properties. A PropertySet's properties are stored as name/value pairs. Examples for the use of a property set are to hold the properties required for opening an SDE workspace or geocoding an address. To learn more about PropertySet objects, see the online developer documentation.

Buffering the result and querying the toxic sites

You will buffer this point and use the resulting geometry to query the toxic sites from the ToxicSites feature class. Since the ToxicSites feature class is in the same workspace as the geocode server object's locator's reference data, you do not have to create a new connection to the geodatabase but can use the geocode server's connection.

  1. Add the following code to your try block to buffer the point, open the feature class, and query it:
          segmentCollection = new ISegmentCollectionProxy(serverContext.createObject(
              Polygon.getClsid()));
          segmentCollection.setCircle(geocodedPoint, distance);
          geometry = new IGeometryProxy(segmentCollection);
    
          geocodeServerObjects = new IGeocodeServerObjectsProxy(geocodeServer);
          referenceTables = new IReferenceDataTablesProxy(geocodeServerObjects.
              getAddressLocator());
          referenceDataTableEnum = new IEnumReferenceDataTableProxy(
        		  referenceTables.getTables());
    
          referenceDataTableEnum.reset();
          referenceTable = new IReferenceDataTableProxy(referenceDataTableEnum.
              next());
          datasetName = new IDatasetNameProxy(referenceTable.getName());
          workspaceName = new INameProxy(datasetName.getWorkspaceName());
          featureWorkspace = new IFeatureWorkspaceProxy(workspaceName.open());
    
          featureClass = featureWorkspace.openFeatureClass("toxicsites");
          spatialFilter = new ISpatialFilterProxy(serverContext.createObject(SpatialFilter.
              getClsid()));
          spatialFilter.setGeometryByRef(geometry);
          spatialFilter.setGeometryField(featureClass.getShapeFieldName());
          spatialFilter.setSpatialRel(esriSpatialRelEnum.esriSpatialRelIntersects);
    
          featureCursor = featureClass.search(spatialFilter, true);
    

    Since you can get the workspace from the geocode server object, the connection is pooled by the geocode server. The code as written still has to open the ToxicSites feature class. This application could be further optimized by using a pooled map server object that has a single layer whose source data is the ToxicSites feature class. The application would then get the feature class from the map server object, effectively using the map server to pool the feature class.

    If the ToxicSites feature class was not in the same workspace as the locator, this method would be the recommended approach for pooling both the workspace connection and the feature class.

    To complete the method you will now add code to loop through the features returned by the query and accumulate the numToxicSites variable.

  2. Add the following code to your try block:
          while ( featureCursor.nextFeature() != null) {
        	  numToxicLocations++;
          }
    

    Your ToxicSiteLocator class should now look like the following:

    package toxiclocation;
    
    import com.esri.arcgis.geodatabase.*;
    import com.esri.arcgis.geometry.*;
    import com.esri.arcgis.location.*;
    import com.esri.arcgis.server.*;
    import com.esri.arcgis.system.*;
    
    public class ToxicSiteLocator {
      private String m_host = "erikloanuc";
      private String m_domain = "avworld";
      private String m_username = "eric4490";
      private String m_password = "2thefuture";
    
      public int findToxicLocations(String address, String zipCode,
    		  double distance) throws Exception {
    
    	  int numToxicLocations = 0;
    	  IServerContext serverContext = null;
    	  IServerConnection serverConnection = new ServerConnection();
    	  ServerInitializer serverInitializer = null;
    	  IServerObjectManager som = null;
    	  IServerObject serverObject = null;
    	  IGeocodeServer geocodeServer = null;
    	  IPropertySet addressPropertySet = null;
    	  IPropertySet resultsPropertySet = null;
    	  IPoint geocodedPoint = null;
    	  ISegmentCollection segmentCollection = null;
    	  IGeometry geometry = null;
    	  IGeocodeServerObjects geocodeServerObjects = null;
    	  IReferenceDataTables referenceTables = null;
    	  IEnumReferenceDataTable referenceDataTableEnum = null;
    	  IReferenceDataTable referenceTable = null;
    	  IDatasetName datasetName = null;
    	  IName workspaceName = null;
    	  IFeatureWorkspace featureWorkspace = null;
    	  IFeatureClass featureClass = null;
    	  ISpatialFilter spatialFilter = null;
    	  IFeatureCursor featureCursor = null;
    
    	  // Verify input values
    	  if (address == null || zipCode == null || distance == 0.0) {
    		  return 0;
          }
    
        try {
          serverInitializer = new ServerInitializer();
          serverInitializer.initializeServer(m_domain, m_username, m_password);
          serverConnection.connect(m_host);
          som = serverConnection.getServerObjectManager();
          serverContext = som.createServerContext("PortlandGC", "GeocodeServer");
          serverObject = new IServerObjectProxy(serverContext.getServerObject());
          geocodeServer = new IGeocodeServerProxy(serverObject);
    
          addressPropertySet = new IPropertySetProxy(serverContext.createObject(PropertySet.
              getClsid()));
          addressPropertySet.setProperty("Street", address);
          addressPropertySet.setProperty("Zone", zipCode);
    
          // locate the address
          resultsPropertySet = geocodeServer.geocodeAddress(addressPropertySet, null);
          geocodedPoint = new IPointProxy(resultsPropertySet.getProperty("Shape"));
    
          segmentCollection = new ISegmentCollectionProxy(serverContext.createObject(
              Polygon.getClsid()));
          segmentCollection.setCircle(geocodedPoint, distance);
          geometry = new IGeometryProxy(segmentCollection);
    
          geocodeServerObjects = new IGeocodeServerObjectsProxy(geocodeServer);
          referenceTables = new IReferenceDataTablesProxy(geocodeServerObjects.
              getAddressLocator());
          referenceDataTableEnum = new IEnumReferenceDataTableProxy(
        		  referenceTables.getTables());
    
          referenceDataTableEnum.reset();
          referenceTable = new IReferenceDataTableProxy(referenceDataTableEnum.
              next());
          datasetName = new IDatasetNameProxy(referenceTable.getName());
          workspaceName = new INameProxy(datasetName.getWorkspaceName());
          featureWorkspace = new IFeatureWorkspaceProxy(workspaceName.open());
    
          featureClass = featureWorkspace.openFeatureClass("toxicsites");
          spatialFilter = new ISpatialFilterProxy(serverContext.createObject(SpatialFilter.
              getClsid()));
          spatialFilter.setGeometryByRef(geometry);
          spatialFilter.setGeometryField(featureClass.getShapeFieldName());
          spatialFilter.setSpatialRel(esriSpatialRelEnum.esriSpatialRelIntersects);
    
          featureCursor = featureClass.search(spatialFilter, true);
    
          while ( featureCursor.nextFeature() != null) {
        	  numToxicLocations++;
          }
          serverContext.releaseContext();
          return numToxicLocations;
        }
        catch (Exception e) {
          serverContext.releaseContext();
          e.printStackTrace();
          return 0;
        }
      }
    }
    

    Your Web service is now ready to be tested. To do so you will now create a web service client JSP application using the tools available in the Eclipse IDE.

Testing the Web service

To create a JSP web application that will test the ToxicSiteLocator web service you just created, perform the following steps in Eclipse.

  1. In the Project Explorer, right-click the ToxicSiteLocator.java file and choose New --> Other from the drop-down menu that appears.

  2. In the "New" dialog box, choose Web Services --> Web Service and click "Next"

  3. In the Web Services dialog box, make sure the following check boxes are selected, then click "Finish":

  4. The Eclipse IDE automatically creates a JSP web application that consumes the ToxicLocations web service. The application will start and the findToxicLocations() web method is available. Click on the findToxicLocations() method, then fill in the Inputs form with the following information:

This indicates that four toxic sites were found within 10,000 feet of the given address.

If you wish to run this client application a second time, expand the ToxicLocationsClient application created by the Eclipse IDE and find the WebContext\sampleToxicSiteLocatorProxy\TestClient.jsp page. Right-click on TestClient.jsp, and choose Run as --> Run on server from the drop down-menu.

When you evoke the Web method, the results are returned from the method in XML.

Creating a client application

Since your Web service exposes a language-neutral interface that can be called using HTTP, your Web service can be called from any language that understands HTTP and WSDL. An elaborate client application, which itself could be a Web application, a desktop application, or even another Web service, is not demonstrated here, but Eclipse IDE has generated a project called ToxicLocationsClient that fully illustrates the details of such an application.

Additional Resources

This scenario includes functionality and programming techniques covering a number of different aspects of ArcObjects and the ArcGIS Server API.

You are encouraged to read the section on ‘Developing ArcGIS Server applications’, to get a better understanding of core ArcGIS Server programming concepts and programming guidelines for working with server contexts and ArcObjects running within those contexts.

ArcGIS Server applications exploit the rich GIS functionality of ArcObjects. This Web service is no exception. It includes the use of ArcObjects to work with the components of a GeocodeServer to locate an address, manipulate geometries, and perform spatial queries against feature classes in a geodatabase. To learn more about these aspects of ArcObjects, refer to the online developer documentation on the Location, GeoDatabase, and Geometry object libraries.