493 changes: 107 additions & 386 deletions doc/en/user/source/community/features-templating/configuration.rst

Large diffs are not rendered by default.

1,374 changes: 1,374 additions & 0 deletions doc/en/user/source/community/features-templating/directives.rst

Large diffs are not rendered by default.

Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
29 changes: 24 additions & 5 deletions doc/en/user/source/community/features-templating/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,34 @@
Features-Templating Extension
=============================

This plugin brings the possibility to output features by using a template file as a second level mapping over features, in order to obtain either a JSON-LD or a customized GeoJSON output.
`JSON-LD <https://json-ld.org>`_ is a Linked Data format, based on JSON format, and revolves around the concept of "context" to provide additional mappings from JSON to an RDF model.
The Features Templating plug-in works by allowing us to define a What You See Is What You Get template, that will be applied on a stream of features respecting the defined content negotiation rules. Both Simple and Complex features are supported. The following services and operations are supported:

The plugin works for both simple and complex-features.
.. list-table::
:widths: 20 20

* - **Service**
- **Operation**
* - WFS
- GetFeature
* - WMS
- GetFeatureInfo
* - OGCAPI Features
- Collection

The following output formats are supported:

* ``GeoJSON``
* ``GML``
* ``JSON-LD`` `JSON-LD <https://json-ld.org>`_ is a Linked Data format, based on JSON format, and revolves around the concept of "context" to provide additional mappings from JSON to an RDF model.
* ``HTML``

The first part of the plug-in documentation will go through the template syntax. The second one will show how to configure the template to apply it to a vector layer. The third part shows the backwards mapping functionality.

.. toctree::
:maxdepth: 1

installing
configuration
output
directives
configuration
querying
rest
313 changes: 0 additions & 313 deletions doc/en/user/source/community/features-templating/output.rst

This file was deleted.

147 changes: 140 additions & 7 deletions doc/en/user/source/community/features-templating/querying.rst
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,143 @@ Backward Mapping

When performing queries, using CQL filters, against layers that support a templated output, it will be possible to reference the template attributes in the CQL expressions. The plugin will take care of interpreting the CQL filter and translate it, when possible, to a data source native filter. For example, if that data source is a relational database, the CQL filter will be translated to one or multiple SQL queries that will be used to retrieve only the needed data.

Consider the following JSON-LD output example:
Consider the following GML output example:

.. code-block:: xml
<?xml version="1.0" encoding="UTF-8"?>
<wfs:FeatureCollection xmlns:wfs="http://www.opengis.net/wfs" xmlns:gml="http://www.opengis.net/gml" xmlns:st="http://www.stations.org/1.0" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" numberOfFeature="0" timeStamp="2021-07-16T08:38:50.735Z">
<gml:featureMember>
<st:MeteoStations gml:id="MeteoStationsFeature.7">
<st:code>Station_BOL</st:code>
<st:name>Bologna</st:name>
<st:geometry>
<gml:Point srsName="EPSG:4326" srsDimension="2">
<gml:pos>11.34 44.5</gml:pos>
</gml:Point>
</st:geometry>
<st:temperature>
<st:Temperature>
<st:time>2016-12-19T11:28:31.000Z</st:time>
<st:value>35.0</st:value>
</st:Temperature>
</st:temperature>
<st:temperature>
<st:Temperature>
<st:time>2016-12-19T11:28:55.000Z</st:time>
<st:value>25.0</st:value>
</st:Temperature>
</st:temperature>
<st:pressure>
<st:Pressure>
<st:time>2016-12-19T11:30:26.000Z</st:time>
<st:value>1019.0</st:value>
</st:Pressure>
</st:pressure>
<st:pressure>
<st:Pressure>
<st:time>2016-12-19T11:30:51.000Z</st:time>
<st:value>1015.0</st:value>
</st:Pressure>
</st:pressure>
<st:wind_speed>
<st:Wind_speed>
<st:time>2016-12-19T11:29:24.000Z</st:time>
<st:value>80.0</st:value>
</st:Wind_speed>
</st:wind_speed>
</st:MeteoStations>
</gml:featureMember>
<gml:featureMember>
<st:MeteoStations gml:id="MeteoStationsFeature.13">
<st:code>Station_ALS</st:code>
<st:name>Alessandria</st:name>
<st:geometry>
<gml:Point srsName="EPSG:4326" srsDimension="2">
<gml:pos>8.63 44.92</gml:pos>
</gml:Point>
</st:geometry>
<st:temperature>
<st:Temperature>
<st:time>2016-12-19T11:26:40.000Z</st:time>
<st:value>20.0</st:value>
</st:Temperature>
</st:temperature>
<st:wind_speed>
<st:Wind_speed>
<st:time>2016-12-19T11:27:13.000Z</st:time>
<st:value>155.0</st:value>
</st:Wind_speed>
</st:wind_speed>
</st:MeteoStations>
</gml:featureMember>
<gml:featureMember>
<st:MeteoStations gml:id="MeteoStationsFeature.21">
<st:code>Station_ROV</st:code>
<st:name>Rovereto</st:name>
<st:geometry>
<gml:Point srsName="EPSG:4326" srsDimension="2">
<gml:pos>11.05 45.89</gml:pos>
</gml:Point>
</st:geometry>
</st:MeteoStations>
</gml:featureMember>
</wfs:FeatureCollection>
The following are valid CQL_FILTERS

* :code:`st:name = 'Station_BOL'`.
* :code:`st:temperature.st:Temperature.st:value < 25`.

Given this underlying GML template:

.. code-block:: xml
<gft:Template>
<gft:Options>
<gft:Namespaces xmlns:st="http://www.stations.org/1.0"/>
</gft:Options>
<st:MeteoStations gml:id="${@id}">
<st:code>$${strConcat('Station_',st:code)}</st:code>
<st:name>${st:common_name}</st:name>
<st:geometry>${st:position}</st:geometry>
<st:temperature gft:isCollection="true" gft:source="st:meteoObservations/st:MeteoObservationsFeature" gft:filter="xpath('st:meteoParameters/st:MeteoParametersFeature/st:param_name') = 'temperature'">
<st:Temperature>
<st:time>${st:time}</st:time>
<st:value>${st:value}</st:value>
</st:Temperature>
</st:temperature>
<st:pressure gft:isCollection="true" gft:source="st:meteoObservations/st:MeteoObservationsFeature" gft:filter="xpath('st:meteoParameters/st:MeteoParametersFeature/st:param_name') = 'pressure'">
<st:Pressure>
<st:time>${st:time}</st:time>
<st:value>${st:value}</st:value>
</st:Pressure>
</st:pressure>
<st:wind_speed gft:isCollection="true" gft:source="st:meteoObservations/st:MeteoObservationsFeature" gft:filter="xpath('st:meteoParameters/st:MeteoParametersFeature/st:param_name') = 'wind speed'">
<st:Wind_speed>
<st:time>${st:time}</st:time>
<st:value>${st:value}</st:value>
</st:Wind_speed>
</st:wind_speed>
</st:MeteoStations>
</gft:Template>
The above cql_filter will be internally translated to:

* :code:`strConcat('Station_',st:code) = 'Station_BOL'`.
* :code:`st:meteoObservations/st:MeteoObservationsFeature/st:MeteoParametersFeature/st:value < 25 AND st:meteoObservations/st:MeteoObservationsFeature/st:MeteoParametersFeature/st:param_name = 'temperature'`.

As it is possible to see from the second example, if a template filter is defined for the value we want to filter by, the filter will be automatically included in the query.



Backwards mapping capability is availabel for all the output formats. Consider the following JSON-LD output example:

The following are example of valid CQL filters:

* gsml:GeologicUnit.description = 'some string value'
* name in ("MERCIA MUDSTONE", "UKNOWN")
* gsml:positionalAccuracy.valueArray1 = "100"

.. code-block:: json
Expand Down Expand Up @@ -122,11 +258,8 @@ Consider the following JSON-LD output example:
The following are example of valid CQL filters:

* features.gsml:GeologicUnit.description = 'some string value'
* features."@id" = "3245"
* features.name in ("MERCIA MUDSTONE", "UKNOWN")
* features.gsml:positionalAccuracy.valueArray1 = "100"
* gsml:GeologicUnit.description = 'some string value'
* name in ("MERCIA MUDSTONE", "UKNOWN")
* gsml:positionalAccuracy.valueArray1 = "100"

As the last example shows, to refer to elements in arrays listing simple attributes, the index of the attribute is needed, starting from 1, in the form ``{attributeName}{index}``, as in ``features.gsml:positionalAccuracy.valueArray1.``

Is worth mentioning that, as demonstrated in the examples above, ``""`` can be used to escape the attributes path components.
288 changes: 288 additions & 0 deletions doc/en/user/source/community/features-templating/rest.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,288 @@
.. _features_templating_rest:

Features Templatring Rest API
==============================

Introduction
-------------

The Features Templating Rest API allows performing CRUD operation over Features Templates and Template Layer Rules.

Template Configuration
-----------------------


``/rest/featurestemplates``

Finds all templates in the global (``features-templating``) directory or creates a new template in the global directory.

