Skip to content

Commit 52f832f

Browse files
Merge pull request #120 from sanctuuary/cwl-annotations
APE 2.5.2 - Enhance CWL parsing and error handling
2 parents 50a5222 + f264171 commit 52f832f

33 files changed

+1082
-168
lines changed

pom.xml

Lines changed: 10 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
<modelVersion>4.0.0</modelVersion>
55
<groupId>io.github.sanctuuary</groupId>
66
<artifactId>APE</artifactId>
7-
<version>2.4.0</version>
7+
<version>2.5.2</version>
88
<packaging>jar</packaging>
99
<name>io.github.sanctuuary:APE</name>
1010
<description>APE is a command line tool and an API for the automated exploration of possible computational pipelines (workflows) from large collections of computational tools.</description>
@@ -27,7 +27,7 @@
2727
<scm>
2828
<connection>scm:git:git://github.com/sanctuuary/APE.git</connection>
2929
<developerConnection>scm:git:ssh://github.com:sanctuuary/APE.git</developerConnection>
30-
<url>https://github.com/sanctuuary/APE/tree/master</url>
30+
<url>https://github.com/sanctuuary/APE/tree/main</url>
3131
</scm>
3232
<properties>
3333
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
@@ -115,16 +115,14 @@
115115
</executions>
116116
</plugin>
117117
<plugin>
118-
<groupId>org.sonatype.plugins</groupId>
119-
<artifactId>nexus-staging-maven-plugin</artifactId>
120-
<version>1.6.13</version>
121-
<extensions>true</extensions>
122-
<configuration>
123-
<serverId>ossrh</serverId>
124-
<nexusUrl>https://oss.sonatype.org/</nexusUrl>
125-
<autoReleaseAfterClose>true</autoReleaseAfterClose>
126-
</configuration>
127-
</plugin>
118+
<groupId>org.sonatype.central</groupId>
119+
<artifactId>central-publishing-maven-plugin</artifactId>
120+
<version>0.7.0</version>
121+
<extensions>true</extensions>
122+
<configuration>
123+
<publishingServerId>central</publishingServerId>
124+
</configuration>
125+
</plugin>
128126
<plugin>
129127
<groupId>org.apache.maven.plugins</groupId>
130128
<artifactId>maven-gpg-plugin</artifactId>
@@ -229,6 +227,5 @@
229227
<version>1.18.26</version>
230228
<scope>provided</scope>
231229
</dependency>
232-
233230
</dependencies>
234231
</project>

