source: cows/tags/cows-ddp-0.1/cows/pylons/ows_controller.py @ 4366

Subversion URL: http://proj.badc.rl.ac.uk/svn/ndg/cows/tags/cows-ddp-0.1/cows/pylons/ows_controller.py@4366
Revision 4366, 8.5 KB checked in by spascoe, 13 years ago (diff)

Tagging current release used by DDP stack

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