source: TI05-delivery/ows_framework/branches/ows_framework-refactor/ows_common/ows_common/pylons/ows_controller.py @ 3561

Subversion URL: http://proj.badc.rl.ac.uk/svn/ndg/TI05-delivery/ows_framework/branches/ows_framework-refactor/ows_common/ows_common/pylons/ows_controller.py@3561
Revision 3561, 7.8 KB checked in by spascoe, 13 years ago (diff)

Several bugs that were making the Capabilities document invalid.

The DDP test WMS server is now viewable through Cadcorp's Map Browser.

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