.. list-table::
:header-rows: 1
:widths: 5 20 20 20 10 10

* - Method
- Consumes
- Produces
- Action
- Supported parameters
- Response
* - GET
-
- application/xml, application/json.
- List of all the templates available in the ``features-templating`` directory.
-
- 200. List of rules in XML or JSON.
* - POST
- application/xml, text/xml, application/json, text/json, application/xhtml+xml, application/zip.
- text/plain.
- Add the template in the request body (text or zip file) as a new Template in the ``features-templating`` directory.
- templateName (mandatory when posting a raw template, optional when posting a zip file)
- 201. Created ``Location`` header.


``/rest/workspaces/<workspace name>/featurestemplates``

Finds all templates in the ``workspace`` directory or creates a new template in the ``workspace`` directory.

.. list-table::
:header-rows: 1
:widths: 5 20 20 20 10 10

* - Method
- Consumes
- Produces
- Action
- Supported parameters
- Response
* - GET
-
- application/xml, application/json.
- List of all the templates available in the ``workspace`` directory
-
- 200. List of rules in XML or JSON.
* - POST
- application/xml, text/xml, application/json, text/json, application/xhtml+xml, application/zip.
- text/plain.
- Add the template in the request body (text or zip file) as a new Template in the ``workspace`` directory.
- templateName (mandatory when posting a raw template, optional when posting a zip file)
- 201. Created ``Location`` header.


``/rest/workspaces/<workspace name>/featuretypes/<featureType name>/featurestemplates``


Finds all templates in the ``featuretype`` directory or creates a new template in the ``featuretype`` directory.

.. list-table::
:header-rows: 1
:widths: 5 20 20 20 10 10

* - Method
- Consumes
- Produces
- Action
- Supported parameters
- Response
* - GET
-
- application/json, application/xml.
- List of all the templates available in the ``featuretype`` directory
-
- 200. List of rules in XML or JSON.
* - POST
- application/xml, text/xml, application/json, text/json, application/xhtml+xml, application/zip.
- text/plain.
- Add the template in the request body (text or zip file) as a new Template in the ``Feature Type`` directory.
- templateName (mandatory when posting a raw template, optional when posting a zip file)
- 201. Created ``Location`` header.



``/rest/featurestemplates/<template name>``

If the template with the specified name exisits in the global (``features-templating``) directory, returns the template or replaces the template content with the one in the request body.

.. list-table::
:header-rows: 1
:widths: 5 20 20 20 10

* - Method
- Consumes
- Produces
- Action
- Response
* - GET
-
- application/xml, application/json, application/xhtml+xml.
- the template with the specified name if present in the ``features-templating`` directory.
- 200. The template.
* - PUT
- application/xml, text/xml, application/json, text/json, application/xhtml+xml, application/zip.
- text/plain.
- replace the template, if found in the ``features-templating`` directory with the template in the request body (text or zip file).
- 201.
* - DELETE
-
-
- delete the template, if found in the ``features-templating`` directory.
- 204.


``/rest/workspaces/<workspace name>/featurestemplates/<template name>``


If the template with the specified name exisits in the ``workspace`` directory, returns the template or replaces the template content with the one in the request body.

.. list-table::
:header-rows: 1
:widths: 5 20 20 20 10

* - Method
- Consumes
- Produces
- Action
- Response
* - GET
-
- application/xml, application/json, application/xhtml+xml.
- the template with the specified name if present in the ``workspace`` directory.
- 200. The template.
* - PUT
- application/xml, text/xml, application/json, text/json, application/xhtml+xml, application/zip.
- text/plain.
- replace the exisiting template, if found in the ``workspace`` directory with the template in the request body (text or zip file).
- 201.
* - DELETE
-
-
- delete the template, if found in the ``workspace`` directory.
- 204.


``/rest/workspaces/<workspace name>/featuretypes/<featureType name>``
``/featurestemplates/<template name>``

If the template with the specified name exisits in the ``featuretype`` directory, returns the template or replaces the template content with the one in the request body.

.. list-table::
:header-rows: 1
:widths: 5 20 20 20 10

* - Method
- Consumes
- Produces
- Action
- Response
* - GET
-
- application/xml, application/json, application/xhtml+xml.
- the template with the specified name if present in the ``featuretype`` directory.
- 200. The template.
* - PUT
- application/xml, text/xml, application/json, text/json, application/xhtml+xml, application/zip.
- text/plain.
- replace the exisiting template, if found in the ``featuretype`` directory with the template in the request body (text or zip file).
- 201.
* - DELETE
-
-
- delete the template, if found in the ``featuretype`` directory.
- 204.


Template Rule Configuration
----------------------------


``/rest/workspaces/<workspace name>/featuretypes/<featureType name>/templaterules``

Finds all the configured template rules for the ``featuretype`` or creates a new one.

.. list-table::
:header-rows: 1
:widths: 5 20 20 20 10

* - Method
- Consumes
- Produces
- Action
- Response
* - GET
-
- application/xml, application/json.
- List of all the template rules available for the ``featuretype``.
- 200. List of rules in XML or JSON.
* - POST
- application/xml, text/xml, application/json, text/json.
- text/plain.
- Add the template rule in the request body.
- 201. Created ``Location`` header.


``/rest/workspaces/<workspace name>/featuretypes/<featureType name>``
``/templaterules/<rule identifier>``

Finds, replaces, updates or deletes the template rule with the specified identifier.

.. list-table::
:header-rows: 1
:widths: 5 20 20 20 10

* - Method
- Consumes
- Produces
- Action
- Response
* - GET
-
- application/xml, application/json.
- The rule with the specified ``rule identifier``.
- 200. List of rules in XML or JSON.
* - PUT
- application/xml, text/xml, application/json, text/json.
- text/plain.
- Replace the rule with the specified id with the one provided in the request body.
- 201.
* - PATCH
- application/xml, text/xml, application/json, text/json.
- text/plain.
- Allows partial updates of the rule with the specified id using the fields specified in the rule provided in the request body. It uses a `JSON merge patch like strategy <https://datatracker.ietf.org/doc/html/rfc7386>`_
- 201.
* - DELETE
-
-
- Delete the rule with the specified id.
- 204.


Data Object Transfer
~~~~~~~~~~~~~~~~~~~~
Both XML and JSON are supported for transfer of data objects.

Encoding of a template rule in XML::

<Rule>
<ruleId>..</ruleId>
<priority>..</priority>
<templateName>..</templateName>
<outputFormat>..</outputFormat>
<cqlFilter>..</cqlFilter>
<profileFilter>...</profileFilter>
</Rule>

Encoding of a rule in JSON::

{"Rule": {"ruleId":..,"priority":..,"templateName":"..","outputFormat":"..","cqlFilter":"..","profileFilter":".."}}

When applying partial updates missing attributes/element in incoming object are left unchanged. Properties can be set to null. E.g. the following example will allow to set the profileFilter to null:

XML::

<Rule>
<profileFilter xsi:nil="true"/>
</Rule>

JSON::

{"Rule":{"profileFilter":null}}

2 changes: 1 addition & 1 deletion doc/en/user/source/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@
version = '2.19'

# The full version, including alpha/beta/rc tags.
release = '2.19-SNAPSHOT'
release = '2.19.3'

# Used in build and documentation links
branch = '2.19.x'
Expand Down
2 changes: 1 addition & 1 deletion doc/en/user/source/extensions/inspire/using.rst
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ Extended WMS and WMTS Capabilities

The WMS 1.3.0 and WMTS 1.0.0 capabilities document will contain two additional entries in the ``xsi:schemaLocation`` of the root ``<WMS_Capabilities>`` tag once the INSPIRE extension is installed:

* ``https://inspire.ec.europa.eu/schemas/inspire_vs/1.0``
* ``http://inspire.ec.europa.eu/schemas/inspire_vs/1.0``
* ``https://inspire.ec.europa.eu/schemas/inspire_vs/1.0/inspire_vs.xsd``

If you have enabled the check box to create the INSPIRE ExtendedCapabilities element and entered the values described in the previous section then there will also be an additional ExtendedCapabilities block. This tag block shows up in between the tags for ``<Exception>`` and ``<Layer>``. It contains the following information:
Expand Down
14 changes: 11 additions & 3 deletions doc/en/user/source/extensions/wps-download/rawDownload.rst
Original file line number Diff line number Diff line change
Expand Up @@ -394,9 +394,14 @@ This time, when issued (and process has finished on the server), the GET request
</wps:ExecuteResponse>
Raster Output Format and Response mime-type
+++++++++++++++++++++++++++++++++++++++++++
By default, the Downloaded raster gets zipped, along with the SLD style associated to the layer.
Output Format and Response mime-types
+++++++++++++++++++++++++++++++++++++

By default, downloading vector data results in a Shapefile, compressed in a zip file along with
its SLD file. It's also possible to download a GeoPackage file using ``application/geopackage+sqlite3``
as the value for the ``mimeType`` parameter.

