Config Resolver (v0)

Config Resolver (v0)

Adaptation to the context

The final configuration depends on an invocation context that constitutes different variables, mainly from the surfer, in order to have different configurations depending on the connected user.

There are therefore folders in the path associated with a mapping whose name depends on the invocation context that allow specific configurations.

Post processors

A postprocessor is a component that determines a folder name according to elements of the invocation context.

A postprocessor provides

  • a list of possible values, at least one, but preferably several (for a single value, you might as well use a static file).

  • a function to convert the invocation context into a value that must be one of the possible values

Plugin settings

Mappings

Mappings associate configuration identifiers with paths defining a configuration.

A path consists of a succession of folder names or references to a postprocessor, separated by arobase (@).

For example _portal@picker defines a configuration whose base is in the _portal folder, overridden by what is in the _portal@picker folder.

Postprocessors are identified by their associated variable name preceded by a colon (:).

For example _portal@picker@:surferRole defines a configuration whose base is the portal folder, overridden by what is in the portal@picker@picker folder, overridden by what is in a folder whose name is determined by the postprocessor associated with the surferRole variable, by example _portal@picker@role_4 for the role 4.

For example _portal@config@picker designates a configuration defined in 3 folders whose relative paths are successively :

  • _portal

  • _portal@config

  • _portal@config@picker

The path _portal@$config@picker designates a configuration defined in 3 folders whose relative paths are successively :

  • _portal

  • config

  • config@picker

The dollar character has this effect whether it is in the path or in the value returned by a postprocessor. Only the first character is considered.

The dollar character can be used in a variable:

  • in name (like for example here _portal@config@$:surferRole@custo)

  • in a value produced by a post processors
    for example, _portal@config@$:customSurferRole and post processor return $role-1, $role-2, role-4..., to get the following folders (or files):

    • _portal

    • _portal@config

    • role-1@custo

    • role-2@custo

    • _portal@config@role-4@custo

To configure the mappings, use the config_settings plugin parameter. This is a JSON object whose the mappings property that designs the mappings as an object, whose keys are the mapping IDs and whose associated values are the paths.

For example :

{ "mappings": { "portal": "_portal@club-wed@:surferRole@:userAgent", "picker": "_portal@club-wed@:picker" } }

Postprocessors

To configure the post processors, use the config_settings plugin parameter. This is a JSON object in the postProcessors property that designs the post processors as an object, whose keys are the post processors names (variable names) and whose associated values define the corresponding post processors.

To define a post processor you can specify

  • a class name from a set of standard post processors provided in the plugin

  • configuration set-up parameters (depending of the component)

Base locations

By default, folders and configuration files are stored in arbitrary folders in the :

  • the base configurations are in the folder config/config_resolver/bases situé dans le plugin WXM_CONFIG_RESOLVER

  • the other configurations (overrides) are in the folder config/config_resolver/configs

    • if a holding plugin has been configured, located in this one

    • otherwise located in the WXM_CONFIG_RESOLVER plugin

Basic configurations can be placed in other plugins, using the baseLocations section.

This section is a JSON object whose

  • the keys are base names

  • the properties are

    • either a plugin name
      in this case, the storage folder is config/config_resolver/bases relative to this plugin path

    • or a plugin name and a path (relative to the path of this plugin)

Example:

{ "mappings": { "portal": "_portal@club-wed@:surferRole@:userAgent", "picker": "_portal@club-wed@:picker" }, "baseLocations": { "_portal": "PACKAGED_Portal" } }

Example with the configuration file in the config folder of the plugin:

