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 :

http://localhost:8080/geoserver/ows?service=agsows&request=export&bbox=-115.8,30.4,-85.5,50.5&bboxSR=4326&layers=show:0,1,2&size=800,600&imageSR=4326&transparent=true

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);
4: 
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: }
9: 
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.

No comments:

Post a Comment