diff --git a/labkey-client-api/CHANGELOG.md b/labkey-client-api/CHANGELOG.md index ce9390b8..99789d34 100644 --- a/labkey-client-api/CHANGELOG.md +++ b/labkey-client-api/CHANGELOG.md @@ -1,5 +1,10 @@ # The LabKey Remote API Library for Java - Change Log +## version 1.4.0-SNAPSHOT +*Released*: TBD +* Issue 43246: Lineage query NPE while processing an UploadedFile +* Additional lineage options and support additional properties in response + ## version 1.3.2 *Released* : 05 November 2020 * Fix `selectedMetadataInputFormat` serialization diff --git a/labkey-client-api/src/org/labkey/remoteapi/experiment/LineageCommand.java b/labkey-client-api/src/org/labkey/remoteapi/experiment/LineageCommand.java index 0c623a6b..40a161d2 100644 --- a/labkey-client-api/src/org/labkey/remoteapi/experiment/LineageCommand.java +++ b/labkey-client-api/src/org/labkey/remoteapi/experiment/LineageCommand.java @@ -19,71 +19,70 @@ import org.labkey.remoteapi.Command; import org.labkey.remoteapi.Connection; +import java.util.Arrays; import java.util.HashMap; +import java.util.List; import java.util.Map; public class LineageCommand extends Command { - // One of rowId or LSID required - private final Integer _rowId; - private final String _lsid; + // One or more LSIDs are required + private final List _lsids; + // Optional parameters below private final Boolean _parents; private final Boolean _children; private final Integer _depth; private final String _expType; private final String _cpasType; + private final Boolean _includeProperties; + private final Boolean _includeInputsAndOutputs; + private final Boolean _includeRunSteps; - private LineageCommand(/*@Nullable*/ Integer rowId, /*@Nullable*/ String lsid, Boolean parents, Boolean children, Integer depth, String cpasType, String expType) + private LineageCommand( + List lsids, Boolean parents, Boolean children, Integer depth, String cpasType, String expType, + Boolean includeProperites, Boolean includeInputsAndOutputs, Boolean includeRunSteps) { super("experiment", "lineage"); - if (rowId == null && lsid == null) - throw new IllegalArgumentException("One of rowId or lsid required"); + if (lsids == null || lsids.isEmpty()) + throw new IllegalArgumentException("One or more starting LSIDs required"); - if (lsid != null && rowId != null) - throw new IllegalArgumentException("Only one of rowId or lsid allowed"); - - _rowId = rowId; - _lsid = lsid; + _lsids = lsids; _depth = depth; _parents = parents; _children = children; _expType = expType; _cpasType = cpasType; + _includeProperties = includeProperites; + _includeInputsAndOutputs = includeInputsAndOutputs; + _includeRunSteps = includeRunSteps; } public static final class Builder { - private final Integer _rowId; - private final String _lsid; + private final List _lsids; private Integer _depth; private Boolean _parents; private Boolean _children; private String _expType; private String _cpasType; + private Boolean _includeProperties; + private Boolean _includeInputsAndOutputs; + private Boolean _includeRunSteps; public Builder(String lsid) { - this(lsid, null); + this(Arrays.asList(lsid)); } - public Builder(Integer rowId) + public Builder(List lsids) { - this(null, rowId); - } - - public Builder(String lsid, Integer rowId) - { - if (lsid == null && rowId == null) - throw new IllegalArgumentException("One of rowId or lsid required"); - - if (lsid != null && rowId != null) - throw new IllegalArgumentException("Only one of rowId or lsid allowed"); + if (lsids == null || lsids.isEmpty()) + throw new IllegalArgumentException("One or more starting LSIDs required"); - _rowId = rowId; - _lsid = lsid; + _lsids = lsids; } public Builder setDepth(Integer depth) @@ -116,9 +115,29 @@ public Builder setCpasType(String cpasType) return this; } + public Builder setIncludeProperties(Boolean includeProperties) + { + _includeProperties = includeProperties; + return this; + } + + public Builder setIncludeInputsAndOutputs(Boolean includeInputsAndOutputs) + { + _includeInputsAndOutputs = includeInputsAndOutputs; + return this; + } + + public Builder setIncludeRunSteps(Boolean includeRunSteps) + { + _includeRunSteps = includeRunSteps; + return this; + } + public LineageCommand build() { - return new LineageCommand(_rowId, _lsid, _parents, _children, _depth, _cpasType, _expType); + return new LineageCommand( + _lsids, _parents, _children, _depth, _cpasType, _expType, + _includeProperties, _includeInputsAndOutputs, _includeRunSteps); } } @@ -130,10 +149,7 @@ protected LineageResponse createResponse(String text, int statusCode, String con public Map getParameters() { Map params = new HashMap<>(); - if (null != _rowId) - params.put("rowId", _rowId); - if (null != _lsid) - params.put("lsid", _lsid); + params.put("lsids", _lsids); if (null != _parents) params.put("parents", _parents); if (null != _children) @@ -144,6 +160,12 @@ public Map getParameters() params.put("expType", _expType); if (null != _cpasType) params.put("cpasType", _cpasType); + if (null != _includeProperties) + params.put("includeProperties", _includeProperties); + if (null != _includeInputsAndOutputs) + params.put("includeInputsAndOutputs", _includeInputsAndOutputs); + if (null != _includeRunSteps) + params.put("includeRunSteps", _includeRunSteps); return params; } @@ -152,28 +174,27 @@ public Map getParameters() @Override public LineageCommand copy() { - return new LineageCommand(_rowId, _lsid, _parents, _children, _depth, _cpasType, _expType); + return new LineageCommand(_lsids, _parents, _children, _depth, _cpasType, _expType, + _includeProperties, _includeInputsAndOutputs, _includeRunSteps); } public static void main(String[] args) throws Exception { String url = "http://localhost:8080/labkey"; - String folderPath = "/bl"; + String folderPath = "/AssayImportProvenance Test"; String user = "kevink@labkey.com"; String password = "xxxxxx"; - String lsid = null; - Integer rowId = 7523; - Boolean parents = false; - Boolean children = null; + String lsid = "urn:lsid:labkey.com:GeneralAssayRun.Folder-4780:40166791-3d3f-1039-a854-9b7575483a25"; - Builder builder = new Builder(lsid, rowId); - if (parents != null) - builder.setParents(parents); - if (children != null) - builder.setChildren(children); + LineageCommand cmd = new Builder(lsid) + .setParents(false) + .setChildren(true) + .setIncludeProperties(true) + .setIncludeInputsAndOutputs(true) + .setIncludeRunSteps(true) + .build(); - LineageCommand cmd = builder.build(); Connection conn = new Connection(url, user, password); LineageResponse resp = cmd.execute(conn, folderPath); System.out.println(resp.dump()); diff --git a/labkey-client-api/src/org/labkey/remoteapi/experiment/LineageNode.java b/labkey-client-api/src/org/labkey/remoteapi/experiment/LineageNode.java index ffb6c31c..05d59166 100644 --- a/labkey-client-api/src/org/labkey/remoteapi/experiment/LineageNode.java +++ b/labkey-client-api/src/org/labkey/remoteapi/experiment/LineageNode.java @@ -16,21 +16,43 @@ package org.labkey.remoteapi.experiment; +import org.apache.commons.logging.LogFactory; import org.labkey.remoteapi.ResponseObject; +import org.labkey.remoteapi.query.DateParser; +import org.labkey.remoteapi.query.Filter; +import org.labkey.remoteapi.query.SelectRowsResponse; +import java.text.ParseException; import java.util.ArrayList; +import java.util.Collections; +import java.util.Date; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.stream.Collectors; public class LineageNode extends ResponseObject { private final String _lsid; private final Integer _id; private final String _name; + private final String _container; private final String _url; private final String _type; private final String _cpasType; + private final String _expType; + private final Date _created; + private final String _createdBy; + private final Date _modified; + private final String _modifiedBy; + private final String _comment; + + // query reference + private final String _schemaName; + private final String _queryName; + private final List _pkFilters; + + private final Map _properties; private List _parents; private List _children; @@ -40,11 +62,44 @@ public LineageNode(String lsid, Map map) { super(map); _lsid = lsid; - _id = ((Long)map.get("id")).intValue(); + _id = ((Number)map.getOrDefault("id", -1)).intValue(); _name = (String)map.get("name"); + _container = (String)map.get("container"); _type = (String)map.get("type"); _cpasType = (String)map.get("cpasType"); + _expType = (String)map.get("expType"); _url = (String)map.get("url"); + + Date created = null; + Date modified = null; + DateParser dateParser = new DateParser(); + try + { + String createdStr = (String) map.get("created"); + if (createdStr != null && createdStr.length() > 0) + created = dateParser.parse(createdStr); + String modifiedStr = (String) map.get("modified"); + if (modifiedStr != null && modifiedStr.length() > 0) + modified = dateParser.parse(modifiedStr); + } + catch (ParseException e) + { + //just log it--if it doesn't parse, we can't fix it up + LogFactory.getLog(SelectRowsResponse.class).warn("Failed to parse date: " + e.getMessage(), e); + } + _created = created; + _modified = modified; + + _createdBy = (String) map.get("createdBy"); + _modifiedBy = (String)map.get("modifiedBy"); + _comment = (String)map.get("comment"); + + // query row ref + _schemaName = (String)map.get("schemaName"); + _queryName = (String)map.get("queryName"); + _pkFilters = createPkFilters((List>)map.get("pkFilters")); + + _properties = (Map)map.getOrDefault("properties", Collections.emptyMap()); } void fixup(Map nodes) @@ -53,6 +108,21 @@ void fixup(Map nodes) _parents = fixupEdges(nodes, (List>) getAllProperties().get("parents")); } + List createPkFilters(List> filters) + { + if (filters == null) + return Collections.emptyList(); + + List result = new ArrayList<>(filters.size()); + for (Map filter : filters) + { + String fieldKey = (String)filter.get("fieldKey"); + Object value = filter.get("value"); + result.add(new Filter(fieldKey, value, Filter.Operator.EQUAL)); + } + return Collections.unmodifiableList(result); + } + List fixupEdges(Map nodes, List> edges) { List fixedEdges = new ArrayList<>(); @@ -85,16 +155,110 @@ public String getUrl() return _url; } + /** + * The namespace portion of the LSID. + * Examples include: + *
+ *
Sample
+ *
The type for a Sample. + * + *
GeneralAssayRun
+ *
The type for a Standard (formerly called General) assay run
+ * + *
AssayRunTSVData
+ *
The type for the Data output file of an assay run
+ * + *
GeneralAssayResultRow
+ *
The type for an individual assay result row of a Standard assay. (requires provenance module)
+ *
+ */ public String getType() { return _type; } + /** + * The CPAS type of the object as an LSID. + * For a Sample node, the CPAS type is the LSID of the Sample Type. + * For a Data in a DataClass, the CPAS type is the LSID of the Data Class. + * For a run, the CPAS type is the LSID of the run's protocol. + */ public String getCpasType() { return _cpasType; } + /** + * The Experiment type of the object. + *
+ *
Data
+ *
A file or a data in a DataClass
+ * + *
Material
+ *
A sample in a Sample Type
+ * + *
ExperimentRun
+ *
An experiment run, e.g. a derivation or an assay import
+ *
+ */ + public String getExpType() + { + return _expType; + } + + /** + * Container entity ID of the object. + */ + public String getContainer() + { + return _container; + } + + public Date getCreated() + { + return _created; + } + + public String getCreatedBy() + { + return _createdBy; + } + + public Date getModified() + { + return _modified; + } + + public String getModifiedBy() + { + return _modifiedBy; + } + + public String getComment() + { + return _comment; + } + + public String getSchemaName() + { + return _schemaName; + } + + public String getQueryName() + { + return _queryName; + } + + public List getPkFilters() + { + return _pkFilters; + } + + public Map getProperties() + { + return _properties; + } + public List getParents() { return _parents; @@ -118,9 +282,23 @@ void dump(int indent, StringBuilder sb, Set seen) indent(indent+1, sb).append("lsid: ").append(getLsid()).append("\n"); indent(indent+1, sb).append("type: ").append(getType()).append("\n"); + indent(indent+1, sb).append("exp: ").append(getExpType()).append("\n"); indent(indent+1, sb).append("cpas: ").append(getCpasType()).append("\n"); indent(indent+1, sb).append("url: ").append(getUrl()).append("\n"); + if (getComment() != null) + indent(indent+1, sb).append("comment: ").append(getComment()).append("\n"); + + if (getSchemaName() != null) + indent(indent+1, sb).append("schemaName: ").append(getSchemaName()).append("\n"); + if (getQueryName() != null) + indent(indent+1, sb).append("queryName: ").append(getQueryName()).append("\n"); + if (getPkFilters() != null && getPkFilters().size() > 0) + indent(indent + 1, sb).append("pkFilters: ").append(getPkFilters().stream().map(f -> f.getColumnName() + "=" + f.getValue()).collect(Collectors.joining(", ", "[", "]"))).append("\n"); + + if (!getProperties().isEmpty()) + indent(indent+1, sb).append("properties: ").append(getProperties()).append("\n"); + indent(indent+1, sb).append("parents:"); if (_parents.isEmpty()) sb.append(" (none)"); diff --git a/labkey-client-api/src/org/labkey/remoteapi/experiment/LineageResponse.java b/labkey-client-api/src/org/labkey/remoteapi/experiment/LineageResponse.java index 60bb9eb2..9ac5c7cd 100644 --- a/labkey-client-api/src/org/labkey/remoteapi/experiment/LineageResponse.java +++ b/labkey-client-api/src/org/labkey/remoteapi/experiment/LineageResponse.java @@ -16,25 +16,29 @@ package org.labkey.remoteapi.experiment; import org.json.simple.JSONObject; -import org.labkey.remoteapi.Command; import org.labkey.remoteapi.CommandResponse; +import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; +import java.util.List; import java.util.Map; +import java.util.Set; public class LineageResponse extends CommandResponse { - LineageNode _seed; + List _seeds; Map _nodes; - public LineageResponse(String text, int statusCode, String contentType, JSONObject json, Command sourceCommand) + public LineageResponse(String text, int statusCode, String contentType, JSONObject json, LineageCommand sourceCommand) { super(text, statusCode, contentType, json, sourceCommand); - String seedLsid = getProperty("seed"); + List seedLsids = getProperty("seeds"); + List seeds = new ArrayList<>(seedLsids.size()); + Map nodeMap = getProperty("nodes"); Map nodes = new HashMap<>(); @@ -45,19 +49,27 @@ public LineageResponse(String text, int statusCode, String contentType, JSONObje LineageNode node = new LineageNode(lsid, (Map)entry.getValue()); nodes.put(lsid, node); - if (_seed == null && lsid.equals(seedLsid)) - _seed = node; + if (seeds.size() != seedLsids.size() && seedLsids.contains(lsid)) + seeds.add(node); } for (LineageNode node : nodes.values()) node.fixup(nodes); + _seeds = Collections.unmodifiableList(seeds); _nodes = Collections.unmodifiableMap(nodes); } public LineageNode getSeed() { - return _seed; + if (_seeds.size() > 0) + return _seeds.get(0); + return null; + } + + public List getSeeds() + { + return _seeds; } public Map getNodes() @@ -74,10 +86,17 @@ public String dump() private void dump(StringBuilder sb) { - if (_seed == null) - sb.append("No seed found"); + if (_seeds == null) + { + sb.append("No seeds found"); + return; + } - _seed.dump(0, sb, new HashSet()); + Set seen = new HashSet<>(); + for (LineageNode seed : _seeds) + { + seed.dump(0, sb, seen); + } } }