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
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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<Object>) e.getInputValues().computeIfAbsent(key, k -> new ArrayList<>())).add(value);
}
// retrieves results directory
List<Object> resDirList = (List<Object>) 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<String, Object> inputMap : e.getInputValues()) {
((List<Object>) inputMap.computeIfAbsent(key, k -> new ArrayList<>())).add(value);

// retrieves results directory
List<Object> resDirList = (List<Object>) 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<InOutData> outputs = workflowBusiness.getOutputData(s.getID(), userFolder);
for (InOutData iod : outputs) {
String key = iod.getProcessor();
Expand Down Expand Up @@ -315,25 +321,35 @@ public void updateExecution(Execution execution) throws VipException {
}

public String initExecution(Execution execution) throws VipException {
Map<String, String> inputMap = new HashMap<>();
List<Map<String, String>> inputMaps = new ArrayList<>();
Object resultsLocation = execution.getResultsLocation();
boolean isInputMapList = execution.getInputValues().size() > 1;
for (Map<String, Object> inputValuesMap : execution.getInputValues()) {
Map<String, String> inputMap = new HashMap<>();
for (Entry<String, Object> 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<String,Object> 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<String>
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<String>
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());
}

Expand Down Expand Up @@ -378,7 +394,7 @@ private void checkInputExecNameIsValid(String input) throws VipException {
}

private String initExecution(String pipelineId,
Map<String, String> inputValues,
List<Map<String, String>> inputValues,
Integer timeoutInSeconds,
String executionName,
String studyId) throws VipException {
Expand All @@ -400,40 +416,38 @@ private String initExecution(String pipelineId,
if (pp.isReturnedValue()) {
continue;
}

List<Map<String, String>> 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<String, String> 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<String, String> 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));

Expand Down Expand Up @@ -538,4 +552,78 @@ public void checkIfUserCanAccessExecution(String executionId) throws VipExceptio
throw new VipException("Permission denied");
}

private void fillInOverriddenInputs(List<Map<String, String>> inputValues, Pipeline p) throws VipException {
// fill in overriddenInputs from explicit inputs
Map<String, String> overriddenInputs = p.getOverriddenInputs();
if (overriddenInputs != null) {
for (String key : overriddenInputs.keySet()) {
String value = overriddenInputs.get(key);
for (Map<String, String> 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<Map<String, String>> inputValues, Pipeline p) throws VipException {
List<String> 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<PipelineParameter> 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<String, String> 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<String> valuesList = new ArrayList<>(Arrays.asList(currentValues));
while (valuesList.size() < dotMaxCount) {
valuesList.add(defaultVal);
}

inputMap.put(pp.getName(),
String.join(ApplicationConstants.SEPARATOR_LIST, valuesList));
}
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,7 @@ private Pipeline getPipelineFromBoutiquesDescriptor(String pipelineId) throws Vi
p.setDescription(boutiques.getDescription());

Map<String, String> overriddenInputs = boutiquesBusiness.getOverriddenInputs(boutiques);
List<String> dotInputs = boutiquesBusiness.getDotInputs(boutiques);
for (Input input : boutiques.getInputs()) {
if (overriddenInputs != null && overriddenInputs.containsKey(input.getId())) {
continue; // hide overriddenInputs from pipeline visible parameters
Expand All @@ -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;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -13,7 +18,8 @@ public class Execution {
private int timeout;
private ExecutionStatus status;
@NotNull
private Map<String, java.lang.Object> inputValues;
@JsonDeserialize(using = InputValuesDeserializer.class)
private List<Map<String, Object>> inputValues;
private Map<String, List<java.lang.Object>> returnedFiles;

// optional arguments
Expand All @@ -25,7 +31,7 @@ public class Execution {
private Map<Integer, Map<String, Object>> jobs; // jobId -> status

public Execution() {
inputValues = new HashMap<>();
inputValues = new ArrayList<>();
returnedFiles = new HashMap<>();
jobs = new HashMap<>();
}
Expand Down Expand Up @@ -94,11 +100,11 @@ public void setStatus(ExecutionStatus status) {
this.status = status;
}

public Map<String, java.lang.Object> getInputValues() {
public List<Map<String, Object>> getInputValues() {
return inputValues;
}

public void setInputValues(Map<String, java.lang.Object> inputValues) {
public void setInputValues(List<Map<String, Object>> inputValues) {
this.inputValues = inputValues;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -16,6 +17,8 @@ public class Pipeline {
private boolean canExecute;
@JsonIgnore
private Map<String, String> overriddenInputs;
@JsonIgnore
private List<String> dotInputs;

public Pipeline() {
}
Expand Down Expand Up @@ -64,4 +67,13 @@ public Map<String, String> getOverriddenInputs() {
public void setOverriddenInputs(Map<String, String> overriddenInputs) {
this.overriddenInputs = overriddenInputs;
}

public List<String> getDotInputs() {
return dotInputs;
}

public void setDotInputs(List<String> dotInputs) {
this.dotInputs = dotInputs;
}

}
Original file line number Diff line number Diff line change
@@ -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<List<Map<String, Object>>> {

public InputValuesDeserializer() {
super(List.class);
}

@Override
public List<Map<String, Object>> 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<Map<String, Object>> 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<String, Object> 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");
}
}
}
Loading
Loading