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

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

Removing unneeded matplotlib import from wfs. Adding try except to url_for import which has changed in new version of pylons.

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