Saturday, January 9, 2010

Extend GeoServer with customized OWS service :: part 4

In previous post of this series I created request bean class AgsOwsExportRequest for “ags-ows” service and helper class AgsOwsExportRequestKvpReader to populate request bean with parsed value objects from kvp parsers. Now it’s time to have a close look at how OWS dispatcher exactly dispatches requests and produces responses.

In part 2 I briefly mentioned GeoServer’s OWS dispather (org.geoserver.ows.Dispatcher), which is also re-used by “ags-ows” service to handle requests. So what really happens under the hood? GeoServer as J2EE application is built upon Java Servlet technology and Spring framework (Wicket was introduced later for the web UI). No matter whether you start GeoServer in built Jetty web server or deploy it in other containers like Tomcat, the start point is always the DispatcherServlet (org.springframework.web.servlet.DispatcherServlet) declared in the “web-app” project of GeoServer. So if you look at the “web.xml” in “web-app” project, you will find following servlet definition:
1: <servlet>
2:       <servlet-name>dispatcher</servlet-name>
3:       <servlet-class>
4:           org.springframework.web.servlet.DispatcherServlet
5:       </servlet-class>
6: </servlet>
you will also find following servlet mapping for different GeoServer url patterns as below:
1:     <servlet-mapping>
2:         <servlet-name>dispatcher</servlet-name>
3:         <url-pattern>/web/*</url-pattern>
4:     </servlet-mapping>
5:     <servlet-mapping>
6:       <servlet-name>dispatcher</servlet-name>
7:       <url-pattern>/rest/*</url-pattern>
8:     </servlet-mapping>
9:     <servlet-mapping>
10:       <servlet-name>dispatcher</servlet-name>
11:       <url-pattern>/security/*</url-pattern>
12:     </servlet-mapping>    
13:     <servlet-mapping>
14:       <servlet-name>dispatcher</servlet-name>
15:       <url-pattern>/wms/*</url-pattern>
16:     </servlet-mapping>
17:     <servlet-mapping>
18:       <servlet-name>dispatcher</servlet-name>
19:       <url-pattern>/wcs/*</url-pattern>
20:     </servlet-mapping>
21:     <servlet-mapping>
22:       <servlet-name>dispatcher</servlet-name>
23:       <url-pattern>/wfs/*</url-pattern>
24:     </servlet-mapping>
25:     <servlet-mapping>
26:       <servlet-name>dispatcher</servlet-name>
27:       <url-pattern>/ows/*</url-pattern>
28:     </servlet-mapping>
29:     <servlet-mapping>
30:       <servlet-name>dispatcher</servlet-name>
31:       <url-pattern>/wfsv/*</url-pattern>
32:     </servlet-mapping>
33:     <servlet-mapping>
34:       <servlet-name>dispatcher</servlet-name>
35:       <url-pattern>/wcs111/*</url-pattern>
36:     </servlet-mapping>
37:     <servlet-mapping>
38:       <servlet-name>dispatcher</servlet-name>
39:       <url-pattern>/kml/*</url-pattern>
40:     </servlet-mapping>
41:     <servlet-mapping>
42:      <servlet-name>dispatcher</servlet-name>
43:      <url-pattern>/styles/*</url-pattern>
44:     </servlet-mapping>
45:     <servlet-mapping>
46:      <servlet-name>dispatcher</servlet-name>
47:      <url-pattern>/www/*</url-pattern>
48:     </servlet-mapping>
49:     <servlet-mapping>
50:      <servlet-name>dispatcher</servlet-name>
51:      <url-pattern>/temp/*</url-pattern>
52:     </servlet-mapping>
53:     <servlet-mapping>
54:      <servlet-name>dispatcher</servlet-name>
55:      <url-pattern>/history/*</url-pattern>
56:     </servlet-mapping>
57:     <servlet-mapping>
58:      <servlet-name>dispatcher</servlet-name>
59:      <url-pattern>/gwc/*</url-pattern>
60:     </servlet-mapping>
61:     <servlet-mapping>
62:      <servlet-name>dispatcher</servlet-name>
63:      <url-pattern>/pdf/*</url-pattern>
64:     </servlet-mapping>
almost all types of requests will be handled by that Spring DispatcherServlet at the first place. According to Spring framework documentation, DispatcherServlet is: “the central dispatcher for HTTP request handlers/controllers, e.g. for web UI controllers or HTTP-based remote service exporters. Dispatches to registered handlers for processing a web request, providing convenient mapping and exception handling facilities.” So HTTP requests that reach it will be dispatched to different sub-routine based on registered handlers, and as mentioned in part 2 OWS dispatcher is one of those registered handler (as a org.springframework.web.servlet.handler.SimpleUrlHandlerMapping):
1:   <bean id="dispatcher" class="org.geoserver.ows.Dispatcher"/>
2:   <bean id="dispatcherMapping" 
3:             class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
4:     <property name="alwaysUseFullPath" value="true"/>
5:     <property name="mappings">
6:       <props>
7:         <prop key="/ows">dispatcher</prop>
8:         <prop key="/ows/**">dispatcher</prop>
9:         <prop key="/styles/**">filePublisher</prop>
10:         <prop key="/www/**">filePublisher</prop>
11:       </props>
12:     </property>
13:   </bean>
When ows requests come, they will be captured and handled in handleRequestInternal() method of dispatcher.
1: protected ModelAndView handleRequestInternal(HttpServletRequest httpRequest,
2:         HttpServletResponse httpResponse) throws Exception {
3:     ...
4:     // handle OWS requests
5:     ...
6: }
Within the dispatcher, for each request parameter it loops through all registered kvp parsers and apply them when “service” and “version” match. After that dispatcher will try to find the correct service and operation among all those being registered in application context based on the value of request parameter “service”, “version” and “request”. Take my “ags-ows” service as an example, if a request comes in like :


the dispatcher will find the instance of ArcGISServerOwsService and dispatch request to its export method because of what I’ve registered below in application context:
1: <bean id="ags-ows" 
2:     class="org.geoserver.ows.arcgisserver.ArcGISServerOWSService">
3:     <constructor-arg ref="geoServer"></constructor-arg>
4: </bean>   
5: <bean id="ags-ows-1.0.0" class="org.geoserver.platform.Service">
6:     <constructor-arg index="0" value="agsows"></constructor-arg>
7:     <constructor-arg index="1" ref="ags-ows"></constructor-arg>
8:     <constructor-arg index="2" value="1.0.0"></constructor-arg>
9:     <constructor-arg index="3">
10:         <list>            
11:             <value>Export</value>                            
12:         </list>
13:     </constructor-arg>
14: </bean>
After dispatcher locate the appropriate service and operation, it is aware which method in which service class to dispatch request to (in this case it’s the export() method in ArcGISServerOWSService class), it also knows number of types of input parameters for that method. So it will create the request bean by using an appropriate kvp request reader, and finally calls the operation method with request bean. You will see following in ows dispatcher.
1: ...
2: //dispatch the operation
3: Operation operation = dispatch(request, service);
5: //execute it
6: Object result = execute(request, operation);
7: ...
If you follow previous posts closely, you probably notice all necessary parts so far in this work flow have been covered such that the OWS dispatcher is able to find ArcGISServerOWSService class and call its export method
1: /**
2:   * a sample request:  
3:   * 
4:   * 
5:   */
6: public AgsOwsExportResponse export(AgsOwsExportRequest request) {
7:     return new AgsOwsExportResponse(this.geoServer);
8: }
What happens next is the most import part in this “ags-ows” service sample. In next post of this series I will focus on extending org.geoserver.ows.Response class to produce map response for “ags-ows” services and sends back to clients.