Similarly, for raster data, by default the downloaded raster gets zipped, along with the SLD style associated to the layer.
In some cases, this can be unnecessary, especially if the output TIFF already has some type of internal compression or if we simply want to get back the TIFF output file without the ancillary SLD. Let's consider downloading a RGB TIFF: the default raster.sld style won't add anything useful to the output. In that case it's possible to specify ``image/tiff`` in the Response's output ``mimeType``: the output TIFF will be provided as is, without extra steps of compression and file management.


Expand All @@ -411,6 +416,9 @@ In some cases, this can be unnecessary, especially if the output TIFF already ha
</wps:ResponseDocument>
</wps:ResponseForm>
It is also possible to download the raster data as a GeoPackage, using ``application/geopackage+sqlite3``
as the value for the ``mimeType`` parameter.


Writing buffering options
+++++++++++++++++++++++++
Expand Down
32 changes: 32 additions & 0 deletions doc/en/user/source/geowebcache/webadmin/defaults.rst
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,38 @@ Button for updating cache statistics seen above. The statistics are always relat

.. note:: Note that in the *TileCaching* tab for each Layer, you may decide to disable in memory caching for the selected Layer by clicking on the **Enable In Memory Caching for this Layer** checkbox. This option is disabled for those caches which don't support this feature.

Skip caching on dimension warnings
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

WMS dimension handling can be complex, with ability to return tiles where the specified time
was not a match, or when the request contained no time at all.
This may not be a good match for tile caching, as it breaks the unique link between URL and tile content.

The following settings allow to disable caching when a WMS dimension warning is issued:


.. figure:: img/skipCacheWarnings.png
:align: center

*Skip caching on cache warnings*

The best settings depend on the type of dataset and disk-quota configurations:

* For **static datasets with dimensions**, the default value skip could be removed, as it's going to
generate at most one copy of the tiles. The nearest match and failed nearest
could be cached if there is a disk quota (to speed up clients that repeatedly fail to perform an exact time match),
but it's best not to cache it if there is no disk quota, as the mismatches can be potentially infinite, leading to
an uncontrolled growth of the cache.
* For a **datasets growing over time**, it's better to disable caching on the default value, as it's often
the "latest", that is, the most recently added to the dataset. This means the tiles contents
change based on when they are asked for. The considerations for nearest and failed matches
are the same as for the static datasets.

Caution is advised if the data ingestion might happen to skip some time/elevation values,
to fill them only at a later time. In this case, nearest matches could cause the system to cache
a tile for a nearby time value, which would hide the actual values if they get ingested at a later time.


Default Cached Gridsets
~~~~~~~~~~~~~~~~~~~~~~~

Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
17 changes: 17 additions & 0 deletions doc/en/user/source/services/wfs/outputformats.rst
Original file line number Diff line number Diff line change
Expand Up @@ -135,3 +135,20 @@ JSON output ``format_options``:
JSON output ``system properties``:

* ``json.maxDepth=<max_value>`` is used to determine the max number of allowed JSON nested objects on encoding phase. By default the value is 100.


CSV output
----------------

A Default CSV file uses a comma to separate values. Each line of the file is a data record. Each record consists of one or more fields, separated by commas. The separator can be changed using format_options as specified below.

csv file output ``format_options``:

* ``format_option=filename:<file>``: if a file name is provided, the name is used as the output file name. For example, ``format_options=filename:roads.csv``.
* ``format_option=csvseparator:<csvseparator>`` (default is ```,``` ): if a separator is provided, it is used to separate values in output csv file. For example, ``format_options=csvseparator:-`` is used to get dash separated file.

Some special characters need to be handled using keywords as below:

* space separated: ``format_options=csvseparator:space``
* tab separated: ``format_options=csvseparator:tab``
* semicolon separated: ``format_options=csvseparator:semicolon``
1 change: 1 addition & 0 deletions doc/en/user/source/services/wfs/vendor.rst
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ The supported format option is:
* ``callback`` (default is ``parseResponse``)—Specifies the callback function name for the JSONP response format
* ``id_policy`` (default is ``true``)- Specifies id generation for the JSON output format. To include feature id in output use an attribute name, or use ``format_options=id_policy:true`` for feature id generation. To avoid the use of feature id completely use ``format_options=id_policy:false``.
* ``filename`` (default is :file:`features` or generated from feature type name)- provide a ``Content-Disposition`` header indicating the attachment filename (used as a suggestion by browsers saving content to disk using :command:`Save-As`). For example ``format_options=filename:content.txt``.
* ``csvseparator`` (default is ```,``` )- Specifies a separator that can be used in output csv file

Reprojection
------------
Expand Down
2 changes: 1 addition & 1 deletion src/community/app-schema/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
<parent>
<groupId>org.geoserver</groupId>
<artifactId>community</artifactId>
<version>2.19-SNAPSHOT</version>
<version>2.19.3</version>
</parent>

<groupId>org.geoserver.community</groupId>
Expand Down
2 changes: 1 addition & 1 deletion src/community/app-schema/webservice-test/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
<parent>
<groupId>org.geoserver.community</groupId>
<artifactId>gs-app-schema-community</artifactId>
<version>2.19-SNAPSHOT</version>
<version>2.19.3</version>
</parent>

<groupId>org.geoserver.community</groupId>
Expand Down
2 changes: 1 addition & 1 deletion src/community/backup-restore/core/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
<parent>
<groupId>org.geoserver.community</groupId>
<artifactId>gs-backup-restore</artifactId>
<version>2.19-SNAPSHOT</version>
<version>2.19.3</version>
</parent>

<groupId>org.geoserver.community.backuprestore</groupId>
Expand Down
2 changes: 1 addition & 1 deletion src/community/backup-restore/extension/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
<parent>
<groupId>org.geoserver.community</groupId>
<artifactId>gs-backup-restore</artifactId>
<version>2.19-SNAPSHOT</version>
<version>2.19.3</version>
</parent>

<groupId>org.geoserver.community.backuprestore</groupId>
Expand Down
2 changes: 1 addition & 1 deletion src/community/backup-restore/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
<parent>
<groupId>org.geoserver</groupId>
<artifactId>community</artifactId>
<version>2.19-SNAPSHOT</version>
<version>2.19.3</version>
</parent>

<groupId>org.geoserver.community</groupId>
Expand Down
2 changes: 1 addition & 1 deletion src/community/backup-restore/rest/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
<parent>
<groupId>org.geoserver.community</groupId>
<artifactId>gs-backup-restore</artifactId>
<version>2.19-SNAPSHOT</version>
<version>2.19.3</version>
</parent>

<groupId>org.geoserver.community.backuprestore</groupId>
Expand Down
2 changes: 1 addition & 1 deletion src/community/backup-restore/web/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
<parent>
<groupId>org.geoserver.community</groupId>
<artifactId>gs-backup-restore</artifactId>
<version>2.19-SNAPSHOT</version>
<version>2.19.3</version>
</parent>

<groupId>org.geoserver.community.backuprestore</groupId>
Expand Down
2 changes: 1 addition & 1 deletion src/community/cog/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
<parent>
<groupId>org.geoserver</groupId>
<artifactId>community</artifactId>
<version>2.19-SNAPSHOT</version>
<version>2.19.3</version>
</parent>

<groupId>org.geoserver.community</groupId>
Expand Down
2 changes: 1 addition & 1 deletion src/community/colormap/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<parent>
<groupId>org.geoserver</groupId>
<artifactId>community</artifactId>
<version>2.19-SNAPSHOT</version>
<version>2.19.3</version>
</parent>

<groupId>org.geoserver.community</groupId>
Expand Down
2 changes: 1 addition & 1 deletion src/community/cov-json/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
<parent>
<groupId>org.geoserver</groupId>
<artifactId>community</artifactId>
<version>2.19-SNAPSHOT</version>
<version>2.19.3</version>
</parent>

<groupId>org.geoserver.community</groupId>
Expand Down
2 changes: 1 addition & 1 deletion src/community/csw-iso/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
<parent>
<groupId>org.geoserver</groupId>
<artifactId>community</artifactId>
<version>2.19-SNAPSHOT</version>
<version>2.19.3</version>
</parent>

<groupId>org.geoserver.csw</groupId>
Expand Down
2 changes: 1 addition & 1 deletion src/community/dds/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
<parent>
<groupId>org.geoserver</groupId>
<artifactId>community</artifactId>
<version>2.19-SNAPSHOT</version>
<version>2.19.3</version>
</parent>

<groupId>org.geoserver.community</groupId>
Expand Down
2 changes: 1 addition & 1 deletion src/community/dyndimension/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<parent>
<groupId>org.geoserver</groupId>
<artifactId>community</artifactId>
<version>2.19-SNAPSHOT</version>
<version>2.19.3</version>
</parent>

<groupId>org.geoserver.community</groupId>
Expand Down
2 changes: 1 addition & 1 deletion src/community/elasticsearch/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
<parent>
<groupId>org.geoserver</groupId>
<artifactId>community</artifactId>
<version>2.19-SNAPSHOT</version>
<version>2.19.3</version>
</parent>

<groupId>org.geoserver.community</groupId>
Expand Down
45 changes: 20 additions & 25 deletions src/community/features-templating/features-templating-core/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<parent>
<groupId>org.geoserver.community</groupId>
<artifactId>gs-features-templating</artifactId>
<version>2.19-SNAPSHOT</version>
<version>2.19.3</version>
</parent>