src/main/java/nl/uu/cs/ape/Main.java

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,11 @@
33
import lombok.extern.slf4j.Slf4j;
44
import nl.uu.cs.ape.configuration.APEConfigException;
55
import nl.uu.cs.ape.configuration.APERunConfig;
6+
import nl.uu.cs.ape.domain.APEDimensionsException;
67
import nl.uu.cs.ape.domain.BioToolsAPI;
78
import nl.uu.cs.ape.utils.APEFiles;
89
import nl.uu.cs.ape.utils.APEUtils;
10+
import nl.uu.cs.ape.utils.WorkflomicsConstants;
911
import nl.uu.cs.ape.solver.solutionStructure.SolutionsList;
1012
import nl.uu.cs.ape.solver.solutionStructure.cwl.ToolCWLCreator;
1113
import nl.uu.cs.ape.models.Module;
@@ -30,7 +32,7 @@
3032
@Slf4j
3133
public class Main {
3234

33-
private static final String biotools_config_URL = "https://raw.githubusercontent.com/Workflomics/tools-and-domains/refs/heads/main/domains/bio.tools/config.json";
35+
private static final String biotools_config_URL = WorkflomicsConstants.BIOTOOLS_CONFIG_URL;
3436

3537
/**
3638
* The entry point of application when the library is used in a Command Line
@@ -84,7 +86,17 @@ private static void pullATool(String[] args) {
8486

8587
try {
8688
JSONArray tool = BioToolsAPI.getAndConvertToolList(List.of(biotoolsID)).getJSONArray("functions");
87-
APEFiles.write2file(tool.toString(4), new File("./tool.json"), false);
89+
90+
String cwlURL = WorkflomicsConstants.getCwlToolUrl(biotoolsID);
91+
92+
JSONArray toolArray = new JSONArray();
93+
JSONObject toolEntry = new JSONObject();
94+
toolEntry.put("type", "CWL_ANNOTATION");
95+
toolEntry.put("cwl_reference", cwlURL);
96+
toolArray.put(toolEntry);
97+
98+
APEFiles.write2file(toolArray.toString(4), new File("./tool.json"), false);
99+
88100
for (JSONObject toolAnnotation : APEUtils.getJSONListFromJSONArray(tool)) {
89101
APE apeFramework = new APE(biotools_config_URL);
90102

@@ -97,9 +109,9 @@ private static void pullATool(String[] args) {
97109
}
98110
ToolCWLCreator toolCWLCreator = new ToolCWLCreator(cometModule.get());
99111

100-
APEFiles.write2file(toolCWLCreator.generate(), new File("./" + toolAnnotation.getString("id") + ".cwl"), false);
112+
APEFiles.write2file(toolCWLCreator.generate(), new File("./" + toolAnnotation.getString("id") + ".cwl"),
113+
false);
101114
}
102-
103115

104116
} catch (IOException e) {
105117
log.error("Error in fetching the tool from bio.tools.");
@@ -207,6 +219,10 @@ public static void executeSynthesis(String[] args) {
207219
log.error("Error in synthesis execution.");
208220
log.error(e.getMessage());
209221
return;
222+
} catch (APEDimensionsException e) {
223+
log.error("Error in synthesis execution.");
224+
log.error(e.getMessage());
225+
return;
210226
}
211227

212228
/*

src/main/java/nl/uu/cs/ape/configuration/APECoreConfig.java

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@
2020
import java.io.IOException;
2121
import java.nio.charset.StandardCharsets;
2222
import java.util.List;
23-
import java.util.Optional;
2423
import java.util.stream.Collectors;
2524

2625
/**

src/main/java/nl/uu/cs/ape/configuration/ToolAnnotationTag.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,11 @@
1010
@AllArgsConstructor
1111
public enum ToolAnnotationTag {
1212

13+
/**
14+
* Type of the tool annotation.
15+
*/
16+
TYPE("type"),
17+
1318
/**
1419
* Represents the ID of a tool.
1520
*/

src/main/java/nl/uu/cs/ape/domain/APEDimensionsException.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ private APEDimensionsException(String message) {
3232
* the problem.
3333
*/
3434
public static APEDimensionsException dimensionsOverlap(String message) {
35-
return new APEDimensionsException(String.format("The data dimensions cannot overlap. %s", message));
35+
return new APEDimensionsException(String.format("Issue with the ontology structure: The data dimensions cannot overlap. %s", message));
3636
}
3737

3838
/**

src/main/java/nl/uu/cs/ape/domain/APEDomainSetup.java

Lines changed: 170 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@
1919
import nl.uu.cs.ape.constraints.ConstraintTemplateParameter;
2020
import nl.uu.cs.ape.utils.APEFiles;
2121
import nl.uu.cs.ape.utils.APEUtils;
22+
import nl.uu.cs.ape.utils.cwl_parser.CWLData;
23+
import nl.uu.cs.ape.utils.cwl_parser.CWLParser;
2224
import nl.uu.cs.ape.models.AbstractModule;
2325
import nl.uu.cs.ape.models.AllModules;
2426
import nl.uu.cs.ape.models.AllTypes;
@@ -27,6 +29,7 @@
2729
import nl.uu.cs.ape.models.Module;
2830
import nl.uu.cs.ape.models.SATAtomMappings;
2931
import nl.uu.cs.ape.models.Type;
32+
import nl.uu.cs.ape.models.enums.ToolAnnotationType;
3033
import nl.uu.cs.ape.models.logic.constructs.TaxonomyPredicate;
3134

3235
/**
@@ -59,7 +62,8 @@ public class APEDomainSetup {
5962

6063
@Setter
6164
/**
62-
* Object used to write locally CNF SAT problem specification (in human readable format).
65+
* Object used to write locally CNF SAT problem specification (in human readable
66+
* format).
6367
*/
6468
private String writeLocalCNF = null;
6569

@@ -325,15 +329,162 @@ public boolean annotateToolFromJson(JSONObject toolAnnotationsFile) throws IOExc
325329
}
326330

327331
/**
328-
* Creates/updates a module from a tool annotation instance from a JSON file and
329-
* updates the list of modules ({@link AllModules}) in the domain accordingly.
330-
*
331-
* @param jsonModule JSON representation of a module
332+
* Parse the tool annotation from a JSON file and update the module in the
333+
* domain ({@link AllModules}) accordingly.
334+
*
335+
* @param jsonModule JSON annotation of a module/tool
332336
* @return {@code true} if the domain was updated, false otherwise.
333337
* @throws JSONException Error if the JSON file was not properly formatted.
338+
* @throws APEDimensionsException
339+
* @throws IOException
334340
*/
335341
public Optional<Module> updateModuleFromJson(JSONObject jsonModule)
342+
throws JSONException, APEDimensionsException, IOException {
343+
344+
ToolAnnotationType annotationType;
345+
try {
346+
annotationType = ToolAnnotationType
347+
.fromString(jsonModule.getString(ToolAnnotationTag.TYPE.toString()));
348+
} catch (JSONException e) {
349+
log.debug("Tool annotation type not specified. Defaulting to APE annotation.");
350+
return updateModuleFromJsonAPE(jsonModule);
351+
}
352+
353+
switch (annotationType) {
354+
case APE_ANNOTATION:
355+
return updateModuleFromJsonAPE(jsonModule);
356+
case CWL_ANNOTATION:
357+
String cwlURL = jsonModule.getString(ToolAnnotationTag.CWL_REFERENCE.toString());
358+
return updateModuleFromCWL(cwlURL);
359+
default:
360+
log.warn("Tool annotation format not specified. Using default APE annotations.");
361+
return updateModuleFromJsonAPE(jsonModule);
362+
}
363+
}
364+
365+
/**
366+
* Parse the tool annotation from a CWL file and update the module in the
367+
* domain ({@link AllModules}) accordingly. The annotations are expected to
368+
* follow EDAM ontology.
369+
*
370+
* @param cwlFileLocation path to the CWL file (URL or local file) containing
371+
* the tool annotations
372+
* @return {@code true} if the domain was updated, false otherwise.
373+
* @throws IOException Error in accessing or parsing the CWL file.
374+
*/
375+
public Optional<Module> updateModuleFromCWL(String cwlFileLocation) throws IOException {
376+
377+
// Initialize CWL parser
378+
CWLParser cwlParser = new CWLParser(cwlFileLocation);
379+
380+
// Extract the module's label and ID
381+
String moduleLabel = (String) cwlParser.getField("label");
382+
String moduleIRI = APEUtils.createClassIRI(moduleLabel, ontologyPrefixIRI);
383+
384+
if (allModules.get(moduleIRI) != null) {
385+
moduleIRI = moduleIRI + "[tool]";
386+
}
387+
388+
// Extract taxonomy operations
389+
Set<String> taxonomyOperations = new HashSet<>(cwlParser.getOperations());
390+
Set<String> taxonomyParentModules = APEUtils.createIRIsFromLabels(taxonomyOperations, ontologyPrefixIRI);
391+
392+
// Validate taxonomy parent modules
393+
List<String> toRemove = new ArrayList<>();
394+
for (String parentModule : taxonomyParentModules) {
395+
String parentModuleIRI = APEUtils.createClassIRI(parentModule, ontologyPrefixIRI);
396+
if (allModules.get(parentModuleIRI) == null) {
397+
log.debug("Tool '" + moduleIRI + "' annotation issue. " +
398+
"Referenced taxonomy operation: '" + parentModuleIRI + "' cannot be found.");
399+
wrongToolTax.add(moduleLabel);
400+
toRemove.add(parentModuleIRI);
401+
}
402+
}
403+
taxonomyParentModules.removeAll(toRemove);
404+
405+
if (taxonomyParentModules.isEmpty()) {
406+
log.debug("Tool '" + moduleIRI + "' annotation issue. " +
407+
"No valid taxonomy operations found. Using root module as fallback.");
408+
taxonomyParentModules.add(allModules.getRootModuleID());
409+
}
410+
411+
/*
412+
* Set the inputs and outputs of the module. If the inputs and outputs are not
413+
* valid, the module is not added to the domain.
414+
*/
415+
List<Type> inputs = new ArrayList<>();
416+
List<String> inputCWLKeys = new ArrayList<>();
417+
List<Type> outputs = new ArrayList<>();
418+
List<String> outputCWLKeys = new ArrayList<>();
419+
try {
420+
List<CWLData> inputsRaw = cwlParser.getInputs();
421+
for (CWLData inputRaw : inputsRaw) {
422+
inputs.add(Type.taxonomyInstanceFromCWLData(inputRaw, this, false));
423+
inputCWLKeys.add(inputRaw.getCwlFieldID());
424+
}
425+
updateMaxNoToolInputs(inputs.size());
426+
427+
List<CWLData> outputsRaw = cwlParser.getOutputs();
428+
for (CWLData outputRaw : outputsRaw) {
429+
outputs.add(Type.taxonomyInstanceFromCWLData(outputRaw, this, true));
430+
outputCWLKeys.add(outputRaw.getCwlFieldID());
431+
}
432+
updateMaxNoToolOutputs(outputs.size());
433+
434+
if (inputs.isEmpty() && outputs.isEmpty()) {
435+
emptyTools.add(moduleLabel);
436+
log.debug("Operation '" + moduleLabel
437+
+ "' was not included as it has no (valid) inputs and outputs specified.");
438+
return Optional.empty();
439+
}
440+
441+
} catch (APEDimensionsException badDimension) {
442+
wrongToolIO.add(moduleLabel);
443+
log.debug("Operation '" + moduleLabel + "' was not included. " + badDimension.getMessage());
444+
return Optional.empty();
445+
}
446+
447+
/* Set the implementation cwl file reference of the module, if specified. */
448+
String cwlReference = cwlFileLocation;
449+
450+
/*
451+
* Add the module and make it sub module of the currSuperModule (if it was not
452+
* previously defined)
453+
*/
454+
Module currModule = (Module) allModules
455+
.addPredicate(
456+
new Module(moduleLabel, moduleIRI, allModules.getRootModuleID(), cwlReference, null));
457+
458+
/* For each parent module add the current module as a subset and vice versa. */
459+
for (String parentModuleID : taxonomyParentModules) {
460+
AbstractModule parentModule = allModules.get(parentModuleID);
461+
if (parentModule != null) {
462+
parentModule.addSubPredicate(currModule);
463+
currModule.addParentPredicate(parentModule);
464+
}
465+
}
466+
467+
currModule.setModuleInput(inputs);
468+
currModule.setModuleCWLInputKeys(inputCWLKeys);
469+
currModule.setModuleOutput(outputs);
470+
currModule.setModuleCWLOutputKeys(outputCWLKeys);
471+
currModule.setAsRelevantTaxonomyTerm(allModules);
472+
473+
return Optional.of(currModule);
474+
}
475+
476+
477+
/**
478+
* Parse the tool annotation from a JSON file and update the module in the
479+
* domain ({@link AllModules}) accordingly.
480+
*
481+
* @param jsonModule JSON annotation of a module/tool
482+
* @return {@code true} if the domain was updated, false otherwise.
483+
* @throws JSONException Error if the JSON file was not properly formatted.
484+
*/
485+
public Optional<Module> updateModuleFromJsonAPE(JSONObject jsonModule)
336486
throws JSONException, APEDimensionsException {
487+
337488
String moduleIRI = APEUtils.createClassIRI(jsonModule.getString(ToolAnnotationTag.ID.toString()),
338489
ontologyPrefixIRI);
339490
if (allModules.get(moduleIRI) != null) {
@@ -384,7 +535,8 @@ public Optional<Module> updateModuleFromJson(JSONObject jsonModule)
384535

385536
if (inputs.isEmpty() && outputs.isEmpty()) {
386537
emptyTools.add(moduleLabel);
387-
log.debug("Operation '" + moduleLabel + "' was not included as it has no (valid) inputs and outputs specified.");
538+
log.debug("Operation '" + moduleLabel
539+
+ "' was not included as it has no (valid) inputs and outputs specified.");
388540
return Optional.empty();
389541
}
390542

@@ -405,7 +557,8 @@ public Optional<Module> updateModuleFromJson(JSONObject jsonModule)
405557
* previously defined)
406558
*/
407559
Module currModule = (Module) allModules
408-
.addPredicate(new Module(moduleLabel, moduleIRI, allModules.getRootModuleID(), cwlReference, executionCode));
560+
.addPredicate(
561+
new Module(moduleLabel, moduleIRI, allModules.getRootModuleID(), cwlReference, executionCode));
409562

410563
/* For each parent module add the current module as a subset and vice versa. */
411564
for (String parentModuleID : taxonomyParentModules) {
@@ -461,16 +614,20 @@ private List<Type> getToolOutputsFromAnnotation(JSONObject jsonModule) throws AP
461614
return outputs;
462615
}
463616

464-
/**
617+
/**
465618
* Get the implementation code of the module, if specified.
466619
*
467620
* @param jsonToolAnnotation the json tool annotation
468-
* @param implementationType the implementation type ({@link ToolAnnotationTag#CWL_REFERENCE} or {@link ToolAnnotationTag#CODE})
621+
* @param implementationType the implementation type
622+
* ({@link ToolAnnotationTag#CWL_REFERENCE} or
623+
* {@link ToolAnnotationTag#CODE})
469624
* @return The implementation code of the module, if specified.
470625
*/
471-
private String getModuleImplementationFromAnnotation(JSONObject jsonToolAnnotation, ToolAnnotationTag implementationType) {
626+
private String getModuleImplementationFromAnnotation(JSONObject jsonToolAnnotation,
627+
ToolAnnotationTag implementationType) {
472628
try {
473-
JSONObject implementationJson = jsonToolAnnotation.getJSONObject(ToolAnnotationTag.IMPLEMENTATION.toString());
629+
JSONObject implementationJson = jsonToolAnnotation
630+
.getJSONObject(ToolAnnotationTag.IMPLEMENTATION.toString());
474631
String implementation = implementationJson.getString(implementationType.toString());
475632
if (implementation.equals("")) {
476633
return null;
@@ -566,15 +723,15 @@ public List<AuxiliaryPredicate> getHelperPredicates() {
566723
* Write locally the SAT (CNF) workflow specification in human readable format.
567724
*
568725
* @param satInputFile File containing the SAT problem specification.
569-
* @param mappings Mappings between the SAT problem and the domain.
726+
* @param mappings Mappings between the SAT problem and the domain.
570727
* @throws IOException Error in writing the file to the local file system.
571728
*/
572729
public void localCNF(File satInputFile, SATAtomMappings mappings) throws IOException {
573730
if (writeLocalCNF != null) {
574731
FileInputStream cnfStream = new FileInputStream(satInputFile);
575732
String encoding = APEUtils.convertCNF2humanReadable(cnfStream, mappings);
576733
cnfStream.close();
577-
734+
578735
APEFiles.write2file(encoding, new File(writeLocalCNF), false);
579736
}
580737
}

0 commit comments

Comments
 (0)