Skip to content

Commit 9edaf59

Browse files
committed
add "requirements" and "auxFilesExist" to external tools IQSS#9153
The use case is an external tool that operates on aux files pulled out of NetCDF/HDF5 files.
1 parent 0e00766 commit 9edaf59

10 files changed

Lines changed: 306 additions & 12 deletions

File tree

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
{
2+
"displayName": "AuxFileViewer",
3+
"description": "Show an auxiliary file from a dataset file.",
4+
"toolName": "auxPreviewer",
5+
"scope": "file",
6+
"types": [
7+
"preview"
8+
],
9+
"toolUrl": "https://example.com/AuxFileViewer.html",
10+
"toolParameters": {
11+
"queryParameters": [
12+
{
13+
"fileid": "{fileId}"
14+
}
15+
]
16+
},
17+
"requirements": {
18+
"auxFilesExist": [
19+
{
20+
"formatTag": "myFormatTag",
21+
"formatVersion": "0.1"
22+
}
23+
]
24+
},
25+
"contentType": "application/foobar"
26+
}

doc/sphinx-guides/source/api/external-tools.rst

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,15 +53,21 @@ External tools must be expressed in an external tool manifest file, a specific J
5353
Examples of Manifests
5454
+++++++++++++++++++++
5555

56-
Let's look at two examples of external tool manifests (one at the file level and one at the dataset level) before we dive into how they work.
56+
Let's look at a few examples of external tool manifests (both at the file level and at the dataset level) before we dive into how they work.
57+
58+
.. _tools-for-files:
5759

5860
External Tools for Files
5961
^^^^^^^^^^^^^^^^^^^^^^^^
6062

61-
:download:`fabulousFileTool.json <../_static/installation/files/root/external-tools/fabulousFileTool.json>` is a file level both an "explore" tool and a "preview" tool that operates on tabular files:
63+
:download:`fabulousFileTool.json <../_static/installation/files/root/external-tools/fabulousFileTool.json>` is a file level (both an "explore" tool and a "preview" tool) that operates on tabular files:
6264

6365
.. literalinclude:: ../_static/installation/files/root/external-tools/fabulousFileTool.json
6466

67+
:download:`auxFileTool.json <../_static/installation/files/root/external-tools/auxFileTool.json>` is a file level preview tool that operates on auxiliary files associated with a data file (note the "requirements" section):
68+
69+
.. literalinclude:: ../_static/installation/files/root/external-tools/auxFileTool.json
70+
6571
External Tools for Datasets
6672
^^^^^^^^^^^^^^^^^^^^^^^^^^^
6773

@@ -113,6 +119,10 @@ Terminology
113119
allowedApiCalls httpMethod Which HTTP method the specified callback uses such as ``GET`` or ``POST``.
114120

115121
allowedApiCalls timeOut For non-public datasets and datafiles, how many minutes the signed URLs given to the tool should be valid for. Must be an integer.
122+
123+
requirements **Resources your tool needs to function.** For now, the only requirement you can specify is that one or more auxiliary files exist (see auxFilesExist in the :ref:`tools-for-files` example). Currently, requirements only apply to preview tools. If the requirements are not met, the preview tool is not shown.
124+
125+
auxFilesExist **An array containing formatTag and formatVersion pairs** for each auxiliary file that your tool needs to download to function properly. For example, a required aux file could have a ``formatTag`` of "NcML" and a ``formatVersion`` of "1.0". See also :doc:`/developers/aux-file-support`.
116126

117127
toolName A **name** of an external tool that is used to differentiate between external tools and also used in bundle.properties for localization in the Dataverse installation web interface. For example, the toolName for Data Explorer is ``explorer``. For the Data Curation Tool the toolName is ``dct``. This is an optional parameter in the manifest JSON file.
118128
=========================== ==========

