From f532d53c59d113621f39a931341034b43eb40847 Mon Sep 17 00:00:00 2001 From: Roman Szturc Date: Sun, 9 Mar 2025 18:37:09 +0100 Subject: [PATCH 1/4] Introduce jitpack.yml with temurin 17.0.5 --- jitpack.yml | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 jitpack.yml diff --git a/jitpack.yml b/jitpack.yml new file mode 100644 index 0000000..8d8e4cc --- /dev/null +++ b/jitpack.yml @@ -0,0 +1,5 @@ +jdk: + - openjdk17 +before_install: + - sdk install java 17.0.5-tem + - sdk use java 17.0.5-tem From e1931b3c62187a93c11b02bdde2e57fd9cec58bd Mon Sep 17 00:00:00 2001 From: Roman Szturc Date: Sun, 9 Mar 2025 19:45:02 +0100 Subject: [PATCH 2/4] Introduce jitpack.yml with Eclipse Temurin 17.0.5 --- CHANGELOG.md | 3 +++ pom.xml | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7261453..c10df5d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,6 @@ +## 2.4.1 +- Ensure Eclipse Temurin is used to build the library + ## 2.4.0 - Switched to Orizaba diff --git a/pom.xml b/pom.xml index 06d3f10..570743c 100644 --- a/pom.xml +++ b/pom.xml @@ -7,7 +7,7 @@ 2.0.12 eiffel-remrem-semantics - 2.4.0 + 2.4.1 jar 5.0.1.201806211838-r From 9dd1ff9ec74040cb3efccdaa4a6283b5a07d6b07 Mon Sep 17 00:00:00 2001 From: Roman Szturc Date: Sun, 9 Mar 2025 19:55:42 +0100 Subject: [PATCH 3/4] Introduce jitpack.yml with Eclipse Temurin 17.0.5 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 570743c..734d28c 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ com.github.eiffel-community eiffel-remrem-parent - 2.0.12 + 2.0.14 eiffel-remrem-semantics 2.4.1 From d0c98a49b832e98d405ae03aaa799c455f3a5f18 Mon Sep 17 00:00:00 2001 From: Roman Szturc Date: Thu, 25 Sep 2025 12:58:22 +0200 Subject: [PATCH 4/4] Optional validation of PURLs added --- CHANGELOG.md | 3 + pom.xml | 11 ++- .../remrem/semantics/SemanticsService.java | 62 +++++++++++---- .../remrem/semantics/util/PropertiesUtil.java | 29 +++++++ .../semantics/validator/EiffelValidator.java | 79 +++++++++++++++++-- .../eiffel/remrem/semantics/ServiceTest.java | 68 +++++++++++++--- .../input/valid-purl/ArtifactCreated.json | 71 +++++++++++++++++ 7 files changed, 288 insertions(+), 35 deletions(-) create mode 100644 src/main/java/com/ericsson/eiffel/remrem/semantics/util/PropertiesUtil.java create mode 100644 src/test/resources/input/valid-purl/ArtifactCreated.json diff --git a/CHANGELOG.md b/CHANGELOG.md index 8425cb0..41ed536 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,6 @@ +## 2.4.3 +- Optional validation of PURLs added. + ## 2.4.2 - Typo fix. diff --git a/pom.xml b/pom.xml index e31dda0..f1cd239 100644 --- a/pom.xml +++ b/pom.xml @@ -4,10 +4,10 @@ com.github.eiffel-community eiffel-remrem-parent - 2.0.14 + 2.0.18 eiffel-remrem-semantics - 2.4.2 + 2.4.3 jar 5.0.1.201806211838-r @@ -24,7 +24,7 @@ com.github.eiffel-community eiffel-remrem-protocol-interface - 2.2.0 + 2.2.1 compile @@ -48,6 +48,11 @@ semver4j 3.1.0 + + com.github.package-url + packageurl-java + 1.5.0 + org.glassfish.hk2.external javax.inject diff --git a/src/main/java/com/ericsson/eiffel/remrem/semantics/SemanticsService.java b/src/main/java/com/ericsson/eiffel/remrem/semantics/SemanticsService.java index 14948f0..0ff5bc1 100644 --- a/src/main/java/com/ericsson/eiffel/remrem/semantics/SemanticsService.java +++ b/src/main/java/com/ericsson/eiffel/remrem/semantics/SemanticsService.java @@ -121,8 +121,8 @@ public class SemanticsService implements MsgService { private static final String SUPPORTED_EVENT_TYPES = "SUPPORTED_EVENT_TYPES"; private static final String RESULT = "result"; private static final String UNKNOWN_EVENT_TYPE_REQUESTED = "Unknown event type requested"; - private static final String EVENT_PARAMS = "eventParams"; - private static final String MSG_PARAMS = "msgParams"; + public static final String EVENT_PARAMS = "eventParams"; + public static final String MSG_PARAMS = "msgParams"; private static final String MESSAGE = "message"; private static final String CAUSE = "cause"; private static final String EIFFELSEMANTICS = "eiffelsemantics"; @@ -131,6 +131,7 @@ public class SemanticsService implements MsgService { private static final String TYPE = "type"; private static final String SOURCE = "source"; private static final String DOMAIN_ID = "domainId"; + public static final String IDENTITY = "identity"; private static final String PROTOCOL = "eiffel"; private static final String DOT = "."; private static final String SEMANTICS_EDITION_NAME = "semanticsEditionName"; @@ -143,6 +144,11 @@ public class SemanticsService implements MsgService { private static Gson gson = new Gson(); private static Map> eventTypes = SemanticsService.eventType(); + /** + * Name of validation property applicable for generateMsg(String, JsonObject, Map). + */ + public static final String VALIDATE_PURL_FOR_ART_C = "validatePurlForArtC"; + public SemanticsService() { for (final EiffelEventType msg : EiffelEventType.values()) { supportedEventTypes.add(msg.getEventName()); @@ -203,6 +209,24 @@ public String generateMsg(String msgType, JsonObject bodyJson) { @Override public String generateMsg(String msgType, JsonObject bodyJson, Boolean lenientValidation) { + return generateMsg(msgType, bodyJson, createPropertiesWithLenientValidation(lenientValidation)); + } + + /** + * Takes a partial message in JSON format, validates it and adds mandatory fields before outputting a complete, + * valid Eiffel message. + * + * @param msgType Event type name. + * @param bodyJson Event template. + * @param validationProperties At the moment the following properties are supported: + *
    + *
  • lenientValidation: boolean,
  • + *
  • validatePurlForArtC: boolean.
  • + *
