Skip to content
Snippets Groups Projects
server.py 37.1 KiB
Newer Older
#!/usr/bin/env python2.7
# -*- coding: utf-8 -*-

# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
#                                                                       #
#   MapServer REST API is a python wrapper around MapServer which       #
#   allows to manipulate a mapfile in a RESTFul way. It has been        #
#   developped to match as close as possible the way the GeoServer      #
#   REST API acts.                                                      #
#                                                                       #
#   Copyright (C) 2011-2013 Neogeo Technologies.                        #
#                                                                       #
#   This file is part of MapServer Rest API.                            #
#                                                                       #
#   MapServer Rest API is free software: you can redistribute it        #
#   and/or modify it under the terms of the GNU General Public License  #
#   as published by the Free Software Foundation, either version 3 of   #
#   the License, or (at your option) any later version.                 #
#                                                                       #
#   MapServer Rest API is distributed in the hope that it will be       #
#   useful, but WITHOUT ANY WARRANTY; without even the implied warranty #
#   of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the     #
#   GNU General Public License for more details.                        #
#                                                                       #
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #

import web
import json
import urlparse

import mralogs
import logging

import mapfile
from mra import MRA


import webapp
from webapp import HTTPCompatible, urlmap, get_data

import tools

# Some helper functions first.
def get_workspace(ws_name):
    with webapp.mightNotFound():
        return mra.get_workspace(ws_name)

# Now the main classes that handle the REST API.

class index(object):
    @HTTPCompatible()
    def GET(self, format):
        return {
            "workspaces": href("workspaces/"),
            "styles": href("styles/"),
            "layers": href("layers/"),
            "layergroups": href("layergroups/"),
            }
