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

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

replacing ows_server config-option prefix with cows. Keep the old one as a backup for now.

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    try:
176        configFile = config['cows.capabilities_config']
177    except KeyError:
178        configFile = config.get('ows_server.capabilities_config')
179    if configFile is None:
180        raise RuntimeError('No OWS configuration file')
181   
182    c.capabilities = loadConfigFile(configFile)
183
184    om = OperationsMetadata(operationDict={})
185    c.capabilities.operationsMetadata = om
186
187    addOperation('GetCapabilities', formats=['text/xml'])
188
189#-----------------------------------------------------------------------------
190
191class OWSController(OWSControllerBase):
192    """
193    Adds basic GetCapabilities response to OWSControllerBase.
194
195    @cvar service: If None does not enforce the SERVICE parameter.  Otherwise
196        raises exception if SERVICE is not correct on GetCapabilities request.
197    @cvar validVersions: A list of supported version numbers.  Automatic
198        version negotiation is performed according to this attribute.
199   
200    @ivar updateSequence: None if cache-control is not supported or an
201        updateSequence identifier.  This attribute should be set in the
202        controller's __before__() method.
203    """
204
205    owsOperations = ['GetCapabilities']
206
207    # Override these attributes to control how OWSController responds to
208    # GetCapabilities
209    service = None
210    validVersions = NotImplemented
211
212    # To enable cache control set this instance attribute in self.__before__().
213    updateSequence = None
214   
215    def GetCapabilities(self):
216
217        # Retrieve Operation parameters
218        service = self.getOwsParam('service')
219        version = self.getOwsParam('version', default=None)
220        format = self.getOwsParam('format', default='text/xml')
221        updateSequence = self.getOwsParam('updatesequence', default=None)
222
223        # Check update sequence
224        check_updatesequence(self.updateSequence, updateSequence)
225
226        # Do version negotiation
227        version = negotiate_version(self.validVersions, version)
228
229        # Get information required for the capabilities document
230        initCapabilities()
231        self._loadCapabilities()
232       
233        # Render the capabilities document       
234        response.headers['content-type'] = format
235        return self._renderCapabilities(version, format)
236
237
238    def _loadCapabilities(self):
239        """
240        Override in subclasses to populate c.capabilities with
241        operation and contents metadata.
242
243        """
244        pass
245
246    def _renderCapabilities(self, version, format):
247        """
248        Override in subclasses to render the capabilities document.
249        The response mime-type will already have been set.  Raise an
250        OWS exception if the format is not supported.
251
252        @param version: the version as a string
253        @param format: the format as a string
254       
255        @return: a template as expected by pylons.render()
256        """
257        raise NotImplementedError
258
259
260def render_ows_exception(e):
261    tmpl = templateLoader.load('exception_report.xml')
262    return str(tmpl.generate(report=e.report).render('xml'))
263
264
Note: See TracBrowser for help on using the repository browser.