Friday, January 8, 2010

Extend GeoServer with customized OWS service :: part 3

In previous post of this series, I created the application context for “ags-ows” service project and also register a java bean of type org.geoserver.platform.Service in it (a bean instance of ArcGISServerOWSService along with the service name ‘ags-ows’ and version ‘1.0.0’ are passed in into constructor) so that GeoServer’s ows dispatcher can be reused to redirect requests to “ags-ows” service. A set of KvpParsers were also created and registered in application context to parse request parameters. So in this post, I will move onto AgsOwsExportRequest which is the request bean class for “ags-ows” service, and AgsOwsExportRequestKvpReader (a subclass of KvpRequestReader) which is used to to create request bean from parsed objects of request parameters.

Request bean and KvpRequestReader

A request bean is basically a java object carrying all parsed parameter key-value pairs (as well as their getter and setter methods) of a specific service operation’s request. For example GeoServer’s WMS service has GetMap operation so which uses GetMapRequest (org.vfny.geoserver.wms.requests.GetMapRequest) bean, and same idea GetCapabilities operation has WMSCapabilitiesRequest bean. Now for “ags-ows” service, since “export” is the only operation declared in ArcGISServerOWSService, I will create one request bean class and call it AgsOwsExportRequest (below is a sample implementation that omits getters and setters):
1: public class AgsOwsExportRequest {
3:   private LayerInfo[] layers;
4:   private Envelope bbox;
5:   private AgsOwsExportSize size;  
6:   private CoordinateReferenceSystem imageSR;  
7:   private CoordinateReferenceSystem bboxSR;
8:   private boolean transparent;
9:   private Color bgColor = Color.WHITE;
11:         public AgsOwsExportRequest() {}
12:        /*
13:         * omitted getter and setter methods
14:         * you need to add getter and setter in your own code 
15:         */
16: }
you probably notice that each request bean parameter represents one request parameter of “ags-ows” service, and the type of the parameter matches the binding class of parameter’s kvp parser. (e.g. AgsOwsKvpLayersParser binds to List<LayerInfo>, AgsOwsKvpSizeParser binds to AgsOwsExportSize class etc.)

