Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,6 @@
[submodule "pipeline/pipeline.core/configuration-validation"]
path = pipeline/pipeline.core/configuration-validation
url = ../configuration-validation
[submodule "pipeline/derived-properties"]
path = pipeline/derived-properties
url = ../derived-properties
8 changes: 8 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -294,6 +294,14 @@ The Windows pre-build scripts take a toolset and SDK version from the pom.
JavaScript templates and the OWID library are submodules inside `pipeline`,
where the Pipeline API expects them.

`pipeline/derived-properties` is the
[derived-properties](https://github.com/51Degrees/derived-properties)
repository. `pipeline.derivedproperty` packages the scripts in its `scripts`
folder as resources, so a script added there needs a `BuiltInScript`
constant, which `BuiltInScriptTests` checks. The package's tests run every
case file and every rejection case in its `tests` folder, so its answers and
its faults are checked against the other languages'.

## Releases

Pull requests go to `rc`, not `main`, and stay drafts until they have been
Expand Down
2 changes: 2 additions & 0 deletions ci/packages.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
{"Name": "pipeline.cloudrequestengine", "Path": "pipeline/pipeline.cloudrequestengine", "DependsOn": ["pipeline", "pipeline.engines", "pipeline.common"], "Publish": true},
{"Name": "pipeline.engines.fiftyone", "Path": "pipeline/pipeline.engines.fiftyone", "DependsOn": ["pipeline", "pipeline.engines", "pipeline.common"], "Publish": true},
{"Name": "pipeline.translation", "Path": "pipeline/pipeline.translation", "DependsOn": ["pipeline", "pipeline.engines", "pipeline.core"], "Publish": true},
{"Name": "pipeline.derivedproperty", "Path": "pipeline/pipeline.derivedproperty", "ExtraPaths": ["pipeline/derived-properties"], "DependsOn": ["pipeline", "pipeline.engines", "pipeline.core"], "Publish": true},
{"Name": "pipeline.javascriptbuilder", "Path": "pipeline/pipeline.javascriptbuilder", "DependsOn": ["pipeline", "pipeline.jsonbuilder", "pipeline.engines", "pipeline.common"], "Publish": true},
{"Name": "pipeline.jsonbuilder", "Path": "pipeline/pipeline.jsonbuilder", "DependsOn": ["pipeline", "pipeline.engines.fiftyone", "pipeline.common", "pipeline.engines"], "Publish": true},
{"Name": "pipeline.web.packages", "Path": "pipeline/pipeline.web.packages/pom.xml", "DependsOn": ["pipeline"], "Publish": true},
Expand All @@ -24,6 +25,7 @@
{"Name": "pipeline.developerexamples.clientsideelement.mvc", "Path": "pipeline/pipeline.developer-examples/pipeline.developer-examples.clientside-element-mvc", "DependsOn": ["pipeline.developer-examples", "pipeline.developerexamples.clientsideelement", "pipeline.web.mvc", "pipeline.developerexamples.web.shared"], "Publish": true, "Ignore": ["pipeline/pipeline.developer-examples/**"]},
{"Name": "pipeline.developerexamples.usagesharing", "Path": "pipeline/pipeline.developer-examples/pipeline.developer-examples.usage-sharing", "DependsOn": ["pipeline.developer-examples", "pipeline.engines.fiftyone"], "Publish": true, "Ignore": ["pipeline/pipeline.developer-examples/**"]},
{"Name": "pipeline.developerexamples.fodid", "Path": "pipeline/pipeline.developer-examples/pipeline.developer-examples.fodid", "DependsOn": ["pipeline.developer-examples", "pipeline.did"], "Publish": true, "Ignore": ["pipeline/pipeline.developer-examples/**"]},
{"Name": "pipeline.developerexamples.derivedproperty", "Path": "pipeline/pipeline.developer-examples/pipeline.developer-examples.derivedproperty", "DependsOn": ["pipeline.developer-examples", "pipeline.derivedproperty"], "Publish": true, "Ignore": ["pipeline/pipeline.developer-examples/**"]},
{"Name": "pipeline.device-detection", "Path": "device-detection/pom.xml", "DependsOn": ["pipeline-aggregate"], "Publish": true},
{"Name": "device-detection.shared", "Path": "device-detection/device-detection.shared", "DependsOn": ["pipeline.device-detection", "pipeline.engines.fiftyone"], "Publish": true},
{"Name": "device-detection.hash.engine.on-premise", "Path": "device-detection/device-detection.hash.engine.on-premise", "ExtraPaths": ["cxx"], "DependsOn": ["pipeline.device-detection", "device-detection.shared"], "Publish": true},
Expand Down
1 change: 1 addition & 0 deletions pipeline/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ but additional caution should be applied.
- pipeline.engines - Shared functionality that is available to all 'engines' (specialized 'flow elements')
- pipeline.engines.fiftyone - Functionality that is specific to 51Degrees engines.
- pipeline.cloudrequestengine - An engine that is used to retrieve data from 51Degrees' cloud API.
- pipeline.derivedproperty - The Derived Property Element, which produces new properties from the properties other elements produce, following rules written in a script.

## Installation

Expand Down
1 change: 1 addition & 0 deletions pipeline/derived-properties
Submodule derived-properties added at cc3063
86 changes: 86 additions & 0 deletions pipeline/pipeline.derivedproperty/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ This Original Work is copyright of 51 Degrees Mobile Experts Limited.
~ Copyright 2026 51 Degrees Mobile Experts Limited, Davidson House,
~ Forbury Square, Reading, Berkshire, United Kingdom RG1 3EU.
~
~ This Original Work is licensed under the European Union Public Licence
~ (EUPL) v.1.2 and is subject to its terms as set out below.
~
~ If a copy of the EUPL was not distributed with this file, You can obtain
~ one at https://opensource.org/licenses/EUPL-1.2.
~
~ The 'Compatible Licences' set out in the Appendix to the EUPL (as may be
~ amended by the European Commission) shall be deemed incompatible for
~ the purposes of the Work and the provisions of the compatibility
~ clause in Article 5 of the EUPL shall not apply.
~
~ If using the Work as, or as part of, a network application, by
~ including the attribution notice(s) required under Article 5 of the EUPL
~ in the end user terms of the application under an appropriate heading,
~ such notice(s) shall fulfill the requirements of that article.
-->

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.51degrees</groupId>
<artifactId>pipeline</artifactId>
<version>4.5.127-SNAPSHOT</version>
</parent>
<artifactId>pipeline.derivedproperty</artifactId>
<packaging>jar</packaging>

<name>51Degrees :: Pipeline :: Derived Property</name>
<description>The Derived Property Element produces new property values from property values other elements in the Pipeline have already produced, following rules written in a script.</description>
<url>https://github.com/51Degrees/51degrees-java</url>

<dependencies>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>pipeline.engines</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>pipeline.core</artifactId>
<version>${project.version}</version>
</dependency>
<!-- Scripts are read with the compose API, which gives every node
its line, and plain scalars are resolved here by the YAML 1.2
core schema, so "yes" stays the string "yes". -->
<dependency>
<groupId>org.yaml</groupId>
<artifactId>snakeyaml</artifactId>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</dependency>

<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
</dependency>
</dependencies>

<build>
<resources>
<resource>
<directory>src/main/resources</directory>
</resource>
<!-- The built-in scripts, straight from the derived-properties
submodule rather than a copy. BuiltInScript names each one,
and BuiltInScriptTests fails when the two differ. -->
<resource>
<directory>../derived-properties/scripts</directory>
<targetPath>fiftyone/pipeline/derivedproperty/scripts</targetPath>
<includes>
<include>*.yaml</include>
<include>*.yml</include>
<include>*.json</include>
</includes>
</resource>
</resources>
</build>
</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
/* *********************************************************************
* This Original Work is copyright of 51 Degrees Mobile Experts Limited.
* Copyright 2026 51 Degrees Mobile Experts Limited, Davidson House,
* Forbury Square, Reading, Berkshire, United Kingdom RG1 3EU.
*
* This Original Work is licensed under the European Union Public Licence
* (EUPL) v.1.2 and is subject to its terms as set out below.
*
* If a copy of the EUPL was not distributed with this file, You can obtain
* one at https://opensource.org/licenses/EUPL-1.2.
*
* The 'Compatible Licences' set out in the Appendix to the EUPL (as may be
* amended by the European Commission) shall be deemed incompatible for
* the purposes of the Work and the provisions of the compatibility
* clause in Article 5 of the EUPL shall not apply.
*
* If using the Work as, or as part of, a network application, by
* including the attribution notice(s) required under Article 5 of the EUPL
* in the end user terms of the application under an appropriate heading,
* such notice(s) shall fulfill the requirements of that article.
* ********************************************************************* */

package fiftyone.pipeline.derivedproperty;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.UncheckedIOException;
import java.nio.charset.StandardCharsets;

/**
* Names every script shipped in this package. The scripts are the
* {@code scripts} folder of the derived-properties repository, and each
* constant is named exactly as its script is, so {@link #name()} is the
* script's name.
*/
public enum BuiltInScript {

/**
* The confidence that the request came from a device being used by a
* human who is viewing the page, being High, Medium, Low or Unknown.
*/
HumanConfidence,

/**
* Whether the General Data Protection Regulation, or the United
* Kingdom's version of it, is likely to apply to the person making the
* request, judged from ip.CountryCode.
*/
IsGdpr;

/**
* The folder on the class path the scripts are packaged in.
*/
public static final String RESOURCE_FOLDER =
"/fiftyone/pipeline/derivedproperty/scripts/";

/**
* The file extensions a script is read from, in the order tried.
*/
private static final String[] EXTENSIONS = {".yaml", ".yml", ".json"};

/**
* The size of each read from the packaged script.
*/
private static final int BUFFER_BYTES = 8192;

/**
* The text of the script, read from the package.
* @return the text, or null where the package was built without the
* script
*/
public String readText() {
for (String extension : EXTENSIONS) {
try (InputStream stream = BuiltInScript.class.getResourceAsStream(
RESOURCE_FOLDER + name() + extension)) {
if (stream != null) {
return readAll(stream);
}
} catch (IOException e) {
throw new UncheckedIOException(
"The built-in script '" + name() + "' cannot be read.",
e);
}
}
return null;
}

/**
* The built-in script with the name given, matched exactly.
* @param name the script's name
* @return the script, or null where no built-in script has the name
*/
public static BuiltInScript fromName(String name) {
for (BuiltInScript script : values()) {
if (script.name().equals(name)) {
return script;
}
}
return null;
}

/**
* Every built-in script's name, for a message.
* @return the names, separated by a comma and a space
*/
public static String names() {
StringBuilder names = new StringBuilder();
for (BuiltInScript script : values()) {
if (names.length() > 0) {
names.append(", ");
}
names.append(script.name());
}
return names.toString();
}

private static String readAll(InputStream stream) throws IOException {
ByteArrayOutputStream bytes = new ByteArrayOutputStream();
byte[] buffer = new byte[BUFFER_BYTES];
int read;
while ((read = stream.read(buffer)) > 0) {
bytes.write(buffer, 0, read);
}
return new String(bytes.toByteArray(), StandardCharsets.UTF_8);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
/* *********************************************************************
* This Original Work is copyright of 51 Degrees Mobile Experts Limited.
* Copyright 2026 51 Degrees Mobile Experts Limited, Davidson House,
* Forbury Square, Reading, Berkshire, United Kingdom RG1 3EU.
*
* This Original Work is licensed under the European Union Public Licence
* (EUPL) v.1.2 and is subject to its terms as set out below.
*
* If a copy of the EUPL was not distributed with this file, You can obtain
* one at https://opensource.org/licenses/EUPL-1.2.
*
* The 'Compatible Licences' set out in the Appendix to the EUPL (as may be
* amended by the European Commission) shall be deemed incompatible for
* the purposes of the Work and the provisions of the compatibility
* clause in Article 5 of the EUPL shall not apply.
*
* If using the Work as, or as part of, a network application, by
* including the attribution notice(s) required under Article 5 of the EUPL
* in the end user terms of the application under an appropriate heading,
* such notice(s) shall fulfill the requirements of that article.
* ********************************************************************* */

package fiftyone.pipeline.derivedproperty;

import java.io.Serializable;

/**
* One thing wrong with a derived property script.
*/
public final class DerivedScriptFault implements Serializable {

private static final long serialVersionUID = 1L;

private final String script;
private final String source;
private final String path;
private final int line;
private final String message;

/**
* Construct a fault.
* @param script the name the script was given, or empty where it had
* none
* @param source the built-in script's name, the file path, or "code"
* @param path the place in the document, such as Rules[3].When.All[1],
* or empty for the document as a whole
* @param line the one based line, or 0 where the parser gave none
* @param message what is wrong
*/
public DerivedScriptFault(
String script,
String source,
String path,
int line,
String message) {
this.script = script == null ? "" : script;
this.source = source == null ? "" : source;
this.path = path == null ? "" : path;
this.line = line;
this.message = message;
}

/**
* @return the name the script was given, or empty where it had none
*/
public String getScript() {
return script;
}

/**
* @return the built-in script's name, the file path, or "code"
*/
public String getSource() {
return source;
}

/**
* @return the place in the document, such as Rules[3].When.All[1], or
* empty for the document as a whole
*/
public String getPath() {
return path;
}

/**
* @return the one based line, or 0 where the parser gave none
*/
public int getLine() {
return line;
}

/**
* @return what is wrong
*/
public String getMessage() {
return message;
}

/**
* The fault as one line, giving the script, the source, the line where
* known, the path and the message.
* @return the fault as one line
*/
@Override
public String toString() {
return (script.isEmpty() ? "script" : script) +
" (" + source + ")" +
(line > 0 ? " line " + line : "") +
" at " + (path.isEmpty() ? "(document)" : path) +
": " + message;
}
}
Loading
Loading