blob: 76652ea6258ddb192b0a015a0625a0bf1b8406b4 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.taverna.scufl2.cwl;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Set;
import java.util.Map;
import java.util.Map.Entry;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.fasterxml.jackson.databind.node.TextNode;
import org.apache.taverna.scufl2.cwl.components.Process;
import org.apache.taverna.scufl2.cwl.components.*;
public class YAMLHelper {
public static final String ARRAY_SPLIT_BRACKETS = "\\[\\]";
public static final String ARRAY_SIGNATURE_BRACKETS = "\\[\\]$";
private static final String INPUTS = "inputs";
private static final String OUTPUTS = "outputs";
private static final String STEPS = "steps";
private static final String ID = "id";
private static final String TYPE = "type";
private static final String ARRAY = "array";
private static final String DESCRIPTION = "description";
private static final int DEPTH_0 = 0;
private static final int DEPTH_1 = 1;
private static final int DEPTH_2 = 2;
private static final String FLOAT = "float";
private static final String NULL = "null";
private static final String BOOLEAN = "boolean";
private static final String INT = "int";
private static final String DOUBLE = "double";
private static final String STRING = "string";
private static final String LABEL = "label";
private static final String FILE = "file";
private static final String DIRECTORY = "directory";
private static final String FORMAT = "format";
private static final String RUN = "run";
private static final String SOURCE = "source";
private JsonNode nameSpace;
public YAMLHelper() {
this.nameSpace = null;
}
public JsonNode getNameSpace() {
return nameSpace;
}
/**
* This method is processing the CWL NameSpace for later use such as
* figuring out the Format of a input or output
*/
public void processNameSpace(JsonNode file) {
if (file != null && file.has("$namespaces")) {
nameSpace = file.path("$namespaces");
}
}
public Map<String, Integer> processInputDepths(JsonNode file) {
return process(file.get(INPUTS));
}
public Map<String, Integer> processOutputDepths(JsonNode file) {
return process(file.get(OUTPUTS));
}
public Map<String, PortDetail> processInputDetails(JsonNode file) {
return processdetails(file.get(INPUTS));
}
public Map<String, PortDetail> processOutputDetails(JsonNode file) {
return processdetails(file.get(OUTPUTS));
}
/**
*
*/
public Set<Step> processSteps(JsonNode file) {
Set<Step> result = new HashSet<>();
if(file == null || !file.has(STEPS)) {
return result;
}
JsonNode steps = file.get(STEPS);
if(steps.isArray()) {
for (JsonNode stepNode : steps) {
Step step = new Step();
String id = stepNode.get(ID).asText();
JsonNode runNode = stepNode.get(RUN);
Process run = ProcessFactory.createProcess(runNode);
run.parse(); // Recursively parse nested process
Set<InputPort> inputs = processStepInput(stepNode.get(INPUTS));
Set<OutputPort> outputs = processStepOutput(stepNode.get(OUTPUTS));
step.setId(id);
step.setRun(run);
step.setInputs(inputs);
step.setOutputs(outputs);
result.add(step);
}
} else if(steps.isObject()) {
Iterator<Entry<String, JsonNode>> iterator = steps.fields();
while(iterator.hasNext()) {
Entry<String, JsonNode> entry = iterator.next();
Step step = new Step();
String id = entry.getKey();
JsonNode value = entry.getValue();
if(value.has(RUN)) {
JsonNode runNode = value.get(RUN);
Process run = ProcessFactory.createProcess(runNode);
run.parse();
step.setRun(run);
}
Set<InputPort> inputs = processStepInput(value.get(INPUTS));
Set<OutputPort> outputs = processStepOutput(value.get(OUTPUTS));
step.setId(id);
step.setInputs(inputs);
step.setOutputs(outputs);
result.add(step);
}
}
return result;
}
private Set<InputPort> processStepInput(JsonNode inputs) {
Set<InputPort> result = new HashSet<>();
if(inputs == null) {
return result;
}
if (inputs.isArray()) {
for (JsonNode input : inputs) {
String id = input.get(ID).asText();
String source = input.get(SOURCE).asText();
result.add(new InputPort(id, source));
}
} else if (inputs.isObject()) {
Iterator<Entry<String, JsonNode>> iterator = inputs.fields();
while (iterator.hasNext()) {
Entry<String, JsonNode> entry = iterator.next();
String id = entry.getKey();
String source = entry.getValue().get(SOURCE).asText();
result.add(new InputPort(id, source));
}
}
return result;
}
private Set<OutputPort> processStepOutput(JsonNode outputs) {
Set<OutputPort> result = new HashSet<>();
if(outputs == null) {
return result;
}
if (outputs.isArray()) {
for (JsonNode output : outputs) {
String id = output.get(ID).asText();
result.add(new OutputPort(id));
}
} else if (outputs.isObject()) {
Iterator<Entry<String, JsonNode>> iterator = outputs.fields();
while (iterator.hasNext()) {
Entry<String, JsonNode> entry = iterator.next();
String id = entry.getKey();
result.add(new OutputPort(id));
}
}
return result;
}
/**
* This method will go through CWL tool input or out puts and figure outs
* their IDs and the respective depths
*
* @param inputs
* This is JsonNode object which contains the Inputs or outputs
* of the respective CWL tool
* @return This the respective, ID and the depth of the input or output
*/
public Map<String, Integer> process(JsonNode inputs) {
Map<String, Integer> result = new HashMap<>();
if (inputs == null)
return result;
if (inputs.getClass() == ArrayNode.class) {
Iterator<JsonNode> iterator = inputs.iterator();
while (iterator.hasNext()) {
JsonNode input = iterator.next();
String currentInputId = input.get(ID).asText();
JsonNode typeConfigurations;
try {
typeConfigurations = input.get(TYPE);
// if type :single argument
if (typeConfigurations.getClass() == TextNode.class) {
// inputs:
/// -id: input_1
//// type: int[]
if (isValidArrayType(typeConfigurations.asText()))
result.put(currentInputId, DEPTH_1);
// inputs:
/// -id: input_1
//// type: int or int?
else
result.put(currentInputId, DEPTH_0);
// type : defined as another map which contains type:
} else if (typeConfigurations.getClass() == ObjectNode.class) {
// inputs:
/// -id: input_1
//// type:
///// type: array or int[]
String inputType = typeConfigurations.get(TYPE).asText();
if (inputType.equals(ARRAY) || isValidArrayType(inputType)) {
result.put(currentInputId, DEPTH_1);
}
// inputs:
// -id: input_1
// type:
// type: ["null",int]
} else if (typeConfigurations.getClass() == ArrayNode.class) {
if (isValidDataType(typeConfigurations)) {
result.put(currentInputId, DEPTH_0);
}
}
} catch (ClassCastException e) {
System.out.println("Class cast exception !!!");
}
}
} else if (inputs.getClass() == ObjectNode.class) {
Iterator<Entry<String, JsonNode>> iterator = inputs.fields();
while (iterator.hasNext()) {
Entry<String, JsonNode> entry = iterator.next();
String currentInputId = entry.getKey();
JsonNode typeConfigurations = entry.getValue();
if (typeConfigurations.getClass() == TextNode.class) {
if (typeConfigurations.asText().startsWith("$")) {
System.out.println("Exception");
}
// inputs:
/// input_1: int[]
else if (isValidArrayType(typeConfigurations.asText()))
result.put(currentInputId, DEPTH_1);
// inputs:
/// input_1: int or int?
else
result.put(currentInputId, DEPTH_0);
} else if (typeConfigurations.getClass() == ObjectNode.class) {
if (typeConfigurations.has(TYPE)) {
JsonNode inputType = typeConfigurations.get(TYPE);
// inputs:
/// input_1:
//// type: [int,"null"]
if (inputType.getClass() == ArrayNode.class) {
if (isValidDataType(inputType))
result.put(currentInputId, DEPTH_0);
} else {
// inputs:
/// input_1:
//// type: array or int[]
if (inputType.asText().equals(ARRAY) || isValidArrayType(inputType.asText()))
result.put(currentInputId, DEPTH_1);
// inputs:
/// input_1:
//// type: int or int?
else
result.put(currentInputId, DEPTH_0);
}
}
}
}
}
return result;
}
/**
* This method is used for extracting details of the CWL tool inputs or
* outputs. ex:Label, Format, Description
*
* @param inputs
* This is JsonNode object which contains the Inputs or outputs
* of the respective CWL tool
* @return
*/
private Map<String, PortDetail> processdetails(JsonNode inputs) {
Map<String, PortDetail> result = new HashMap<>();
if(inputs == null) {
return result;
}
if (inputs.getClass() == ArrayNode.class) {
for (JsonNode input : inputs) {
PortDetail detail = new PortDetail();
String currentInputId = input.get(ID).asText();
getParamDetails(result, input, detail, currentInputId);
}
} else if (inputs.getClass() == ObjectNode.class) {
Iterator<Entry<String, JsonNode>> iterator = inputs.fields();
while (iterator.hasNext()) {
PortDetail detail = new PortDetail();
Entry<String, JsonNode> entry = iterator.next();
getParamDetails(result, entry.getValue(), detail, entry.getKey());
}
}
return result;
}
private void getParamDetails(Map<String, PortDetail> result, JsonNode input, PortDetail detail,
String currentInputId) {
extractDescription(input, detail);
extractFormat(input, detail);
extractLabel(input, detail);
result.put(currentInputId, detail);
}
/**
* This method is used for extracting the Label of a CWL input or Output
*
* @param input
* Single CWL input or output as a JsonNode
* @param detail
* respective PortDetail Object to hold the extracted Label
*/
public void extractLabel(JsonNode input, PortDetail detail) {
if (input != null)
if (input.has(LABEL)) {
detail.setLabel(input.get(LABEL).asText());
} else {
detail.setLabel(null);
}
}
/**
*
* @param input
* Single CWL input or output as a JsonNode
* @param detail
* respective PortDetail Object to hold the extracted Label
*/
public void extractDescription(JsonNode input, PortDetail detail) {
if (input != null)
if (input.has(DESCRIPTION)) {
detail.setDescription(input.get(DESCRIPTION).asText());
} else {
detail.setDescription(null);
}
}
/**
* This method is used for extracting the Formats of a CWL input or Output
* Single argument(Input or Output) can have multiple Formats.
*
* @param input
* Single CWL input or output as a JsonNode
* @param detail
* respective PortDetail Object to hold the extracted Label
*/
public void extractFormat(JsonNode input, PortDetail detail) {
if (input != null)
if (input.has(FORMAT)) {
JsonNode formatInfo = input.get(FORMAT);
ArrayList<String> format = new ArrayList<>();
detail.setFormat(format);
if (formatInfo.getClass() == TextNode.class) {
figureOutFormats(formatInfo.asText(), detail);
} else if (formatInfo.getClass() == ArrayNode.class) {
for (JsonNode eachFormat : formatInfo) {
figureOutFormats(eachFormat.asText(), detail);
}
}
}
}
/**
* Re Format the CWL format using the NameSpace in CWL Tool if possible
* otherwise it doesn't change the current nameSpace => edam:http://edam.org
* format : edam :1245 => http://edamontology.org/1245
*
* @param formatInfoString
* Single Format
* @param detail
* respective PortDetail Object to hold the extracted Label
*/
public void figureOutFormats(String formatInfoString, PortDetail detail) {
if (formatInfoString.startsWith("$")) {
detail.addFormat(formatInfoString);
} else if (formatInfoString.contains(":")) {
String format[] = formatInfoString.split(":");
String namespaceKey = format[0];
String urlAppednd = format[1];
if (nameSpace.has(namespaceKey))
detail.addFormat(nameSpace.get(namespaceKey).asText() + urlAppednd);
else
// can't figure out the format
detail.addFormat(formatInfoString);
} else {
// can't figure out the format
detail.addFormat(formatInfoString);
}
}
/**
* This method is used to check whether the input/output is valid CWL TYPE
* when the type is represented as type: ["null","int"]
*
* @param typeConfigurations
* Type of the CWl input or output
* @return
*/
public boolean isValidDataType(JsonNode typeConfigurations) {
if (typeConfigurations == null)
return false;
for (JsonNode type : typeConfigurations) {
if (!(type.asText().equals(FLOAT) || type.asText().equals(NULL) || type.asText().equals(BOOLEAN)
|| type.asText().equals(INT) || type.asText().equals(STRING) || type.asText().equals(DOUBLE)
|| type.asText().equals(FILE)||type.asText().equals(DIRECTORY)))
return false;
}
return true;
}
/**
*
* This method is for figure out whether the parameter is an array or not.
* As from CWL document v1.0, array can be defined as "TYPE[]". For Example
* : int[] This method will look for "[]" sequence of characters in the end
* of the type and is provided type is a valid CWL TYPE or not
*
* @param type
* type of the CWL parameter
* @return
*/
public boolean isValidArrayType(String type) {
if (type == null)
return false;
Pattern pattern = Pattern.compile(ARRAY_SIGNATURE_BRACKETS);
Matcher matcher = pattern.matcher(type);
ObjectMapper mapper = new ObjectMapper();
ArrayNode node = mapper.createArrayNode();
node.add(type.split(ARRAY_SPLIT_BRACKETS)[0]);
if (matcher.find() && isValidDataType(node))
return true;
else
return false;
}
}