src/main/java/edu/harvard/iq/dataverse/DatasetPage.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5490,7 +5490,7 @@ public List<ExternalTool> getCachedToolsForDataFile(Long fileId, ExternalTool.Ty
54905490
return cachedTools;
54915491
}
54925492
DataFile dataFile = datafileService.find(fileId);
5493-
cachedTools = ExternalToolServiceBean.findExternalToolsByFile(externalTools, dataFile);
5493+
cachedTools = externalToolService.findExternalToolsByFile(externalTools, dataFile);
54945494
cachedToolsByFileId.put(fileId, cachedTools); //add to map so we don't have to do the lifting again
54955495
return cachedTools;
54965496
}

src/main/java/edu/harvard/iq/dataverse/FilePage.java

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
import edu.harvard.iq.dataverse.util.JsfHelper;
4040
import static edu.harvard.iq.dataverse.util.JsfHelper.JH;
4141
import edu.harvard.iq.dataverse.util.SystemConfig;
42+
import edu.harvard.iq.dataverse.util.json.JsonUtil;
4243
import java.io.IOException;
4344
import java.time.format.DateTimeFormatter;
4445
import java.util.ArrayList;
@@ -57,6 +58,9 @@
5758
import javax.faces.view.ViewScoped;
5859
import javax.inject.Inject;
5960
import javax.inject.Named;
61+
import javax.json.JsonArray;
62+
import javax.json.JsonObject;
63+
import javax.json.JsonValue;
6064
import javax.validation.ConstraintViolation;
6165

