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

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

Made a global CSMLConnector instance with a list function. Configuring the CSML backend has moved to util.py. Calling this module will now be the responsibility of the Pylons app.

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