changeset 871:f0b6852c14d1 geo-style

More on uploading styles to gemma.
author Sascha L. Teichmann <sascha.teichmann@intevation.de>
date Sun, 30 Sep 2018 11:49:23 +0200
parents 29c11f4bf9db
children b882b2c796c1
files pkg/controllers/geostyling.go pkg/models/intservices.go schema/auth.sql schema/gemma.sql
diffstat 4 files changed, 131 insertions(+), 4 deletions(-) [+]
line wrap: on
line diff
--- a/pkg/controllers/geostyling.go	Sat Sep 29 23:33:14 2018 +0200
+++ b/pkg/controllers/geostyling.go	Sun Sep 30 11:49:23 2018 +0200
@@ -2,9 +2,16 @@
 
 import (
 	"bytes"
+	"database/sql"
+	"fmt"
 	"io"
 	"log"
 	"net/http"
+	"strings"
+
+	"gemma.intevation.de/gemma/pkg/auth"
+	"gemma.intevation.de/gemma/pkg/models"
+	"github.com/gorilla/mux"
 )
 
 const (
@@ -12,6 +19,65 @@
 	styleName    = "style"
 )
 
+const (
+	replaceNameXSLT = `<?xml version="1.0"?>
+<xsl:stylesheet version="1.0"
+  xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
+  xmlns:sld="http://www.opengis.net/sld"
+  xmlns:se="http://www.opengis.net/se">
+
+  <xsl:param name="name"/>
+
+  <xsl:template
+    match="/sld:StyledLayerDescriptor/sld:NamedLayer/sld:Name/text()">
+	<xsl:value-of select="$name"/>
+  </xsl:template>
+
+  <xsl:template
+    match="/sld:StyledLayerDescriptor/sld:NamedLayer/se:Name/text()">
+	<xsl:value-of select="$name"/>
+  </xsl:template>
+
+  <xsl:template match="@*|node()">
+    <xsl:copy>
+      <xsl:apply-templates select="@*|node()"/>
+    </xsl:copy>
+  </xsl:template>
+</xsl:stylesheet>`
+
+	xsltSQL = `SELECT xslt_process($1, $2, $3)`
+)
+
+func runXSLT(
+	req *http.Request,
+	document, stylesheet string,
+	params ...string,
+) (string, error) {
+	var result string
+
+	var args strings.Builder
+
+	for len(params) > 1 {
+		if args.Len() > 0 {
+			args.WriteByte(',')
+		}
+		fmt.Fprintf(&args, "%s='%s'",
+			strings.Replace(params[0], ",", "", -1),
+			strings.Replace(params[1], ",", "", -1))
+		params = params[2:]
+	}
+
+	log.Printf("params: %s\n", args.String())
+
+	err := auth.RunAsSessionUser(req, func(conn *sql.Conn) error {
+		return conn.QueryRowContext(
+			req.Context(), xsltSQL,
+			document, stylesheet, args.String()).Scan(&result)
+	})
+
+	return result, err
+}
+
 func extractStyle(req *http.Request) ([]byte, error) {
 
 	f, _, err := req.FormFile(styleName)
@@ -28,15 +94,46 @@
 	return buf.Bytes(), nil
 }
 
+func supportedWMSFeature(name string) bool {
+	return len(models.InternalServices.Filter(
+		models.IntAnd(models.IntWMS, models.IntByName(name)))) > 0
+}
+
 func uploadStyle(rw http.ResponseWriter, req *http.Request) {
 
-	data, err := extractStyle(req)
+	feature := mux.Vars(req)["feature"]
+
+	// only allow internal WMS features
+	if !supportedWMSFeature(feature) {
+		http.Error(rw,
+			fmt.Sprintf("WMS feature %s is not found.", feature),
+			http.StatusNotFound)
+		return
+	}
+
+	style, err := extractStyle(req)
 	if err != nil {
 		log.Printf("error: %v\n", err)
 		http.Error(rw, "error: "+err.Error(), http.StatusBadRequest)
 		return
 	}
 
-	log.Printf("uploaded file length: %d\n", len(data))
-	// TODO: Implement me!
+	log.Printf("uploaded file length: %d\n", len(style))
+
+	result, err := runXSLT(
+		req, string(style), replaceNameXSLT, "name", feature)
+
+	if err != nil {
+		log.Printf("error: %v\n", err)
+		http.Error(rw, "error: "+err.Error(), http.StatusBadRequest)
+		return
+	}
+
+	if err := models.UpdateInternalStyle(feature, result); err != nil {
+		log.Printf("error: %v\n", err)
+		http.Error(rw, "error: "+err.Error(), http.StatusInternalServerError)
+		return
+	}
+
+	// TODO: Configure GeoServer
 }