6266
import org.primefaces.PrimeFaces;
@@ -125,6 +129,8 @@ public class FilePage implements java.io.Serializable {
125129
ExternalToolServiceBean externalToolService;
126130
@EJB
127131
PrivateUrlServiceBean privateUrlService;
132+
@EJB
133+
AuxiliaryFileServiceBean auxiliaryFileService;
128134

129135
@Inject
130136
DataverseRequestServiceBean dvRequestService;
@@ -285,8 +291,15 @@ public void setDatasetVersionId(Long datasetVersionId) {
285291
this.datasetVersionId = datasetVersionId;
286292
}
287293

294+
// findPreviewTools would be a better name
288295
private List<ExternalTool> sortExternalTools(){
289-
List<ExternalTool> retList = externalToolService.findFileToolsByTypeAndContentType(ExternalTool.Type.PREVIEW, file.getContentType());
296+
List<ExternalTool> retList = new ArrayList<>();
297+
List<ExternalTool> previewTools = externalToolService.findFileToolsByTypeAndContentType(ExternalTool.Type.PREVIEW, file.getContentType());
298+
for (ExternalTool previewTool : previewTools) {
299+
if (externalToolService.meetsRequirements(previewTool, file)) {
300+
retList.add(previewTool);
301+
}
302+
}
290303
Collections.sort(retList, CompareExternalToolName);
291304
return retList;
292305
}

src/main/java/edu/harvard/iq/dataverse/api/TestApi.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,9 @@ public Response getExternalToolsForFile(@PathParam("id") String idSupplied, @Que
6363
ApiToken apiToken = externalToolService.getApiToken(getRequestApiKey());
6464
ExternalToolHandler externalToolHandler = new ExternalToolHandler(tool, dataFile, apiToken, dataFile.getFileMetadata(), null);
6565
JsonObjectBuilder toolToJson = externalToolService.getToolAsJsonWithQueryParameters(externalToolHandler);
66-
tools.add(toolToJson);
66+
if (externalToolService.meetsRequirements(tool, dataFile)) {
67+
tools.add(toolToJson);
68+
}
6769
}
6870
return ok(tools);
6971
} catch (WrappedResponse wr) {

src/main/java/edu/harvard/iq/dataverse/externaltools/ExternalTool.java

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ public class ExternalTool implements Serializable {
3939
public static final String CONTENT_TYPE = "contentType";
4040
public static final String TOOL_NAME = "toolName";
4141
public static final String ALLOWED_API_CALLS = "allowedApiCalls";
42+
public static final String REQUIREMENTS = "requirements";
4243

4344
@Id
4445
@GeneratedValue(strategy = GenerationType.IDENTITY)
@@ -103,6 +104,15 @@ public class ExternalTool implements Serializable {
103104
@Column(nullable = true, columnDefinition = "TEXT")
104105
private String allowedApiCalls;
105106

107+
/**
108+
* When non-null, the tool has indicated that it has certain requirements
109+
* that must be met before it should be shown to the user. This
110+
* functionality was added for tools that operate on aux files rather than
111+
* data files so "auxFilesExist" is one of the possible values.
112+
*/
113+
@Column(nullable = true, columnDefinition = "TEXT")
114+
private String requirements;
115+
106116
/**
107117
* This default constructor is only here to prevent this error at
108118
* deployment:
@@ -118,10 +128,10 @@ public ExternalTool() {
118128
}
119129

120130
public ExternalTool(String displayName, String toolName, String description, List<ExternalToolType> externalToolTypes, Scope scope, String toolUrl, String toolParameters, String contentType) {
121-
this(displayName, toolName, description, externalToolTypes, scope, toolUrl, toolParameters, contentType, null);
131+
this(displayName, toolName, description, externalToolTypes, scope, toolUrl, toolParameters, contentType, null, null);
122132
}
123133

124-
public ExternalTool(String displayName, String toolName, String description, List<ExternalToolType> externalToolTypes, Scope scope, String toolUrl, String toolParameters, String contentType, String allowedApiCalls) {
134+
public ExternalTool(String displayName, String toolName, String description, List<ExternalToolType> externalToolTypes, Scope scope, String toolUrl, String toolParameters, String contentType, String allowedApiCalls, String requirements) {
125135
this.displayName = displayName;
126136
this.toolName = toolName;
127137
this.description = description;
@@ -131,6 +141,7 @@ public ExternalTool(String displayName, String toolName, String description, Lis
131141
this.toolParameters = toolParameters;
132142
this.contentType = contentType;
133143
this.allowedApiCalls = allowedApiCalls;
144+
this.requirements = requirements;
134145
}
135146

136147
public enum Type {
@@ -326,5 +337,12 @@ public void setAllowedApiCalls(String allowedApiCalls) {
326337
this.allowedApiCalls = allowedApiCalls;
327338
}
328339

340+
public String getRequirements() {
341+
return requirements;
342+
}
343+
344+
public void setRequirements(String requirements) {
345+
this.requirements = requirements;
346+
}
329347

330348
}

src/main/java/edu/harvard/iq/dataverse/externaltools/ExternalToolServiceBean.java

Lines changed: 41 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
package edu.harvard.iq.dataverse.externaltools;
22

3+
import edu.harvard.iq.dataverse.AuxiliaryFile;
4+
import edu.harvard.iq.dataverse.AuxiliaryFileServiceBean;
35
import edu.harvard.iq.dataverse.DataFile;
46
import edu.harvard.iq.dataverse.DataFileServiceBean;
57
import edu.harvard.iq.dataverse.authorization.users.ApiToken;
@@ -30,6 +32,8 @@
3032
import static edu.harvard.iq.dataverse.externaltools.ExternalTool.*;
3133
import java.util.stream.Collectors;
3234
import java.util.stream.Stream;
35+
import javax.ejb.EJB;
36+
import javax.json.JsonValue;
3337

3438
@Stateless
3539
@Named
@@ -40,6 +44,9 @@ public class ExternalToolServiceBean {
4044
@PersistenceContext(unitName = "VDCNet-ejbPU")
4145
private EntityManager em;
4246

47+
@EJB
48+
AuxiliaryFileServiceBean auxiliaryFileService;
49+
4350
public List<ExternalTool> findAll() {
4451
TypedQuery<ExternalTool> typedQuery = em.createQuery("SELECT OBJECT(o) FROM ExternalTool AS o ORDER BY o.id", ExternalTool.class);
4552
return typedQuery.getResultList();
@@ -133,20 +140,45 @@ public ExternalTool save(ExternalTool externalTool) {
133140
* file supports The list of tools is passed in so it doesn't hit the
134141
* database each time
135142
*/
136-
public static List<ExternalTool> findExternalToolsByFile(List<ExternalTool> allExternalTools, DataFile file) {
143+
public List<ExternalTool> findExternalToolsByFile(List<ExternalTool> allExternalTools, DataFile file) {
137144
List<ExternalTool> externalTools = new ArrayList<>();
138145
//Map tabular data to it's mimetype (the isTabularData() check assures that this code works the same as before, but it may need to change if tabular data is split into subtypes with differing mimetypes)
139146
final String contentType = file.isTabularData() ? DataFileServiceBean.MIME_TYPE_TSV_ALT : file.getContentType();
140147
allExternalTools.forEach((externalTool) -> {
141-
//Match tool and file type
142-
if (contentType.equals(externalTool.getContentType())) {
148+
//Match tool and file type, then check requirements
149+
if (contentType.equals(externalTool.getContentType()) && meetsRequirements(externalTool, file)) {
143150
externalTools.add(externalTool);
144151
}
145152
});
146153

147154
return externalTools;
148155
}
149156

157+
public boolean meetsRequirements(ExternalTool externalTool, DataFile dataFile) {
158+
String requirements = externalTool.getRequirements();
159+
if (requirements == null) {
160+
logger.fine("Data file id" + dataFile.getId() + ": no requirements for tool id " + externalTool.getId());
161+
return true;
162+
}
163+
boolean meetsRequirements = true;
164+
JsonObject requirementsObj = JsonUtil.getJsonObject(requirements);
165+
JsonArray auxFilesExist = requirementsObj.getJsonArray("auxFilesExist");
166+
for (JsonValue jsonValue : auxFilesExist) {
167+
String formatTag = jsonValue.asJsonObject().getString("formatTag");
168+
String formatVersion = jsonValue.asJsonObject().getString("formatVersion");
169+
AuxiliaryFile auxFile = auxiliaryFileService.lookupAuxiliaryFile(dataFile, formatTag, formatVersion);
170+
if (auxFile == null) {
171+
logger.fine("Data file id" + dataFile.getId() + ": cannot find required aux file. formatTag=" + formatTag + ". formatVersion=" + formatVersion);
172+
meetsRequirements = false;
173+
break;
174+
} else {
175+
logger.fine("Data file id" + dataFile.getId() + ": found required aux file. formatTag=" + formatTag + ". formatVersion=" + formatVersion);
176+
meetsRequirements = true;
177+
}
178+
}
179+
return meetsRequirements;
180+
}
181+
150182
public static ExternalTool parseAddExternalToolManifest(String manifest) {
151183

152184
if (manifest == null || manifest.isEmpty()) {
@@ -170,6 +202,7 @@ public static ExternalTool parseAddExternalToolManifest(String manifest) {
170202
JsonObject toolParametersObj = jsonObject.getJsonObject(TOOL_PARAMETERS);
171203
JsonArray queryParams = toolParametersObj.getJsonArray("queryParameters");
172204
JsonArray allowedApiCallsArray = jsonObject.getJsonArray(ALLOWED_API_CALLS);
205+
JsonObject requirementsObj = jsonObject.getJsonObject(REQUIREMENTS);
173206

174207
boolean allRequiredReservedWordsFound = false;
175208
if (scope.equals(Scope.FILE)) {
@@ -227,8 +260,12 @@ public static ExternalTool parseAddExternalToolManifest(String manifest) {
227260
if(allowedApiCallsArray !=null) {
228261
allowedApiCalls = allowedApiCallsArray.toString();
229262
}
263+
String requirements = null;
264+
if (requirementsObj != null) {
265+
requirements = requirementsObj.toString();
266+
}
230267

231-
return new ExternalTool(displayName, toolName, description, externalToolTypes, scope, toolUrl, toolParameters, contentType, allowedApiCalls);
268+
return new ExternalTool(displayName, toolName, description, externalToolTypes, scope, toolUrl, toolParameters, contentType, allowedApiCalls, requirements);
232269
}
233270

234271
private static String getRequiredTopLevelField(JsonObject jsonObject, String key) {
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
ALTER TABLE externaltool ADD COLUMN IF NOT EXISTS requirements TEXT;

0 commit comments

Comments
 (0)