Commit 40ac6a1f authored by Andrea Aime's avatar Andrea Aime
Browse files

[GEOS-9146] Add REST API to configure the params-extractor community module

parent aed7b6a4
---
swagger: '2.0'
info:
version: 1.0.0
title: GeoServer Parameter Extractor
description: A parameter extractor rule allows specific request parameters as URL path fragments instead of using the query string. A echo parameter makes sure that certain URL paratemers are added to the capabilities documents backlinks.
paths:
/params-extractor/echoes:
get:
operationId: getEchoParameters
summary: Get a list of echo parameters
description: List all echo parameters currently configured. Use the "Accept:" header to specify format or append an extension to the endpoint (example "/datastores.xml" for XML)
produces:
- application/xml
- application/json
responses:
200:
description: OK
schema:
$ref: "#/definitions/EchoParameters"
examples:
application/xml: |
<EchoParameters>
<EchoParameter>
<id>0</id>
<atom:link xmlns:atom="http://www.w3.org/2005/Atom" href="http://localhost:8080/geoserver/rest/params-extractor/echoes/0.xml" rel="alternate" type="application/atom+xml"/>
</EchoParameter>
</EchoParameters>
application/json: |
{"EchoParameters": {"EchoParameter": [
{
"id": 0,
"href": "http://localhost:8080/geoserver/rest/params-extractor/echoes/0.json"
}
]}}
post:
operationId: postEchoParameter
summary: Create a new echo parameter
description: Adds a new echo parameter
parameters:
- $ref: '#/parameters/echoBodyParameter'
consumes:
- application/xml
- application/json
responses:
201:
description: Created
schema:
type: string
headers:
Location:
description: URL where the newly created echo parameter can be found
type: string
/params-extractor/echoes/{parameterId}:
get:
operationId: getEchoParameter
summary: Retrieve a particular echo parameter definition
description: Controls a particular echo parameter. Use the "Accept:" header to specify format or append an extension to the endpoint (example "/echos/{parameterId}.xml" for XML).
produces:
- application/xml
- application/json
parameters:
- name: parameterId
in: path
required: true
description: The identifier of the echo parameter to retrieve.
type: string
responses:
200:
description: OK
schema:
$ref: "#/definitions/EchoParameter"
examples:
application/xml: |
<EchoParameter activated="true" id="0" parameter="CQL_FILTER"/>
application/json: |
{"EchoParameter": {
"id": 0,
"parameter": "CQL_FILTER",
"activated": true
}}
put:
operationId: putEchoParameter
summary: Modify an echo parametr
description: Modify an echo parameter
parameters:
- name: parameterId
in: path
required: true
description: The identifier of the echo parameter to retrieve.
type: string
- $ref: '#/parameters/echoBodyParameter'
consumes:
- application/xml
- application/json
responses:
200:
description: The echo parameter was successfully updated.
delete:
operationId: deleteEchoParameter
summary: Delete an echo parameter
description: Deletes an echo parameter from the configuration
parameters:
- name: parameterId
in: path
required: true
description: The identifier of the echo parameter to retrieve.
type: string
responses:
200:
description: OK
/params-extractor/rules:
get:
operationId: getRules
summary: Get a list of rules
description: List all rules currently configured. Use the "Accept:" header to specify format or append an extension to the endpoint (example "/datastores.xml" for XML)
produces:
- application/xml
- application/json
responses:
200:
description: OK
schema:
$ref: "#/definitions/Rules"
examples:
application/xml: |
<Rules>
<Rule>
<id>0</id>
<atom:link xmlns:atom="http://www.w3.org/2005/Atom" href="http://localhost:8080/geoserver/rest/params-extractor/rules/0.xml" rel="alternate" type="application/atom+xml"/>
</Rule>
</Rules>
application/json: |
{"Rules": {"Rule": [
{
"id": 0,
"href": "http://localhost:8080/geoserver/rest/params-extractor/rules/0.json"
}
]}}
post:
operationId: postRule
summary: Create a new rule
description: Adds a new rule
parameters:
- $ref: '#/parameters/ruleBodyParameter'
consumes:
- application/xml
- application/json
responses:
201:
description: Created
schema:
type: string
headers:
Location:
description: URL where the newly created rule can be found
type: string
/params-extractor/rules/{ruleId}:
get:
operationId: getRule
summary: Retrieve a particular rule definition
description: Controls a particular rule . Use the "Accept:" header to specify format or append an extension to the endpoint (example "/echos/{parameterId}.xml" for XML).
produces:
- application/xml
- application/json
parameters:
- name: ruleId
in: path
required: true
description: The identifier of the rule to retrieve.
type: string
responses:
200:
description: OK
schema:
$ref: "#/definitions/Rule"
examples:
application/xml: |
<Rule activated="true" id="0" parameter="cql_filter" position="3" remove="1" transform="seq='$2'"/>
application/json: |
{"Rule": {
"id": 0,
"activated": true,
"position": 3,
"parameter": "cql_filter",
"transform": "seq='$2'",
"remove": 1
}}
put:
operationId: putRule
summary: Modify a rule
description: Modify a rule
parameters:
- name: ruleId
in: path
required: true
description: The identifier of the echo parameter to retrieve.
type: string
- $ref: '#/parameters/ruleBodyParameter'
consumes:
- application/xml
- application/json
responses:
200:
description: The rule was successfully updated.
delete:
operationId: deleteRule
summary: Delete a rule
description: Deletes a rule from the configuration
parameters:
- name: ruleId
in: path
required: true
description: The identifier of the rule to retrieve.
type: string
responses:
200:
description: OK
parameters:
echoBodyParameter:
name: body
in: body
schema:
$ref: '#/definitions/EchoParameter'
ruleBodyParameter:
name: body
in: body
schema:
$ref: '#/definitions/Rule'
definitions:
EchoParameters:
title: Echo parameter links list
type: array
items:
title: echoParameterListItem
type: object
properties:
id:
type: string
description: Identifeir of the echo paameter
link:
type: string
description: URL to the echo parameter definition
EchoParameter:
title: EchoParameter
type: object
properties:
id:
type: string
description: identifier of the echo parameter
parameter:
type: string
description: The parameter name
activated:
type: boolean
description: Whether or not the parameter echoing is active
Rules:
title: Rule link list
type: array
items:
title: ruleListItem
type: object
properties:
id:
type: string
description: Identifeir of the rule
link:
type: string
description: URL to the rule definition
Rule:
title: Rule
type: object
properties:
id:
type: string
description: identifier of the rule
activated:
type: boolean
description: Whether or not the parameter echoing is active
position:
type: integer
description: The position of the URL base path element to be selected
parameter:
type: string
description: The name of the parameter produced by this rule
transform:
type: string
description: Expression that defines the value of the parameter, use {PARAMETER} as a placeholder for the selected path element
match:
type: string
description: Regex match expression with groups, for example ^(?:/[^/]*){3}(/([^/]+)).*$ selects the URL base path third element
activation:
type: string
description: If defined this rule will only be applied to URLs that match this regex expression
remove:
type: integer
description: The match expression group to be removed from URL, by default 1
combine:
type: string
description: Defines how to combine parameter existing value ($1 existing value, $2 new value), by default the value is overridden
\ No newline at end of file
......@@ -155,4 +155,12 @@ Follow a print screen of the rules management UI with all the rules previously d
*Rules management UI*
Note that the first rule (the advanced one) is not active.
\ No newline at end of file
Note that the first rule (the advanced one) is not active.
REST API
--------
The rules and echo parameters can also be managed by means of a REST API found at
``geoserver/rest/params-extractor``. Documentation for it is available in
:api:`Swagger format <params-extractor.yaml>`
......@@ -18,16 +18,45 @@
<dependency>
<groupId>org.geoserver</groupId>
<artifactId>gs-main</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.geoserver</groupId>
<artifactId>gs-restconfig</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.geoserver.web</groupId>
<artifactId>gs-web-core</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.geoserver</groupId>
<artifactId>gs-main</artifactId>
<version>${project.version}</version>
<scope>test</scope>
<classifier>tests</classifier>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>xmlunit</groupId>
<artifactId>xmlunit</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.hamcrest</groupId>
<artifactId>hamcrest-library</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>
......@@ -8,9 +8,9 @@ public class EchoParameter {
private final String id;
private final String parameter;
private final Boolean activated;
private final boolean activated;
public EchoParameter(String id, String parameter, Boolean activated) {
public EchoParameter(String id, String parameter, boolean activated) {
this.id = id;
this.parameter = parameter;
this.activated = activated;
......
......@@ -10,6 +10,13 @@ public class EchoParameterBuilder {
private String parameter;
private Boolean activated;
public EchoParameterBuilder copy(EchoParameter other) {
this.id = other.getId();
this.parameter = other.getParameter();
this.activated = other.getActivated();
return this;
}
public EchoParameterBuilder withId(String id) {
this.id = id;
return this;
......
/* (c) 2019 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.params.extractor;
import com.thoughtworks.xstream.converters.Converter;
import com.thoughtworks.xstream.converters.MarshallingContext;
import com.thoughtworks.xstream.converters.UnmarshallingContext;
import com.thoughtworks.xstream.io.HierarchicalStreamReader;
import com.thoughtworks.xstream.io.HierarchicalStreamWriter;
import java.util.Optional;
import java.util.UUID;
/** Custom XStream converter for {@link EchoParameter} */
public class EchoParameterConverter implements Converter {
@Override
public void marshal(
Object source, HierarchicalStreamWriter writer, MarshallingContext context) {
EchoParameter param = (EchoParameter) source;
writer.addAttribute("id", param.getId());
writer.addAttribute("parameter", param.getParameter());
writer.addAttribute("activated", String.valueOf(param.getActivated()));
}
@Override
public Object unmarshal(HierarchicalStreamReader reader, UnmarshallingContext context) {
return new EchoParameterBuilder()
.withId(
Optional.ofNullable(reader.getAttribute("id"))
.orElse(UUID.randomUUID().toString()))
.withActivated(Boolean.valueOf(reader.getAttribute("activated")))
.withParameter(reader.getAttribute("parameter"))
.build();
}
@Override
public boolean canConvert(Class type) {
return EchoParameter.class.equals(type);
}
}
......@@ -6,7 +6,6 @@ package org.geoserver.params.extractor;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
......@@ -15,11 +14,8 @@ import java.util.function.Consumer;
import java.util.function.Function;
import java.util.logging.Logger;
import java.util.stream.Collectors;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
import javax.xml.stream.XMLOutputFactory;
import javax.xml.stream.XMLStreamWriter;
import org.geoserver.config.GeoServerDataDirectory;
import org.geoserver.config.util.SecureXStream;
import org.geoserver.platform.GeoServerExtensions;
import org.geoserver.platform.resource.Resource;
import org.geotools.util.logging.Logging;
......@@ -30,10 +26,17 @@ import org.xml.sax.helpers.DefaultHandler;
public final class EchoParametersDao {
private static final Logger LOGGER = Logging.getLogger(EchoParametersDao.class);
private static final String NEW_LINE = System.getProperty("line.separator");
private static final GeoServerDataDirectory DATA_DIRECTORY =
(GeoServerDataDirectory) GeoServerExtensions.bean("dataDirectory");
private static SecureXStream xStream;
static {
xStream = new SecureXStream();
xStream.registerConverter(new EchoParameterConverter());
xStream.alias("EchoParameter", EchoParameter.class);
xStream.alias("EchoParameters", EchoParametersDao.EchoParametersList.class);
xStream.addImplicitCollection(EchoParametersDao.EchoParametersList.class, "parameters");
xStream.allowTypes(
new Class[] {EchoParameter.class, EchoParametersDao.EchoParametersList.class});
}
public static String getEchoParametersPath() {
return "params-extractor/echo-parameters.xml";
......@@ -44,20 +47,22 @@ public final class EchoParametersDao {
}
public static List<EchoParameter> getEchoParameters() {
Resource echoParameters = DATA_DIRECTORY.get(getEchoParametersPath());
Resource echoParameters = getDataDirectory().get(getEchoParametersPath());
return getEchoParameters(echoParameters.in());
}
private static GeoServerDataDirectory getDataDirectory() {
return (GeoServerDataDirectory) GeoServerExtensions.bean("dataDirectory");
}
public static List<EchoParameter> getEchoParameters(InputStream inputStream) {
try {
if (inputStream.available() == 0) {
Utils.debug(LOGGER, "Echo parameters file seems to be empty.");
return new ArrayList<>();
}
EchoParameterHandler handler = new EchoParameterHandler();
SAXParser saxParser = SAXParserFactory.newInstance().newSAXParser();
saxParser.parse(inputStream, handler);
return handler.echoParameters;
EchoParametersList list = (EchoParametersList) xStream.fromXML(inputStream);
return list.parameters == null ? new ArrayList<>() : list.parameters;
} catch (Exception exception) {
throw Utils.exception(exception, "Error parsing echo parameters files.");
} finally {
......@@ -66,8 +71,8 @@ public final class EchoParametersDao {
}
public static void saveOrUpdateEchoParameter(EchoParameter echoParameter) {
Resource echoParameters = DATA_DIRECTORY.get(getEchoParametersPath());
Resource tmpEchoParameters = DATA_DIRECTORY.get(getTmpEchoParametersPath());
Resource echoParameters = getDataDirectory().get(getEchoParametersPath());
Resource tmpEchoParameters = getDataDirectory().get(getTmpEchoParametersPath());
saveOrUpdateEchoParameter(echoParameter, echoParameters.in(), tmpEchoParameters.out());
echoParameters.delete();
tmpEchoParameters.renameTo(echoParameters);
......@@ -95,8 +100,8 @@ public final class EchoParametersDao {
}
public static void deleteEchoParameters(String... echoParametersIds) {
Resource echoParameters = DATA_DIRECTORY.get(getEchoParametersPath());
Resource tmpEchoParameters = DATA_DIRECTORY.get(getTmpEchoParametersPath());
Resource echoParameters = getDataDirectory().get(getEchoParametersPath());
Resource tmpEchoParameters = getDataDirectory().get(getTmpEchoParametersPath());
deleteEchoParameters(echoParameters.in(), tmpEchoParameters.out(), echoParametersIds);
echoParameters.delete();
tmpEchoParameters.renameTo(echoParameters);
......@@ -127,45 +132,12 @@ public final class EchoParametersDao {
private static void writeEchoParameters(
List<EchoParameter> echoParameters, OutputStream outputStream) {
try {
XMLStreamWriter output =
XMLOutputFactory.newInstance()
.createXMLStreamWriter(new OutputStreamWriter(outputStream, "utf-8"));
output.writeStartDocument();
output.writeCharacters(NEW_LINE);
output.writeStartElement("EchoParameters");
output.writeCharacters(NEW_LINE);
echoParameters.forEach(echoParameter -> writeEchoParameter(echoParameter, output));
output.writeEndElement();
output.writeCharacters(NEW_LINE);
output.writeEndDocument();
output.close();
xStream.toXML(new EchoParametersList(echoParameters), outputStream);
} catch (Exception exception) {
throw Utils.exception(exception, "Something bad happen when writing echo parameters.");
}
}
private static void writeEchoParameter(EchoParameter echoParameter, XMLStreamWriter output) {
try {
output.writeCharacters(" ");
output.writeStartElement("EchoParameter");
writeAttribute("id", echoParameter.getId(), output);
writeAttribute("parameter", echoParameter.getParameter(), output);
writeAttribute("activated", echoParameter.getActivated(), output);
output.writeEndElement();
output.writeCharacters(NEW_LINE);
} catch (Exception exception) {
throw Utils.exception(
exception, "Error writing echo parameter %s.", echoParameter.getId());
}
}
private static <T> void writeAttribute(String name, T value, XMLStreamWriter output)
throws Exception {
if (value != null) {
output.writeAttribute(name, value.toString());
}
}
private static final class EchoParameterHandler extends DefaultHandler {