+ * @return the generated and validate Eiffel messages as json String + */ + @Override + public String generateMsg(String msgType, JsonObject bodyJson, HashMap validationProperties) { try { if (purlSerializerFlag) { return createErrorResponse("Serializer info of eiffel-remrem-semantics is missing", @@ -228,10 +252,8 @@ public String generateMsg(String msgType, JsonObject bodyJson, Boolean lenientVa } // Compare the input JSON EventType with query parameter(-t) and - // also - // check type exist or not, - // if input JSON EventType is missing adding query parameter as - // Type. + // also check if type exists. + // If input JSON EventType is missing adding query parameter as Type. String inputEventType = getInputEventType(bodyJson); if (inputEventType == null || inputEventType.isEmpty()) { bodyJson.get(MSG_PARAMS).getAsJsonObject().get(META).getAsJsonObject().addProperty(TYPE, @@ -243,7 +265,7 @@ public String generateMsg(String msgType, JsonObject bodyJson, Boolean lenientVa } Event event = eventCreation(eventType, msgNodes, eventNodes); String result = gson.toJson(event); - JsonObject outputValidate = outputValidate(eiffelType, result, lenientValidation); + JsonObject outputValidate = outputValidate(eiffelType, result, validationProperties); return gson.toJson(outputValidate); } catch (EiffelValidationException e) { @@ -284,13 +306,19 @@ private String createErrorResponse(final String message, final ArrayList return errorResponse.toString(); } - private JsonObject outputValidate(EiffelEventType eiffelType, String jsonStringInput, Boolean lenientValidation) throws EiffelValidationException { + private JsonObject outputValidate(EiffelEventType eiffelType, String jsonStringInput, HashMap validationProperiets) throws EiffelValidationException { EiffelValidator validator = EiffelOutputValidatorFactory.getEiffelValidator(eiffelType); JsonObject jsonObject = new JsonParser().parse(jsonStringInput).getAsJsonObject(); - JsonObject validate = validator.validate(jsonObject, lenientValidation); + JsonObject validated = validator.validate(jsonObject, validationProperiets); // custom validations on an event which is not covered in schema. - validator.customValidation(validate); - return validate; + validator.customValidation(validated, validationProperiets); + return validated; + } + + private HashMap createPropertiesWithLenientValidation(boolean lenientValidation) { + HashMap validationProperties = new HashMap<>(); + validationProperties.put(LENIENT_VALIDATION, lenientValidation); + return validationProperties; } @Override @@ -299,12 +327,17 @@ public ValidationResult validateMsg(String msgType, JsonObject jsonvalidateMessa } @Override - public ValidationResult validateMsg(String msgType, JsonObject jsonvalidateMessage, Boolean lenientValidation) { + public ValidationResult validateMsg(String msgType, JsonObject jsonValidationMessage, Boolean lenientValidation) { + return validateMsg(msgType, jsonValidationMessage, createPropertiesWithLenientValidation(lenientValidation)); + } + + @Override + public ValidationResult validateMsg(String msgType, JsonObject jsonvalidateMessage, HashMap validationProperties) { ValidationResult validationResult = null; EiffelEventType eiffelType = EiffelEventType.fromString(msgType); String result = gson.toJson(jsonvalidateMessage); try { - outputValidate(eiffelType, result, lenientValidation); + outputValidate(eiffelType, result, validationProperties); validationResult = new ValidationResult(true, ""); } catch (EiffelValidationException e) { validationResult = new ValidationResult(false, e.getLocalizedMessage()); @@ -375,8 +408,7 @@ public JsonElement getEventTemplate(String eventType) { * Returns Family Routing Key Word from the messaging library based on the * eiffel message eventType. * - * @param JsonObject - * eiffelMessage + * @param eiffelMessage the message * @return family routing key word in String format. */ private String getFamily(JsonObject eiffelMessage) { diff --git a/src/main/java/com/ericsson/eiffel/remrem/semantics/util/PropertiesUtil.java b/src/main/java/com/ericsson/eiffel/remrem/semantics/util/PropertiesUtil.java new file mode 100644 index 0000000..1b3aed3 --- /dev/null +++ b/src/main/java/com/ericsson/eiffel/remrem/semantics/util/PropertiesUtil.java @@ -0,0 +1,29 @@ +package com.ericsson.eiffel.remrem.semantics.util; + +import com.ericsson.eiffel.remrem.semantics.validator.EiffelValidator; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.HashMap; + +public class PropertiesUtil { + public static final Logger log = LoggerFactory.getLogger(PropertiesUtil.class); + + public static T getProperty(HashMap validationProperties, String property) { + return getProperty(validationProperties, property, null); + } + + public static T getProperty(HashMap validationProperties, String property, T defValue) { + Object object = validationProperties.get(property); + if (object == null) + return defValue; + if (object instanceof Boolean) { + return (T)object; + } + else { + log.error("Value of property '{}' should be a boolean, but is '{}'; returning default value '{}'", + property, object.getClass().getName(), defValue); + return defValue; + } + } +} diff --git a/src/main/java/com/ericsson/eiffel/remrem/semantics/validator/EiffelValidator.java b/src/main/java/com/ericsson/eiffel/remrem/semantics/validator/EiffelValidator.java index c977ffb..a86b101 100644 --- a/src/main/java/com/ericsson/eiffel/remrem/semantics/validator/EiffelValidator.java +++ b/src/main/java/com/ericsson/eiffel/remrem/semantics/validator/EiffelValidator.java @@ -24,6 +24,12 @@ import java.util.regex.Pattern; import javax.xml.bind.ValidationException; + +import com.ericsson.eiffel.remrem.semantics.EiffelEventType; +import com.ericsson.eiffel.remrem.semantics.util.PropertiesUtil; +import com.github.packageurl.MalformedPackageURLException; +import com.github.packageurl.PackageURL; +import com.google.gson.*; import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -36,13 +42,13 @@ import com.github.fge.jsonschema.core.report.ProcessingReport; import com.github.fge.jsonschema.main.JsonSchema; import com.github.fge.jsonschema.main.JsonSchemaFactory; -import com.google.gson.JsonArray; -import com.google.gson.JsonElement; -import com.google.gson.JsonObject; -import com.google.gson.JsonParser; import com.jayway.jsonpath.DocumentContext; import com.jayway.jsonpath.JsonPath; +import static com.ericsson.eiffel.remrem.protocol.MsgService.LENIENT_VALIDATION; +import static com.ericsson.eiffel.remrem.semantics.SemanticsService.*; +import static com.ericsson.eiffel.remrem.semantics.schemas.EiffelConstants.META; + public class EiffelValidator { private static final String REGEX_PATH = "\\/\\d*"; @@ -94,11 +100,19 @@ public JsonObject validate(JsonObject jsonObjectInput) throws EiffelValidationEx } public JsonObject validate(JsonObject jsonObjectInput, Boolean lenientValidation) throws EiffelValidationException { + HashMap validationProperties = new HashMap<>(); + validationProperties.put(LENIENT_VALIDATION, lenientValidation); + return validate(jsonObjectInput, validationProperties); + } + + public JsonObject validate(JsonObject jsonObjectInput, HashMap validationProperties) throws EiffelValidationException { JsonArray remremGenerateFailures = new JsonArray(); log.debug("VALIDATING Schema used: {}", schemaResourceName); try { final ProcessingReport report = validationSchema.validate(JsonLoader.fromString(jsonObjectInput.toString()), true); + boolean lenientValidation = PropertiesUtil.getProperty(validationProperties, LENIENT_VALIDATION, false); + if (!report.isSuccess() && lenientValidation) { log.info("Trying to revalidate the Eiffel message with only mandatory Eiffel message fields."); String revalidatedJson = removeErrorProperties(report, jsonObjectInput, remremGenerateFailures); @@ -143,8 +157,8 @@ private String removeErrorProperties(ProcessingReport report, JsonObject jsonObj } String errorPath = element.getAsJsonObject().get(INSTANCE).getAsJsonObject().get(POINTER) .getAsString(); - JsonObject addValidationFailurs = addValidationFailures(element, processingMessage.getMessage()); - remremGenerateFailures.add(addValidationFailurs); + JsonObject addValidationFailures = addValidationFailures(element, processingMessage.getMessage()); + remremGenerateFailures.add(addValidationFailures); doc.delete(getPath(errorPath)); } } @@ -228,6 +242,16 @@ private String getErrorsList(final ProcessingReport report) { * @throws EiffelValidationException if any validation fails */ public void customValidation(JsonObject jsonObjectInput) throws EiffelValidationException { + customValidation(jsonObjectInput, new HashMap<>()); + + } + + public void customValidation(JsonObject jsonObjectInput, HashMap validationProperties) throws EiffelValidationException { + validateLinks(jsonObjectInput); + validateArtC(jsonObjectInput, validationProperties); + } + + public void validateLinks(JsonObject jsonObjectInput) throws EiffelValidationException { // Links validation Map linksCountMapForEvent = new HashMap(); try { @@ -271,4 +295,47 @@ public void customValidation(JsonObject jsonObjectInput) throws EiffelValidation throw new EiffelValidationException(message, e); } } + + public void validateArtC(JsonObject jsonObjectInput, HashMap validationProperties) throws EiffelValidationException { + Object validate = PropertiesUtil.getProperty(validationProperties, VALIDATE_PURL_FOR_ART_C, true); + if (validate == null || validate instanceof Boolean && !((Boolean)validate)) + // Validation property not set, no need to continue. + return; + + JsonObject meta = jsonObjectInput.getAsJsonObject(META); + if (meta == null) { + return; + } + + JsonPrimitive type = meta.getAsJsonPrimitive(TYPE); + if (type == null) { + return; + } + + if (EiffelEventType.fromString(type.getAsString()) != EiffelEventType.ARTIFACT_CREATED) { + // Not EiffelArtifactCreatedEvent, nothing to do... + return; + } + + // OK, this is EiffelArtifactCreatedEvent, lets start checks... + JsonObject data = jsonObjectInput.getAsJsonObject(DATA); + if (data == null) { + return; + } + + JsonPrimitive identity = data.getAsJsonPrimitive(IDENTITY); + if (identity == null) { + return; + } + + String purl = identity.getAsString(); + try { + PackageURL packageUrl = new PackageURL(purl); + } + catch (MalformedPackageURLException e) { + String message = "Cannot validate PURL '" + purl + "'"; + log.debug(message, e); + throw new EiffelValidationException(message, e); + } + } } \ No newline at end of file diff --git a/src/test/java/com/ericsson/eiffel/remrem/semantics/ServiceTest.java b/src/test/java/com/ericsson/eiffel/remrem/semantics/ServiceTest.java index 200bc32..31134c2 100644 --- a/src/test/java/com/ericsson/eiffel/remrem/semantics/ServiceTest.java +++ b/src/test/java/com/ericsson/eiffel/remrem/semantics/ServiceTest.java @@ -31,10 +31,12 @@ import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Collection; +import java.util.HashMap; import java.util.List; import java.util.jar.Attributes; import java.util.jar.Manifest; +import com.ericsson.eiffel.remrem.semantics.validator.EiffelValidator; import org.junit.Assert; import org.junit.Before; import org.junit.BeforeClass; @@ -88,13 +90,19 @@ public void setUp() throws Exception { @Test public void testGenerate() { + HashMap properties = new HashMap<>(); + // TODO Enable PURL validation + properties.put(SemanticsService.VALIDATE_PURL_FOR_ART_C, false); try { File file = new File("src/test/resources/input"); if (file.exists()) { for (File inputFile : file.listFiles()) { + if (inputFile.isDirectory()) + continue; + JsonObject object = parser.parse(new FileReader(inputFile)).getAsJsonObject(); String msgType=object.get("msgParams").getAsJsonObject().get("meta").getAsJsonObject().get("type").getAsString(); - String msg = service.generateMsg(msgType, object); + String msg = service.generateMsg(msgType, object, properties); Assert.assertTrue(msg.contains("data")); Assert.assertTrue(msg.contains("meta")); Assert.assertTrue(msg.contains("links")); @@ -106,7 +114,39 @@ public void testGenerate() { Assert.assertFalse(true); } } - + + @Test + public void testGeneratePurlValidationOnPass() { + String fileName = "src/test/resources/input/valid-purl/ArtifactCreated.json"; + String msg = testGeneratePurlValidationOn(fileName); + Assert.assertTrue(msg.contains("data")); + Assert.assertTrue(msg.contains("meta")); + Assert.assertTrue(msg.contains("links")); + } + + @Test + public void testGeneratePurlValidationOnFail() { + String fileName = "src/test/resources/input/ArtifactCreated.json"; + String msg = testGeneratePurlValidationOn(fileName); + Assert.assertTrue(msg.contains("Cannot validate PURL")); + } + + private String testGeneratePurlValidationOn(String fileName) { + HashMap properties = new HashMap<>(); + properties.put(SemanticsService.VALIDATE_PURL_FOR_ART_C, true); + try { + File inputFile = new File(fileName); + JsonObject object = parser.parse(new FileReader(inputFile)).getAsJsonObject(); + String msgType=object.get("msgParams").getAsJsonObject().get("meta").getAsJsonObject().get("type").getAsString(); + return service.generateMsg(msgType, object, properties); + } catch (JsonIOException | JsonSyntaxException | FileNotFoundException e) { + System.out.println("Exception occurred while generating event"); + e.printStackTrace(); + Assert.assertFalse(true); + } + + return null; + } @Test public void testUnknownMessage() throws UnsupportedEncodingException, JsonIOException, JsonSyntaxException, FileNotFoundException { @@ -251,16 +291,22 @@ public void testValidateTemplates() { @Test public void testInvalid_lv_Message_Success() throws JsonIOException, JsonSyntaxException, FileNotFoundException, UnsupportedEncodingException { - URL url = getClass().getClassLoader().getResource("invalid/lv_EiffelArtifactCreatedEventInvalid.json"); - String path = URLDecoder.decode(url.getPath(), StandardCharsets.UTF_8.name()); - File file = new File(path); - JsonObject input = parser.parse(new FileReader(file)).getAsJsonObject(); - String msg = service.generateMsg("EiffelArtifactCreatedEvent", input, true); - Assert.assertTrue(msg.contains("remremGenerateFailures")); - Assert.assertTrue(msg.contains("data")); - Assert.assertTrue(msg.contains("meta")); - Assert.assertTrue(msg.contains("links")); + HashMap properties = new HashMap<>(); + // TODO enable PURL validation + properties.put(SemanticsService.VALIDATE_PURL_FOR_ART_C, false); + properties.put(SemanticsService.LENIENT_VALIDATION, true); + + URL url = getClass().getClassLoader().getResource("invalid/lv_EiffelArtifactCreatedEventInvalid.json"); + String path = URLDecoder.decode(url.getPath(), StandardCharsets.UTF_8.name()); + File file = new File(path); + JsonObject input = parser.parse(new FileReader(file)).getAsJsonObject(); + String msg = service.generateMsg("EiffelArtifactCreatedEvent", input, properties); + Assert.assertTrue(msg.contains("remremGenerateFailures")); + Assert.assertTrue(msg.contains("data")); + Assert.assertTrue(msg.contains("meta")); + Assert.assertTrue(msg.contains("links")); } + @Test public void testInvalid_lv_Message_fail() throws JsonIOException, JsonSyntaxException, FileNotFoundException, UnsupportedEncodingException { URL url = getClass().getClassLoader().getResource("invalid/lv_EiffelArtifactCreatedEventInvalid.json"); diff --git a/src/test/resources/input/valid-purl/ArtifactCreated.json b/src/test/resources/input/valid-purl/ArtifactCreated.json new file mode 100644 index 0000000..d604ecc --- /dev/null +++ b/src/test/resources/input/valid-purl/ArtifactCreated.json @@ -0,0 +1,71 @@ +{ + "msgParams": { + "meta": { + "type": "EiffelArtifactCreatedEvent", + "version": "3.3.0", + "schemaUri": "https://eiffel/schemas/EiffelArtifactCreatedEvent/3.3.0.json", + "tags": [ + "tag1", + "tag2" + ], + "source": { + "domainId": "domainID", + "host": "host", + "name": "name", + "uri": "http:\/\/java.sun.com\/j2se\/1.3\/", + "serializer":"pkg:maven" + }, + "security": { + "authorIdentity": "test", + "encryptedDigest": "sample" + } + } + }, + "eventParams": { + "data": { + "gav": { + "groupId": "G", + "artifactId": "A", + "version": "V" + }, + "fileInformation": [{ + "name":"name" + }], + "buildCommand": "trigger", + "requiresImplementation": "NONE", + "name": "event", + "dependsOn": [ + ], + "implement": [ + + ], + "identity":"pkg:abc/def", + "name":"name", + "customData": [{ + "key": "firstLog", + "value": "http:\/\/myHost.com\/firstLog" + }, + { + "key": "otherLog", + "value": "http:\/\/myHost.com\/firstLog33" + } + ] + }, + "links": [{ + "type": "CAUSE", + "target": "aaaaaaaa-bbbb-5ccc-8ddd-eeeeeeeeeee1" + }, + { + "type": "PREVIOUS_VERSION", + "target": "aaaaaaaa-bbbb-5ccc-8ddd-eeeeeeeeeee2" + }, + { + "type": "COMPOSITION", + "target": "aaaaaaaa-bbbb-5ccc-8ddd-eeeeeeeeeee1" + }, + { + "type": "ENVIRONMENT", + "target": "aaaaaaaa-bbbb-5ccc-8ddd-eeeeeeeeeee3" + }] + } +} \ No newline at end of file