view pkg/geoserver/templates.go @ 4606:dfe9cde6a20c geoserver_sql_views

Reflect database model changes for SQL views in backend In principle, we could use many datasources with different database schemas, but this would imply changing GeoServer initialization, service filtering, endpoints and eventually more. Since we do not need it, just hard-code the schema name as a constant.
author Tom Gottfried <tom@intevation.de>
date Thu, 05 Sep 2019 12:23:31 +0200
parents 152b9eb5ca47
children 124a5a7fe8d6
line wrap: on
line source

// This is Free Software under GNU Affero General Public License v >= 3.0
// without warranty, see README.md and license for details.
//
// SPDX-License-Identifier: AGPL-3.0-or-later
// License-Filename: LICENSES/AGPL-3.0.txt
//
// Copyright (C) 2019 by via donau
//   – Österreichische Wasserstraßen-Gesellschaft mbH
// Software engineering by Intevation GmbH
//
// Author(s):
//  * Sascha L. Teichmann <sascha.teichmann@intevation.de>
//  * Markus Kottländer <markus.kottlaender@intevation.de>

package geoserver

import (
	"context"
	"database/sql"
	"regexp"
	"strings"
	"text/template"

	"gemma.intevation.de/gemma/pkg/auth"
	"gemma.intevation.de/gemma/pkg/models"
)

const (
	selectConfigValSQL = `
SELECT config_val FROM sys_admin.system_config
WHERE config_key = $1`
)

func init() {
	RegisterStylePreprocessor(
		"sounding_results_contour_lines_geoserver",
		templateContourLinesFunc("morphology_classbreaks"))
	RegisterStylePreprocessor(
		"sounding_differences",
		templateContourLinesFunc("morphology_classbreaks_compare"))
	RegisterStylePreprocessor(
		"distance_marks_geoserver",
		templateConfigValues)
	RegisterStylePreprocessor(
		"distance_marks_ashore_geoserver",
		templateConfigValues)
	RegisterStylePreprocessor(
		"waterway_area",
		templateConfigValues)
	RegisterStylePreprocessor(
		"waterway_axis",
		templateConfigValues)
	// TODO: Add more layers.
}

func templateConfigValues(tmplTxt string) (string, error) {
	tmpl, err := template.New("template").Parse(tmplTxt)
	if err != nil {
		return "", err
	}

	// Try to extract the needed keys from the template.
	keys := extractKeysFromTemplate(tmplTxt)
	kv, err := loadConfigValues(keys)
	if err != nil {
		return "", err
	}

	var buf strings.Builder
	if err = tmpl.Execute(&buf, kv); err != nil {
		return "", err
	}
	return buf.String(), nil

}

// TODO: Use the parse tree of the template to extract keys.
var findKeysRe = regexp.MustCompile(`{{[-]?\s*\.([^-.\s]+)\s*[-]?}}`)

func extractKeysFromTemplate(tmpl string) []string {
	parts := findKeysRe.FindAllStringSubmatch(tmpl, -1)
	keys := make(map[string]struct{})
	for _, part := range parts {
		keys[part[1]] = struct{}{}
	}
	out := make([]string, len(keys))
	var i int
	for key := range keys {
		out[i] = key
		i++
	}
	return out
}

func loadConfigValues(keys []string) (map[string]string, error) {
	kv := make(map[string]string, len(keys))
	if len(keys) == 0 {
		return kv, nil
	}
	ctx := context.Background()
	if err := auth.RunAs(
		ctx,
		"sys_admin",
		func(conn *sql.Conn) error {
			stmt, err := conn.PrepareContext(ctx, selectConfigValSQL)
			if err != nil {
				return err
			}
			defer stmt.Close()
			for _, key := range keys {
				var val string
				if err := stmt.QueryRowContext(ctx, key).Scan(&val); err != nil {
					return err
				}
				kv[key] = val
			}
			return nil
		},
	); err != nil {
		return nil, err
	}

	return kv, nil
}

func templateContourLinesFunc(configKey string) func(string) (string, error) {
	return func(data string) (string, error) {
		return templateContourLines(data, configKey)
	}
}

func templateContourLines(data, configKey string) (string, error) {
	tmpl, err := template.New("template").Parse(data)
	if err != nil {
		return "", err
	}

	var cb []models.ClassBreak

	if cb, err = countourLinesClassBreaks(configKey); err != nil {
		return "", err
	}

	var buf strings.Builder
	if err = tmpl.Execute(&buf, cb); err != nil {
		return "", err
	}
	return buf.String(), nil
}

func countourLinesClassBreaks(configKey string) ([]models.ClassBreak, error) {

	var config string
	ctx := context.Background()
	if err := auth.RunAs(
		ctx,
		"sys_admin",
		func(conn *sql.Conn) error {
			return conn.QueryRowContext(
				ctx,
				selectConfigValSQL,
				configKey,
			).Scan(&config)
		},
	); err != nil {
		return nil, err
	}

	cc, err := models.ParseColorValues(config)
	if err != nil {
		return nil, err
	}

	return cc.ClassBreaks(), nil
}