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

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

Fixed bugs in qs_util.py via a regression test (test_wxs.py).

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