Skip to content

Commit

Permalink
[Backport to 2.25.x][Community Plugin] Features-Autopopulate
Browse files Browse the repository at this point in the history
  • Loading branch information
afabiani authored and aaime committed Mar 20, 2024
1 parent 5d310c7 commit 9dedfc0
Show file tree
Hide file tree
Showing 14 changed files with 1,042 additions and 0 deletions.
20 changes: 20 additions & 0 deletions doc/en/user/source/community/features-autopopulate/index.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
.. _community_wfsautopopulate:

Features-Autopopulate Extension
===============================

The Features Autopopulate plug-in listens to transactions (so far only issued by WFS), and autopopulates the feature type attributes according to the values retrieved from the properties file.

The plugin uses a custom TransactionCallback that alters the insert/update WFS-T operations, forcing in specific values into them, based on configuration files.

To support configuration for multiple layers, the easiest thing is to place a configuration, file in the directories of the layers themselves, pretty much like the featureinfo templates.

A "transactionCustomizer.properties" file that contains a set of names and CQL expressions
e.g.:

```
UTENTE=env('GSUSER') # this will be replaced with the current user see @EnviromentInjectionCallback
AGGIORNAMENTO=now() # this will be replaced with the current date
```

To keep things simple, the expressions will just use environment variables, but not see the other values provided in the update/insert, and will not be differentiated by insert/update cases.
1 change: 1 addition & 0 deletions doc/en/user/source/community/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ officially part of the GeoServer releases. They are however built along with the
cov-json/index
dds/index
elasticsearch/index
features-autopopulate/index
features-templating/index
flatgeobuf/index
gdal/index
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<parent>
<groupId>org.geoserver.community</groupId>
<artifactId>gs-features-autopopulate</artifactId>
<version>2.26-SNAPSHOT</version>
</parent>

<groupId>org.geoserver.community</groupId>
<artifactId>gs-features-autopopulate-core</artifactId>
<packaging>jar</packaging>
<name>Features Autopopulate Core</name>
<description>Features autopopulate core functionality, protocol agnostic</description>

