diff --git a/vip-api/src/main/java/fr/insalyon/creatis/vip/api/business/ExecutionBusiness.java b/vip-api/src/main/java/fr/insalyon/creatis/vip/api/business/ExecutionBusiness.java index b0b16cce7..575be7097 100644 --- a/vip-api/src/main/java/fr/insalyon/creatis/vip/api/business/ExecutionBusiness.java +++ b/vip-api/src/main/java/fr/insalyon/creatis/vip/api/business/ExecutionBusiness.java @@ -11,6 +11,9 @@ import java.util.Map; import java.util.Map.Entry; import java.util.function.Supplier; +import java.util.Collections; +import java.util.Arrays; +import java.util.stream.Collectors; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -174,18 +177,21 @@ private Execution getExecutionFromSimulation(Simulation s, boolean summarize) th for (InOutData iod : inputs) { String key = iod.getProcessor(); String value = iod.getPath(); - - ((List) e.getInputValues().computeIfAbsent(key, k -> new ArrayList<>())).add(value); - } - // retrieves results directory - List resDirList = (List) e.getInputValues().get(RESULTS_DIRECTORY_PARAM_NAME); - if (resDirList == null) { - resDirList = new ArrayList<>(); - } - if (!resDirList.isEmpty()) { - e.setResultsLocation(resDirList); - e.getInputValues().remove(RESULTS_DIRECTORY_PARAM_NAME); + for (Map inputMap : e.getInputValues()) { + ((List) inputMap.computeIfAbsent(key, k -> new ArrayList<>())).add(value); + + // retrieves results directory + List resDirList = (List) inputMap.get(RESULTS_DIRECTORY_PARAM_NAME); + if (resDirList == null) { + resDirList = new ArrayList<>(); + } + if (!resDirList.isEmpty()) { + e.setResultsLocation(resDirList); + inputMap.remove(RESULTS_DIRECTORY_PARAM_NAME); + } + } } + List outputs = workflowBusiness.getOutputData(s.getID(), userFolder); for (InOutData iod : outputs) { String key = iod.getProcessor(); @@ -315,25 +321,35 @@ public void updateExecution(Execution execution) throws VipException { } public String initExecution(Execution execution) throws VipException { - Map inputMap = new HashMap<>(); + List> inputMaps = new ArrayList<>(); + Object resultsLocation = execution.getResultsLocation(); + boolean isInputMapList = execution.getInputValues().size() > 1; + for (Map inputValuesMap : execution.getInputValues()) { + Map inputMap = new HashMap<>(); + for (Entry restInput : inputValuesMap.entrySet()) { + if (isInputMapList && restInput.getValue() instanceof List) { + throw new VipException( + "Parameter '" + restInput.getKey() + "' contains a list, it should only have a single value when providing a list of input maps."); + } - for (Entry restInput : execution.getInputValues().entrySet()) { - inputMap.put( - restInput.getKey(), - handleRestParameter(restInput.getKey(), restInput.getValue())); - } + inputMap.put( + restInput.getKey(), + handleRestParameter(restInput.getKey(), restInput.getValue())); + } - // We handle resultsLocation the same as others restInputs, since it can either be a String or a List - Object resultsLocation = execution.getResultsLocation(); - if (resultsLocation != null) { - inputMap.put( - CoreConstants.RESULTS_DIRECTORY_PARAM_NAME, - handleRestParameter(CoreConstants.RESULTS_DIRECTORY_PARAM_NAME, resultsLocation)); + // We handle resultsLocation the same as others restInputs, since it can either be a String or a List + if (resultsLocation != null) { + inputMap.put( + CoreConstants.RESULTS_DIRECTORY_PARAM_NAME, + handleRestParameter(CoreConstants.RESULTS_DIRECTORY_PARAM_NAME, resultsLocation)); + } + + inputMaps.add(inputMap); } checkInputExecNameIsValid(execution.getName()); return initExecution( - execution.getPipelineIdentifier(), inputMap, execution.getTimeout(), + execution.getPipelineIdentifier(), inputMaps, execution.getTimeout(), execution.getName(), execution.getStudyIdentifier()); } @@ -378,7 +394,7 @@ private void checkInputExecNameIsValid(String input) throws VipException { } private String initExecution(String pipelineId, - Map inputValues, + List> inputValues, Integer timeoutInSeconds, String executionName, String studyId) throws VipException { @@ -400,40 +416,38 @@ private String initExecution(String pipelineId, if (pp.isReturnedValue()) { continue; } + + List> mapsWithoutKey = inputValues.stream() + .filter(inputMap -> !inputMap.containsKey(pp.getName())) + .toList(); + // ok if input is present - if (inputValues.get(pp.getName()) != null) { - continue; - } - // then ok if input has a default value (and we set it) - if (pp.getDefaultValue() != null) { - inputValues.put(pp.getName(), pp.getDefaultValue().toString()); + if (mapsWithoutKey.isEmpty()) { continue; } - // then ok if it is optional - if (pp.isOptional()) { - continue; - } - // error : pp is an empty input with no default value and it is not optional - logger.error("Error initialising {}, missing {} parameter", pipelineId, pp.getName()); - throw new VipException(ApiError.INPUT_FIELD_MISSING, pp.getName()); - } - // fill in overriddenInputs from explicit inputs - Map overriddenInputs = p.getOverriddenInputs(); - if (overriddenInputs != null) { - for (String key : overriddenInputs.keySet()) { - String value = overriddenInputs.get(key); - if (inputValues.containsKey(value)) { - inputValues.put(key, inputValues.get(value)); - } else { - logger.error("Error initialising {}, missing {} parameter", pipelineId, value); - throw new VipException(ApiError.INPUT_FIELD_MISSING, value); + for (Map inputMap : mapsWithoutKey) { + // then ok if input has a default value (and we set it) + if (pp.getDefaultValue() != null) { + inputMap.put(pp.getName(), pp.getDefaultValue().toString()); + continue; + } + // then ok if it is optional + if (pp.isOptional()) { + continue; } + + // error : pp is an empty input with no default value and it is not optional + logger.error("Error initialising {}, missing {} parameter", pipelineId, pp.getName()); + throw new VipException(ApiError.INPUT_FIELD_MISSING, pp.getName()); } } - boolean inputsContainsResultsDirectoryInput = inputValues - .containsKey(CoreConstants.RESULTS_DIRECTORY_PARAM_NAME); + fillInOverriddenInputs(inputValues, p); + fillInDefaultDotInputs(inputValues, p); + + boolean inputsContainsResultsDirectoryInput = inputValues.stream() + .allMatch(inputMap -> inputMap.containsKey(CoreConstants.RESULTS_DIRECTORY_PARAM_NAME)); boolean pipelineHasResultsDirectoryInput = p.getParameters().stream() .anyMatch(param -> param.getName().equals(CoreConstants.RESULTS_DIRECTORY_PARAM_NAME)); @@ -538,4 +552,78 @@ public void checkIfUserCanAccessExecution(String executionId) throws VipExceptio throw new VipException("Permission denied"); } + private void fillInOverriddenInputs(List> inputValues, Pipeline p) throws VipException { + // fill in overriddenInputs from explicit inputs + Map overriddenInputs = p.getOverriddenInputs(); + if (overriddenInputs != null) { + for (String key : overriddenInputs.keySet()) { + String value = overriddenInputs.get(key); + for (Map inputMap : inputValues) { + if (inputMap.containsKey(value)) { + inputMap.put(key, inputMap.get(value)); + } else { + logger.error("Error initialising {}, missing {} parameter", p.getIdentifier(), value); + throw new VipException(ApiError.INPUT_FIELD_MISSING, value); + } + } + } + } + } + + private void fillInDefaultDotInputs(List> inputValues, Pipeline p) throws VipException { + List dotInputList = p.getDotInputs(); + if (dotInputList == null || dotInputList.isEmpty()) { + return; + } + + boolean isInputMapList = inputValues.size() > 1; + // Find the maximum number of dot inputs + int dotMaxCount = isInputMapList ? 1 : inputValues.getFirst().entrySet().stream() + .filter(entry -> dotInputList.contains(entry.getKey())) + .mapToInt(v -> v.getValue().split(ApplicationConstants.SEPARATOR_LIST, -1).length) + .max() + .orElse(1); + + ArrayList dotParameters = p.getParameters().stream() + .filter(pp -> dotInputList.contains(pp.getName())) + .collect(Collectors.toCollection(ArrayList::new)); + + for (PipelineParameter pp : dotParameters) { + // always true on vip + if (pp.isReturnedValue()) { + continue; + } + + if (pp.getDefaultValue() == null) { + continue; + } + + String defaultVal = pp.getDefaultValue().toString(); + for (Map inputMap : inputValues) { + if (!inputMap.containsKey(pp.getName())) { + // One map per job + if (isInputMapList) { + inputMap.put(pp.getName(), defaultVal); + } else { + inputMap.put(pp.getName(), + String.join(ApplicationConstants.SEPARATOR_LIST, + Collections.nCopies(dotMaxCount, defaultVal))); + } + } else { + String[] currentValues = inputMap.get(pp.getName()) + .split(ApplicationConstants.SEPARATOR_LIST, -1); + // Complete the input with default values if it has fewer values than the maximum + if (currentValues.length < dotMaxCount) { + List valuesList = new ArrayList<>(Arrays.asList(currentValues)); + while (valuesList.size() < dotMaxCount) { + valuesList.add(defaultVal); + } + + inputMap.put(pp.getName(), + String.join(ApplicationConstants.SEPARATOR_LIST, valuesList)); + } + } + } + } + } } diff --git a/vip-api/src/main/java/fr/insalyon/creatis/vip/api/business/PipelineBusiness.java b/vip-api/src/main/java/fr/insalyon/creatis/vip/api/business/PipelineBusiness.java index 5d58a8dbd..504305f38 100644 --- a/vip-api/src/main/java/fr/insalyon/creatis/vip/api/business/PipelineBusiness.java +++ b/vip-api/src/main/java/fr/insalyon/creatis/vip/api/business/PipelineBusiness.java @@ -151,6 +151,7 @@ private Pipeline getPipelineFromBoutiquesDescriptor(String pipelineId) throws Vi p.setDescription(boutiques.getDescription()); Map overriddenInputs = boutiquesBusiness.getOverriddenInputs(boutiques); + List dotInputs = boutiquesBusiness.getDotInputs(boutiques); for (Input input : boutiques.getInputs()) { if (overriddenInputs != null && overriddenInputs.containsKey(input.getId())) { continue; // hide overriddenInputs from pipeline visible parameters @@ -165,6 +166,11 @@ private Pipeline getPipelineFromBoutiquesDescriptor(String pipelineId) throws Vi if (overriddenInputs != null) { p.setOverriddenInputs(overriddenInputs); } + + if (dotInputs != null) { + p.setDotInputs(dotInputs); + } + return p; } diff --git a/vip-api/src/main/java/fr/insalyon/creatis/vip/api/model/Execution.java b/vip-api/src/main/java/fr/insalyon/creatis/vip/api/model/Execution.java index 1a87194eb..895d27b87 100644 --- a/vip-api/src/main/java/fr/insalyon/creatis/vip/api/model/Execution.java +++ b/vip-api/src/main/java/fr/insalyon/creatis/vip/api/model/Execution.java @@ -1,8 +1,13 @@ package fr.insalyon.creatis.vip.api.model; import java.util.*; + import jakarta.validation.constraints.NotNull; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; + +import fr.insalyon.creatis.vip.api.model.serializing.InputValuesDeserializer; + public class Execution { private String identifier; @@ -13,7 +18,8 @@ public class Execution { private int timeout; private ExecutionStatus status; @NotNull - private Map inputValues; + @JsonDeserialize(using = InputValuesDeserializer.class) + private List> inputValues; private Map> returnedFiles; // optional arguments @@ -25,7 +31,7 @@ public class Execution { private Map> jobs; // jobId -> status public Execution() { - inputValues = new HashMap<>(); + inputValues = new ArrayList<>(); returnedFiles = new HashMap<>(); jobs = new HashMap<>(); } @@ -94,11 +100,11 @@ public void setStatus(ExecutionStatus status) { this.status = status; } - public Map getInputValues() { + public List> getInputValues() { return inputValues; } - public void setInputValues(Map inputValues) { + public void setInputValues(List> inputValues) { this.inputValues = inputValues; } diff --git a/vip-api/src/main/java/fr/insalyon/creatis/vip/api/model/Pipeline.java b/vip-api/src/main/java/fr/insalyon/creatis/vip/api/model/Pipeline.java index 85ca2dac2..45c985dcc 100644 --- a/vip-api/src/main/java/fr/insalyon/creatis/vip/api/model/Pipeline.java +++ b/vip-api/src/main/java/fr/insalyon/creatis/vip/api/model/Pipeline.java @@ -4,6 +4,7 @@ import com.fasterxml.jackson.annotation.JsonIgnore; import java.util.ArrayList; +import java.util.List; import java.util.Map; public class Pipeline { @@ -16,6 +17,8 @@ public class Pipeline { private boolean canExecute; @JsonIgnore private Map overriddenInputs; + @JsonIgnore + private List dotInputs; public Pipeline() { } @@ -64,4 +67,13 @@ public Map getOverriddenInputs() { public void setOverriddenInputs(Map overriddenInputs) { this.overriddenInputs = overriddenInputs; } + + public List getDotInputs() { + return dotInputs; + } + + public void setDotInputs(List dotInputs) { + this.dotInputs = dotInputs; + } + } diff --git a/vip-api/src/main/java/fr/insalyon/creatis/vip/api/model/serializing/InputValuesDeserializer.java b/vip-api/src/main/java/fr/insalyon/creatis/vip/api/model/serializing/InputValuesDeserializer.java new file mode 100644 index 000000000..deecaee7b --- /dev/null +++ b/vip-api/src/main/java/fr/insalyon/creatis/vip/api/model/serializing/InputValuesDeserializer.java @@ -0,0 +1,43 @@ +package fr.insalyon.creatis.vip.api.model.serializing; + +import com.fasterxml.jackson.core.JsonToken; +import com.fasterxml.jackson.databind.JavaType; +import com.fasterxml.jackson.databind.deser.std.StdDeserializer; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.databind.DeserializationContext; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +public class InputValuesDeserializer extends StdDeserializer>> { + + public InputValuesDeserializer() { + super(List.class); + } + + @Override + public List> deserialize(JsonParser p, DeserializationContext ctx) throws IOException { + JavaType mapType = ctx.getTypeFactory().constructMapType(Map.class, String.class, Object.class); + // Array of dictionaries + if (p.currentToken() == JsonToken.START_ARRAY) { + List> list = new ArrayList<>(); + p.nextToken(); + while (p.currentToken() != JsonToken.END_ARRAY) { + list.add(ctx.readValue(p, mapType)); + p.nextToken(); + } + + return list; + // Unique dictionary + } else if (p.currentToken() == JsonToken.START_OBJECT) { + Map map = ctx.readValue(p, mapType); + return Collections.singletonList(map); + } else { + throw ctx.wrongTokenException(p, List.class, JsonToken.START_ARRAY, + "InputValues must be a dictionary or an array of dictionaries"); + } + } +} diff --git a/vip-api/src/test/java/fr/insalyon/creatis/vip/api/data/ExecutionTestUtils.java b/vip-api/src/test/java/fr/insalyon/creatis/vip/api/data/ExecutionTestUtils.java index ef9997d5b..0ab441f46 100644 --- a/vip-api/src/test/java/fr/insalyon/creatis/vip/api/data/ExecutionTestUtils.java +++ b/vip-api/src/test/java/fr/insalyon/creatis/vip/api/data/ExecutionTestUtils.java @@ -30,11 +30,12 @@ public class ExecutionTestUtils { new GregorianCalendar(2016, 9, 2).getTime(), "Exec test 1", SimulationStatus.Running.toString(), "engine 1", null); execution1 = getExecution(simulation1, ExecutionStatus.RUNNING); - execution1.setInputValues(new HashMap() {{ - put("param 1", "value 1"); - put("param 2", "42"); - }} - ); + List> parametersMaps = new ArrayList<>(); + Map map = new HashMap<>(); + map.put("param 1", "value 1"); + map.put("param 2", "42"); + parametersMaps.add(map); + execution1.setInputValues(parametersMaps); execution1.clearReturnedFiles(); simulation1InData = Arrays.asList( @@ -47,10 +48,11 @@ public class ExecutionTestUtils { new GregorianCalendar(2016, 4, 29).getTime(), "Exec test 2", SimulationStatus.Completed.toString(), "engine 1", null); execution2 = getExecution(simulation2, ExecutionStatus.FINISHED); - execution2.setInputValues(new HashMap() {{ - put("param2-1", "5.3"); - }} - ); + parametersMaps = new ArrayList<>(); + parametersMaps.add(new HashMap<>() {{ + put("param2-1", "5.3"); + }}); + execution2.setInputValues(parametersMaps); execution2.setReturnedFiles(new HashMap>() {{ put("param2-res", Collections.singletonList("/vip/Home/testFile1.xml")); }}); diff --git a/vip-api/src/test/java/fr/insalyon/creatis/vip/api/rest/itest/processing/ExecutionControllerIT.java b/vip-api/src/test/java/fr/insalyon/creatis/vip/api/rest/itest/processing/ExecutionControllerIT.java index c381585ef..e3f44e1b6 100644 --- a/vip-api/src/test/java/fr/insalyon/creatis/vip/api/rest/itest/processing/ExecutionControllerIT.java +++ b/vip-api/src/test/java/fr/insalyon/creatis/vip/api/rest/itest/processing/ExecutionControllerIT.java @@ -426,7 +426,9 @@ public void testInitBoutiquesExecution() throws Exception expectedParams.put("testTextInput", List.of("best test text value")); expectedParams.put("testFlagInput", List.of("false")); expectedParams.put("results-directory", List.of("lfn:" + ServerMockConfig.TEST_USERS_ROOT + "/" + baseUser1.getFolder())); - String expectedInputs = workflowExecutionBusiness.getParametersAsJSONInput(expectedParams); + List>> paramsList = new ArrayList<>(); + paramsList.add(expectedParams); + String expectedInputs = workflowExecutionBusiness.getParametersAsJSONInput(paramsList); Assertions.assertEquals(expectedInputs, inputs); // verify created workflow diff --git a/vip-api/src/test/resources/jsonObjects/execution1-name-updated.json b/vip-api/src/test/resources/jsonObjects/execution1-name-updated.json index 7f77ef80a..9be93400c 100644 --- a/vip-api/src/test/resources/jsonObjects/execution1-name-updated.json +++ b/vip-api/src/test/resources/jsonObjects/execution1-name-updated.json @@ -1,8 +1,10 @@ { "name" : "Exec test 1 - modified", "pipelineIdentifier" : "application 1/4.2", - "inputValues" : { - "param 1" : "test text", - "param 2" : "/path/test" - } + "inputValues" : [ + { + "param 1" : "test text", + "param 2" : "/path/test" + } + ] } \ No newline at end of file diff --git a/vip-api/src/test/resources/jsonObjects/execution1.json b/vip-api/src/test/resources/jsonObjects/execution1.json index 657cd07db..556bacea1 100644 --- a/vip-api/src/test/resources/jsonObjects/execution1.json +++ b/vip-api/src/test/resources/jsonObjects/execution1.json @@ -1,8 +1,10 @@ { "name" : "Exec test 1", "pipelineIdentifier" : "test application/4.2", - "inputValues" : { - "testFileInput" : "/vip/Home/path/to/input.in", - "testTextInput" : "best test text value" - } + "inputValues" : [ + { + "testFileInput" : "/vip/Home/path/to/input.in", + "testTextInput" : "best test text value" + } + ] } \ No newline at end of file diff --git a/vip-application/src/main/java/fr/insalyon/creatis/vip/application/server/business/BoutiquesBusiness.java b/vip-application/src/main/java/fr/insalyon/creatis/vip/application/server/business/BoutiquesBusiness.java index 2c73381b2..ee5ad1083 100644 --- a/vip-application/src/main/java/fr/insalyon/creatis/vip/application/server/business/BoutiquesBusiness.java +++ b/vip-application/src/main/java/fr/insalyon/creatis/vip/application/server/business/BoutiquesBusiness.java @@ -272,4 +272,27 @@ public Map getOverriddenInputs(BoutiquesDescriptor descriptor) { } return result; } + + public List getDotInputs(BoutiquesDescriptor descriptor) { + final String customKeyName = "vip:dot"; + Custom custom = descriptor.getCustom(); + if (custom == null) { + return null; + } + + Map customProperties = custom.getAdditionalProperties(); + if (!customProperties.containsKey(customKeyName)) { + return null; + } +; + Object dotInputs = customProperties.get(customKeyName); + if (!(dotInputs instanceof List)) { + return null; + } + + return ((List) dotInputs).stream() + .filter(String.class::isInstance) + .map(String.class::cast) + .toList(); + } } diff --git a/vip-application/src/main/java/fr/insalyon/creatis/vip/application/server/business/WorkflowBusiness.java b/vip-application/src/main/java/fr/insalyon/creatis/vip/application/server/business/WorkflowBusiness.java index 7f2dc8725..f155dee9c 100644 --- a/vip-application/src/main/java/fr/insalyon/creatis/vip/application/server/business/WorkflowBusiness.java +++ b/vip-application/src/main/java/fr/insalyon/creatis/vip/application/server/business/WorkflowBusiness.java @@ -139,15 +139,18 @@ protected InputFileParser getInputFileParser(String currentUserFolder) { return null; } - public synchronized String launch(User user, List groups, Map parametersMap, - String appName, String version, String simulationName) throws VipException { + public synchronized String launch(User user, List groups, List> parametersMaps, + String appName, String version, String simulationName) throws VipException { Workflow workflow = null; try { checkVIPCapacities(user, simulationName); AppVersion appVersion = appVersionBusiness.getVersion(appName, version); - Map> parameters = getParameters(appVersion.getDescriptor(), parametersMap, user, groups); + List>> parametersMapList = new ArrayList<>(); + for (Map parameterMap : parametersMaps) { + parametersMapList.add(getParameters(appVersion.getDescriptor(), parameterMap, user, groups)); + } List resources = resourceBusiness.getAvailableForExecution(user, appVersion); if (resources.isEmpty()) { @@ -159,7 +162,7 @@ public synchronized String launch(User user, List groups, Map> parameters, String executorConfig) throws VipException { + List>> parameters, String executorConfig) throws VipException { try { String workflowContent = appVersion.getDescriptor(); @@ -66,7 +66,7 @@ public void kill(String engineEndpoint, String simulationID) throws VipException engine.kill(engineEndpoint, simulationID); } - public String getParametersAsJSONInput(Map> parameters) throws VipException { + public String getParametersAsJSONInput(List>> parameters) throws VipException { try { ObjectMapper mapper = new ObjectMapper(); diff --git a/vip-application/src/main/java/fr/insalyon/creatis/vip/application/server/rpc/WorkflowServiceImpl.java b/vip-application/src/main/java/fr/insalyon/creatis/vip/application/server/rpc/WorkflowServiceImpl.java index bcb6d9d51..4cc2e2474 100644 --- a/vip-application/src/main/java/fr/insalyon/creatis/vip/application/server/rpc/WorkflowServiceImpl.java +++ b/vip-application/src/main/java/fr/insalyon/creatis/vip/application/server/rpc/WorkflowServiceImpl.java @@ -4,18 +4,23 @@ import java.io.File; import java.io.FileReader; import java.io.IOException; +import java.util.Arrays; import java.util.ArrayList; import java.util.Date; import java.util.List; import java.util.Map; +import java.util.Collections; +import java.util.stream.Collectors; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import fr.insalyon.creatis.boutiques.model.BoutiquesDescriptor; +import fr.insalyon.creatis.boutiques.model.Input; import fr.insalyon.creatis.devtools.FileUtils; import fr.insalyon.creatis.moteur.plugins.workflowsdb.dao.WorkflowsDBDAOException; import fr.insalyon.creatis.vip.application.client.rpc.WorkflowService; +import fr.insalyon.creatis.vip.application.client.ApplicationConstants; import fr.insalyon.creatis.vip.application.models.Activity; import fr.insalyon.creatis.vip.application.models.AppVersion; import fr.insalyon.creatis.vip.application.models.InOutData; @@ -146,6 +151,56 @@ private void fillInOverriddenInputs(Map parametersMap, } } + private void fillInDefaultDotInputs(Map parametersMap, + String applicationName, String applicationVersion) throws VipException { + AppVersion appVersion = appVersionBusiness.getVersion(applicationName, applicationVersion); + BoutiquesDescriptor descriptor = boutiquesBusiness.parseBoutiquesString(appVersion.getDescriptor()); + // Get the string list of dot inputs + List dotInputList = boutiquesBusiness.getDotInputs(descriptor); + if (dotInputList == null || dotInputList.isEmpty()) { + return; + } + + // Build a map of inputId to Input for easy lookup + Map dotInputsById = descriptor.getInputs().stream() + .filter(input -> dotInputList.contains(input.getId())) + .collect(Collectors.toMap(Input::getId, i -> i)); + // Find the maximum number of dot inputs + int dotMaxCount = parametersMap.entrySet().stream() + .filter(entry -> dotInputsById.containsKey(entry.getKey())) + .mapToInt(v -> v.getValue().split(ApplicationConstants.SEPARATOR_LIST, -1).length) + .max() + .orElse(1); + + for (Input dotInput : dotInputsById.values()) { + if (dotInput.getDefaultValue() == null) { + continue; + } + + String defaultVal = dotInput.getDefaultValue().toString(); + String dotInputId = dotInput.getId(); + // If the input is not set, fill it with default values + if (!parametersMap.containsKey(dotInputId)) { + parametersMap.put(dotInputId, + String.join(ApplicationConstants.SEPARATOR_LIST, + Collections.nCopies(dotMaxCount, defaultVal))); + } else { + String[] currentValues = parametersMap.get(dotInputId) + .split(ApplicationConstants.SEPARATOR_LIST, -1); + // Complete the input with default values if it has fewer values than the maximum + if (currentValues.length < dotMaxCount) { + List valuesList = new ArrayList<>(Arrays.asList(currentValues)); + while (valuesList.size() < dotMaxCount) { + valuesList.add(defaultVal); + } + + parametersMap.put(dotInputId, + String.join(ApplicationConstants.SEPARATOR_LIST, valuesList)); + } + } + } + } + @Override public void launchSimulation(Map parametersMap, String applicationName, String applicationVersion, @@ -164,9 +219,13 @@ public void launchSimulation(Map parametersMap, for (Map.Entry p : parametersMap.entrySet()) { logger.info("received param {} : {}", p.getKey(), p.getValue()); } + fillInOverriddenInputs(parametersMap, applicationName, applicationVersion); + fillInDefaultDotInputs(parametersMap, applicationName, applicationVersion); + List> parametersMaps = new ArrayList<>(); + parametersMaps.add(parametersMap); String simulationID = workflowBusiness.launch(user, groups, - parametersMap, applicationName, applicationVersion, simulationName); + parametersMaps, applicationName, applicationVersion, simulationName); trace(logger, "Simulation '" + simulationName + "' launched with ID '" + simulationID + "'."); }