Usually an OWS service’s operation logic takes its own request bean as input parameter (that’s why you saw AgsOwsExportRequest in previous post which I didn’t explain much then). Before the dispatcher redirects request the a service operation logic for further processing, it will create an instance of appropriate type of request bean and pass it in, and it’s actually a KvpRequestReader subclass’s job to create such request bean and populate it with parsed parameter objects from kvp parsers. So I created AgsOwsExportRequestKvpReader as below, which extends KvpRequestReader:
1: public class AgsOwsExportRequestKvpReader extends KvpRequestReader {
4:   public AgsOwsExportRequestKvpReader() {
5:     super(AgsOwsExportRequest.class);    
6:   }
8:   @Override
9:   public Object createRequest() throws Exception {
10:     AgsOwsExportRequest requestBean = new AgsOwsExportRequest();    
11:     return requestBean;
12:         }
14:   @Override
15:   public Object read(Object request, Map kvp, Map rawKvp) throws Exception {
16:     AgsOwsExportRequest exportRequest = (AgsOwsExportRequest), kvp, rawKvp);
17:     /*
18:      * do any specific setting for AgsOwsExportRequest request bean instance - 
19:      * - based on kvp and rawKvp 
20:      */
21:     // TODO: here shall I set default value for certain parameters -
22:     // - if they are missing from kvp?    
23:     return exportRequest;
24:   }
25: }
To extend KvpRequestReader, you basically override its createRequest() and read() method, and optionally set attribute “filter”.
  • createRequest() creates and returns a new instance of appropriate request bean object. Default creatRequest() does that by creating the new instance reflectively using KvpRequestReader.requestBean class (you set that class in constructor), but if you need to set extra attribute of request bean object then you should probably override it.
  • read() is usually called in ows dispather, and it takes a request bean instance created by createReques() as well as kvp (java.util.Map) parsed by KvpParsers and unparsed raw kvp (java.util.Map), and it is suppose to loop through each key-value pair of parsed kvp map and set the applicable value in request bean instance if there is a setter defined for the key.
  • filter” is an optional attribute (through setFilter(Set<String> filter)) you can set so that certain keys in kvp can be filtered out without setting the its value to request bean instance.
Finally as always, you need register your KvpRequestReader subclass in application context (but you don’t need do so for request bean class):
1:   <bean id="agsOwsExportKvpRequestReader" 
2:     class="org.geoserver.ows.argisserver.kvp.AgsOwsExportRequestKvpReader">
3:   </bean>
Now it’s time to validate what I’ve added in “ags-ows” service project in this post so far. Restart GeoServer in debug mode, and set a break point in org.geoserver.ows.Dispatcher’s parseRequestKvp method (see highlighted line below), and send a “ags-ows” service request:

1:     Object parseRequestKVP(Class type, Request request)
2:         throws Exception {
3:         KvpRequestReader kvpReader = findKvpRequestReader(type);
5:         if (kvpReader != null) {
6:             //check for http request awareness
7:             if (kvpReader instanceof HttpServletRequestAware) {
8:                 ((HttpServletRequestAware) kvpReader).setHttpRequest(request.httpRequest);
9:             }
11:             Object requestBean = kvpReader.createRequest();
13:             if (requestBean != null) {
14:                 requestBean =, request.kvp, request.rawKvp);
15:             }
17:             return requestBean;
18:         }
20:         return null;
21:     }
when it stops at the break point, check if variable “requestBean” is of class AgsOwsExportRequest, and also check if those attributes of AgsOwsExportRequest (e.g. “layers”, “bbox”, “imageSR” etc.) are populated with correct type of object values from kvp parsers. If so, it means AgsOwsExportRequest and AgsOwsExportRequestKvpReader are working as expected.

