source: cows/trunk/cows/pylons/ows_controller.py @ 4101

Subversion URL: http://proj.badc.rl.ac.uk/svn/ndg/cows/trunk/cows/pylons/ows_controller.py@4101
Revision 4101, 8.7 KB checked in by spascoe, 11 years ago (diff)

GetFeatureInfo? bug fix.

Line 
1"""
2Base controller for OGC Web Services (OWS).
3
4@author: Stephen Pascoe
5@todo: Add pluggable security so replicate what was previously implemented
6    for the NDG discovery portal in ows_server.lib.BaseController.
7"""
8
9
10from pylons import request, response, config, c
11from pylons.controllers import WSGIController
12from pylons.templating import render
13from webhelpers import url_for
14
15from cows import exceptions as OWS_E
16from cows.util import negotiate_version, check_updatesequence
17from cows.builder import loadConfigFile
18from cows import helpers
19
20from cows.model import *
21
22from genshi.template import TemplateLoader
23from pkg_resources import resource_filename
24
25try:
26    from xml.etree import ElementTree as ET
27except ImportError:
28    from elementtree import ElementTree as ET
29
30import logging
31logger = logging.getLogger(__name__)
32
33# Instantiate Genshi template loader
34templateLoader = TemplateLoader(
35    resource_filename('cows.pylons', 'templates'),
36    auto_reload=True,
37    )
38
39# Configure
40#!TODO: rename this configuration object to something non-NDG specific
41#EXCEPTION_TYPE = request.environ['ndgConfig'].get('OWS_SERVER', 'exception_type', 'ogc').lower()
42EXCEPTION_TYPE = config.get('ows_server.exception_type', 'ogc')
43
44
45
46class OWSControllerBase(WSGIController):
47    """
48    Base class for all COWS Service controllers. 
49   
50    Subclasses should add supported operations to the cls.owsOperations attribute
51    and supply the a method of the same name (in UpperCammelCase). 
52   
53    When a request is received first self.__before__() will be called with the
54    parameters supplied by routes.  Override this method to configure the service
55    for a particular URL.  Then the operation method will be called
56    with the same parameters as self.__before__().  The operation parameters
57    (query_string) are not sent as method arguments.  Use self.owsParams or
58    self.getOwsParam() to retrieve these parameters.
59   
60    @ivar owsParams: A dictionary of parameters passed to the service.
61        Initially these comes from the query string but could come from
62        a HTTP POST in future.
63    @cvar owsOperations: A list of operation names
64   
65    """
66
67    owsOperations = []
68   
69    def __call__(self, environ, start_response):
70
71        self._loadOwsParams()
72
73        # If the EXCEPTION_TYPE is 'pylons' let Pylons catch any exceptions.
74        # Otherwise send an OGC exception report for any OWS_E.OwsError
75        if 'pylons' in EXCEPTION_TYPE:
76            self._fixOwsAction(environ)
77            return super(OWSControllerBase, self).__call__(environ, start_response)
78        else:
79            try:
80                self._fixOwsAction(environ)
81                return super(OWSControllerBase, self).__call__(environ, start_response)
82            except OWS_E.OwsError, e:
83                logger.exception(e)
84
85                tmpl = templateLoader.load('exception_report.xml')
86                start_response('200 OK', [('Content-type', 'text/xml')])
87                return [str(tmpl.generate(report=e.report).render('xml'))]
88
89    def _loadOwsParams(self):
90        # All OWS parameter names are case insensitive.
91        self._owsParams = {}
92        print request
93        #if request.has_key('REQUEST'):
94        #print request['REQUEST']
95        for k in request.params:
96            if k.lower() == 'x':
97                self._owsParams['i'] = request.params[k]
98            elif k.lower() == 'y':
99                self._owsParams['j'] = request.params[k]
100            else:
101                self._owsParams[k.lower()] = request.params[k]
102                       
103    def _fixOwsAction(self, environ):
104        rdict = environ['pylons.routes_dict']
105       
106        # Override the Routes action from the request query parameter
107        action = self.getOwsParam('request')
108
109        # Check action is a method in self and is defined as an OWS operation
110        if action not in self.owsOperations:
111            raise OWS_E.InvalidParameterValue('request=%s not supported' % action,
112                                              'REQUEST')
113        rdict['action'] = action
114
115    def getOwsParam(self, param, **kwargs):
116        """
117        Returns the value of a OWS parameter passed to the operation.
118        If kwargs['default'] is given it is taken to be the default
119        value otherwise the parameter is treated as mandatory and an
120        exception is raised if the parameter is not present.
121
122        """
123        try:
124            return self._owsParams[param.lower()]
125        except KeyError:
126            if 'default' in kwargs:
127                return kwargs['default']
128            else:
129                raise OWS_E.MissingParameterValue('%s parameter is not specified' % param,
130                                                  param)
131
132#-----------------------------------------------------------------------------
133# Functions that populate c.capabilities
134
135def addOperation(opName, formats=[]):
136    ops = c.capabilities.operationsMetadata.operationDict
137    ops[opName] = helpers.operation(url_for(qualified=True, action="index")+'?', formats=formats)
138
139def addLayer(name, title, abstract, srss, bbox, dimensions={}):
140    """
141    @param dimensions: Dictionary of dictionaries D[k1][k2]=val where
142        k1 is dimension name, k2 is a keyword parameter to send to
143        helpers.wms_dimension and val is it's value.
144
145    @todo: The helpers interface is leaking through.  Could make cleaner.
146
147    """
148       
149    if c.capabilities.contents is None:
150        c.capabilities.contents = Contents()
151
152    layer = helpers.wms_layer(name, title, srss, bbox, abstract)
153
154    for k1, kwargs in dimensions.items():
155        dim = helpers.wms_dimension(**kwargs)
156        layer.dimensions[k1] = dim
157
158    c.capabilities.contents.datasetSummaries.append(layer)
159
160def initCapabilities():
161    """
162    Initialise the capabilities object c.capabilities.
163
164    By default the server-wide configuration file is loaded and
165    used to populate some standard metadata.  The GetCapabilites
166    operation is added.
167
168    """
169    # Load the basic ServiceMetadata from a config file
170    configFile = config.get('ows_server.capabilities_config')
171    if configFile is None:
172        raise RuntimeError('No OWS configuration file')
173   
174    c.capabilities = loadConfigFile(configFile)
175
176    om = OperationsMetadata(operationDict={})
177    c.capabilities.operationsMetadata = om
178
179    addOperation('GetCapabilities', formats=['text/xml'])
180
181#-----------------------------------------------------------------------------
182
183class OWSController(OWSControllerBase):
184    """
185    Adds basic GetCapabilities response to OWSControllerBase.
186
187    @cvar service: If None does not enforce the SERVICE parameter.  Otherwise
188        raises exception if SERVICE is not correct on GetCapabilities request.
189    @cvar validVersions: A list of supported version numbers.  Automatic
190        version negotiation is performed according to this attribute.
191   
192    @ivar updateSequence: None if cache-control is not supported or an
193        updateSequence identifier.  This attribute should be set in the
194        controller's __before__() method.
195    """
196
197    owsOperations = ['GetCapabilities']
198
199    # Override these attributes to control how OWSController responds to
200    # GetCapabilities
201    service = None
202    validVersions = NotImplemented
203
204    # To enable cache control set this instance attribute in self.__before__().
205    updateSequence = None
206   
207    def GetCapabilities(self):
208
209        # Retrieve Operation parameters
210        service = self.getOwsParam('service')
211        version = self.getOwsParam('version', default=None)
212        format = self.getOwsParam('format', default='text/xml')
213        updateSequence = self.getOwsParam('updatesequence', default=None)
214
215        # Check update sequence
216        check_updatesequence(self.updateSequence, updateSequence)
217
218        # Do version negotiation
219        version = negotiate_version(self.validVersions, version)
220
221        # Get information required for the capabilities document
222        initCapabilities()
223        self._loadCapabilities()
224       
225        # Render the capabilities document       
226        response.headers['content-type'] = format
227        return self._renderCapabilities(version, format)
228
229
230    def _loadCapabilities(self):
231        """
232        Override in subclasses to populate c.capabilities with
233        operation and contents metadata.
234
235        """
236        pass
237
238    def _renderCapabilities(self, version, format):
239        """
240        Override in subclasses to render the capabilities document.
241        The response mime-type will already have been set.  Raise an
242        OWS exception if the format is not supported.
243
244        @param version: the version as a string
245        @param format: the format as a string
246       
247        @return: a template as expected by pylons.render()
248        """
249        raise NotImplementedError
250
251   
252
Note: See TracBrowser for help on using the repository browser.