class workspaces(object):
    @HTTPCompatible()
    def GET(self, format, *args, **kwargs):
        return {"workspaces": [{
                    "name": ws_name,
                    "href": "%s/workspaces/%s.%s" % (web.ctx.home, ws_name, format)
                    } for ws_name in mra.list_workspaces()]
                }
    def POST(self, format):
        data = get_data(name="workspace", mandatory=["name"], authorized=["name"])
        ws_name = data.pop("name")
        with webapp.mightConflict():
            mra.create_workspace(ws_name).save()
        webapp.Created("%s/workspaces/%s.%s" % (web.ctx.home, ws_name, format))
    def GET(self, ws_name, format):
        return {"workspace": ({
                    "name": ws.name,
                    "dataStores":
                        href("%s/workspaces/%s/datastores.%s" % (web.ctx.home, ws.name, format)),
                        href("%s/workspaces/%s/coveragestores.%s" % (web.ctx.home, ws.name, format)),
                    "wmsStores": "", # TODO
    def GET(self, ws_name, format):
                    "href": "%s/workspaces/%s/datastores/%s.%s" % (
                        web.ctx.home, ws.name, ds_name, format)
                    } for ds_name in ws.iter_datastore_names()]
                }

    @HTTPCompatible()
    def POST(self, ws_name, format):
        data = get_data(name="dataStore", mandatory=["name"], authorized=["name", "title", "abstract", "connectionParameters"])
        with webapp.mightConflict("dataStore", workspace=ws_name):
        webapp.Created("%s/workspaces/%s/datastores/%s.%s" % (
                web.ctx.home, ws_name, ds_name, format))
    def GET(self, ws_name, ds_name, format):
        with webapp.mightNotFound("dataStore", workspace=ws_name):
            info = ws.get_datastore_info(ds_name)
        connectionParameters = info.get("connectionParameters", {})
        return {"dataStore": {
                    "name": info["name"],
                    "enabled": True, # TODO
                    "__default": False, # TODO
                    "workspace": {
                        "name": ws.name,
                        "href": "%s/workspaces/%s.%s" % (
                            web.ctx.home, ws.name, format),
                    "featureTypes": href("%s/workspaces/%s/datastores/%s/featuretypes.%s" % (
                                        web.ctx.home, ws.name, ds_name, format)
                    "connectionParameters": Entries(connectionParameters, tag_name="entry")
    @HTTPCompatible()
    def PUT(self, ws_name, ds_name, format):
        data = get_data(name="dataStore", mandatory=["name"], authorized=["name", "title", "abstract", "connectionParameters"])
            raise webapp.Forbidden("Can't change the name of a data store.")
        with webapp.mightNotFound("dataStore", workspace=ws_name):
            ws.update_datastore(ds_name, data)
        ws.save()

    @HTTPCompatible()
    def DELETE(self, ws_name, ds_name, format):
        # TODO: check, this is not consistent between ds/cs.
        # We need to check if this datastore is empty.
        assert_is_empty(ws.iter_featuretypemodels(ds_name=ds_name), "datastore", ds_name)
        with webapp.mightNotFound("dataStore", workspace=ws_name):
            ws.delete_datastore(ds_name)
        ws.save()


class featuretypes(object):
    @HTTPCompatible()
    def GET(self, ws_name, ds_name, format):
                    "href": "%s/workspaces/%s/datastores/%s/featuretypes/%s.%s" % (
                        web.ctx.home, ws.name, ds_name, ft.name, format)
                    } for ft in ws.iter_featuretypemodels(ds_name)]
    @HTTPCompatible()
    def POST(self, ws_name, ds_name, format):
        data = get_data(name="featureType",
                        mandatory=["name"],
                        authorized=["name", "title", "abstract"])

        # Creates first the feature type:
        with webapp.mightConflict("featureType", datastore=ds_name):
            with webapp.mightNotFound("featureType", datastore=ds_name):
                ws.create_featuretypemodel(ds_name, data["name"], data)
        # Then creates the associated layer by default:
        model = ws.get_featuretypemodel(ds_name, data["name"])

        mf = mra.get_available()

        with webapp.mightConflict():
            mf.create_layer(model, data["name"], True)
        mf.save()

        webapp.Created("%s/workspaces/%s/datastores/%s/featuretypes/%s.%s" % (
                web.ctx.home, ws.name, ds_name, data["name"], format))
    def GET(self, ws_name, ds_name, ft_name, format):
        with webapp.mightNotFound("dataStore", datastore=ds_name):
            dsft = ds[ft_name]
Maël Méliani's avatar
Maël Méliani committed
        with webapp.mightNotFound("featureType", datastore=ds_name):
            ft = ws.get_featuretypemodel(ds_name, ft_name)
Maël Méliani's avatar
Maël Méliani committed

        extent = ft.get_extent()
        latlon_extent = ft.get_latlon_extent()
                    "name": ft.name,
                    "nativeName": ft.name,
                    "namespace": None, # TODO
                    "title": ft.get_mra_metadata("title", ft.name),
                    "abstract": ft.get_mra_metadata("abstract", None),
                    "keywords": ft.get_mra_metadata("keywords", []),
                    "srs": "%s:%s" % (ft.get_authority()[0], ft.get_authority()[1]),
                    "nativeCRS": ft.get_wkt(),
                    "attributes": [{
                            "name": f.get_name(),
                            "minOccurs": 0 if f.is_nullable() else 1,
                            "maxOccurs": 1,
                            "nillable": f.is_nullable(),
                            "binding": f.get_type_name(),
                            "length": f.get_width(),
                            } for f in dsft.iterfields()],
                    "nativeBoundingBox": {
                        "minx": extent.minX(),
                        "miny": extent.minY(),
                        "maxx": extent.maxX(),
                        "maxy": extent.maxY(),
                        "crs": "%s:%s" % (ft.get_authority_name(), ft.get_authority_code()),
                        },
                    "latLonBoundingBox": {
                        "minx": latlon_extent.minX(),
                        "miny": latlon_extent.minY(),
                        "maxx": latlon_extent.maxX(),
                        "maxy": latlon_extent.maxY(),
                        "crs": "EPSG:4326",
                        },
                    "projectionPolicy": None, # TODO
                    "enabled": True, # TODO
                    "store": { # TODO: add key: class="dataStore"
                        "href": "%s/workspaces/%s/datastores/%s.%s" % (
                            web.ctx.home, ws_name, ds_name, format)
                    "maxFeatures": 0, # TODO
                    "numDecimals": 0, # TODO
    @HTTPCompatible()
    def PUT(self, ws_name, ds_name, ft_name, format):
        data = get_data(name="featureType", mandatory=["name"], authorized=["name", "title", "abstract"])
            raise webapp.Forbidden("Can't change the name of a feature type.")
        metadata = dict((k, v) for k, v in data.iteritems() if k in ["title", "abstract"])

        with webapp.mightNotFound("featureType", datastore=ds_name):
            ws.update_featuretypemodel(ds_name, ft_name, metadata)
    @HTTPCompatible()
    def DELETE(self, ws_name, ds_name, ft_name, format):
        # We need to check if there are any layers using this.
        assert_is_empty(ws.iter_layers(mra={"name":ft_name, "workspace":ws_name, "storage":ds_name,
                                            "type":"featuretype"}),"featuretype", ft_name)
        with webapp.mightNotFound("featureType", datastore=ds_name):
            ws.delete_featuretypemodel(ds_name, ft_name)
        ws.save()


class coveragestores(object):
    @HTTPCompatible()
    def GET(self, ws_name, format):
                    "href": "%s/workspaces/%s/coveragestores/%s.%s" % (
                        web.ctx.home, ws.name, cs_name, format)
                    } for cs_name in ws.iter_coveragestore_names()]
                }

    @HTTPCompatible()
    def POST(self, ws_name, format):
        data = get_data(name="coverageStore", mandatory=["name"], authorized=["name", "title", "abstract", "connectionParameters"])
        with webapp.mightConflict("coverageStore", workspace=ws_name):
            ws.create_coveragestore(cs_name, data)
        ws.save()

        webapp.Created("%s/workspaces/%s/coveragestores/%s.%s" % (
                web.ctx.home, ws_name, cs_name, format))
    def GET(self, ws_name, cs_name, format):
        with webapp.mightNotFound("coverageStore", workspace=ws_name):
            info = ws.get_coveragestore_info(cs_name)
        connectionParameters = info.get("connectionParameters", {})

        return {"coverageStore": {
                    "name": info["name"],
                    "type": None, # TODO
                    "enabled": True, # TODO
                    "__default": False, # TODO
                    "workspace": {
                        "name": ws.name,
                        "href": "%s/workspaces/%s.%s" % (
                            web.ctx.home, ws.name, format),
                    "coverages": href("%s/workspaces/%s/coveragestores/%s/coverages.%s" % (
                                    web.ctx.home, ws.name, cs_name, format)
                    "connectionParameters": connectionParameters and Entries({
                        "url": info["connectionParameters"]["url"],
                        "namespace": None, # TODO
                        }, tag_name="entry")
                    }
                }


    @HTTPCompatible()
    def PUT(self, ws_name, cs_name, format):
        data = get_data(name="coverageStore", mandatory=["name"], authorized=["name", "title", "abstract", "connectionParameters"])
            raise webapp.Forbidden("Can't change the name of a coverage store.")
        with webapp.mightNotFound("coverageStore", workspace=ws_name):
            ws.update_coveragestore(cs_name, data)
        ws.save()

    @HTTPCompatible()
    def DELETE(self, ws_name, cs_name, format):
        # TODO: check, this is not consistent between ds/cs.
        # We need to check if this datastore is empty.
        assert_is_empty(ws.iter_coverages(cs_name=cs_name), "coveragestore", ds_name)

        with webapp.mightNotFound("coverageStore", workspace=ws_name):
            ws.delete_coveragestore(cs_name)
        ws.save()


class coverages(object):
    @HTTPCompatible()
    def GET(self, ws_name, cs_name, format):
        return {"coverages": [{
                    "name": c.name,
                    "href": "%s/workspaces/%s/coveragestores/%s/coverages/%s.%s" % (
                        web.ctx.home, ws.name, cs_name, c.name, format)
    @HTTPCompatible()
    def POST(self, ws_name, cs_name, format):
        data = get_data(name="coverage", mandatory=["name"], authorized=["name", "title", "abstract"])

        with webapp.mightConflict("coverage", coveragestore=cs_name):
            with webapp.mightNotFound("coverage", coveragestore=cs_name):
                ws.create_coveragemodel(data["name"], cs_name, data)
        webapp.Created("%s/workspaces/%s/coveragestores/%s/coverages/%s.%s" % (
                web.ctx.home, ws.name, cs_name, data["name"], format))
    def GET(self, ws_name, cs_name, c_name, format):

        # with webapp.mightNotFound("coveragestore", workspace=ws_name):
        #     cs = ws.get_coveragestore(cs_name)

        with webapp.mightNotFound("coverage", coveragestore=cs_name):
Maël Méliani's avatar
Maël Méliani committed
        extent = c.get_extent()

        return {"coverage": ({
                    "name": c.name,
                    "nativeName": c.name,
                    "namespace": None, # TODO
                    "title": c.get_mra_metadata("title", c.name),
                    "abstract": c.get_mra_metadata("abstract", None),
                    "keywords": c.get_mra_metadata("keywords", []),
                    "nativeCRS": c.get_wkt(), # TODO: Add key class="projected" if projected...
                    "srs": "%s:%s" % (c.get_authority_name(), c.get_authority_code()),
                    "nativeBoundingBox": {
                        "minx": extent.minX(),
                        "miny": extent.minY(),
                        "maxx": extent.maxX(),
                        "maxy": extent.maxY(),
                        "crs": "%s:%s" % (c.get_authority_name(), c.get_authority_code()), # TODO: Add key class="projected" if projected...
                        },
                    "latLonBoundingBox":{
                        "minx": latlon_extent.minX(),
                        "miny": latlon_extent.minY(),
                        "maxx": latlon_extent.maxX(),
                        "maxy": latlon_extent.maxY(),
                        "crs": "EPSG:4326"
                        },
                    "enabled": True, # TODO
                    "metadata": None, # TODO
                    "store": { # TODO: Add attr class="coverageStore"
                        "href": "%s/workspaces/%s/coveragestores/%s.%s" % (
                            web.ctx.home, ws_name, cs_name, format)
                        },
                    "nativeFormat": None, # TODO
                    "grid": { # TODO: Add attr dimension
                        "range": {
                            "low": None, # TODO
                            "high": None, # TODO
                            },
                        "transform": {
                            "scaleX": None, # TODO
                            "scaleY": None, # TODO
                            "shearX": None, # TODO
                            "shearY": None, # TODO
                            "translateX": None, # TODO
                            "translateY": None, # TODO
                            },
                        "crs": None,
                        },
                    "supportedFormats": [], # TODO
                    "interpolationMethods": [], # TODO
                    "defaultInterpolationMethod": None,
                    "dimensions": [], # TODO
                    "projectionPolicy": None, # TODO
                    "requestSRS": None, # TODO
                    "responseSRS": None, # TODO
    @HTTPCompatible()
    def PUT(self, ws_name, cs_name, c_name, format):
        data = get_data(name="coverage", mandatory=["name"], authorized=["name", "title", "abstract"])
        if c_name != data["name"]:
            raise webapp.Forbidden("Can't change the name of a coverage.")


        metadata = dict((k, v) for k, v in data.iteritems() if k in ["title", "abstract"])

        with webapp.mightNotFound("coverage", coveragestore=cs_name):
            ws.update_coveragemodel(c_name, cs_name, metadata)
    @HTTPCompatible()
    def DELETE(self, ws_name, cs_name, c_name, format):
        # We need to check if there are any layers using this.
        assert_is_empty(ws.iter_layers(mra={"name":c_name, "workspace":ws_name, "storage":cs_name,
                                            "type":"coverage"}), "coverage", ft_name)
        with webapp.mightNotFound("coverage", coveragestore=cs_name):
    @HTTPCompatible(allow_all=True)
    def PUT(self, ws_name, st_type, st_name, f_type, format):
        import zipfile

        # TODO: According to geoserv's examples we might have to handle
        # directories as well as files, in that case we want to upload
        # all the files from the directory.

        # Lets first try to get the file.
        if f_type == "file":
            # Check if zip or not...
            data = web.data()
        elif f_type == "url":
        # Now we make sure the store exists.
            ws.get_store_info(st_type, st_name)
        except KeyError:
            # Create the store if it seems legit and it does not exist.
            if st_type == "datastores" or st_type == "coveragestores":
                st_type = st_type[:-1] # Remove trailing 's'
                with webapp.mightConflict("dataStore", workspace=ws_name):
                    ws.create_store(st_type, st_name, {})
            # Finaly check if its OK now.
            with webapp.mightNotFound(message="Store {exception} does not exist "
                                      "and it could not be created."):
                ws.get_store_info(st_type, st_name)

        # Then we store the file.
        ext = web.ctx.env.get('CONTENT_TYPE', '').split("/")[-1]
        path = mra.create_file(st_name + (".%s" % ext) if ext else "", data=data)
        dest = os.path.join(os.path.split(path)[0], st_name)

        # We also unzip it if its ziped.
        ctype = web.ctx.env.get('CONTENT_TYPE', None)
        if ctype == "application/zip":
            z = zipfile.ZipFile(path)
            for f in z.namelist():
                fp = mra.mk_path(mra.get_file_path(st_name, f))

                # If the file has the correct target we might want it.
                if format and fp.endswith(format) and not tools.is_hidden(fp):
                    path = fp

        ws.update_store(st_type, st_name, {"connectionParameters":{"url":"file:"+mra.pub_file_path(path)}})
        ws.save()

        # Finally we might have to configure it.
        params = web.input(configure="none")
        if params.configure == "first":
        elif params.configure == "none":
            pass
        elif params.configure == "all":
            raise webapp.BadRequest(message="configure must be one of 'first', 'none' or 'all'.")
    def GET(self, format):
Wannes Rombouts's avatar
Wannes Rombouts committed
                    "name": s_name,
                    "href": "%s/styles/%s.%s" % (web.ctx.home, s_name, format)
                    } for s_name in mra.list_styles()]
    @HTTPCompatible()
    def POST(self, format):
        params = web.input(name=None)
        name = params.name
        if name == None:
            raise webapp.BadRequest(message="no parameter 'name' given.")

        data = web.data()
Wannes Rombouts's avatar
Wannes Rombouts committed
        mra.create_style(name, data)
        webapp.Created("%s/styles/%s.%s" % (web.ctx.home, name, format))


class style(object):
    @HTTPCompatible(authorize=["sld"])
    def GET(self, s_name, format):
        with webapp.mightNotFound():
Wannes Rombouts's avatar
Wannes Rombouts committed
            style = mra.get_style(s_name)
Wannes Rombouts's avatar
Wannes Rombouts committed
            return style
        return {"style": {
                    "name": s_name,
                    "sldVersion": Entries(["1.0.0"], tag_name="version"),
                    "filename": s_name + ".sld",
                    }
                }
    @HTTPCompatible()
    def PUT(self, s_name, format):
Wannes Rombouts's avatar
Wannes Rombouts committed
        #TODO: Also update layers using this style.
        with webapp.mightNotFound():
Wannes Rombouts's avatar
Wannes Rombouts committed
            mra.delete_style(s_name)
Wannes Rombouts's avatar
Wannes Rombouts committed
        mra.create_style(s_name, data)
    @HTTPCompatible()
    def DELETE(self, s_name, format):
Wannes Rombouts's avatar
Wannes Rombouts committed
        #TODO: Maybe check for layers using this style?
        with webapp.mightNotFound():
Wannes Rombouts's avatar
Wannes Rombouts committed
            mra.delete_style(s_name)
    def GET(self, format):
Wannes Rombouts's avatar
Wannes Rombouts committed
        mf = mra.get_available()
        return {"layers": [{
                    "name": layer.ms.name,
                    "href": "%s/layers/%s.%s" % (web.ctx.home, layer.ms.name, format),
    @HTTPCompatible()
    def POST(self, format):
        data = get_data(name="layer",
                        mandatory=["name", "resource"],
                        authorized=["name", "title", "abstract", "resource", "enabled"])

        l_name = data.pop("name")
        l_enabled = data.pop("enabled", True)

Wannes Rombouts's avatar
Wannes Rombouts committed
        href = data["resource"]["href"]
Wannes Rombouts's avatar
Wannes Rombouts committed
            ws_name, st_type, st_name, r_type, r_name = mra.href_parse(href, 5)
Wannes Rombouts's avatar
Wannes Rombouts committed
            raise webapp.NotFound(message="ressource '%s' was not found." % href)
        st_type, r_type = st_type[:-1], r_type[:-1] # Remove trailing s.
        with webapp.mightNotFound(r_type, workspace=ws_name):
            try:
                model = ws.get_layermodel(r_type, st_name, r_name)
Wannes Rombouts's avatar
Wannes Rombouts committed
        mf = mra.get_available()
        with webapp.mightConflict():
            mf.create_layer(model, l_name, l_enabled)
        webapp.Created("%s/layers/%s.%s" % (web.ctx.home, l_name, format))
    def GET(self, l_name, format):
Wannes Rombouts's avatar
Wannes Rombouts committed
        mf = mra.get_available()
        with webapp.mightNotFound():
            layer = mf.get_layer(l_name)

        data_type, store_type = {
            "featuretype": ("featuretype", "datastore"),
            "coverage": ("coverage", "coveragestore")
            }[layer.get_mra_metadata("type")]

        return {"layer" : {
                    "path": "/", # TODO
                    "defaultStyle": {
                        "name": layer.ms.classgroup,
                        "href": "%s/styles/%s.%s" % (web.ctx.home, layer.ms.classgroup, format),
                    "styles": [{ # TODO: Add attr class="linked-hash-set"
                            "href": "%s/styles/%s.%s" % (web.ctx.home, s_name, format),
                            } for s_name in layer.iter_styles()],
                    "resource": { # TODO: Add attr class="featureType|coverage"
                        "name": layer.get_mra_metadata("name"),
                        "href": "%s/workspaces/%s/%ss/%s/%ss/%s.%s" % (
                            web.ctx.home, layer.get_mra_metadata("workspace"),
                            store_type, layer.get_mra_metadata("storage"), data_type, layer.get_mra_metadata("name"), format),
                        },
                    "enabled": bool(layer.ms.status),
                    "attribution": { # TODO
                        "logoHeight": 0,
                        },
                    }
    @HTTPCompatible()
    def PUT(self, l_name, format):
Wannes Rombouts's avatar
Wannes Rombouts committed
        mf = mra.get_available()
        data = get_data(name="layer", mandatory=["name", "resource"],
                        authorized=["name", "title", "abstract", "resource", "enabled"])
        if l_name != data.pop("name"):
            raise webapp.Forbidden("Can't change the name of a layer.")

        l_enabled = data.pop("enabled", True)

Wannes Rombouts's avatar
Wannes Rombouts committed

Wannes Rombouts's avatar
Wannes Rombouts committed
        href = data["resource"]["href"]
Wannes Rombouts's avatar
Wannes Rombouts committed
            ws_name, st_type, st_name, r_type, r_name = mra.href_parse(href, 5)
Wannes Rombouts's avatar
Wannes Rombouts committed
            raise webapp.NotFound(message="ressource '%s' was not found." % href)
        st_type, r_type = st_type[:-1], r_type[:-1] # Remove trailing s.
        with webapp.mightNotFound(r_type, workspace=ws_name):
            try:
                model = ws.get_layermodel(r_type, st_name, r_name)
Wannes Rombouts's avatar
Wannes Rombouts committed
        mf = mra.get_available()
        with webapp.mightNotFound():
            if layer.get_mra_metadata("type") != r_type:
Wannes Rombouts's avatar
Wannes Rombouts committed
                raise webapp.BadRequest("Can't change a '%s' layer into a '%s'."
                                        % (layer.get_mra_metadata("type"), r_type))
            model.configure_layer(layer, l_enabled)
    @HTTPCompatible()
    def DELETE(self, l_name, format):
Wannes Rombouts's avatar
Wannes Rombouts committed
        mf = mra.get_available()
        with webapp.mightNotFound():
            mf.delete_layer(l_name)
        mf.save()


class layerstyles(object):
    @HTTPCompatible()
    def GET(self, l_name, format):
Wannes Rombouts's avatar
Wannes Rombouts committed
        mf = mra.get_available()
        with webapp.mightNotFound():
            layer = mf.get_layer(l_name)

        if format == "sld":
            return layer.getSLD()

        return {"styles": [{
                    "name": s_name,
                    "href": "%s/styles/%s.%s" % (web.ctx.home, s_name, format),
    @HTTPCompatible()
    def POST(self, l_name, format):
        data = get_data(name="style", mandatory=["resource"],
                        authorized=["name", "title", "abstract", "resource"])
Wannes Rombouts's avatar
Wannes Rombouts committed
        href = data["resource"]["href"]
Wannes Rombouts's avatar
Wannes Rombouts committed
            _, s_name = mra.href_parse(href, 2)
Wannes Rombouts's avatar
Wannes Rombouts committed
            raise webapp.NotFound(message="style '%s' was not found." % href)
        with webapp.mightNotFound():
Wannes Rombouts's avatar
Wannes Rombouts committed
            style = mra.get_style(s_name)
        mf = mra.get_available()
        with webapp.mightNotFound():
            layer = mf.get_layer(l_name)

        layer.add_style_sld(mf, s_name, style)
        mf.save()

        webapp.Created("%s/layers/%s/layerstyles/%s.%s" % (web.ctx.home, l_name, s_name, format))
    @HTTPCompatible()
    def DELETE(self, l_name, s_name, format):
Wannes Rombouts's avatar
Wannes Rombouts committed
        mf = mra.get_available()
        with webapp.mightNotFound():
            layer = mf.get_layer(l_name)
        with webapp.mightNotFound("style", layer=l_name):
            layer.remove_style(s_name)
        mf.save()


class layerfields(object):
    @HTTPCompatible()
    def GET(self, l_name, format):
Wannes Rombouts's avatar
Wannes Rombouts committed
        mf = mra.get_available()
        with webapp.mightNotFound():
                    "name": layer.get_metadata("gml_%s_alias" % field, None),
                    "fieldType": layer.get_metadata("gml_%s_type" % field, None),
    def GET(self, format):
Wannes Rombouts's avatar
Wannes Rombouts committed
        mf = mra.get_available()
                    "href": "%s/layergroups/%s.%s" % (web.ctx.home, lg.name, format)
    @HTTPCompatible()
    def POST(self, format):
Wannes Rombouts's avatar
Wannes Rombouts committed
        mf = mra.get_available()
        data = get_data(name="layerGroup", mandatory=["name"], authorized=["name", "title", "abstract", "layers"])
        with webapp.mightNotFound():
            layers = [mf.get_layer(l_name) for l_name in data.pop("layers", [])]
        with webapp.mightConflict():
            lg = mf.create_layergroup(lg_name, data)
        lg.add(*layers)

        mf.save()

        webapp.Created("%s/layergroups/%s.%s" % (web.ctx.home, lg.name, format))
    def GET(self, lg_name, format):
Wannes Rombouts's avatar
Wannes Rombouts committed
        mf = mra.get_available()
        with webapp.mightNotFound():
        latlon_extent = lg.get_latlon_extent()
                    "mode": None, # TODO
                    "publishables": [{
                            "href": "%s/layers/%s.%s" % (web.ctx.home, layer.ms.name, format),
                            } for layer in lg.iter_layers()],
                    "bounds": {
                        "minx": latlon_extent.minX(),
                        "miny": latlon_extent.minY(),
                        "maxx": latlon_extent.maxX(),
                        "maxy": latlon_extent.maxY(),
                        "crs": "EPSG:4326",
                    "styles": [],
    @HTTPCompatible()
    def PUT(self, lg_name, format):
Wannes Rombouts's avatar
Wannes Rombouts committed
        mf = mra.get_available()
        with webapp.mightNotFound():
        data = get_data(name="layerGroup", mandatory=["name"], authorized=["name", "title", "abstract", "layers"])
        if lg_name != data.pop("name"):
            raise webapp.Forbidden("Can't change the name of a layergroup.")

        layers = data.pop("layers", [])
        if not isinstance(layers, list) or any(not isinstance(x, basestring) for x in layers):
            raise webapp.BadRequest("layers must be a list of layer names.")

    @HTTPCompatible()
    def DELETE(self, lg_name, format):
Wannes Rombouts's avatar
Wannes Rombouts committed
        mf = mra.get_available()
        with webapp.mightNotFound():
class OWSGlobalSettings(object):

    @HTTPCompatible()
    def GET(self, ows, format):
        mf = mra.get_available()
        try:
            if mf.get_metadata("%s_enable_request" % ows) == "*":
                is_enabled = True
        except:
            is_enabled = False

            ows: {
                "enabled": is_enabled,
                "name": ows,
                "schemaBaseURL": mf.get_metadata("ows_schemas_location", "http://schemas.opengis.net"),
                }
            }
    @HTTPCompatible()
    def PUT(self, ows, format):
        mf = mra.get_available()
        data = get_data(name=ows, mandatory=["enabled"], authorized=["enabled"])
        is_enabled = data.pop("enabled")
        values = {True: "*", "true": "*", False: "", "false": ""}
        if is_enabled not in values:
            raise KeyError("'%s' is not valid" % is_enabled)
        mf.set_metadata("%s_enable_request" % ows, values[is_enabled])
        mf.save()


class OWSSettings(object):

    @HTTPCompatible()
    def GET(self, ows, ws_name, format):
        ws = get_workspace(ws_name)
        try:
            if ws.get_metadata("%s_enable_request" % ows) == "*":
                is_enabled = True
        except:
            is_enabled = False

        return {
            ows: {
                "enabled": is_enabled,
                "name": ows,
                "schemaBaseURL": ws.get_metadata("ows_schemas_location", "http://schemas.opengis.net"),
                }
            }

    @HTTPCompatible()
    def PUT(self, ows, ws_name, format):
        ws = get_workspace(ws_name)
        data = get_data(name=ows, mandatory=["enabled"], authorized=["enabled"])
        is_enabled = data.pop("enabled")
        values = {True: "*", "true": "*", False: "", "false": ""}
        if is_enabled not in values:
            raise KeyError("'%s' is not valid" % is_enabled)
        ws.set_metadata("%s_enable_request" % ows, values[is_enabled])
        ws.save()

    @HTTPCompatible()
    def DELETE(self, ows, ws_name, format):
        ws = get_workspace(ws_name)
        ws.set_metadata("%s_enable_request" % ows, "")
        ws.save()

urlmap(workspaces, "workspaces")
urlmap(workspace, "workspaces", ())
urlmap(datastores, "workspaces", (), "datastores")
urlmap(datastore, "workspaces", (), "datastores", ())
urlmap(featuretypes, "workspaces", (), "datastores", (), "featuretypes")
urlmap(featuretype, "workspaces", (), "datastores", (), "featuretypes", ())
urlmap(coveragestores, "workspaces", (), "coveragestores")
urlmap(coveragestore, "workspaces", (), "coveragestores", ())