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

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

Changed print statements to loggging calls where appropriate.

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