<dependencies>
<dependency>
<groupId>org.geoserver</groupId>
<artifactId>gs-main</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.geoserver</groupId>
<artifactId>gs-wfs</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.geoserver.web</groupId>
<artifactId>gs-web-core</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.geotools</groupId>
<artifactId>gt-main</artifactId>
<version>${gt.version}</version>
</dependency>
<dependency>
<groupId>org.geoserver</groupId>
<artifactId>gs-main</artifactId>
<version>${project.version}</version>
<classifier>tests</classifier>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.geoserver</groupId>
<artifactId>gs-ows</artifactId>
<classifier>tests</classifier>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.geoserver</groupId>
<artifactId>gs-platform</artifactId>
<classifier>tests</classifier>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.geotools</groupId>
<artifactId>gt-sample-data</artifactId>
<version>${gt.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.geotools</groupId>
<artifactId>gt-jdbc</artifactId>
<version>${gt.version}</version>
<classifier>tests</classifier>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.geotools</groupId>
<artifactId>gt-jdbc</artifactId>
<version>${gt.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.geotools.jdbc</groupId>
<artifactId>gt-jdbc-postgis</artifactId>
<version>${gt.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.easymock</groupId>
<artifactId>easymock</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
/* (c) 2024 Open Source Geospatial Foundation - all rights reserved
* This code is licensed under the GPL 2.0 license, available at the root
* application directory.
*/
package org.geoserver.autopopulate;

import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.Properties;
import java.util.logging.Logger;
import org.geoserver.platform.GeoServerExtensions;
import org.geoserver.platform.GeoServerResourceLoader;
import org.geoserver.security.PropertyFileWatcher;
import org.geotools.api.filter.expression.Expression;
import org.geotools.filter.text.cql2.CQLException;
import org.geotools.filter.text.ecql.ECQL;
import org.geotools.util.logging.Logging;

/**
* AutopopulateTemplate class is used to load the properties from the file and store them in a map
* for further use.
*
* <p>It also provides the methods to get the properties and set the properties.
*
* @author Alessio Fabiani, GeoSolutions SRL, alessio.fabiani@geosolutionsgroup.com
*/
public class AutopopulateTemplate {
/** logger */
private static final Logger LOGGER = Logging.getLogger(AutopopulateTransactionCallback.class);
/** The properties map */
private final Map<String, String> propertiesMap;

/** The file watcher */
private final PropertyFileWatcher watcher;

/**
* Constructs the template loader.
*
* @param filePath The file path to load the properties from
*/
public AutopopulateTemplate(String filePath) {
this.propertiesMap = new HashMap<>();
GeoServerResourceLoader loader = GeoServerExtensions.bean(GeoServerResourceLoader.class);
this.watcher = new PropertyFileWatcher(loader.get(filePath));
loadProperties(filePath);
}

/**
* Load the properties from the file and store them in the map.
*
* @param filePath The file path to load the properties from
*/
private void loadProperties(String filePath) {
try {
Properties properties = this.watcher.getProperties();
if (properties == null) {
LOGGER.warning("Unable to load the properties file: " + filePath);
return;
} else {
for (String key : properties.stringPropertyNames()) {
String expression = properties.getProperty(key);
propertiesMap.put(key, expression);

// First check on the Syntax of the expression
try {
Expression ecql = ECQL.toExpression(expression);
if (ecql != null) {
propertiesMap.put(key, ecql.evaluate(null, String.class));
}
} catch (CQLException e) {
LOGGER.warning(
"Unable to parse the following Expression" + e.getSyntaxError());
}
}
}
} catch (IOException e) {
// Handle file loading error here
LOGGER.severe("Unable to load the properties file: " + e.getMessage());
throw new RuntimeException("Unable to load the properties file: " + e.getMessage());
}
}

/**
* Get the property from the map.
*
* @param key The key to get the property
* @return The property value
*/
public String getProperty(String key) {
return propertiesMap.get(key);
}

/**
* Get all the properties from the map.
*
* @return The properties map
*/
public Map<String, String> getAllProperties() {
return propertiesMap;
}

/**
* Check if the template file has been modified on the filesystem.
*
* @return true if the template file has been modified and must be reloaded, false otherwise
*/
public boolean needsReload() {
if (watcher != null) return watcher.isStale();
return true;
}

@Override
public String toString() {
return "AutopopulateTemplate{" + "propertiesMap=" + propertiesMap + '}';
}

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof AutopopulateTemplate)) return false;
AutopopulateTemplate that = (AutopopulateTemplate) o;
return Objects.equals(propertiesMap, that.propertiesMap);
}

@Override
public int hashCode() {
return Objects.hash(propertiesMap);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
/* (c) 2024 Open Source Geospatial Foundation - all rights reserved
* This code is licensed under the GPL 2.0 license, available at the root
* application directory.
*/
package org.geoserver.autopopulate;

import java.io.File;
import java.io.IOException;
import java.util.logging.Logger;
import org.geoserver.catalog.ResourceInfo;
import org.geoserver.config.GeoServerDataDirectory;
import org.geoserver.platform.GeoServerExtensions;
import org.geoserver.platform.GeoServerResourceLoader;
import org.geoserver.platform.resource.Resources;

/**
* AutopopulateTemplateLoader class is used to load the template from the file system and return the
* AutopopulateTemplate object.
*
* <p>It also provides the methods to load the template from the file system.
*
* @author Alessio Fabiani, GeoSolutions SRL, alessio.fabiani@geosolutionsgroup.com
*/
public class AutopopulateTemplateLoader {

/** logger */
private static final Logger LOGGER =
org.geotools.util.logging.Logging.getLogger(AutopopulateTemplateLoader.class);
/**
* Feature type directory to load template against. Its presence is mutually exclusive with
* coverageName
*/
protected ResourceInfo resource;
/** GeoServer data directory */
GeoServerDataDirectory dd;

/**
* Constructs the template loader.
*
* @param rl The geoserver resource loader
* @param resource The resource to load the template from
*/
public AutopopulateTemplateLoader(GeoServerResourceLoader rl, ResourceInfo resource) {
this(
rl == null
? new GeoServerDataDirectory(
GeoServerExtensions.bean(GeoServerResourceLoader.class))
: new GeoServerDataDirectory(rl),
resource);
}

/**
* Constructs the template loader.
*
* @param dd The geoserver data directory
* @param resource The resource to load the template from
*/
public AutopopulateTemplateLoader(GeoServerDataDirectory dd, ResourceInfo resource) {
this.dd = dd;
this.resource = resource;
}

/**
* Load the template from the file system.
*
* @param path The path to the template
* @return The AutopopulateTemplate object
* @throws IOException If the template cannot be loaded
*/
public AutopopulateTemplate loadTemplate(String path) throws IOException {
File template = null;

// template look up order
// 1. Relative to resource
// 2. Relative to store of the resource
// 3. Relative to workspace of resource
// 4. Relative to workspaces directory
if (resource != null) {
// first check relative to set resource
template = Resources.file(dd.get(resource, path));

if (template == null) {
// next try relative to the store
template = Resources.file(dd.get(resource.getStore(), path));
}

if (template == null) {
// next try relative to the workspace
template = Resources.file(dd.get(resource.getStore().getWorkspace(), path));
}

if (template == null) {
// try global supplementary files
template = Resources.file(dd.getWorkspaces(path));
}

if (template != null) {
return new AutopopulateTemplate(template.getAbsolutePath());
}

if (resource.getStore() != null && resource.getStore().getWorkspace() != null) {
// next try relative to the workspace
template = Resources.file(dd.get(resource.getStore().getWorkspace(), path));

if (template == null) {
// try global supplementary files
template = Resources.file(dd.getWorkspaces(path));
}

if (template != null) {
return new AutopopulateTemplate(template.getAbsolutePath());
}
}
}

return null;
}
}
Loading

0 comments on commit 9dedfc0

Please sign in to comment.