<groupId>org.geoserver.community</groupId>
Expand All @@ -27,14 +27,29 @@
</dependency>
<dependency>
<groupId>org.geotools</groupId>
<artifactId>gt-geojson</artifactId>
<artifactId>gt-geojsondatastore</artifactId>
<version>${gt.version}</version>
</dependency>
<dependency>
<groupId>com.github.jsonld-java</groupId>
<artifactId>jsonld-java</artifactId>
<version>0.12.5</version>
</dependency>
<dependency>
<groupId>org.geotools</groupId>
<artifactId>gt-app-schema</artifactId>
<version>${gt.version}</version>
</dependency>
<dependency>
<groupId>org.geotools</groupId>
<artifactId>gt-http</artifactId>
<version>${gt.version}</version>
</dependency>
<dependency>
<groupId>org.geotools</groupId>
<artifactId>gt-referencing</artifactId>
<version>${gt.version}</version>
</dependency>
<dependency>
<groupId>org.geoserver</groupId>
<artifactId>gs-main</artifactId>
Expand All @@ -43,9 +58,9 @@
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.geotools</groupId>
<artifactId>gt-app-schema</artifactId>
<version>${gt.version}</version>
<groupId>org.geoserver</groupId>
<artifactId>gs-ows</artifactId>
<classifier>tests</classifier>
<scope>test</scope>
</dependency>
<dependency>
Expand Down Expand Up @@ -109,24 +124,4 @@
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.15</version>
<configuration>
<runOrder>alphabetical</runOrder>
<includes>
<include>**/*Test.java</include>
</includes>
<reuseForks>false</reuseForks>
<argLine>-Xmx${test.maxHeapSize}</argLine>
<enableAssertions>true</enableAssertions>
<printSummary>true</printSummary>
</configuration>
</plugin>
</plugins>
</build>

</project>
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
package org.geoserver.featurestemplating.builders;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import org.geoserver.featurestemplating.builders.impl.TemplateBuilderContext;
import org.geoserver.featurestemplating.expressions.TemplateCQLManager;
Expand Down Expand Up @@ -33,8 +34,9 @@ public abstract class AbstractTemplateBuilder implements TemplateBuilder {
protected EncodingHints encodingHints;

public AbstractTemplateBuilder(String key, NamespaceSupport namespaces) {
this.key = getKeyAsExpression(key);
this.key = getKeyAsExpression(key, namespaces);
this.namespaces = namespaces;
this.children = new ArrayList<>();
}

/**
Expand All @@ -52,12 +54,18 @@ protected boolean evaluateFilter(TemplateBuilderContext context) {
return filter.evaluate(evaluationContenxt.getCurrentObj());
}

public String getKey() {
return key != null ? key.evaluate(null).toString() : null;
public Expression getKey() {
return key;
}

public String getKey(TemplateBuilderContext context) {
if (key == null) return null;
Object currentObj = context != null ? context.getCurrentObj() : null;
return key.evaluate(currentObj, String.class);
}

public void setKey(String key) {
this.key = getKeyAsExpression(key);
this.key = getKeyAsExpression(key, null);
}

/**
Expand Down Expand Up @@ -86,37 +94,43 @@ public void setFilter(String filter) {
}

/**
* Get context position of the filter if present
* Set the filter to the builder
*
* @return
* @param filter the filter to be setted
* @throws CQLException
*/
public int getFilterContextPos() {
return filterContextPos;
public void setFilter(Filter filter) {
this.filter = filter;
}

/**
* Write the attribute key if present
* Get context position of the filter if present
*
* @param writer the template writer
* @throws IOException
* @return
*/
protected void writeKey(TemplateOutputWriter writer) throws IOException {
if (key != null && !key.evaluate(null).equals(""))
// key might be and EnvFunction or a Literal. In both cases
// no argument is needed for the evaluation thus passing null.
writer.writeElementName(key.evaluate(null), getEncodingHints());
public int getFilterContextPos() {
return filterContextPos;
}

public NamespaceSupport getNamespaces() {
return namespaces;
}

private Expression getKeyAsExpression(String key) {
private Expression getKeyAsExpression(String key, NamespaceSupport namespaces) {
Expression keyExpr;
if (key != null) {
if (key.startsWith("$${")) {
TemplateCQLManager cqlManager = new TemplateCQLManager(key, null);
keyExpr = cqlManager.getExpressionFromString();
if (key.startsWith("$${") || key.startsWith("${")) {
TemplateCQLManager cqlManager = new TemplateCQLManager(key, namespaces);
if (key.startsWith("$${")) {
return cqlManager.getExpressionFromString();
} else if (key.equals("${.}")) {
return cqlManager.getThis();
} else if (key.startsWith("${")) {
return cqlManager.getAttributeExpressionFromString();
} else {
// should not really happen, but need to pacify the compiler
throw new IllegalArgumentException("Invalid key: " + key);
}
} else {
keyExpr = new LiteralExpressionImpl(key);
}
Expand All @@ -142,4 +156,34 @@ public void addEncodingHint(String key, Object value) {
if (this.encodingHints == null) this.encodingHints = new EncodingHints();
this.encodingHints.put(key, value);
}

@Override
public void addChild(TemplateBuilder builder) {
if (this.children == null) this.children = new ArrayList<>();
this.children.add(builder);
}

protected void addChildrenEvaluationToEncodingHints(
TemplateOutputWriter writer, TemplateBuilderContext context) {
if (children != null && !children.isEmpty()) {
ChildrenEvaluation childrenEvaluation = getChildrenEvaluation(writer, context);
getEncodingHints().put(EncodingHints.CHILDREN_EVALUATION, childrenEvaluation);
}
}

protected ChildrenEvaluation getChildrenEvaluation(
TemplateOutputWriter writer, TemplateBuilderContext context) {
ChildrenEvaluation action =
() -> {
for (TemplateBuilder b : children) {
b.evaluate(writer, context);
}
};
return action;
}

@FunctionalInterface
public interface ChildrenEvaluation {
void evaluate() throws IOException;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@
package org.geoserver.featurestemplating.builders;

import java.util.HashMap;
import java.util.Optional;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;

/**
* This class represent a Map of encoding hints. An encoding hint is a value giving additional
Expand All @@ -13,16 +16,38 @@
*/
public class EncodingHints extends HashMap<String, Object> {

// encoding hint to retrieve the json-ld context
public static final String CONTEXT = "@context";

// encoding hint to retrieve gml namespaces
public static final String NAMESPACES = "NAMESPACES";

// encoding hint to retrieve gml schema_location
public static final String SCHEMA_LOCATION = "SCHEMA_LOCATION";

// encoding hint to tell when a value builder has to be encoded as an xml attribute
public static final String ENCODE_AS_ATTRIBUTE = "ENCODE_AS_ATTRIBUTE";

// encoding hint to tell if the Iterating builder should iterate also
// the key value (eg. for xml output)
public static final String ITERATE_KEY = "INTERATE_KEY";

// encoding hint to issue children evaluation in value builder (Dynamic and Static)
// this is needed for xml output where a value builder can have children for xml attributes
public static final String CHILDREN_EVALUATION = "CHILDREN_EVALUATION";

// encoding hint to tell the writer to skip start and end object
// when we are dealing with a single feature request
public static final String SKIP_OBJECT_ENCODING = "SKIP_OBJECT_ENCODING";

public EncodingHints() {
super();
}

public EncodingHints(EncodingHints encodingHints) {
super(encodingHints);
}

/**
* Check if the hint is present.
*
Expand All @@ -43,4 +68,33 @@ public boolean hasHint(String hint) {
public <T> T get(String key, Class<T> cast) {
return cast.cast(get(key));
}

/**
* Get the hint value with the requested type.
*
* @param key the hint name.
* @param cast the type requested.
* @return the value of the hint if found, otherwise null.
*/
public <T> T get(String key, Class<T> cast, T defaultValue) {
T value = cast.cast(get(key));
if (value == null) value = defaultValue;
return value;
}

/**
* Check if the current request is an OGCAPI request by feature id.
*
* @return true if is a single feature request, false otherwise.
*/
public static boolean isSingleFeatureRequest() {
return Optional.ofNullable(RequestContextHolder.getRequestAttributes())
.map(
att ->
(String)
att.getAttribute(
"OGCFeatures:ItemId",
RequestAttributes.SCOPE_REQUEST))
.isPresent();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,18 @@
import java.util.Optional;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.geotools.filter.function.JsonPointerFunction;
import org.geotools.util.Converters;
import org.geotools.util.logging.Logging;
import org.opengis.feature.Attribute;
import org.opengis.feature.ComplexAttribute;
import org.opengis.feature.type.PropertyDescriptor;
import org.opengis.filter.expression.Expression;

/** Supports handling Feature properties backed by JSON columns. Currently supports */
/**
* Supports handling Feature properties backed by JSON columns. Currently supports native JSON and
* JSONB fields from PostgresSQL, as well as usage of the <code>jsonPointer</code> function
*/
public class JSONFieldSupport {

static final Logger LOGGER = Logging.getLogger(JSONFieldSupport.class);
Expand All @@ -42,11 +46,12 @@ public static Object parseWhenJSON(Expression expression, Object contextObject,
try {
if (contextObject instanceof ComplexAttribute) {
// see if there is an indication it was a JSON field
if (isJSONField(
Optional.ofNullable(((ComplexAttribute) contextObject).getType())
.map(ct -> expression.evaluate(ct))
.filter(d -> d instanceof PropertyDescriptor)
.map(d -> (PropertyDescriptor) d))) {
if (expression instanceof JsonPointerFunction
|| isJSONField(
Optional.ofNullable(((ComplexAttribute) contextObject).getType())
.map(ct -> expression.evaluate(ct))
.filter(d -> d instanceof PropertyDescriptor)
.map(d -> (PropertyDescriptor) d))) {
return parseJSON(result);
}
} else if (result instanceof Attribute) {
Expand Down Expand Up @@ -94,9 +99,7 @@ public static Object parseJSON(Object value) throws JsonProcessingException {
private static String getJSON(Object value) {
if (value instanceof Attribute) {
return Converters.convert(((Attribute) value).getValue(), String.class);
} else if (value instanceof String) {
return (String) value;
}
return null;
return Converters.convert(value, String.class);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
*/
package org.geoserver.featurestemplating.builders;

import static org.geoserver.featurestemplating.builders.EncodingHints.SKIP_OBJECT_ENCODING;

import java.util.LinkedList;
import java.util.Optional;
import org.geoserver.featurestemplating.builders.impl.TemplateBuilderContext;
Expand All @@ -18,9 +20,24 @@ public abstract class SourceBuilder extends AbstractTemplateBuilder {

private Expression source;

public SourceBuilder(String key, NamespaceSupport namespaces) {
/**
* A SourceBuilder hasNotOwnOutput when it not invoke the writer to encode any output but simply
* call the evaluation of children builder. This is the case when the builder does not map any
* feature attribute but part of an output format that are handle by ${@link
* org.geoserver.featurestemplating.writers.TemplateOutputWriter#startTemplateOutput(EncodingHints)}
*/
protected boolean ownOutput = true;

/**
* A SourceBuilder is topLevelFeature when its mapping the start of a Feature or of the root
* Feature in case of complex features.
*/
protected boolean topLevelFeature;

public SourceBuilder(String key, NamespaceSupport namespaces, boolean topLevelFeature) {
super(key, namespaces);
this.children = new LinkedList<>();
this.topLevelFeature = topLevelFeature;
}

/**
Expand Down Expand Up @@ -55,11 +72,6 @@ public Object evaluateSource(Object o) {
return source.evaluate(o);
}

@Override
public void addChild(TemplateBuilder builder) {
this.children.add(builder);
}

/**
* Get the source as an Expression
*
Expand Down Expand Up @@ -98,4 +110,24 @@ public void setSource(String source) {
new AttributeExpressionImpl(sourceExpr.evaluate(null).toString(), namespaces);
else this.source = sourceExpr;
}

public boolean hasOwnOutput() {
return ownOutput;
}

public void setOwnOutput(boolean ownOutput) {
this.ownOutput = ownOutput;
}

protected void addSkipObjectEncodingHint(TemplateBuilderContext context) {
if (topLevelFeature) addEncodingHint(SKIP_OBJECT_ENCODING, true);
}

public boolean isTopLevelFeature() {
return topLevelFeature;
}

public void setTopLevelFeature(boolean topLevelFeature) {
this.topLevelFeature = topLevelFeature;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import java.util.List;
import java.util.Map;
import org.geoserver.featurestemplating.builders.impl.TemplateBuilderContext;
import org.geoserver.featurestemplating.builders.visitors.TemplateVisitor;
import org.geoserver.featurestemplating.writers.TemplateOutputWriter;

/** Basic interface for all the builders */
Expand Down Expand Up @@ -50,4 +51,6 @@ default List<TemplateBuilder> getChildren() {
* @param value the hint value.
*/
void addEncodingHint(String key, Object value);

Object accept(TemplateVisitor visitor, Object value);
}
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,9 @@ public class TemplateBuilderMaker {

private boolean flatOutput;

private boolean rootCollection;
private boolean ownOutput = true;

private boolean topLevelFeature;

private EncodingHints encondingHints;

Expand Down Expand Up @@ -69,7 +71,7 @@ public TemplateBuilderMaker(String rootCollectionName) {
* @return this TemplateBuilderMaker
*/
public TemplateBuilderMaker textContent(String textContent) {
this.textContent = textContent;
this.textContent = textContent.trim();
return this;
}

Expand Down Expand Up @@ -188,12 +190,14 @@ public TemplateBuilderMaker flatOutput(boolean flatOutput) {
* Set a boolean to tell the TemplateBuilderMaker if the IteratingBuilder being created should
* be considered as the first IteratingBuilder of the builder tree.
*
* @param root true if the IteratingBuilder being created is the root IteratingBuilder of the
* builder tree.
* @param hasOwnOutput false if the Builder being created is mapping element that are wrote by
* ${@link
* org.geoserver.featurestemplating.writers.TemplateOutputWriter#startTemplateOutput(EncodingHints)}
* method
* @return this TemplateBuilderMaker.
*/
public TemplateBuilderMaker rootCollection(boolean root) {
rootCollection = root;
public TemplateBuilderMaker hasOwnOutput(boolean hasOwnOutput) {
this.ownOutput = hasOwnOutput;
return this;
}

Expand Down Expand Up @@ -243,6 +247,19 @@ public TemplateBuilderMaker rootBuilder(boolean rootBuilder) {
return this;
}

/**
* Set to true if the builder is the top level feature builder: a top level builder is a
* SourceBuilder that is mapping the start of a Feature or of the root Feature in case of
* complex features
*
* @param topLevelFeature
* @return
*/
public TemplateBuilderMaker topLevelFeature(boolean topLevelFeature) {
this.topLevelFeature = topLevelFeature;
return this;
}

/** Reset all the attributes of this TemplateBuilderMaker. */
public void globalReset() {
localReset();
Expand All @@ -257,12 +274,13 @@ public void localReset() {
this.vendorOptions = new VendorOptions();
this.filter = null;
this.isCollection = false;
this.rootCollection = false;
this.ownOutput = true;
this.name = null;
this.source = null;
this.textContent = null;
this.jsonNode = null;
this.rootBuilder = false;
this.topLevelFeature = false;
}

/**
Expand All @@ -282,26 +300,31 @@ public RootBuilder buildRootBuilder() {

private IteratingBuilder buildIteratingBuilder() {
IteratingBuilder iteratingBuilder;
if (flatOutput) iteratingBuilder = new FlatIteratingBuilder(name, namespaces, separator);
else iteratingBuilder = new IteratingBuilder(name, namespaces);
if (flatOutput)
iteratingBuilder =
new FlatIteratingBuilder(name, namespaces, separator, topLevelFeature);
else iteratingBuilder = new IteratingBuilder(name, namespaces, topLevelFeature);
if (source != null) iteratingBuilder.setSource(source);
if (filter != null) iteratingBuilder.setFilter(filter);
if (!encondingHints.isEmpty()) iteratingBuilder.getEncodingHints().putAll(encondingHints);
if (name != null && rootCollectionName != null && rootCollectionName.equals(name))
rootCollection = true;
iteratingBuilder.setRootCollection(rootCollection);
ownOutput = false;
iteratingBuilder.setOwnOutput(ownOutput);
iteratingBuilder.setTopLevelFeature(topLevelFeature);
return iteratingBuilder;
}

private CompositeBuilder buildCompositeBuilder() {
CompositeBuilder compositeBuilder;
if (flatOutput) compositeBuilder = new FlatCompositeBuilder(name, namespaces, separator);
else compositeBuilder = new CompositeBuilder(name, namespaces);
if (flatOutput)
compositeBuilder =
new FlatCompositeBuilder(name, namespaces, separator, topLevelFeature);
else compositeBuilder = new CompositeBuilder(name, namespaces, topLevelFeature);

if (source != null) compositeBuilder.setSource(source);
if (filter != null) compositeBuilder.setFilter(filter);
if (!encondingHints.isEmpty()) compositeBuilder.getEncodingHints().putAll(encondingHints);

compositeBuilder.setOwnOutput(ownOutput);
return compositeBuilder;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,41 @@
import java.util.HashMap;
import org.opengis.filter.expression.Expression;

/**
* Class listing the supported options a user can specify from a template to customize the output.
*/
public class VendorOptions extends HashMap<String, Object> {

// encoding hint to retrieve the json-ld context
public static final String CONTEXT = "@context";

// encoding hint to retrieve gml namespaces
public static final String NAMESPACES = "NAMESPACES";

// encoding hint to retrieve gml schema_location
public static final String SCHEMA_LOCATION = "SCHEMA_LOCATION";

// vendor option used to issue a GeoJSON flat output
public static final String FLAT_OUTPUT = "flat_output";

// vendor option used to customize the separator in GeoJSON flat output attributes
public static final String SEPARATOR = "separator";

public boolean hasOption(String key) {
return get(key) != null;
}
// vendor option used to define Javascript for XHTML templates
public static final String SCRIPT = "script";

// vendor option used to define Style for XHTML templates
public static final String STYLE = "style";

public static final String LINK = "link";

// encoding hint to encode all json-ld attributes as string
public static final String JSON_LD_STRING_ENCODE = "encode_as_string";

public <T> T get(String key, Class<T> cast) {
Object value = get(key);
T result = null;
if (value instanceof Expression) {
if (value instanceof Expression && !cast.isAssignableFrom(Expression.class)) {
result = ((Expression) value).evaluate(null, cast);
} else {
result = cast.cast(value);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
*/
package org.geoserver.featurestemplating.builders.flat;

import org.geoserver.featurestemplating.builders.impl.TemplateBuilderContext;
import org.opengis.filter.expression.Expression;

/** An helper class to help creating attribute names when producing a flat geoJson output */
Expand All @@ -21,9 +22,10 @@ class AttributeNameHelper {
this.separator = separator;
}

String getFinalAttributeName() {
String getFinalAttributeName(TemplateBuilderContext context) {
String parentKey = this.parentKey;
String key = this.key != null ? this.key.evaluate(null).toString() : null;
String key =
this.key != null ? this.key.evaluate(context.getCurrentObj()).toString() : null;
if (parentKey != null && !parentKey.equals(PROPERTIES_KEY) && key != null)
key = parentKey + separator + key;
else if (key == null) key = parentKey;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import org.geoserver.featurestemplating.builders.impl.CompositeBuilder;
import org.geoserver.featurestemplating.builders.impl.TemplateBuilderContext;
import org.geoserver.featurestemplating.writers.TemplateOutputWriter;
import org.opengis.feature.Feature;
import org.xml.sax.helpers.NamespaceSupport;

/**
Expand All @@ -20,26 +21,47 @@ public class FlatCompositeBuilder extends CompositeBuilder implements FlatBuilde
private AttributeNameHelper attributeNameHelper;

public FlatCompositeBuilder(String key, NamespaceSupport namespaces, String separator) {
super(key, namespaces);
super(key, namespaces, false);
attributeNameHelper = new AttributeNameHelper(this.key, separator);
}

public FlatCompositeBuilder(
String key, NamespaceSupport namespaces, String separator, boolean topLevelComplex) {
super(key, namespaces, topLevelComplex);
attributeNameHelper = new AttributeNameHelper(this.key, separator);
}

@Override
protected void evaluateChildren(TemplateOutputWriter writer, TemplateBuilderContext context)
throws IOException {
String key = getKey();
if (key != null && key.equals(AttributeNameHelper.PROPERTIES_KEY)) {
Object o = context.getCurrentObj();
addSkipObjectEncodingHint(context);
String key = getKey(context);
boolean isFeatureTypeBuilder = isFeatureTypeBuilder(o);
if (isFeatureTypeBuilder
|| (key != null && key.equals(AttributeNameHelper.PROPERTIES_KEY))) {
writer.startObject(key, encodingHints);
}
for (TemplateBuilder jb : children) {
((FlatBuilder) jb)
.setParentKey(attributeNameHelper.getCompleteCompositeAttributeName());
jb.evaluate(writer, context);
}
if (key != null && key.equals(AttributeNameHelper.PROPERTIES_KEY))
if (isFeatureTypeBuilder(o)
|| (key != null && key.equals(AttributeNameHelper.PROPERTIES_KEY)))
writer.endObject(key, encodingHints);
}

private boolean isFeatureTypeBuilder(Object o) {
boolean result = false;
if (o instanceof Feature) {
Feature f = (Feature) o;
result = getStrSource() != null && getSource().evaluate(f.getType()) == null;
}
return result;
}

@Override
public void setParentKey(String parentKey) {
this.attributeNameHelper.setParentKey(parentKey);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

import java.io.IOException;
import org.geoserver.featurestemplating.builders.impl.DynamicValueBuilder;
import org.geoserver.featurestemplating.builders.impl.TemplateBuilderContext;
import org.geoserver.featurestemplating.writers.TemplateOutputWriter;
import org.xml.sax.helpers.NamespaceSupport;

Expand All @@ -20,10 +21,12 @@ public FlatDynamicBuilder(
nameHelper = new AttributeNameHelper(this.key, separator);
}

protected void writeValue(TemplateOutputWriter writer, Object value) throws IOException {

@Override
protected void writeValue(
TemplateOutputWriter writer, Object value, TemplateBuilderContext context)
throws IOException {
writer.writeElementNameAndValue(
nameHelper.getFinalAttributeName(), value, getEncodingHints());
nameHelper.getFinalAttributeName(context), value, getEncodingHints());
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import org.geoserver.featurestemplating.builders.impl.IteratingBuilder;
import org.geoserver.featurestemplating.builders.impl.TemplateBuilderContext;
import org.geoserver.featurestemplating.writers.TemplateOutputWriter;
import org.geotools.util.Converters;
import org.xml.sax.helpers.NamespaceSupport;

/**
Expand All @@ -26,42 +27,57 @@ public FlatIteratingBuilder(String key, NamespaceSupport namespaces, String sepa
nameHelper = new AttributeNameHelper(this.key, separator);
}

public FlatIteratingBuilder(
String key, NamespaceSupport namespaces, String separator, boolean topLevelComplex) {
super(key, namespaces, topLevelComplex);
nameHelper = new AttributeNameHelper(this.key, separator);
}

@Override
public void evaluate(TemplateOutputWriter writer, TemplateBuilderContext context)
throws IOException {
if (!rootCollection) {
if (ownOutput) {
context = evaluateSource(context);
if (context.getCurrentObj() != null) {
if (context.getCurrentObj() instanceof List)
evaluateCollection(writer, context, false);
else evaluateInternal(writer, context, 0, 1);
Object o = context.getCurrentObj();
if (o != null) {
if (o instanceof List) {
evaluateCollection(writer, (List) o, context.getParent(), false);
} else if (o.getClass().isArray()) {
List list = Converters.convert(o, List.class);
evaluateCollection(writer, list, context.getParent(), false);
} else {
evaluateInternal(writer, context, 0, 1);
}
}
} else {
if (evaluateFilter(context)) {
addSkipObjectEncodingHint(context);
for (TemplateBuilder child : children) {
AbstractTemplateBuilder abstractChild = (AbstractTemplateBuilder) child;
if (child instanceof FlatCompositeBuilder)
writer.startObject(abstractChild.getKey(), encodingHints);
writer.startObject(abstractChild.getKey(context), encodingHints);
child.evaluate(writer, context);
if (child instanceof FlatCompositeBuilder)
writer.endObject(abstractChild.getKey(), encodingHints);
writer.endObject(abstractChild.getKey(context), encodingHints);
}
}
}
}

@Override
public void evaluateCollection(
TemplateOutputWriter writer, TemplateBuilderContext context, boolean iterateKey)
TemplateOutputWriter writer,
List elements,
TemplateBuilderContext parent,
boolean iterateKey)
throws IOException {

List elements = (List) context.getCurrentObj();
int elementsSize = elements.size();
int actualIndex = 1;
for (int i = 0; i < elementsSize; i++) {
Object o = elements.get(i);
TemplateBuilderContext childContext = new TemplateBuilderContext(o);
childContext.setParent(context.getParent());
childContext.setParent(parent);
if (evaluateFilter(childContext)) {
evaluateInternal(writer, childContext, elementsSize, actualIndex);
actualIndex++;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,12 @@ protected void evaluateInternal(TemplateOutputWriter writer, TemplateBuilderCont
GeoJSONWriter geoJsonWriter = (GeoJSONWriter) writer;
if (strValue != null)
geoJsonWriter.writeStaticContent(
nameHelper.getFinalAttributeName(), strValue, nameHelper.getSeparator());
nameHelper.getFinalAttributeName(context), strValue, nameHelper.getSeparator());
else
geoJsonWriter.writeStaticContent(
nameHelper.getFinalAttributeName(), staticValue, nameHelper.getSeparator());
nameHelper.getFinalAttributeName(context),
staticValue,
nameHelper.getSeparator());
}

public void setParentKey(String parentKey) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import java.util.stream.Collectors;
import org.geoserver.featurestemplating.builders.SourceBuilder;
import org.geoserver.featurestemplating.builders.TemplateBuilder;
import org.geoserver.featurestemplating.builders.visitors.TemplateVisitor;
import org.geoserver.featurestemplating.writers.TemplateOutputWriter;
import org.xml.sax.helpers.NamespaceSupport;

Expand All @@ -21,14 +22,15 @@ public class CompositeBuilder extends SourceBuilder {

protected List<TemplateBuilder> children;

public CompositeBuilder(String key, NamespaceSupport namespaces) {
super(key, namespaces);
public CompositeBuilder(String key, NamespaceSupport namespaces, boolean topLevelComplex) {
super(key, namespaces, topLevelComplex);
this.children = new LinkedList<>();
}

@Override
public void evaluate(TemplateOutputWriter writer, TemplateBuilderContext context)
throws IOException {
addSkipObjectEncodingHint(context);
context = evaluateSource(context);
Object o = context.getCurrentObj();
if (o != null && evaluateFilter(context) && canWrite(context)) {
Expand All @@ -45,11 +47,11 @@ public void evaluate(TemplateOutputWriter writer, TemplateBuilderContext context
*/
protected void evaluateChildren(TemplateOutputWriter writer, TemplateBuilderContext context)
throws IOException {
writer.startObject(getKey(), encodingHints);
if (ownOutput) writer.startObject(getKey(context), encodingHints);
for (TemplateBuilder jb : children) {
jb.evaluate(writer, context);
}
writer.endObject(getKey(), encodingHints);
if (ownOutput) writer.endObject(getKey(context), encodingHints);
}

/**
Expand All @@ -62,16 +64,15 @@ protected void evaluateChildren(TemplateOutputWriter writer, TemplateBuilderCont
public boolean canWrite(TemplateBuilderContext context) {
List<TemplateBuilder> filtered =
children.stream()
.filter(
b ->
b instanceof DynamicValueBuilder
|| b instanceof CompositeBuilder)
.filter(b -> b instanceof DynamicValueBuilder || b instanceof SourceBuilder)
.collect(Collectors.toList());
if (filtered.size() == children.size()) {
int falseCounter = 0;
for (TemplateBuilder b : filtered) {
if (b instanceof CompositeBuilder) {
if (!((CompositeBuilder) b).canWrite(context)) falseCounter++;
} else if (b instanceof IteratingBuilder) {
if (!((IteratingBuilder) b).canWrite(context)) falseCounter++;
} else {
if (!((DynamicValueBuilder) b).checkNotNullValue(context)) falseCounter++;
}
Expand All @@ -92,7 +93,7 @@ public List<TemplateBuilder> getChildren() {
}

@Override
protected void writeKey(TemplateOutputWriter writer) throws IOException {
if (key != null && !key.equals("")) writer.writeElementName(key, getEncodingHints());
public Object accept(TemplateVisitor visitor, Object value) {
return visitor.visit(this, value);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import java.util.logging.Logger;
import org.geoserver.featurestemplating.builders.AbstractTemplateBuilder;
import org.geoserver.featurestemplating.builders.JSONFieldSupport;
import org.geoserver.featurestemplating.builders.visitors.TemplateVisitor;
import org.geoserver.featurestemplating.expressions.TemplateCQLManager;
import org.geoserver.featurestemplating.writers.TemplateOutputWriter;
import org.geotools.feature.ComplexAttributeImpl;
Expand Down Expand Up @@ -39,6 +40,8 @@ public DynamicValueBuilder(String key, String expression, NamespaceSupport names
TemplateCQLManager cqlManager = new TemplateCQLManager(expression, namespaces);
if (expression.startsWith("$${")) {
this.cql = cqlManager.getExpressionFromString();
} else if (expression.equals("${.}")) {
this.cql = cqlManager.getThis();
} else if (expression.startsWith("${")) {
this.xpath = cqlManager.getAttributeExpressionFromString();
} else {
Expand All @@ -50,14 +53,15 @@ public DynamicValueBuilder(String key, String expression, NamespaceSupport names
@Override
public void evaluate(TemplateOutputWriter writer, TemplateBuilderContext context)
throws IOException {
Object o = null;
if (evaluateFilter(context)) {
Object o = null;
if (xpath != null) {
o = evaluateXPath(context);
} else if (cql != null) {
o = evaluateExpressions(context);
o = evaluateExpressions(cql, context);
}
writeValue(writer, o);
addChildrenEvaluationToEncodingHints(writer, context);
writeValue(writer, o, context);
}
}

Expand All @@ -66,11 +70,14 @@ public void evaluate(TemplateOutputWriter writer, TemplateBuilderContext context
*
* @param writer the template writer
* @param value the value to write
* @param context
* @throws IOException
*/
protected void writeValue(TemplateOutputWriter writer, Object value) throws IOException {
protected void writeValue(
TemplateOutputWriter writer, Object value, TemplateBuilderContext context)
throws IOException {
if (canWriteValue(value)) {
writer.writeElementNameAndValue(getKey(), value, getEncodingHints());
writer.writeElementNameAndValue(getKey(context), value, getEncodingHints());
}
}

Expand All @@ -88,6 +95,7 @@ public AttributeExpressionImpl getXpath() {
* @param context the context against which evaluate the xpath
* @return the evaluation result
*/
// TODO: thi and evaluateExpression are almost identical. Can they be merged?
protected Object evaluateXPath(TemplateBuilderContext context) {
int i = 0;
while (i < contextPos) {
Expand All @@ -96,7 +104,7 @@ protected Object evaluateXPath(TemplateBuilderContext context) {
}
Object result = null;
try {
Object contextObject = context.getCurrentObj();
Object contextObject = getContextObject(context);
result = xpath.evaluate(contextObject);
result = JSONFieldSupport.parseWhenJSON(xpath, contextObject, result);
} catch (Exception e) {
Expand All @@ -111,10 +119,11 @@ protected Object evaluateXPath(TemplateBuilderContext context) {
/**
* Evaluate the Expression against the provided context
*
* @param expression
* @param context the context against which evaluate the xpath
* @return the evaluation result
*/
protected Object evaluateExpressions(TemplateBuilderContext context) {
protected Object evaluateExpressions(Expression expression, TemplateBuilderContext context) {
Object result = null;
try {
int i = 0;
Expand All @@ -123,7 +132,7 @@ protected Object evaluateExpressions(TemplateBuilderContext context) {
i++;
}
Object contextObject = context.getCurrentObj();
result = cql.evaluate(contextObject);
result = expression.evaluate(contextObject);
result = JSONFieldSupport.parseWhenJSON(cql, contextObject, result);
} catch (Exception e) {
LOGGER.log(Level.INFO, "Unable to evaluate expression. Exception: {0}", e.getMessage());
Expand Down Expand Up @@ -167,9 +176,33 @@ public boolean checkNotNullValue(TemplateBuilderContext context) {
o = evaluateXPath(context);

} else if (cql != null) {
o = evaluateExpressions(context);
o = evaluateExpressions(cql, context);
}
if (o == null) return false;
return true;
}

public void setCql(Expression cql) {
this.cql = cql;
}

public void setXpath(AttributeExpressionImpl xpath) {
this.xpath = xpath;
}

@Override
public Object accept(TemplateVisitor visitor, Object value) {
return visitor.visit(this, value);
}

private Object getContextObject(TemplateBuilderContext context) {
Object contextObject = context.getCurrentObj();
if (contextObject != null && contextObject instanceof List) {
List<Object> multipleValue = (List<Object>) contextObject;
if (!multipleValue.isEmpty()) {
contextObject = multipleValue.get(0);
}
}
return contextObject;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,13 @@
import static org.geoserver.featurestemplating.builders.EncodingHints.ITERATE_KEY;

import java.io.IOException;
import java.lang.reflect.Array;
import java.util.List;
import org.geoserver.featurestemplating.builders.SourceBuilder;
import org.geoserver.featurestemplating.builders.TemplateBuilder;
import org.geoserver.featurestemplating.builders.visitors.TemplateVisitor;
import org.geoserver.featurestemplating.writers.TemplateOutputWriter;
import org.geotools.util.Converters;
import org.xml.sax.helpers.NamespaceSupport;

/**
Expand All @@ -19,22 +22,24 @@
*/
public class IteratingBuilder extends SourceBuilder {

protected boolean rootCollection;

public IteratingBuilder(String key, NamespaceSupport namespaces) {
super(key, namespaces);
super(key, namespaces, false);
}

public IteratingBuilder(String key, NamespaceSupport namespaces, boolean topLevelComplex) {
super(key, namespaces, topLevelComplex);
}

@Override
public void evaluate(TemplateOutputWriter writer, TemplateBuilderContext context)
throws IOException {
if (!rootCollection) {
if (ownOutput) {
context = evaluateSource(context);
if (context.getCurrentObj() != null) {
evaluateNonFeaturesField(writer, context);
}
} else {
evaluateInternal(writer, context);
evaluateInternal(writer, context, false);
}
}

Expand All @@ -49,45 +54,54 @@ protected void evaluateNonFeaturesField(
TemplateOutputWriter writer, TemplateBuilderContext context) throws IOException {
if (canWrite(context)) {
boolean iterateKey = isIterateKey();
String key = getKey();
boolean isList = context.getCurrentObj() instanceof List;
// if this is not a context as list or we don't have
String key = getKey(context);
Object o = context.getCurrentObj();
boolean isList = o instanceof List;
boolean isArray = o != null && o.getClass().isArray();
// if this is not a context as list and we don't have
// iterate key hint we start the array and encode the key once here
if (!iterateKey || !isList) writer.startArray(key, encodingHints);
if (!iterateKey && hasOwnOutput()) writer.startArray(key, encodingHints);

if (isList) {
evaluateCollection(writer, context, iterateKey);
evaluateCollection(
writer, (List) context.getCurrentObj(), context.getParent(), iterateKey);
} else if (isArray) {
List list = Converters.convert(o, List.class);
evaluateCollection(writer, list, context.getParent(), iterateKey);
} else {
evaluateInternal(writer, context);
evaluateInternal(writer, context, iterateKey);
}

if (!iterateKey || !isList) writer.endArray(key, encodingHints);
if (!iterateKey && hasOwnOutput()) writer.endArray(key, encodingHints);
}
}

/**
* Evaluate a context which is a List
*
* @param writer the template writer
* @param context the context against which evaluate
* @param elements
* @param parent
* @throws IOException
*/
protected void evaluateCollection(
TemplateOutputWriter writer, TemplateBuilderContext context, boolean iterateKey)
TemplateOutputWriter writer,
List elements,
TemplateBuilderContext parent,
boolean iterateKey)
throws IOException {

List elements = (List) context.getCurrentObj();
for (Object o : elements) {
TemplateBuilderContext childContext = new TemplateBuilderContext(o);
childContext.setParent(context.getParent());
childContext.setParent(parent);
if (evaluateFilter(childContext)) {
String key = getKey();
String key = getKey(parent);
// repeat the key attribute according to the hint
if (iterateKey && !rootCollection) writer.startArray(key, encodingHints);
if (iterateKey && hasOwnOutput()) writer.startArray(key, encodingHints);
for (TemplateBuilder child : children) {
child.evaluate(writer, childContext);
}
if (iterateKey && !rootCollection) writer.endArray(key, encodingHints);
if (iterateKey && hasOwnOutput()) writer.endArray(key, encodingHints);
}
}
}
Expand All @@ -99,12 +113,16 @@ protected void evaluateCollection(
* @param context the current context
* @throws IOException
*/
protected void evaluateInternal(TemplateOutputWriter writer, TemplateBuilderContext context)
protected void evaluateInternal(
TemplateOutputWriter writer, TemplateBuilderContext context, boolean iterateKey)
throws IOException {
if (evaluateFilter(context)) {
String key = getKey(context);
if (iterateKey && hasOwnOutput()) writer.startArray(key, encodingHints);
for (TemplateBuilder child : children) {
child.evaluate(writer, context);
}
if (iterateKey && hasOwnOutput()) writer.endArray(key, encodingHints);
}
}

Expand All @@ -113,6 +131,8 @@ protected boolean canWrite(TemplateBuilderContext context) {
boolean result;
if (o instanceof List) {
result = canWriteList((List) o, context);
} else if (o != null && o.getClass().isArray()) {
result = canWriteArray(o, context);
} else {
result = canWriteSingle(o, context);
}
Expand All @@ -128,23 +148,31 @@ private boolean canWriteList(List elements, TemplateBuilderContext context) {
return false;
}

private boolean canWriteArray(Object array, TemplateBuilderContext context) {
int length = Array.getLength(array);
for (int i = 0; i < length; i++) {
Object item = Array.get(array, i);
TemplateBuilderContext childContext = new TemplateBuilderContext(item);
childContext.setParent(context.getParent());
if (evaluateFilter(childContext)) return true;
}
return false;
}

private boolean canWriteSingle(Object element, TemplateBuilderContext context) {
TemplateBuilderContext childContext = new TemplateBuilderContext(element);
childContext.setParent(context.getParent());
if (evaluateFilter(childContext)) return true;
return false;
}

public boolean isRootCollection() {
return rootCollection;
}

public void setRootCollection(boolean rootCollection) {
this.rootCollection = rootCollection;
}

private boolean isIterateKey() {
Object iterateKey = getEncodingHints().get(ITERATE_KEY);
return iterateKey != null && Boolean.valueOf(iterateKey.toString()).booleanValue();
}

@Override
public Object accept(TemplateVisitor visitor, Object value) {
return visitor.visit(this, value);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,10 @@
import org.geoserver.featurestemplating.builders.TemplateBuilder;
import org.geoserver.featurestemplating.builders.VendorOptions;
import org.geoserver.featurestemplating.builders.flat.FlatBuilder;
import org.geoserver.featurestemplating.builders.visitors.TemplateVisitor;
import org.geoserver.featurestemplating.expressions.TemplateCQLManager;
import org.geoserver.featurestemplating.writers.TemplateOutputWriter;
import org.geoserver.platform.FileWatcher;

/** The root of the builders' tree. It triggers the evaluation process */
public class RootBuilder implements TemplateBuilder {
Expand All @@ -24,6 +26,11 @@ public class RootBuilder implements TemplateBuilder {

protected List<String> supportedOptions = new ArrayList<>();

/**
* List of template watchers for included template. Used to know if cache needs to be reloaded
*/
private List<FileWatcher<Object>> watchers;

public RootBuilder() {
super();
this.children = new ArrayList<>(2);
Expand Down Expand Up @@ -57,7 +64,7 @@ public void setVendorOptions(String[] vendorOption) {
vendorOptions.put(vendorOption[0], cqlManager.getExpressionFromString());
}

public void addVendorOption(String name, String value) {
public void addVendorOption(String name, Object value) {
vendorOptions.put(name, value);
}

Expand All @@ -72,7 +79,12 @@ public boolean needsReload() {
vendorOptions.get(VendorOptions.FLAT_OUTPUT, Boolean.class, false).booleanValue();
if (isCachedFlattened && !isFlatOutput) return true;
else if (!isCachedFlattened && isFlatOutput) return true;
else return false;
else if (watchers != null && !watchers.isEmpty()) {
for (FileWatcher<Object> watcher : watchers) {
if (watcher.isModified()) return true;
}
}
return false;
}

@Override
Expand All @@ -90,4 +102,13 @@ public EncodingHints getEncodingHints() {
public VendorOptions getVendorOptions() {
return this.vendorOptions;
}

@Override
public Object accept(TemplateVisitor visitor, Object value) {
return visitor.visit(this, value);
}

public void setWatchers(List<FileWatcher<Object>> watchers) {
this.watchers = watchers;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import com.fasterxml.jackson.databind.JsonNode;
import java.io.IOException;
import org.geoserver.featurestemplating.builders.AbstractTemplateBuilder;
import org.geoserver.featurestemplating.builders.visitors.TemplateVisitor;
import org.geoserver.featurestemplating.writers.TemplateOutputWriter;
import org.xml.sax.helpers.NamespaceSupport;

Expand Down Expand Up @@ -36,8 +37,10 @@ public void evaluate(TemplateOutputWriter writer, TemplateBuilderContext context

protected void evaluateInternal(TemplateOutputWriter writer, TemplateBuilderContext context)
throws IOException {
if (strValue != null) writer.writeStaticContent(getKey(), strValue, getEncodingHints());
else writer.writeStaticContent(getKey(), staticValue, getEncodingHints());
addChildrenEvaluationToEncodingHints(writer, context);
String key = getKey(context);
if (strValue != null) writer.writeStaticContent(key, strValue, getEncodingHints());
else writer.writeStaticContent(key, staticValue, getEncodingHints());
}

/**
Expand All @@ -52,4 +55,9 @@ public JsonNode getStaticValue() {
public String getStrValue() {
return strValue;
}

@Override
public Object accept(TemplateVisitor visitor, Object value) {
return visitor.visit(this, value);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package org.geoserver.featurestemplating.builders.visitors;

import org.geoserver.featurestemplating.builders.AbstractTemplateBuilder;
import org.geoserver.featurestemplating.builders.SourceBuilder;
import org.geoserver.featurestemplating.builders.impl.CompositeBuilder;
import org.geoserver.featurestemplating.builders.impl.DynamicValueBuilder;
import org.geoserver.featurestemplating.builders.impl.IteratingBuilder;
import org.geoserver.featurestemplating.builders.impl.RootBuilder;
import org.geoserver.featurestemplating.builders.impl.StaticBuilder;

/**
* A default implementation of a {@link TemplateVisitor}. It simply traverse a TemplateBuilder tree.
*/
public class DefaultTemplateVisitor implements TemplateVisitor {

@Override
public Object visit(RootBuilder rootBuilder, Object extradata) {
rootBuilder.getChildren().forEach(b -> b.accept(this, extradata));
return extradata;
}

@Override
public Object visit(IteratingBuilder iteratingBuilder, Object extradata) {
iteratingBuilder.getChildren().forEach(b -> b.accept(this, extradata));
return extradata;
}

@Override
public Object visit(CompositeBuilder compositeBuilder, Object extradata) {
compositeBuilder.getChildren().forEach(b -> b.accept(this, extradata));
return extradata;
}

@Override
public Object visit(DynamicValueBuilder dynamicBuilder, Object extradata) {
dynamicBuilder.getChildren().forEach(b -> b.accept(this, extradata));
return extradata;
}

@Override
public Object visit(StaticBuilder staticBuilder, Object extradata) {
staticBuilder.getChildren().forEach(b -> b.accept(this, extradata));
return extradata;
}

@Override
public Object visit(SourceBuilder sourceBuilder, Object extradata) {
sourceBuilder.getChildren().forEach(b -> b.accept(this, extradata));
return extradata;
}

@Override
public Object visit(AbstractTemplateBuilder abstractTemplateBuilder, Object extradata) {
abstractTemplateBuilder.getChildren().forEach(b -> b.accept(this, extradata));
return extradata;
}
}
Loading