--- a/pkg/models/intservices.go	Sat Sep 29 23:33:14 2018 +0200
+++ b/pkg/models/intservices.go	Sun Sep 30 11:49:23 2018 +0200
@@ -22,11 +22,30 @@
 	mu      sync.Mutex
 }
 
-const selectPublishedServices = `SELECT relname, style, as_wms, as_wfs
+const (
+	selectPublishedServices = `SELECT relname, style, as_wms, as_wfs
 FROM sys_admin.published_services JOIN pg_class ON name = oid ORDER by relname`
 
+	updateStyleSQL = `
+UPDATE sys_admin.published_services
+SET style = $1
+WHERE name IN (SELECT oid FROM pg_class WHERE relname = $2)`
+)
+
 var InternalServices = &IntServices{}
 
+func UpdateInternalStyle(name, style string) error {
+	return auth.RunAs("sys_admin", context.Background(),
+		func(conn *sql.Conn) error {
+			_, err := conn.ExecContext(
+				context.Background(), updateStyleSQL, style, name)
+			if err == nil {
+				InternalServices.Invalidate()
+			}
+			return err
+		})
+}
+
 func (ps *IntServices) Find(name string) (string, bool) {
 	ps.mu.Lock()
 	defer ps.mu.Unlock()
@@ -97,6 +116,14 @@
 func IntWMS(entry IntEntry) bool { return entry.WMS }
 func IntWFS(entry IntEntry) bool { return entry.WFS }
 
+func IntByName(name string) func(IntEntry) bool {
+	return func(entry IntEntry) bool { return entry.Name == name }
+}
+
+func IntAnd(a, b func(IntEntry) bool) func(IntEntry) bool {
+	return func(entry IntEntry) bool { return a(entry) && b(entry) }
+}
+
 func (ps *IntServices) Filter(accept func(IntEntry) bool) []IntEntry {
 	ps.mu.Lock()
 	defer ps.mu.Unlock()
--- a/schema/auth.sql	Sat Sep 29 23:33:14 2018 +0200
+++ b/schema/auth.sql	Sun Sep 30 11:49:23 2018 +0200
@@ -33,6 +33,7 @@
 GRANT SELECT ON ALL TABLES IN SCHEMA sys_admin TO sys_admin;
 GRANT UPDATE ON sys_admin.system_config TO sys_admin;
 GRANT UPDATE ON systemconf.feature_colours TO sys_admin;
+GRANT UPDATE ON sys_admin.published_services TO sys_admin;
 GRANT INSERT, DELETE ON sys_admin.password_reset_requests TO sys_admin;
 GRANT INSERT, DELETE, UPDATE ON waterway.sounding_results_contour_lines TO sys_admin;
 
--- a/schema/gemma.sql	Sat Sep 29 23:33:14 2018 +0200
+++ b/schema/gemma.sql	Sun Sep 30 11:49:23 2018 +0200
@@ -5,6 +5,8 @@
 --
 CREATE EXTENSION postgis;
 
+CREATE EXTENSION xml2;
+
 -- Needed for 3D processing e.g. for cross section profiles
 -- FIXME disabled for now, because not available on Ubuntu/Debian
 --CREATE EXTENSION postgis_sfcgal;