Mercurial > gemma
view pkg/models/intservices.go @ 5637:7f9600ab5461
merge
author | Thomas Junk <thomas.junk@intevation.de> |
---|---|
date | Wed, 14 Jun 2023 08:45:11 +0200 |
parents | 1222b777f51f |
children |
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) 2018 by via donau // – Österreichische Wasserstraßen-Gesellschaft mbH // Software engineering by Intevation GmbH // // Author(s): // * Sascha L. Teichmann <sascha.teichmann@intevation.de> // * Tom Gottfried <tom.gottfried@intevation.de> package models import ( "context" "database/sql" "net/http" "sync" "gemma.intevation.de/gemma/pkg/auth" "gemma.intevation.de/gemma/pkg/config" "gemma.intevation.de/gemma/pkg/log" ) // DatabaseScheme is the schema used for the data // accessible for the waterway user. const DatabaseScheme = "waterway" type ( // IntEntry repreents a published layer in the GeoServer. IntEntry struct { Schema string `json:"schema"` Name string `json:"name"` SQL *string `json:"sql"` KeyColumn *string `json:"keycolumn"` SRS *string `json:"srs"` Style bool `json:"style"` WMS bool `json:"wms"` WFS bool `json:"wfs"` WMSTAttribute *string `json:"wmst-attribute"` WMSTEndAttribute *string `json:"wmst-end-attribute"` } // LayerGroup is a layer group in a published GeoServer layer. LayerGroup struct { Name string `json:"name"` Layers []string `json:"layers"` } // IntServices are the internal services published by // the GeoServer. IntServices struct { mu sync.Mutex entries []IntEntry layerGroups []LayerGroup } ) const ( selectServicesSQL = ` SELECT schema, name, view_def, key_column, auth_name || ':' || auth_srid, style IS NOT NULL, as_wms, as_wfs, wmst_attribute, wmst_end_attribute FROM sys_admin.published_services LEFT JOIN spatial_ref_sys USING (srid) WHERE schema = $1 ORDER by name` selectGroupedLayersSQL = ` SELECT group_name, name FROM sys_admin.grouped_layers ORDER BY group_name, ord` selectStyleSQL = ` SELECT style FROM sys_admin.published_services WHERE name = $1 AND schema = $2` updateStyleSQL = ` UPDATE sys_admin.published_services SET style = $1 WHERE name = $2 AND schema = $3` ) // InternalServices is the list of the internal services // managed by the Gemma server. var InternalServices = &IntServices{} // LoadStyle a style for a given entry. func (e *IntEntry) LoadStyle() ([]byte, error) { var style []byte ctx := context.Background() err := auth.RunAs(ctx, "sys_admin", func(conn *sql.Conn) error { return conn.QueryRowContext( ctx, selectStyleSQL, e.Name, e.Schema).Scan(&style) }) return style, err } // UpdateInternalStyle updates a style in the database. func UpdateInternalStyle(req *http.Request, name string, style []byte) error { return auth.RunAsSessionUser(req, func(conn *sql.Conn) error { _, err := conn.ExecContext( req.Context(), updateStyleSQL, style, name, DatabaseScheme) if err == nil { InternalServices.Invalidate() } return err }) } // LayerGroups returns the list of layer groups from the database. func (ps *IntServices) LayerGroups() []LayerGroup { ps.mu.Lock() defer ps.mu.Unlock() if ps.entries == nil { if err := ps.load(); err != nil { log.Errorf("%v\n", err) return nil } } // To avoid races we simple make a deep copy. // As we don't have such many of them it light weight enough for now. groups := make([]LayerGroup, len(ps.layerGroups)) for i := range groups { layers := make([]string, len(ps.layerGroups[i].Layers)) copy(layers, ps.layerGroups[i].Layers) groups[i] = LayerGroup{Name: ps.layerGroups[i].Name, Layers: layers} } return groups } // Find looks for an internal service with a given name. func (ps *IntServices) Find(name string) (string, bool) { ps.mu.Lock() defer ps.mu.Unlock() if ps.entries == nil { if err := ps.load(); err != nil { log.Errorf("%v\n", err) return "", false } } if ps.has(name) { return config.GeoServerURL() + "/" + name, true } return "", false } func (ps *IntServices) has(service string) bool { var check func(*IntEntry) bool switch service { case "wms": check = func(e *IntEntry) bool { return e.WMS } case "wfs": check = func(e *IntEntry) bool { return e.WFS } default: return false } for i := range ps.entries { if check(&ps.entries[i]) { return true } } return false } func (ps *IntServices) load() error { // make empty slice to prevent retry if slice is empty. ps.entries = []IntEntry{} ps.layerGroups = []LayerGroup{} ctx := context.Background() // Load the internal layers. entries := func(conn *sql.Conn) error { rows, err := conn.QueryContext( ctx, selectServicesSQL, DatabaseScheme) if err != nil { return err } defer rows.Close() for rows.Next() { var entry IntEntry if err := rows.Scan( &entry.Schema, &entry.Name, &entry.SQL, &entry.KeyColumn, &entry.SRS, &entry.Style, &entry.WMS, &entry.WFS, &entry.WMSTAttribute, &entry.WMSTEndAttribute, ); err != nil { return err } ps.entries = append(ps.entries, entry) } return rows.Err() } // Load the layer groups. groups := func(conn *sql.Conn) error { rows, err := conn.QueryContext(ctx, selectGroupedLayersSQL) if err != nil { return err } defer rows.Close() for rows.Next() { var group, layer string if err := rows.Scan(&group, &layer); err != nil { return err } if n := len(ps.layerGroups); n > 0 && ps.layerGroups[n-1].Name == group { ps.layerGroups[n-1].Layers = append(ps.layerGroups[n-1].Layers, layer) } else { ps.layerGroups = append(ps.layerGroups, LayerGroup{ Name: group, Layers: []string{layer}, }) } } return rows.Err() } return auth.RunAllAs(ctx, "sys_admin", entries, groups) } // Invalidate invalidates the list on internal services. func (ps *IntServices) Invalidate() { ps.mu.Lock() ps.entries = nil ps.layerGroups = nil ps.mu.Unlock() } // InternalAll passes all entries. func InternalAll(IntEntry) bool { return true } // IntWMS passes only WMS entries. func IntWMS(entry IntEntry) bool { return entry.WMS } // IntWFS passes only WFS entries. func IntWFS(entry IntEntry) bool { return entry.WFS } // IntSQLView passes only entries with a SQL view. func IntSQLView(entry IntEntry) bool { return entry.SQL != nil } // IntWithStyle passes only entries with a style. func IntWithStyle(entry IntEntry) bool { return entry.Style } // IntByName passes only entries with a given name. func IntByName(name string) func(IntEntry) bool { return func(entry IntEntry) bool { return entry.Name == name } } // IntAnd filters internal entries that match all conditions. func IntAnd(accept ...func(IntEntry) bool) func(IntEntry) bool { return func(entry IntEntry) bool { for _, a := range accept { if !a(entry) { return false } } return true } } // Filter returns a list of internal services that match the // given condition. func (ps *IntServices) Filter(accept func(IntEntry) bool) []IntEntry { ps.mu.Lock() defer ps.mu.Unlock() if ps.entries == nil { if err := ps.load(); err != nil { log.Errorf("%v\n", err) return nil } } pe := make([]IntEntry, 0, len(ps.entries)) for _, e := range ps.entries { if accept(e) { pe = append(pe, e) } } return pe }