By now enough logics and functions have been added into “ags-ows” service to enable it to parse the request from client,  in next post of this series, I will discuss more on the whole picture of how an ows request is processed by GeoServer OWS dispatcher, which is also a good half-way summary of what I've done so far.

Thursday, January 7, 2010

Extend GeoServer with customized OWS service :: part 2

In previous post of this series, I've created "ags-ows" project under group id "playground". It will be rebuilt when GeoServer is rebuilt and you can also debug it along with other core GeoServer source code. So start from this post I am going to add more elements in this project little by little.

Create application context (applicationContext.xml)

The first thing to add for "ags-ows" service is to create application context which is configured through applicationContext.xml file in your project's src folder (e.g. \trunk\src\playground\ows-arcgisserver\src\main\java). applicationContext.xml is very important because service url mapping, services and operations, KvpParser, KvpRequestReader and Response almost everything I will create later must be registered as Java beans in it so that they can be loaded when GeoServer starts.
For now, you can start with an empty applicationContext.xml in your ows-arcgisserver project's root source code folder (\src\main\java)
1: <?xml version="1.0" encoding="UTF-8"?>
2: <beans>
3: </beans>
Reuse org.geoserver.ows.Dispatcher to dispatch the request

Class org.geoserver.ows.Dispatcher in GeoServer ows project is like a central hub which dispatches all ows service requests (e.g. WMS, WFS or WCS request) to appropriate OGC server implementation (wms, wfs or wcs GeoServer project) for further processing based a request's "service" and "version" parameters (e.g. if "service=WMS" then the request will be dispatched to wms implementation). If you look at the Spring application context in GeoServer "main" project you will find following servlet url mapping:
1: <bean id="dispatcherMapping" 
2:     class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
3:   <property name="alwaysUseFullPath" value="true"/>
4:   <property name="mappings">
5:     <props>
6:       <prop key="/ows">dispatcher</prop>
7:       <prop key="/ows/**">dispatcher</prop>
8:       <prop key="/styles/**">filePublisher</prop>
9:       <prop key="/www/**">filePublisher</prop>
10:     </props>
11:   </property>
12: </bean>
So all the requests matching pattern ".../geoserver/ows?..." or ".../geoserver/ows/*?..." (e.g. .../geoserver/ows/wms?) will be preprocessed and dispatched by ows dispatcher. This makes thing much easier for me because "ags-ows" service is suppose to be a fake OWS service that expects request like below:


Basically I can just reuse the ows dispatcher without changing even one line of code. But now if you send the requests above, it won't work because the dispatcher does not know the existence of "ags-ows" service. Two things must be done before dispatcher correctly dispatches the request when it contains "service=agsows":

1. Create a class representing "ags-ows" service which declares all the supported operations as its methods; (DefaultWebCoverageService111 class in wcs1_1 project is a good example of such class), in my case, I created one class called ArcGISServerOWSService:
1: public class ArcGISServerOWSService {
2:     public ArcGISServerOWSService(...) {
3:         //... 
4:     }
5:     public AgsOwsExportResponse export(AgsOwsExportRequest request) {
6:     //...
7:     }
8: }
For now don't worry about where class AgsOwsExportResponse and AgsOwsExportRequest come from, and other details which you can find out in attached sample code.

2. Register "ags-ows" service class in application context as below:
1: <?xml version="1.0" encoding="UTF-8"?>
2: <beans>
3:   <bean id="ags-ows" 
4:            class="org.geoserver.ows.arcgisserver.ArcGISServerOWSService">
5:           <constructor-arg ref="geoServer"/>
6:   </bean>
7:   <bean id="ags-ows-1.0.0" class="org.geoserver.platform.Service">
8:     <constructor-arg index="0" value="agsows"/>
9:     <constructor-arg index="1" ref="ags-ows"/>
10:     <constructor-arg index="2" value="1.0.0"/>
11:     <constructor-arg index="3">
12:       <list>
13:         <value>Export</value>
14:       </list>
15:     </constructor-arg>
16:   </bean>
17: </beans>
As you can see, you first need to register ArcGISServerOWSService (geoserver bean is input parameter of the constructor) as a bean in application context. But what's more important is to add another bean in type org.geoserver.platform.Service, in which you must give "service" (agsows), the reference to the ArcGISServerOWSService bean, "version" (1.0.0), and list of supported operations (Export) as constructor input parameters. Once you save the application context and restart GeoServer, OWS Dispatcher will recognize the request with "service=agsows" and redirect to ArcGISServerOWSService implementation.

