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

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

Unicode bug in genshi response

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