From a956bdbb3e441700938a05f654b105ff2b2c9c3d Mon Sep 17 00:00:00 2001 From: Dennis Brinley Date: Fri, 13 Sep 2024 16:09:31 -0400 Subject: [PATCH] updates to groovy scripts for variable topics - updates to composeTopic.groovy - new dynamically generated topicParameters.groovy --- pom.xml | 4 +- .../converter/service/EpActionsService.java | 2 +- .../service/converter/SapIFlowConverter.java | 69 +++++++++++- .../converter/SapIflorConverterConstants.java | 57 ++++++++++ .../main/resources/script/composeTopic.groovy | 100 ++++++++++-------- 5 files changed, 183 insertions(+), 49 deletions(-) diff --git a/pom.xml b/pom.xml index e969093..88bbdb1 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ ep.asyncapi.tool.sap.is ep-asyncapi-sap-is-converter - 1.0.4 + 1.0.5 ep-asyncapi-sap-is-converter ep-asyncapi-sap-is-converter @@ -67,7 +67,7 @@ com.solace.ep.codegen solace-ep-codegen - 1.1.0 + 1.1.1 diff --git a/src/main/java/ep/asyncapi/tool/sap/is/converter/service/EpActionsService.java b/src/main/java/ep/asyncapi/tool/sap/is/converter/service/EpActionsService.java index e5221c4..04dcd3e 100644 --- a/src/main/java/ep/asyncapi/tool/sap/is/converter/service/EpActionsService.java +++ b/src/main/java/ep/asyncapi/tool/sap/is/converter/service/EpActionsService.java @@ -155,7 +155,7 @@ public byte[] generateISWorkflowArtefactForAppVersion(final ApiClient apiClient, //resources/scenarios/integrationflow sapIFlowConverter.createIntegrationFlowFiles(integrationFlowSubDirectory, mapMuleDoc); //resources/script/ - sapIFlowConverter.createDynamicTopicScriptFiles(scriptSubDirectory); + sapIFlowConverter.createDynamicTopicScriptFiles(scriptSubDirectory, mapMuleDoc); //create zip file final File zipFile = createZipFile(mainDirectory); diff --git a/src/main/java/ep/asyncapi/tool/sap/is/converter/service/converter/SapIFlowConverter.java b/src/main/java/ep/asyncapi/tool/sap/is/converter/service/converter/SapIFlowConverter.java index 4400b95..62ddc3a 100644 --- a/src/main/java/ep/asyncapi/tool/sap/is/converter/service/converter/SapIFlowConverter.java +++ b/src/main/java/ep/asyncapi/tool/sap/is/converter/service/converter/SapIFlowConverter.java @@ -3,6 +3,7 @@ import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.solace.ep.codegen.internal.model.MapMuleDoc; +import com.solace.ep.codegen.internal.model.MapSubFlowEgress; import com.solace.ep.codegen.internal.model.SchemaInstance; import com.solace.ep.codegen.sap.iflow.SapIFlowGenerator; @@ -353,14 +354,74 @@ private String generateProjectFileContent(final String asyncAPITitle) { } } - public void createDynamicTopicScriptFiles(final File scriptSubDirectory) { + /** + * This method will create script files: + * - composeTopic.groovy (static) + * - topicParameters.groovy -- generated dynamically with topic parameters in code, one script function per event + * @param scriptSubDirectory + * @param mapMuleDoc + */ + public void createDynamicTopicScriptFiles(final File scriptSubDirectory, MapMuleDoc mapMuleDoc) { try { final InputStream composeTopicScriptInputStream = SapIFlowConverter.class.getResourceAsStream(SapIflorConverterConstants.RESOURCES_SCRIPT_COMPOSE_TOPIC_FILE_PATH); - // final InputStream extractFieldScriptInputStream = SapIFlowConverter.class.getResourceAsStream(SapIflorConverterConstants.RESOURCES_SCRIPT_EXTRACT_FIELD_FILE_PATH); File composeTopicScriptFile = new File(scriptSubDirectory, "composeTopic.groovy"); - // File extractFieldScriptFile = new File(scriptSubDirectory, "extractField.groovy"); FileUtils.copyInputStreamToFile(composeTopicScriptInputStream, composeTopicScriptFile); - // FileUtils.copyInputStreamToFile(extractFieldScriptInputStream, extractFieldScriptFile); + + if (mapMuleDoc.getMapEgressSubFlows().isEmpty()) { + return; + } else { + int count = 0; + for (MapSubFlowEgress pub : mapMuleDoc.getMapEgressSubFlows() ) { + if (pub.getSetVariables().size() > 0) { + count++; + } + } + if (count == 0) { + return; + } + } + + File topicParametersScriptFile = new File(scriptSubDirectory, "topicParameters.groovy"); + FileUtils.writeStringToFile(topicParametersScriptFile, SapIflorConverterConstants.TOPIC_PARAMETERS_GROOVY_HEADER, "UTF-8", false ); + int outputChannel = 0; // , functionCount = 0; + for ( MapSubFlowEgress pub : mapMuleDoc.getMapEgressSubFlows() ) { + if ( pub.getSetVariables().size() == 0 ) { + outputChannel++; + continue; + } + if ( pub.isPublishToQueue() ) { + outputChannel++; + continue; + } + String topicParametersGroovyFx = SapIflorConverterConstants.TOPIC_PARAMETERS_GROOVY_FX; + topicParametersGroovyFx = topicParametersGroovyFx.replace( + SapIflorConverterConstants.TP_TOKEN_EVENT_NAME, + pub.getMessageName() != null ? pub.getMessageName() : "UNKNOWN" + ); + topicParametersGroovyFx = topicParametersGroovyFx.replace( + SapIflorConverterConstants.TP_TOKEN_TOPIC_ADDRESS_PATTERN, + pub.getPublishAddress() != null ? pub.getPublishAddress() : "UNKNOWN" + ); + topicParametersGroovyFx = topicParametersGroovyFx.replace( + SapIflorConverterConstants.TP_TOKEN_FX_INSTANCE, + Integer.toString(outputChannel++) + ); + StringBuilder varsList = new StringBuilder(); + StringBuilder jsonPath = new StringBuilder(); + StringBuilder setValue = new StringBuilder(); + int varIndex = 1; + for( Map.Entry var : pub.getSetVariables().entrySet() ) { + final String topicVar = var.getKey(); + varsList.append(String.format(SapIflorConverterConstants.TOPIC_PARAMETERS_VAR_LIST_PATTERN, topicVar)); + jsonPath.append(String.format(SapIflorConverterConstants.TOPIC_PARAMETERS_JSON_PATH_PATTERN, topicVar, varIndex)); + setValue.append(String.format(SapIflorConverterConstants.TOPIC_PARAMETERS_SET_VALUE_PATTERN, topicVar, varIndex)); + varIndex++; + } + topicParametersGroovyFx = topicParametersGroovyFx.replace(SapIflorConverterConstants.TP_TOKEN_TOPIC_VARS_LIST, varsList.toString()); + topicParametersGroovyFx = topicParametersGroovyFx.replace(SapIflorConverterConstants.TP_TOKEN_VARS_JSON_PATH, jsonPath.toString()); + topicParametersGroovyFx = topicParametersGroovyFx.replace(SapIflorConverterConstants.TP_TOKEN_VARS_SET_VALUE, setValue.toString()); + FileUtils.writeStringToFile(topicParametersScriptFile, topicParametersGroovyFx, "UTF-8", true); + }; } catch (IOException ioException) { log.error("Error encountered in SapIFlowConverter.createDynamicTopicScriptFiles", ioException); } diff --git a/src/main/java/ep/asyncapi/tool/sap/is/converter/service/converter/SapIflorConverterConstants.java b/src/main/java/ep/asyncapi/tool/sap/is/converter/service/converter/SapIflorConverterConstants.java index efcf2b8..dba6d32 100644 --- a/src/main/java/ep/asyncapi/tool/sap/is/converter/service/converter/SapIflorConverterConstants.java +++ b/src/main/java/ep/asyncapi/tool/sap/is/converter/service/converter/SapIflorConverterConstants.java @@ -20,4 +20,61 @@ public class SapIflorConverterConstants { RESOURCES_MAPPING_SOURCE_TO_DESTINATIONFORMAT_TEMPLATE = "/static/iflowdocument-template/src/main/resources/mapping/sourceToDestinationFormatMmap.xml", RESOURCES_SCRIPT_COMPOSE_TOPIC_FILE_PATH = "/static/iflowdocument-template/src/main/resources/script/composeTopic.groovy", RESOURCES_SCRIPT_EXTRACT_FIELD_FILE_PATH = "/static/iflowdocument-template/src/main/resources/script/extractField.groovy"; + + public static final String + TOPIC_PARAMETERS_GROOVY_HEADER = + "/**\n" + // + " * topicParameters.groovy\n" + // + " * ----------------------\n" + // + " * EDIT this script to define sources for published topic variables\n" + // + " * - One function is generated per event type published to Event Mesh\n" + // + " * - Define entry for variable source as JSON Path (payload) or Set Value (explicit)\n" + // + " * - The JSON Path will be evaluated first, the Set Value can be used as a default\n" + // + " * if the field specified in JSON Path is not found in the payload\n" + // + " **/\n" + // + "import com.sap.gateway.ip.core.customdev.util.Message;\n" + // + "\n" + // + "TV_JSON_PATH_PROPERTY = \"__topicVarsJsonPath__\";\n" + // + "TV_SET_VALUE_PROPERTY = \"__topicVarsSetValue__\";\n" + // + "\n", + TOPIC_PARAMETERS_GROOVY_FX = + "/**\n" + // + " * Define Topic Variables for Event:\n" + // + " * >>$$__EVENT_NAME__$$<<\n" + // + " *\n" + // + " * Topic Address Pattern:\n" + // + " * >>$$__TOPIC_ADDRESS_PATTERN__$$<<\n" + // + " *\n" + // + " * Topic Variables:\n" + // + ">>$$__TOPIC_VARS_LIST__$$<<\n" + // + " **/\n" + // + "def Message defineTopicParams_>>$$__FX_INSTANCE__$$<<(Message message) {\n" + // + "\n" + // + " def topicVarsJsonPath = [:]; // Map of topicVariables -> JSON path locations\n" + // + " def topicVarsSetValue = [:]; // Map of topicVariables -> Explicit values\n" + // + "\n" + // + " // Uncomment property value to set JSON Path location in payload\n" + // + " // DO NOT use $. prefix for JSON Path specification\n" + // + ">>$$__TOPIC_VARS_JSON_PATH__$$<<\n" + // + "\n" + // + " // Uncomment property entry below to set a topic value directly\n" + // + ">>$$__TOPIC_VARS_SET_VALUE__$$<<\n" + // + "\n" + // + " message.setProperty(TV_JSON_PATH_PROPERTY, topicVarsJsonPath);\n" + // + " message.setProperty(TV_SET_VALUE_PROPERTY, topicVarsSetValue);\n" + // + "\n" + // + " return message;\n" + // + "}\n" + // + "\n", + TOPIC_PARAMETERS_JSON_PATH_PATTERN = " // topicVarsJsonPath.%s = \"payload.field%d\"\n", + TOPIC_PARAMETERS_SET_VALUE_PATTERN = " // topicVarsSetValue.%s = \"VALUE%d\"\n", + TOPIC_PARAMETERS_VAR_LIST_PATTERN = " * - %s\n"; + + public static String + TP_TOKEN_EVENT_NAME = ">>$$__EVENT_NAME__$$<<", + TP_TOKEN_TOPIC_ADDRESS_PATTERN = ">>$$__TOPIC_ADDRESS_PATTERN__$$<<", + TP_TOKEN_TOPIC_VARS_LIST = ">>$$__TOPIC_VARS_LIST__$$<<\n", + TP_TOKEN_FX_INSTANCE = ">>$$__FX_INSTANCE__$$<<", + TP_TOKEN_VARS_JSON_PATH = ">>$$__TOPIC_VARS_JSON_PATH__$$<<\n", + TP_TOKEN_VARS_SET_VALUE = ">>$$__TOPIC_VARS_SET_VALUE__$$<<\n"; } diff --git a/src/main/resources/static/iflowdocument-template/src/main/resources/script/composeTopic.groovy b/src/main/resources/static/iflowdocument-template/src/main/resources/script/composeTopic.groovy index f64a756..e8226f6 100644 --- a/src/main/resources/static/iflowdocument-template/src/main/resources/script/composeTopic.groovy +++ b/src/main/resources/static/iflowdocument-template/src/main/resources/script/composeTopic.groovy @@ -1,62 +1,78 @@ -/* -This script will build a topic string with variable elements by extracting -values from a JSON message body -- If there are no variable elements in the topic string then then the set value will be passed on -- If there is no composedTopic pattern then the script will do nothing -*/ +/** + * composeTopic.groovy + * ------------------- + * This script will build a topic string with variable elements by extracting + * values from a JSON message body OR by replacing variables with explicit values + * - In general, this script should not require editing + * - This script relies on existing message properties: + * - composedTopic -- Topic pattern with variables enclosed in braces: {var} + * - __topicVarsJsonPath__ -- Map of variable names -> JSON path of value + * - __topicVarsSetValue__ -- Map of variable names -> explicit values +**/ import com.sap.gateway.ip.core.customdev.util.Message; -import java.util.HashMap; import groovy.json.JsonSlurper; -def Message processData(Message message) { - final GP_TOPICEXP = 'gpath_topicexp_'; - final GP_TOPICVAL = 'gpath_topicval_'; - final GP_COMPOSED_PATTERN = "gpath_composedTopicPattern"; - final COMPOSED_TOPIC = "composedTopic"; - +def Message composeTopic(Message message) { + final COMPOSED_TOPIC_PROPERTY = 'composedTopic'; + final TV_JSON_PATH_PROPERTY = '__topicVarsJsonPath__'; + final TV_SET_VALUE_PROPERTY = '__topicVarsSetValue__'; + def properties = message.getProperties(); - def topicValProperties = new HashMap(); - + try { - def composedTopicString = properties.get(GP_COMPOSED_PATTERN); - if (composedTopicString == null || composedTopicString.length() == 0) { - // This could be an error, or the topic may be set directly on the publisher - // (bypass composed topic logic) + def composedTopicPattern = properties.get(COMPOSED_TOPIC_PROPERTY); + + // No topic pattern, nothing to do + if (!composedTopicPattern || composedTopicPattern.isEmpty()) { + // TODO - Error condition? return message; } - if (!composedTopicString.contains("{")) { - // Debug log, static topic detected - message.setProperty(COMPOSED_TOPIC, composedTopicString); + // No variables in topic pattern, nothing to do + if (!composedTopicPattern.contains('{')) { return message; } - // Extract dynamic topic values from message body - final js = new groovy.json.JsonSlurper(); - final request = js.parseText(message.getBody(java.lang.String) as String); - properties.each { prop, texp -> - if (prop.startsWith(GP_TOPICEXP)) { - def varval = groovy.util.Eval.x(request, 'x.' + texp); - topicValProperties.put(GP_TOPICVAL + prop.replace(GP_TOPICEXP, ""), varval.toString()); - } else if (prop.startsWith(GP_TOPICVAL)) { - topicValProperties.put(prop, texp.toString()); + def tvJsonPath = properties.get(TV_JSON_PATH_PROPERTY) + def tvSetValue = properties.get(TV_SET_VALUE_PROPERTY) + + // Special properties with variable content not present, nothing to do + if (!tvJsonPath && !tvSetValue) { + return message; + } else if (tvJsonPath.size() == 0 && tvSetValue.size() == 0) { + return message; + } + + def composedTopicString = composedTopicPattern + + // Set topic variables from payload + if (tvJsonPath && tvJsonPath.size() > 0) { + final js = new groovy.json.JsonSlurper() + final request = js.parseText(message.getBody(java.lang.String) as String) + tvJsonPath.each { entry -> + def varName = "{$entry.key}" + def varValue = groovy.util.Eval.x(request, 'x.' + entry.value) + if (varName && varValue) { + composedTopicString = composedTopicString.replace(varName, varValue) + } } } - - // Add Extracted values to message properties - topicValProperties.each { key, value -> - def varName = "{" + key.replace(GP_TOPICVAL, "") + "}"; - def varVal = value; - if (varVal == null || varVal.isEmpty()) { - varVal = "NULL"; + + // Set topic variables directly + if (tvSetValue) { + tvSetValue.each{ entry -> + def varName = "{$entry.key}" + def varValue = entry.value + if (varName && varValue) { + composedTopicString = composedTopicString.replace(varName, varValue) + } } - composedTopicString = composedTopicString.replace(varName, varVal); } - + // Set the composed topic to expected property - message.setProperty(COMPOSED_TOPIC, composedTopicString); + message.setProperty(COMPOSED_TOPIC_PROPERTY, composedTopicString); } catch (Exception ex) { - message.setProperty('gpath_error', ex.getMessage()); + message.setProperty('composedTopic_error', ex.getMessage()); } return message; } \ No newline at end of file