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

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

Improved docstrings

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