Extend org.geoserver.ows.KvpParser to parse request parameter

A subclass of org.geoserver.ows.KvpParser is used by ows dispatcher to parse a particular request parameter value in HTTP Get request. The reason to parse parameter value is that those values are coming in as String (could be UTF-8 encoded or other) but before they can be correctly recognized and used by GeoServer they must be converted to appropriate type of Java object or primitive types. For example, WMS request parameter 'bbox' needs to be converted as an JTS Envelope object, or WMS request parameter 'bgcolor' must be converted into java.awt.Color object, and a subclass of KvpParser does exactly that job.

To extend KvpParser, you need to give a key which is the target request parameter it parses and a Class type in KvpParser subclass's constructor. The Class type is the type into which string request parameter value will be converted.
1: public abstract class KvpParser {
2:     ...
3:    /**
4:     * The key.
5:     */
6:     String key;
8:    /**
9:     * The class of parsed objects.
10:     */
11:     Class binding;
13:     public KvpParser(String key, Class binding) {
14:         this.key = key;
15:         this.binding = binding;
16:     }
17:     ...
18: }
You also need to override method "parse(String value)" that contains the actual logic to do the converting; for example the parse method in a WMS bbox kvp parser will create and return an JTS Envelope from a string in pattern "-minx, miny, maxx, maxy".
1: ...
2: /**
3: * Parses the string representation into the object representation.
4: *
5: * @param value The string value.
6: *
7: * @return The parsed object, or null if it could not be parsed.
8: *
9: * @throws Exception In the event of an unsuccesful parse.
10: */
11: public abstract Object parse(String value) throws Exception;
12: ...
In "ags-ows" service, one kvp parser is needed for each request parameter. But I don't have to actually create 5 different KvpParser subclasses because I can reuse some existing ones, for example org.geoserver.ows.kvp.BooleanKvpParser can be used for parameter "transparent", and org.geoserver.wfs.kvp.BBoxKvpParser can be used for parameter "bbox". For the rest, I am going to create my own. First I need a new "AgsOwsKvpLayersParser" to convert comma separated layer list into an array of GeoServer LayerInfo. Below is a sample implmentation:
1: public class AgsOwsKvpLayersParser extends KvpParser 
2:   implements IArcGISServerOWSServiceLogger {
4:   private GeoServer geoServer;
6:   public AgsOwsKvpLayersParser(String service, String version, GeoServer geoServer) {
7:     super("layers", List.class);  
8:     setService(service);
9:     setVersion(new Version(version));
10:     this.geoServer = geoServer;
11:   }
13:   @Override
14:   public Object parse(String value) throws Exception {    
15:"layers parsed by AgsOwsKvpLayersParser");                
16:     /*
17:      * Syntax: [show | hide | include | exclude]:layerId1,layerId2
18:      */        
19:     Tokenizer outter_delimeter = new Tokenizer(":");
20:     List unparsed = KvpUtils.readFlat(value, outter_delimeter);
21:     boolean isInclude = true;
22:     if(unparsed.size() == 2) {
23:       if(((String)unparsed.get(0)).equalsIgnoreCase("show") == true) {
24:         isInclude = true;
25:       } else if(((String)unparsed.get(0)).equalsIgnoreCase("hide") == true) {
26:         isInclude = false;
27:       } else if(((String)unparsed.get(0)).equalsIgnoreCase("include") == true) {
28:         // TODO: implement later
29:       } else if(((String)unparsed.get(0)).equalsIgnoreCase("exclude") == true) {
30:         // TODO: implement later
31:       }
32:     }    
33:     List<LayerInfo> layers = null;
34:     List requestedLayers = KvpUtils.readFlat((String)unparsed.get(1), KvpUtils.INNER_DELIMETER);
35:     if(isInclude == true) {
36:       layers = new ArrayList<LayerInfo>();
37:     } else {
38:       layers = this.geoServer.getCatalog().getLayers();
39:     }        
40:     for(int i=0; i<requestedLayers.size(); i++) {
41:       String layerName = (String)requestedLayers.get(i);      
42:       if(layerName != null) {
43:         LayerInfo layerInfo = this.geoServer.getCatalog().getLayerByName(layerName);    
44:         if(layerInfo != null) {
45:           if(isInclude == true) {
46:             // include layers being requested
47:             layers.add(layerInfo);
48:           } else {
49:             // exclude layers being requested
50:             layers.remove(layerInfo);
51:           }          
52:         }
53:       }
54:     }    
55:     return layers;    
56:   }
57: }
Second I need another kvp parser to parse parameter "size" which is in syntax "width,height". Since there is no such "size" class in JTS or GeoTools library I will create a simple class called "AgsOwsExportSize" to hold the width and height of a map and have kvp parser return an instance of that class. Below is a sample implementation of "AgsOwsKvpSizeParser":
1: public class AgsOwsKvpSizeParser extends KvpParser {
2:     public AgsOwsKvpSizeParser(String service, String version) {
3:         super("size", AgsOwsExportSize.class); 
4:         setService(service);
5:         setVersion(new Version(version));
6:     }
8:     @Override
9:     public Object parse(String value) throws Exception {  
10:         List unparsed = KvpUtils.readFlat(value, KvpUtils.INNER_DELIMETER);
11:         if(unparsed.size() != 2) {   
12:             return new AgsOwsExportSize(1024,768);
13:         } else {
14:             int width = Integer.parseInt((String)unparsed.get(0));
15:             int height = Integer.parseInt((String)unparsed.get(1));
16:             return new AgsOwsExportSize(width, height);
17:         }
18:     }
19: }
Finally I need one more kvp parser AgsOwsKvpSRParser to parse coordinate system wkid for request parameters "imageSR", below is the sample implementation:
1: public class AgsOwsKvpSRParser extends KvpParser 
2:   implements IArcGISServerOWSServiceLogger {
4:   public AgsOwsKvpSRParser(String key, String service, String version) {
5:     super(key, CoordinateReferenceSystem.class);  
6:     setService(service);
7:     setVersion(new Version(version));
8:   }
10:   @Override
11:   public Object parse(String value) throws Exception {    
12: + " parsed by AgsOwsKvpSRParser");
13:     CoordinateReferenceSystem crs = null;
14:     if(value!=null && "".equalsIgnoreCase(value)==false) {
15:             try {                
16:                 // create CoordinateReferenceSystem based on wkid
17:               crs = CRS.decode("EPSG:" + value);                
18:             } catch (Exception e) {
19:               AGSOWSLOGGER.warning("invalid parameter");
20:               e.printStackTrace();
21:             }
22:         }    
23:     return crs;    
24:   }
25: }
Two optional attributes you can set when you construct the kvp parser are "service" and "version", which you can either pass in into constructor or set them through parser's setter method. By setting "service" and "version", the kvp parser will only be applied on those requests with specific service type (e.g. service=WMS) and version number (e.g. version=1.3.0). Another important reason for you set service and version of kvp parser is to increase the its priority. Since only be one parser will be picked up to parse one particular request parameter, a matching service and version value will register a parser ahead of the rest if all of them have the same request parameter key.

Register kvp parsers in application context

Kvp parsers must be registered in the application context so that they will be instantiated and be functional when GeoServer starts. To register a kvp parser, simply add a in a project's applicationContext.xml and also give the input parameters for the constructor of parser class.
1: <bean class="kvpparser_class_name" id="kvpparser_id">
2:   <!-- list of constructor input parameters -->
3:   ...
4:         <constructor-arg index="0" value="agsows"/>
5:   <constructor-arg index="1" value="1.0.0"/>
6:         ...
7: </bean>
Up to this point, you've created the application context for "ags-ows" service with beans representing it so that dispatcher will redirect "ags-ows" request to it and you've also registered a few different kvp parsers, once you restart the GeoServer and send the following request:


Those request parameters will be parsed by your kvp parsers. To confirm that you can either set break point within the parse() method of any of those parsers or simply have parse() method print out something to the console.

In next post of the series, I will focusing on KvpRequestReader and request bean of "ags-ows" service.