{ "mappings": { "portal": "_portal@club-wed@:surferRole@:userAgent", "picker": "_portal@club-wed@:picker" }, "baseLocations": { "_portal": { "plugin": "PACKAGED_Portal", "path": "config" } }

Lock system

fs_lock_enabled, boolean (default is true)

enable or disable the lock system

lock_config, json

lock system set-up

  • safeProcessCaptureTimeout, time, optional (default 10000 ms)

Miscellaneous

default_holding_plugin, string, optional

designates the name of a plugin where the configuration will be stored (in its config/config_resolver/configs folder. If no value is specified, the configuration is stored in the config/config_resolver/configs folder of the wxm_config_resolver plugin, unless there is an activated wxm_config_resolver_config plugin.

default_contribution_plugin, string, optional

designate the name of a default plugin to store custom postprocessors (groovy files)

default_service_debug, boolean, optional

enables by default the debug mode on all requests

default_uri_segment, string, optional

the root segment of the end point URI (if no value is provided, the value is portalconfig (so all end point URIs served by this plugin are of the form /api/portalconfig/*))

this means that you can create a new configuration universe by duplicating the plugin and using another uri, for example, /api/whatyouwant/*

Time set-up

The values of type time can be

  • either a number (the unit depends on the parameter)

  • or a number (integer or decimal) followed by a unit, among

    • s (seconds)

    • ms (milliseconds)

    • m (minutes)

    • h (hours)

Contributing to configuration in your plugins

At startup, the config resolver plugin scans all activated tagged plugins to discover contributions to its configuration (this feature can be disabled by the discover_external_config_settings plugin parameter). The discover_external_config_settings_exclusions parameter allows to exclude plugins from this scan.

Discovery of bases (base locations)

For each plugin, the config folder of the plugin is first scanned to determine if there are any bases in its config-resolver/bases subfolder. Then, we scan the resource folder of the plugin (res).

If a base already exists, it is ignored (note that you cannot control the order of the plugins).

Adding mappings

For each plugin, if a config-resolver-mappings.json file is present in its configuration folder (config), it is loaded to add one or more mappings. The format is the same as the mappings section of the config_settings plugin parameter.

 

If a mapping already exists, it is ignored (note that you cannot control the order of the plugins).

Adding post processors

For each plugin, if a config-resolver-postprocessors.json file is present in its configuration folder (config), it is loaded to add one or more post processors. The format is the same as the postProcessorssection of the config_settings plugin parameter, but you can additionally indicate that postprocessor is defined in the plugin itself using object syntax without indicating a plugin property (if you indicate a null plugin property, the default contribution plugin will be used).

If a post processor already exists, it is ignored (note that you cannot control the order of the plugins).

Where to put the groovy script

In config-resolver-postprocessors.json, when the definition is a groovy file path, it is a relative path to the root of the plugin.

By example, if your script file myprocessor.groovy is in the folder res/postprocessors, the json will be like this:

{ "myprocessor": { "processor": "res/postprocessors/myprocessor.groovy", } }

You can also put a slash at beginning like this:

{ "myprocessor": { "processor": "/res/postprocessors/myprocessor.groovy", } }

It’s also possible to put the script in a plugin jar, by using a JAR URI :

{ "myprocessor": { "processor": "jar:file:/thejarfile.jar!/fr/wedia/mypackage/postprocessors/myprocessorinjar.groovy", } }

The file thejarfile.jar must be in plugin lib directory. If you want to put this jar in another folder, prefix you jar path by the relative (to the plugin root) path:

{ "myprocessor": { "processor": "jar:file:/other/path/thejarfile.jar!/fr/wedia/mypackage/postprocessors/myprocessorinjar.groovy", } }

Deal with the host plugin

It is possible to exchange with the contribution plugin. However, it is not certain that it is started when the postprocessor is running, whatever the method, and even less so when the postprocessor is loaded, initialized and started. The plugin can even be stopped and restarted at any time.
This means:

  • always make sure the plugin is started when calling one of its classes, and provide a fallback (for getValue() and getAvailableValues()).

  • to retrieve parameters, you have to proceed in lazy, try to systematically read the parameters as long as it failed, then store them (cache) to use them.

  • before invoking a method of a plugin class, check the state of the plugin
    to have the plugin instance available, implement the injection method (setPlugin(IPlugin) or setPlugin(String))

  • any modification of the plugin requires a restart of WXM_CONFIG_RESOLVER after.

Here is an example of possible interactions with plugin (get parameters and invoke method):

package fr.wedia.confref.postprocessor.examples; import java.util.stream.*; import java.util.Collection; import com.google.gson.*; import com.noheto.plugins.IPlugin; import fr.wedia.confres.core.model.processor.*; import fr.wedia.confres.utils.Utils; import fr.wedia.confres.utils.annot.PostProcessorConfigExclude; import fr.wedia.confres.core.model.processor.EngineToolkit.ParameterType; // need to import explicitly the enum (in groovy) /** * A basic example of postprocessor implementation in groovy. * * <p>This is an example of a postprocessor with interaction with the plugin that declares the postprocessor. * The value is a configurable fixed value.</p> * <p>There are two types of interactions </p> * <ul> * <li>We read some configuration properties of the plugin (they are not used, it is just for the example)</li> * <li>We invoke a method of the plugin at each call (it doesn't matter what it does, it's just for the example)</li> * </ul> * * <p></p> * * @author joel.drigo@wedia-group.com * */ class CustomProcessorExampleOfInteractionWithThePlugin extends AbstractPostProcessor { /** * This value is always returned. It is configurable because there isn't any {@link @PostProcessorConfig} annotation. */ private String value = "fixed"; /** * This is the list of possible returned values. It is initialized at startup, since the returned value is configurable (variable {@code value}) * <p>This variable is excluded explicitly from configuration, because there isn't any {@link @PostProcessorConfig} annotation</p> */ @PostProcessorConfigExclude private Set<String> values; /** * Stores the plugin that declares the postprocessor (initialized at loading) */ @PostProcessorConfigExclude private IPlugin plugin; /** * True when the plugin parameters has been effectly loaded */ @PostProcessorConfigExclude private boolean configured; /** * This is a string parameter from plugin parameters */ @PostProcessorConfigExclude private String paramExample1; /** * This is a JsonObject parameter from plugin parameters */ @PostProcessorConfigExclude private JsonObject paramExample2; /** * This method is used when loading the postprocessor to inject the plugin that declares this postprocessor. * * @param plugin the name of the plugin that declares this postprocessor. */ public void setPlugin(IPlugin plugin) { this.plugin = plugin; loadConfig(); // lazy loading of plugin parameters } /** * Start the processor. */ public void start() { loadConfig(); // lazy loading of plugin parameters value = Utils.withDefault(value, ""); // if value is null or blank, replace with empty string values = Utils.unmodifiableSet(value); } /** * This method reads the configuration from plugin (whose declare this postprocessor). */ private void loadConfig() { if ( !configured ) { // if configuration has not been yet loaded if ( plugin==null ) { PostProcessorLogger.warn("Plugin has not be set correctly"); configured = true; // we force this state to avoid trying to load again the configuration } else if ( plugin.isStarted() ) { if ( PostProcessorLogger.debug() ) PostProcessorLogger.debug(CustomProcessorExampleOfInteractionWithThePlugin.class,"Load parameters from plugin " + plugin.getName() ); try { // we read the desired plugin parameters Map<String,Object> parameters = new EngineToolkit.PluginParameters() .registerParameter("param_example_1", ParameterType.STRING, "default") // reads parameter_example_1 of type string .registerParameter("param_example_2", ParameterType.JSON, ()-> new JsonObject()) // reads parameter_example_2 of type json .collect(plugin); configured = true; paramExample1 = (String)parameters.get("param_example_1"); JsonElement json = (JsonElement)parameters.get("param_example_2"); if ( json instanceof JsonObject ) { paramExample2 = (JsonObject)json; } else { paramExample2 = new JsonObject(); } if ( PostProcessorLogger.debug() ) { PostProcessorLogger.debug(CustomProcessorExampleOfInteractionWithThePlugin.class,"Parameter param_example_1 = " + paramExample1 ); PostProcessorLogger.debug(CustomProcessorExampleOfInteractionWithThePlugin.class,"Parameter param_example_2 = " + paramExample2 ); } } catch(Throwable t) { // show stack trace only in debug level if ( PostProcessorLogger.debug() ) PostProcessorLogger.debug(CustomProcessorExampleOfInteractionWithThePlugin.class,"Error while loading parameters from plugin " + plugin.getName() +".", t ); else if ( PostProcessorLogger.warn() ) PostProcessorLogger.warn(CustomProcessorExampleOfInteractionWithThePlugin.class,"Error while loading parameters from plugin " + plugin.getName() +". Set log to debug to see stacktrace." ); } } } } /** * This method invoke a method of a class of the plugin (whose declare this postprocessor). */ private void invokePlugin(ProcessorContext context) { if ( PostProcessorLogger.trace() ) { PostProcessorLogger.trace("Invoking " + plugin.getName() + " / fr.wedia.confref.postprocessor.examples.Example.helloPlugin()"); } String pluginMessage = EngineToolkit.invokePlugin(plugin, t->{ if ( t!=null ) { // if t is null plugin is not started // show stack trace only in debug level if ( PostProcessorLogger.debug() ) PostProcessorLogger.debug(CustomProcessorExampleOfInteractionWithThePlugin.class,"Error while invoking plugin " + plugin.getName() +".", t ); else if ( PostProcessorLogger.warn() ) PostProcessorLogger.warn(CustomProcessorExampleOfInteractionWithThePlugin.class,"Error while invoking plugin " + plugin.getName() +". Set log to debug to see stacktrace." ); } }, "fr.wedia.confref.postprocessor.examples.Example","helloPlugin", context.getProcessorName() ); if ( pluginMessage!=null ) { if ( PostProcessorLogger.info() ) { PostProcessorLogger.info("The plugin " + plugin.getName() + " says: \"" + pluginMessage + "\""); } } else { if ( PostProcessorLogger.debug() ) { PostProcessorLogger.debug("Invoking " + plugin.getName() + "... seems to be not started."); } } } /** * {@inheritDoc} * Returns the postprocessor value corresponding (always the value {@code value}. */ @Override String getValue(ProcessorContext context) { loadConfig(); // lazy loading of plugin parameters if ( PostProcessorLogger.trace() ) { PostProcessorLogger.trace("Invoking " + plugin.getName() + "..."); } invokePlugin(context); // invoke plugin return value; } /** * Returns all possible values. * * @return all possible values. */ @Override public Collection<String> getAvailableValues() { loadConfig(); // lazy loading of plugin parameters return values; } /** * Returns a hash code value for this postprocessor. */ public int hashCode() { return Objects.hash(value); } /** * Returns true if the specified object is equals to this processor, i.e. the classes of both are equals and indexes are equals. * * return true if the specified object is equalis to this processor. */ @Override public boolean equals(Object obj) { return fr.wedia.confres.utils.Utils.equals(CustomProcessorExampleOfInteractionWithThePlugin.class, this, obj, (o1,o2)-> Objects.equals(o1.value,o2.value)); } }

A complete example of the contribution of mappings and postprocessors

The configuration of the WXM_CONFIG_RESOLVER plugin is

{ "mappings": { "portal": "_portal@club-wed@:surferRole@:userAgent", "picker": "_portal@club-wed@:picker" }, "postProcessors": { "surferRole": "fr.wedia.confres.core.model.processor.SurferRolePostProcessor", "userAgent": { "processor": "fr.wedia.confres.core.model.processor.UserAgentPostProcessor", "config": [ { "pattern": ".*Mobi.*", "name": "mobile" } ] } } }

The service GET /api/portalconfig/mappings (list of mappings) returns the following two mappings:

[ "portal", "picker" ]

The service GET api/portalconfig/postprocessors/list?options=%7B%0A%09withMappings%3A%20true,%0A%09withValues%3A%20true%0A%7D (list of postprocessors, with their associated mappings and values) returns the following two postprocessors :

[ { "name": "surferrole", "mappings": [ "portal" ], "values": [ "", "role-1", "role-4", "role-27", "role-31", "role-32", "role-33" ] }, { "name": "useragent", "mappings": [ "portal" ], "values": [ "", "mobile" ] } ]

I create a contribution plugin WXM_CONFIG_RESOLVER_GPP as (here I put two of the sources of the postprocessors (groovy) in a res folder, but they can be put in another folder). The third processor is in a JAR.

WXM_CONFIG_RESOLVER_GPP

config

📄 config.xml

📄 config-resolver-mappings.json

📄 config-resolver-postprocessors.json

lig

📄 gpp.jar

res

gpp

📄 CustomProcessor.groovy

📄 CustomProcessorExample.groovy

📄 CustomProcessorExampleOfInteractionWithThePlugin.groovy

📄 CustomProcessorWithConfig.groovy

(the plugin may contain other groovy processors not used in this example).

Here is the zip of the plugin (including source of class fr.wedia.confref.postprocessor.examples.Example.

The config-resolver-mappings.json file defines additional mappings:

{ "mapping_GPP_1": "_empty", "mapping_GPP_2": "_empty@:gppProcessor", "mapping_GPP_3": "_empty@:gppProcessor@:gppProcessorWithConfig", "mapping_GPP_4": "_empty@:gppProcessor@:gppProcessorPlugin" }

The config-resolver-postprocessors.json file declares additional postprocessors:

{ "gppProcessor": { "processor": "jar:file:/gpp.jar!/fr/wedia/confref/postprocessor/examples/CustomProcessor.groovy" }, "gppProcessorWithConfig": { "processor": "/res/gpp/CustomProcessorWithConfig.groovy", "config": { "values": ["x","y" ] } }, "gppProcessorPlugin": { "processor": "/res/gpp/CustomProcessorExampleOfInteractionWithThePlugin.groovy" } }

This defines the following postprocessors:

  • gppProcessor source script is located in jar gpp.jar (located in the directory /lib of the plugin). The source is also in folder /res/gpp if you want to look at the source. It is used by mappings mapping_GPP_2, mapping_GPP_3 and mapping_GPP_4.

  • gppProcessorWithConfig source script is located in folder /res/gpp. It is used by mapping mapping_GPP_3.

  • gppProcessorPlugin source script is located in folder /res/gpp. It is used by mapping mapping_GPP_4.

We use here the "object" syntax without "plugin" property, which determines that the groovy file will be searched in this plugin (WXM_CONFIG_RESOLVER_GPP), with a relative path to its root.

 

We install and activate this plugin and restart WXM_CONFIG_RESOLVER.

Le service GET /api/portalconfig/mappings (liste des mappings) retourne :

[ "portal", "picker", "mapping_GPP_1", "mapping_GPP_2", "mapping_GPP_3", "mapping_GPP_4" ]

Le service GET api/portalconfig/postprocessors/list?options=%7B%0A%09withMappings%3A%20true,%0A%09withValues%3A%20true%0A%7D (liste des postprocessors, avec leurs mappings associés et valeurs) retourne :

[ { "name": "surferrole", "mappings": [ "portal" ], "values": [ "", "role-1", "role-4", "role-27", "role-31", "role-32", "role-33" ] }, { "name": "useragent", "mappings": [ "portal" ], "values": [ "", "mobile" ] }, { "name": "gppprocessorplugin", "mappings": [ "mapping_GPP_4" ], "values": [ "fixed" ] }, { "name": "gppprocessor", "mappings": [ "mapping_GPP_2", "mapping_GPP_3", "mapping_GPP_4" ], "values": [ "V1", "V2", "V3", "V4", "V5" ] }, { "name": "gppprocessorwithconfig", "mappings": [ "mapping_GPP_3" ], "values": [ "x", "y" ] } ]

Standard postprocessors

Surfer role

Class name: fr.wedia.confres.core.model.processor.SurferRolePostProcessor

This post processor allows to have a specific configuration for each role. The folder name for a role is formed by concatenating a prefix and the role identifier.

For example: role-1.

Set-up

The prefix can be configured using the property prefix.

For exemple:

"surferRole": { "processor": "fr.wedia.confres.core.model.processor.SurferRolePostProcessor", "prefix": "altrole-" }

The value for not connected mode is ““. It could be changed with the property notConnectedValue.

Abstraction

You can implement your own surfer-dependent post processor by implementing the fr.wedia.confres.core.model.processor.AbstractSurferPostProcessor abstraction and its String getValue(CTSurfer) method.

User agent

Class name: fr.wedia.confres.core.model.processor.UserAgentPostProcessor

This post processor allows you to have a specific configuration for a particular browser.

The association between a configuration folder and a browser is done by configuration, by indicating a folder name associated with a regular expression pattern: the User-Agent header that will match the regular expression will select the folder.

You can indicate several files and patterns. This is done through the config property, which is an array of objects, with a name property for the folder name and a pattern property for the regular expression pattern.

For example:

"userAgent": { "processor": "fr.wedia.confres.core.model.processor.UserAgentPostProcessor", "config": [{ "pattern": ".*Mobi.*", "name": "mobile" }] }

If several patterns are indicated, they are tested in the order of the array. The first one that matches is used.

Set-up

By default, only one User-Agent header is tested, the first one found. You can test all the values of this header using the anyMatch configuration property by setting it to true.

"userAgent": { "processor": "fr.wedia.confres.core.model.processor.UserAgentPostProcessor", "anyMatch": true, "config": [{ "pattern": ".*Mobi.*", "name": "mobile" }] }

Abstraction

You can implement your own header-dependent post processor by implementing the fr.wedia.confres.core.model.processor.AbstractHeaderPostProcessor abstraction and its String getValue(String,Collection<fr.wedia.confres.api.util.Headers>) method. See others methods getValueFoNoHeaders and getValueForNull to return a value respectively for no headers or null headers. See the javadoc for more information

Parameter processor

Abstraction

fr.wedia.confres.core.model.processor.AbstractParameterPostProcessor is a base for processors whose value depends on parameters.

 

Customizings postprocessors

You can create your own postprocessors. Just define the implementation class in a groovy extension file. The class must extend one of the abstractions provided as standard, or possibly a standard postprocessor.

In this case, the component reference is expressed as a path to a file.

For examples:

"surferRole": "customprocessors/mysurferrolepostprocessors.groovy"
"surferRole": { "processor": "customprocessors/mysurferrolepostprocessors.groovy", "prefix": "altrole-" }

Location

The location of the file can be:

  • the SAN

  • a plugin
    To specify a plugin, add the plugin property.

    "surferRole": { "processor": "customprocessors/mysurferrolepostprocessors.groovy", "plugin": name of the plugin }
    • If the value of property plugin is a string, it’s the name of the plugin

    • In all other cases,, the plugin is a default plugin, configured in the default_contribution_plugin parameter of the wxm_config_resolver plugin

Implementation

To implement a postprocessor in groovy, extend the class fr.wedia.confres.core.model.processor.AbstractPostProcessor. You can also base your class on one of the pre-implemented postprocessor classes (see Standard postprocessors).

Example:

package fr.wedia.confref.custom.processor; import fr.wedia.confres.core.model.processor.*; class CustomProcessor extends AbstractPostProcessor { private static final String CONNECTED = "connected"; private static final String NOTCONNECTED = "notconnected"; private static final List<String> VALUES = Arrays.asList("",CONNECTED,NOTCONNECTED); @Override String getValue(ProcessorContext context) { wsnoheto.engine.CTSurfer surfer = context.getSurfer(); return surfer==null?"":(surfer.isConnected()?CONNECTED:NOTCONNECTED); } @Override Collection<String> getAvailableValues() { return VALUES; } }

You must implement two methods:

  1. The getAvailableValues() method must return all possible values returned by the other method.

  2. The method getValue(ProcessorContext) will return the desired value in the invocation context. It is important to consider the context rather than the query, because in some cases, for the purposes of the configuration editing tool, and for testing purposes, the context information may override query values. It is the ProcessorContext class that allows access to the invocation context:

    1. ProcessorContext.getSurfer(): returns the surfer for which we want to execute the postprocessor

    2. ProcessorContext.getSurferProperty(String name): returns the value of a property of the surfer (or null if it does not exist).

    3. ProcessorContext.getHeaders(String name): returns the values of the specified header

    4. ProcessorContext.getParameters(): returns a wrapper to retrieve query parameters

    5. ProcessorContext.getMappingId(): returns the ID of the mapping we are trying to solve

    6. ProcessorContext.getPathInfo(): allows to have a contextual description of invocation to log in particular

    7. ProcessorContext.getProcessorName(): get the name of the post processor

    8. As a last resort, you can retrieve the request, with the method ProcessorContext.getRequest().

Avoid using the query to retrieve context information (like the surfer for example). Always prefer to use the context when possible. Simulation queries can pass context information (like the surfer, or a header) as parameters, and this information is put in ProcessorContext, the query cannot be modified. Moreover, during unit tests your postprocessor could be called without request with a mocked server. In this case, the request instance will be null. If the context does not give you access to the information you want, you can use the request, but in this case your postprocessor cannot be used in simulation for this information nor in testing.

There is no guarantee that any of the returned values are not null: if any of the information provided by the ProcessorContext is used, all cases must be tested (including, for example, testing whether getRequest() is null, as the postprocessor could be invoked as part of a simulation outside of HTTP calls).

A value returned by these methods must meet certain conditions:

  • as it is a folder name, there must not be any forbidden characters in the file names (slash (/), backslash(\), column(:), pipe (|), chevrons (< and >), question mark (?), double-quotes (")...)

  • do not use arobase (@), this character being used as a name separator in configuration folder names, to avoid possible conflicts. Avoid also other separators, like dot (.), whitespaces…

  • generally avoid characters other than letters (without accent), numbers, underscore and dash (avoid dash if possible, this character can cause problems within URLs

  • the initial dollar character ($) is always considered as a cutting order

  • values should be considered case insensitive, but use lower case (as folders can be deployed by nar)

  • the blank values are always ignored (skip the processor value in the final path). For example, if a path is defined by x@:processor@y, and the corresponding postprocessor returns a blank string, the path will be x@y.

Since 2023.2

A processor can return a value

  • composed of several paths (thus containing @, for example x@y)

  • referring to another processor (for example the returned value is x@:otherproc@y
    beware however of an infinite recursion, there is no test. In general, limit the depth of recursion to limit computing time.

  • in addition we can escape a character with the backslash

Set-up

You can configure a custom postprocessor via its declaration in the plugin configuration.

Basic set-up

By default, the properties indicated in the postprocessor configuration, except the reserved properties processor, plugin, config and configDeserializer are all injected in the class variables by compliant setter.

For example, the implementation of postprocessor is (mypostprocessor.groovy):

package fr.wedia.confref.custom.processor; import fr.wedia.confres.core.model.processor.*; class CustomProcessor extends AbstractPostProcessor { String connectedValue = "connected"; String notConnectedValue = "notconnected"; void setConnectedValue(String connectedValue) { if ( StringUtils.isNotBlank(connectedValue) ) { this.connectedValue=connectedValue; } } void setNotConnectedValue(String notConnectedValue) { if ( StringUtils.isNotBlank(notConnectedValue) ) { this.notConnectedValue=notConnectedValue; } } @Override String getValue(ProcessorContext context) { wsnoheto.engine.CTSurfer surfer = context.getSurfer(); return surfer==null?"":(surfer.isConnected()?connectedValue:notconnectedvalue); } @Override Collection<String> getAvailableValues() { return Arrays.asList("", connectedValue, notConnectedValue); } }

The configuration of the postprocessor could be:

"connectionState": { "processor": "customprocessors/mypostprocessor.groovy", "plugin": name of the plugin, "connectedValue": "on", "notConnectedValue": "off" }

The two variables connectedValue and notConnectedValue will have the values "on" and "off" respectively.

Control of configuration variables

You can designate which variables will be configurable by using the @PostProcessorConfig annotation. In this case, only the variables that have this annotation will be configurable.

For example:

package fr.wedia.confref.custom.processor; import fr.wedia.confres.core.model.processor.*; import fr.wedia.confres.utils.annot.PostProcessorConfig; class CustomProcessor extends AbstractPostProcessor { @PostProcessorConfig(blank=false) String connectedValue = "connected"; @PostProcessorConfig(blank=false) String notConnectedValue = "notconnected"; int myvariable = 42; @Override String getValue(ProcessorContext context) { wsnoheto.engine.CTSurfer surfer = context.getSurfer(); return surfer==null?"":(surfer.isConnected()?connectedValue:notConnectedValue); } @Override Collection<String> getAvailableValues() { return Arrays.asList("", connectedValue, notConnectedValue); } }

And

"connectionState": { "processor": "customprocessors/mypostprocessor.groovy", "plugin": name of the plugin, "connectedValue": "on", "notConnectedValue": "off", "myvariable": 0 }

The two variables connectedValue and notConnectedValue will have the values "on" and "off" respectively, but myvariable will remain unchanged.

The annotation has two parameters:

  • name: (string, optional) to select the name of the property in the json
    for example, you can have

    @PostProcessorConfig(name="threshold") int myvar=42;
  • blank: (boolean, optional, default is true) if false, blank (emtpy or null) values are ignored

It’s not possible to mix the setter and the annotation ways to configure the postprocessor.

Advanced set-up

You can also deport the configuration to a separate object. If you configure your postprocessor by specifying properties in a config section, the properties will also be injected into the class variables.

For example

"connectionState": { "processor": "customprocessors/mypostprocessor.groovy", "plugin": name of the plugin, "config": { "connectedValue": "on", "notConnectedValue": "off" } }

You can also take control of the configuration decoding by adding to your class a setter called setConfig (void return type and only one argument).

  1. the type of argument can be com.google.gson.JsonElement.

  2. the type of argument can be fr.wedia.confres.core.model.processor.PostProcessorConfig. It is an encapsulation of com.google.gson.JsonElement that allows to retrieve information more easily.

  3. at any inner class with a no-argument constructor. It will be instancied the properties will be injected as described above
    Example:

    package fr.wedia.confref.custom.processor; import fr.wedia.confres.core.model.processor.*; import com.google.gson.*; import fr.wedia.confres.plugin.PluginLogger; class CustomProcessorWithConfig extends AbstractPostProcessor { private static final List<String> VALUES = Arrays.asList("V1","V2","V3","V4","V5"); /** * this is a debug postprocessor which use a non contextual property (index attribute value) */ @fr.wedia.confres.utils.annot.PostProcessorConfig() private int index=0; private Config config; private List<String> values = VALUES; public void start() { PluginLogger.info(CustomProcessorWithConfig.class,"starting component with config " + config); if ( config!=null ) { if ( config.getValues()!=null ) { PluginLogger.info(CustomProcessorWithConfig.class,"starting component with config " + config.getValues()); values = config.getValues(); } } } @Override String getValue(ProcessorContext context) { return values.get(index); } @Override Collection<String> getAvailableValues() { return values; } @Override String toString() { return this.getClass().getName()+" Groovy:CustomProcessor#"+index+"#"+values; } @Override public boolean equals(Object obj) { return fr.wedia.confres.utils.Utils.equals(CustomProcessorWithConfig.class, this, obj, (o1,o2)-> o1.index==o2.index); } public void setConfig(Config config) { this.config=config; } public static class Config { private List<String> values; public void setValues(List<String> values) { this.values=values; } public List<String> getValues() { return values; } } }

    And the configuration:

    "connectionState": { "processor": "customprocessors/mypostprocessor.groovy", "plugin": name of the plugin, "index": 2, "config": { "values": ["a","b","c"] } }

Startup

You can execute code right after loading the postprocessor by adding the start() method.

For example, here is a postprocessor with a JsonElement configuration:

package fr.wedia.confref.custom.processor; import fr.wedia.confres.core.model.processor.*; import com.google.gson.*; import fr.wedia.confres.plugin.PluginLogger; class CustomProcessorJsonElement extends AbstractPostProcessor { private static final List<String> VALUES = Arrays.asList("V1","V2","V3","V4","V5"); private JsonElement config; private List<String> values = VALUES; public void start() { PluginLogger.info(CustomProcessorJsonElement.class,"starting component with config " + config); if ( config!=null && config.isJsonArray() ) { JsonArray jsonArray = config.getAsJsonArray(); List<String> list = new ArrayList<>(); for(int i=0; i<jsonArray.size(); i++) { list.add(jsonArray.get(i).getAsString()); } values = list; } } @Override String getValue(ProcessorContext context) { return ... } @Override Collection<String> getAvailableValues() { return values; } public void setConfig(JsonElement config) { this.config=config; } }

Plugin

If the postprocessor is contributed by a plugin, you can retrieve this plugin by adding a method setPlugin with an argument of type String or com.noheto.plugins.IPlugin. However, be aware that postprocessors are loaded at the start of the wxm_config_resolver plugin and the contributing plugins are not necessarily started at that time.

Class fr.wedia.confres.core.model.processor.PostProcessorConfig

This class makes it easier to recover a JSON postprocessor configuration.

  1. the method isList() tests if the configuration is a list (a JSON array): if true, you can get it with asList() method, as a fr.wedia.confres.core.model.processor.PostProcessorConfigList.

  2. the method isMap() tests if the configuration is a map (a JSON object): if true, you can get it with asMap() method, as a fr.wedia.confres.core.model.processor.PostProcessorConfigMap.

See the JavaDoc of these classes for more information.

Operation

Postprocessors are loaded when the wxm_config_resolver plugin is started. If startup is not possible at that time (error during instantiation, file not found, etc.), the component will not be available until the plugin is restarted.

In any case a postprocessor loaded from a plugin will be invoked (whether the plugin is activated, started or not). Be careful, however, if you invoke plugin classes in the resolution (with a plugin.invoke() ): remember that a plugin cannot be invoked when it is not fully started (and therefore necessarily activated).

In case of an error during the resolution, the postprocessor is ignored and therefore its value is removed from the resolved path.

Logging

Logging can be done using

  • The fr.wedia.confres.core.model.processor.PostProcessorLogger class for a log specific to the execution of custom postprocessors

  • The fr.wedia.confres.plugin.PluginLogger class to log in the WXM_CONFIG_RESOLVER main log

Example:

package fr.wedia.confref.custom.processor; import fr.wedia.confres.core.model.processor.PostProcessorLogger; class CustomProcessorLoggingExample extends AbstractPostProcessor { private JsonElement config; public void start() { if ( PostProcessorLogger.info() ) { PostProcessorLogger.info(CustomProcessorLoggingExample.class,"starting component with config " + config); } ... } @Override String getValue(ProcessorContext context) { if ( PostProcessorLogger.trace() ) { PostProcessorLogger.trace(context, CustomProcessorLoggingExample.class,"getting value from post processor for surfer " + context.getSurfer()); } return ... } }

Services

general information

JSON request parameters

JSON parameters of options (so except data files, but also verbosity, specific parameters like processorValues for example, etc) can use a lenient syntax.

Errors

The error system is basic and only based on the standard HTTP code. It is possible to get an explicit message in debug mode.

Instance reference parameter

When referring to an instance of an object, in particular a user (to set up a surfer for example), we use a parameter of type "instance reference". The valke:

  • either a uuid

  • either a uid (object type followed by the object instance id, separated by an underscore, a slash or a dash)

  • or an id (the type of the object will automatically be the main type by default, for example user, for a surfer)

  • or, for a surfer/user, the value “notconnected” (case insensitive) to set a not connected surfer

Specific parameters syntax

pathMatcher

The pathMatchers allow to define file filters. The syntax used is based on the Java interface java.nio.file.PathMatcher:

  • it is either a character string, interpreted as a glob filter by default

    • if you specify a full PathMatcher syntax, with the prefix glob: or regex:, the string is used as is

    • if you don’t specify a prefix, the prefix glob; is added automatically
      for example

      { pathMatcher: '**/^*' }
  • or a JSON object, with two fields:

    • type: string/enum (optional, default is glob) the pattern syntax

      • glob: for Glob syntax

      • regex: for Regular Expressions pattern

    • pattern: string (mandatory) the pattern

Beware, the handling of the glob syntax may be system dependent, especially regarding case.

get configuration

Gets a final configuration for a mapping ID.

GET /api/portalconfig/config (operation ID: configGet)

GET /api/portalconfig/config/merge (operation ID: configMerge)

The difference between the two endpoints is that the first one takes no parameters other than the mapping id (and debug). It is intended to retrieve the desired configuration for the requesting surfer (or no surfer). The second one is more for the configuration UX to get different views of the configuration depending on the parameters. The first one is not secured while the second one is.

parameters

  • mappingID: string (mandatory), the mapping for which we want the configuration

  • wip: boolean (optional, false by default), activate the “wip” mode (if false, wip/inherit files are ignored, if true, wip/inherit files are taken into account

  • pathFilter, string/nodePath (optional, none by default), a pathfilter to select nodes (See Filtering)

  • pathFilterMode, string/enum (optional, FILTER by default), the filter mode (See Filtering)

  • nodesWithPath, boolean (optional, false by default), (See Filtering)

  • surfer, instance reference (optional, none by default), a substitute user/surfer to get the configuration that this surfer would have if he called the service. (See Instance reference parameter)

  • processorValues, JSON/map (optional, none by default), a processor/value association map to obtain the corresponding configuration
    For example,

    { "surferRole": "role-31" }
  • options, JSON (optional)

    • xjson: boolean (options, false by default), true to get the configuration as an xjson

explain configuration

Shows a detailed view of the components of a configuration and how they were obtained.

GET /api/portalconfig/config/explain (operation ID: configExplain)

parameters

  • mappingID: string (mandatory), the mapping for which we want the configuration

  • wip: boolean (optional, false by default), activate the “wip” mode (if false, wip/inherit files are ignored, if true, wip/inherit files are taken into account

  • pathFilter, string/nodePath (optional, none by default), a pathfilter to select nodes (See Filtering)

  • pathFilterMode, string/enum (optional, FILTER by default), the filter mode (See Filtering)

  • nodesWithPath, boolean (optional, false by default), (See Filtering)

  • surfer, instance reference (optional, none by default), a substitute user/surfer to get the configuration that this surfer would have if he called the service. (See Instance reference parameter)

  • processorValues, json/map (optional, none by default), a processor/value association map to obtain the corresponding configuration

  • options JSON (optional)

    • xjson: boolean (options, false by default), true to get the configuration as an xjson

  • verbosity JSON, int or string/enum (optional), determines what information will or will not be exposed
    We can configure the verbosity

    • either by a numerical verbosity level (from 0 to 15)

    • or by one of the following words:

      • max

      • min

      • default

    • or by a json which lists the different properties and their values (none is mandatory, all have a default value)

    • or by a combination of both: in the json, the verbosity property is the level, the other properties allowing to select values different from those of the level.
      For example:

      { verbosity: default, includesVerbosity: true }
    • if not specified, the verbosity is

      { "includesVerbosity": false, "includesLocation": true, "includesLayer": true, "includesOverrides": true, "locationVerbose": true, "locationRecursive": true, "pathVerbosity": true, "decomposePaths": false, "decomposePathsVerbose": true, "includeNodePath": true, "includeJsonPath": true, "includeXJsonPath": true, "nameVerbosity": true, "includesIgnored": true, "groupsIgnoredInRoot": true, "ignoredLocationVerbose": true, "includesErrors": true, "groupsErrorsInRoot": true, "errorVerbose": true, "exceptionVerbose": true, "exceptionDepth": -1, "stackTraceVerbosity": 2 }
    • here is the default json for the verbosity (value default or 3)

      { "includesVerbosity": false, "includesLocation": true, "includesLayer": true, "includesOverrides": true, "locationVerbose": false, "locationRecursive": false, "pathVerbosity": true, "decomposePaths": false, "decomposePathsVerbose": false, "includeNodePath": false, "includeJsonPath": false, "includeXJsonPath": false, "nameVerbosity": false, "simpleName": true, "includesIgnored": true, "groupsIgnoredInRoot": false, "ignoredLocationVerbose": false, "includesErrors": true, "groupsErrorsInRoot": false, "errorVerbose": false, "exceptionVerbose": false, "exceptionDepth": 0, "stackTraceVerbosity": 0 }

       

      The meaning of the properties

      • includesVerbosity, boolean (optional, default is false): If true, include the verbosity configuration in the response

      • includesLocation, boolean (optional, default is true): if true, locations are included in the response

      • includesLayer, boolean (optional, default is true): if true, layer (location root folder) are included in the response

      • includesOverrides, boolean (optional, default is true): if true, overridden elements are included in the response

      • locationVerbose, boolean (optional, default is true): if true, the locations are exposed with a maximum of information

      • locationRecursive, boolean (optional, default is true): if true, the locations are exposed with their parent, recursively

      • pathVerbosity, boolean (optional, default is true): if true, the paths of the locations are exported with a maximum of verbosity

      • decomposePaths, boolean (optional, default is false): if true, the path decomposition of the locations is exported (how these paths were built, in particular which postprocessors were used and their value)

      • decomposePathsVerbose, boolean (optional, default is true): if true, the path decomposition of the locations is in maximum verbosity

      • includeNodePath, boolean (optional, default is true): if true, the nodepaths of each element are exported

      • includeJsonPath, boolean (optional, default is true): if true, the JSONpaths of each element are exported

      • includeXJsonPath: boolean (optional, default is true): if true, the XJSONpaths of each element are exported

      • nameVerbosity, boolean (optional, default is false): if true, the names are exported in maximum verbosity (in particular, we get the details of the type of name: if it is a wip, an inherit, an array, etc)

      • simpleName, boolean (optional, default is true): if true, the name doesn’t include decorations( wip, inherit, array…).

      • includesIgnored, boolean (optional, default is true): if true, files found but ignored are exported, with the reason for ignoring them

      • groupsIgnoredInRoot, boolean (optional, default is true): if true, the ignored files are grouped at the root, if false, they are exported at the level where they were detected

      • ignoredLocationVerbose, boolean (optional, default is true): if true, ignored files are exported with maximum verbosity

      • includesErrors, boolean (optional, default is true): if true, the files or folders whose reading triggered an error are exported

      • groupsErrorsInRoot, boolean (optional, default is true): if true, the files in error are grouped at the root, if false, they are exported at the level where they were detected

      • errorVerbose, boolean (optional, default is true): if true, files with errors are exported with maximum verbosity

      • exceptionVerbose, boolean (optional, default is true): if true, the exceptions encountered are exported with a maximum of verbosity

      • exceptionDepth, int (optional, default is 0): defines the depth of the causes of the exceptions (0 corresponds to no cause)

      • stackTraceVerbosity, int (optional, default is 0): defines the verbosity level of the stack traces

configuration merge and explain tests

It is possible to test the config merge and config explain services without having any configuration, mapping, processors, etc.
The config/test service allows you to test the merging of several Jsons (or XJsons) by obtaining either a merge response or an explain response.

GET /api/portalconfig/config/test/merge to make a merge

GET /api/portalconfig/config/test/explain to make an explain

GET /api/portalconfig/config/test also works, with the parameter explain (operation ID: configTest)

parameters

The parameters are a combination of the parameters found in the services config/merge, config/explain and xjson/to and xjson/from.

  • json, string or file, (at least one is mandatory), that contains JSon (or XJSon), multiple values allowed (JSon lenient parsing is not supported)

  • xjson, boolean, optional (default is false). If true, if the jsons passed in parameter must be considered as xjson, false, otherwise (not to be confused with the xjson option which controls how the response json is formed)

  • lenient, boolean, optional (default is false). If true, the xjsons passed in parameter must be processed as lenient

  • explain, boolean optional (default is false). If true, the result is an explain, if false, the result is a merge. (works only with end point /test)

  • locations, optional
    This parameter allows to give location names to each JSon. Indeed, as there is no corresponding physical location (no files or folders), a virtual location is generated with just an incremental index (from 1 to n, and Root for root). This parameter allows to name the layers.

    • either by a JSon array (example: locations=[_base,_base@test])

    • or by strings (example ...&locations=_base&locations=_base%40test&...)

  • pathFilter (see get configuration and explain configuration)

  • pathFilterMode (see get configuration and explain configuration)

  • nodesWithPaths (see get configuration and explain configuration)

  • options (see get configuration and explain configuration)

  • verbosity, only if explain (see get configuration and explain configuration)

processors list

Gets processors list (variable names)

GET /api/portalconfig/postprocessors/list (operation ID: postProcessorsList)

parameters

  • mappingID: string (optional), designates a particular mapping (filter)

  • postProcessor: multiple strings or comma-separated (optional), designates a particular post processor, or a list of post processors (filter)
    (you can have several times the parameter, or list several postProcessor in the same parameter, separated by commas)

  • options: a JSON object (optional), defines options

    • withMappings: (default false) allows to have the list of mappings that use the postprocessor

    • withValues: (default false) allows to have the list of mappings that use the postprocessor

processors value

Gets post processor value for the current context

GET /api/portalconfig/postprocessors/value (operation ID: postProcessorsValue)

parameters

  • mappingID: string (mandatory) the mapping ID

  • postProcessor multiple strings or comma-separated (optional), designates a particular post processor, or a list of post processors (filter)
    (you can have several times the parameter, or list several postProcessor in the same parameter, separated by commas)

  • surfer: string, as UUID, UID, ID… (optional), a particular user for whom we want the resolution

  • options: a JSON object (optional), defines options

    • withMappings (false)

    • withValues (false)

all paths

Gets all possible paths for a mapping to resolve a configuration.

GET /api/portalconfig/path/all (operation ID: pathAll)

parameters

  • mappingID string (mandatory), the mapping ID

  • options a JSON object (optional), defines options

    • explain boolean (optional)

      • true: gives the details of each part of the paths, with the source of the postprocessors and values that determined them

      • false (or absent): no details (default behavior)

    • withFileInfo (optional) boolean

    • sizeOfDirectory boolean (optional, default is true) if true, the size of a folder is calculated by adding the size of all the files in it. If false, it is the size of the file-folder.Files and directories

  • filter JSON object (optional)
    This filter allows you to select files according to criteria. It is defined as a JSON with the following properties:

    • withBase boolean (optional)

      • if true, base config is included in response

      • if false, base config is not included in response

      • if absent or null, has no effect

    • exists boolean (optional)

      • if true, configuration files that do not exist aren’t included in the response

      • if false, configuration files that exist aren’t included in the response

      • if absent or null, has no effect

    • postProcessors (optional) string, or array of strings (A list of post processor names)
      Only the paths that have been determined from the listed processors are included in the response.

    • postProcessorValues (optional) JSON object/map
      Allows to get only the paths that contain certain values of post processors.

      • keys are post processors names

      • values (string, or array) are the list of values from the post processors for which a path will be included in the response

resolve paths

Gets all possible full paths for a mapping. The difference with “all paths” is that we only have the final folders (those that correspond to the evaluation of postprocessors in the context).

GET /api/portalconfig/path/resolve (operation ID: pathResolve)

parameters

  • mappingID, string (mandatory): the mapping ID

  • options, JSON (optional)

    • explain boolean (optional, default is false) if true, expose details on each paths

    • withFileInfo boolean (optional, default is false) if true, informations on files are included (size and length)

    • sizeOfDirectory boolean (optional, default is true) if true, the size of a folder is calculated by adding the size of all the files in it. If false, it is the size of the file-folder.Files and directories

  • filter, JSON (optional)
    This filter allows you to select files according to criteria. It is defined as a JSON with the following properties:

    • withBase, boolean (optional, default is false)

      • if true, base config is included in response

      • if false, base config is not included in response

      • if absent or null, has no effect

    • exists boolean (optional, default is false)

      • if true, configuration files that do not exist aren’t included in the response

      • if false, configuration files that exist aren’t included in the response

      • if absent or null, has no effect

    • postProcessors (optional) string, or array of strings (A list of post processor names)
      Only the paths that have been determined from the listed processors are included in the response.

    • postProcessorValues (optional), JSON object
      Allows to get only the paths that contain certain values of post processors.

      • keys are post processors names

      • values (string, or array) are the list of values from the post processors for which a path will be included in the response

mappings

Gets list of existing mappings

GET /api/portalconfig/mappings (operation ID: mappingsGet)

The configuration is stored in folders and files that will be merged to obtain a final configuration. A base is provided by the product and it is possible to create new files that will successively redefine this base.

A configuration is finally accessed by an identifier. For each identifier, we define a configuration path, consisting of the paths of the different folders or configuration files, separated with the character arobase (@) that will successively overload the database. The combination of the two is called mapping.

parameters

  • options, JSON (optional)

    • withStatus, boolean (optional, default is false)
      Expose status of mapping:

      • OK: the mapping is existing and is valid

      • NO_FILE: the mapping exists but no base file has been found

      • INVALID: the configuration of the base path is invalid (probably the folder is outside the specific base location)

      • VARIABLE: the path associated to mapping is variable. This service could not handle variable bases.

    • withFileInfo, boolean (optional, default is false) if true, informations on base files are included (size and length)

    • sizeOfDirectory, boolean (optional, default is true) if true, the size of a folder is calculated by adding the size of all the files in it. If false, it is the size of the file-folder.Files and directories

layers

Gets a list of configuration layers (configuration layer root directories)

GET /api/portalconfig/layers (operation ID: layersList)

parameters

  • pathMatcher (optional)
    A filter to select layers. See pathMatcher.

  • options, JSON (optional)

    • withRights, boolean, (optional, default is true)
      If true, we expose the writing rights in this layer

    • onlyIfExists, boolean, (optional, default is false)
      If true, we expose that the layers that physically exist

    • withMappings, boolean, (optional, default is false)
      If true, we expose the list of mappings that include the layer stored in this layer

    • withInfo, boolean (optional, default is false)
      If true, we expose some information about the layer:

      • exists, a boolean: if true, the layer physically exists

      • var, a boolean: if true, the layer name has been calculated by postprocessors

      • restarting, a boolean: if true, the layer name is relative (it has been cut by a $ during its resolution)

    • withFileinfo, boolean (optional, default is false)
      If true, we expose the last modification date and the size of the layer (if it exists)

    • sizeOfDirectory, boolean (optional, default is false)
      If true, the size of the folder is the total size of all the files it contains (otherwise, this information is what the java.nio.file.Files.size() method returns)

    • outOfMappings, boolean (option, default is false)
      if true, also retrieves the layers that are not used by a mapping (so all folders created in the configuration storage folder)

This service can be used to know if a layer is used in a mapping. For example to know if _portal@role-4 is used in a mapping:

  • pathMatcher=_portal@role-4

  • options

    • withMappings=true

    • outOfMappings=true

diff

Compares two JSON. The result is a list of differences.

GET/POST /api/portalconfig/diff (operation ID: jsonDiff)

Parameters

  • leftJson, JSON descriptor (mandatory), a JSON to compare to rightJson

  • rightJson, JSON descriptor (mandatory), a JSON to compare to leftJson

  • processorValues, JSON/map (optional), a default set of processor values

  • options, JSON (optional),

JSON descriptor

Describes the reference JSON to compare the other to (left), or the JSON to compare to the reference (right).

It is a JSON object (It can be a string of characters. In this case, it will automatically be the mapping type and the string is the mapping ID). The type field indicates the type of descriptor. Here are the different values for type:

  1. json

    1. In this case, the json field (mandatory) contains the JSON value to compare.
      Example:

      { "type":"json", "json": { "a": 1, "b": true, "c": "abc" } }

       

    2. xjson (optional, default is false) if true, the JSON is a XJSON, if false the JSON will be converted to XJSON to compute differences.

  2. form

    1. In this case, the field field (mandatory) contains the name of the form field that contains the JSON (file or string)
      Example:

      { type: form, field: json1, xjson: true }
    2. xjson (optional, default is false) if true, the JSON is a XJSON, if false the JSON will be converted to XJSON to compute differences.

  3. mapping
    References a mapping.

    1. mappingID, string (mandatory), the mapping ID

    2. wip, boolean (optional, default is false): true activates the wip mode

    3. processorValues, JSON/map (optional, none by default) a set of processor values

    4. surfer, instance reference (optional, none by default), a surfer

  4. config
    References a configuration layer

    1. In this case, the path field (mandatory) is the path of the configuration layer
      Example

      { type: config, path : '_portal@club-wed@role-4' }
  5. base
    References a configuration base

    1. In this case, the mappingID field is the mapping for which we want the base
      Example

      { type: base, mappingID: portal }

dir

To get a filesystem view of a configuration folder.

GET /api/portalconfig/fs/dir (operation ID: dir)

Parameters

  • path string (mandatory): the path of the desired layer
    for exemple, _portal@club-wed@role-4

  • subpath, string (optional): a path relative to the layer path
    for example, boardsview

  • verbosity, JSON (optional):

    • includesFileInfos, boolean (optional, default is true): exports dates and file sizes

    • includesFileCounters, boolean (optional, default is true): exports the number of files and folders

  • options, JSON (optional):

    • depth: int (optional, default is -1=infinite depth)

    • pathMatcher: (optional) defines a filter on paths and file names. The filter is applied to the relative path and name: if it matches one of the two, then the file or folder is exported, and those that do not match one of the two are not. See pathMatcher.

merged dir

To get a filesystem oriented view of a set of configuration folders corresponding to a mapping resolution, in a merged way.

GET /api/portalconfig/fs/dir (operation ID: dirMerged)

Parameters

  • path: string, multiple (mandatory, if mappingID is not present): a list of paths (base or configuration layers). If mappingID is also present, it’s used as an inclusive filter. If mappingID is not present, it's the list of paths to get.

  • mappingID: string (mandatory, if path is not present), the mapping for which we want the configuration

  • wip: boolean (optional, false by default), activate the “wip” mode (if false, wip/inherit files are ignored, if true, wip/inherit files are taken into account

  • surfer, instance reference (optional, none by default), a substitute user/surfer to get the configuration that this surfer would have if he called the service. (See Instance reference parameter)

  • processorValues, JSON/map (optional, none by default), a processor/value association map to obtain the corresponding configuration
    For example,

    { "surferRole": "role-31" }
  • maxLayers, int (optional, default is -1=infinite): only if mappingID present and resolve true, limits the numbers of paths

  • resolve, boolean (optional, default is true): only if mappingID present, if true, resolve the configuration layers against the context (so get only the involved paths), if false, get all possible paths

  • verbosity, JSON (optional):

    • includesErrors, boolean (optional, default is true): export the files or directories that caused read errors

    • includesIgnored, boolean (optional, default is true): export the files or directories ignored during the resolution of a mapping

    • includesNotFound, boolean (optional, default is true): export the configuration layer folders that do not exist

    • includesFileInfos, boolean (optional, default is true): exports dates and file sizes

    • includesFileContent, boolean (optional, default is true): exports the content of files

  • options, JSON (optional):

    • depth: int (optional, default is -1=infinite depth)

    • pathMatcher: (optional) defines a filter on paths and file names. The filter is applied to the relative path and name: if it matches one of the two, then the file or folder is exported, and those that do not match one of the two are not. See pathMatcher.

Lock services

These services allow you to obtain a lock that allows you to call the configuration creation or modification services (and configuration files). Once the lock is obtained, other users cannot obtain one until it is released or expired.

Comming soon: locks per layer, by the layer parameter

lock

POST /fs/lock(operation ID: lockPut)

Put an exclusive access lock (if not already owned).

Response

Http status

 

Http status

 

200

The lock has been obtained or extended

403

Unable to obtain a lock (already owned, or error)

503

Locks are unavailable

500

Other errors

unlock

DELETE /fs/lock(operation ID: lockDelete)

Delete the owned lock.

Response

Http status

 

Http status

 

200

Lock deleted

202

No lock

403

Lock not owned