/* * Copyright (c) 2013, the authors. * * This file is part of 'DXFS'. * * DXFS is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * DXFS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with DXFS. If not, see <http://www.gnu.org/licenses/>. */ package nextflow.fs.dx.api; import static nextflow.fs.dx.DxHelper.jsonToObj; import static nextflow.fs.dx.DxHelper.objToJson; import java.io.IOException; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Objects; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.node.ArrayNode; import com.fasterxml.jackson.databind.node.JsonNodeFactory; import nextflow.fs.dx.DxHelper; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * A wrapper over the {@code DXAPI} class * * * @author Paolo Di Tommaso <paolo.ditommaso@gmail.com> * @author Beatriz San Juan <bmsanjuan@gmail.com> * */ public class DxApi { private static Logger log = LoggerFactory.getLogger(DxApi.class); static class Holder { private static final DxApi INSTANCE = new DxApi( DxHttpClient.getInstance() ); } private final DxHttpClient client; private final JsonNode EMPTY_JSON = objToJson(new HashMap<>(0)); // keep track of the last method abject for testing purpose private DxMethod lastMethodObj; /* * Access to the last method object * NOT thread safe -- ONLY FOR TESTING */ DxMethod getMethodObj() { return lastMethodObj; } Map getMethodMap() { Map result = new HashMap(2); result.put("action", lastMethodObj.action ); result.put("input", DxHelper.jsonToObj(lastMethodObj.input)); return result; } /** * Wrap the API method to be invoked */ public class DxMethod { final String action; final JsonNode input; public DxMethod( String m, JsonNode args) { this.action = m; this.input = args; } /** * Invoke the target method * @param <T> * @return The object as returned by the target API call */ @SuppressWarnings("unchecked") public <T> T call() throws IOException { if( log.isDebugEnabled() ) { log.debug("Call API method '" + action + "' with params: " + input.toString()); } T result = (T) client.request(action, input); if( log.isDebugEnabled() ) { log.debug(String.format("[result of method '%s' ==> %s]", action, String.valueOf(result))); } return result; } } /** * Used only internally, access through the singleton access method * * @param client */ protected DxApi(DxHttpClient client) { this.client = client; } /** * @return {@code DxApi} instance object */ static public DxApi getInstance() { return Holder.INSTANCE; } /** * Get a method reference to the specified api *verb* and arguments * * @param verb * @param args * @return */ DxMethod api( String verb, Object... args) { if( args.length == 0 ) { args = new Object[] { EMPTY_JSON }; } else if( args.length>1 ) { throw new IllegalArgumentException("DxApi api accepts at most one argument"); } if( !(args[0] instanceof JsonNode) ) { throw new IllegalArgumentException("DxApi api argument must be a JsonNode"); } return lastMethodObj = new DxMethod(verb, (JsonNode)args[0]); } // ------------------------ PUBLIC API START HERE ------------------------- /** * See https://wiki.dnanexus.com/API-Specification-v1.0.0/Projects#API-method:-/project/new * * @param name * @return */ public String projectNew(String name) throws IOException { Objects.requireNonNull(name); Map inputs = new HashMap(); inputs.put("name",name); JsonNode node = objToJson(inputs); JsonNode result = api("/project/new", node).call(); return result.get("id").textValue(); } /** * See https://wiki.dnanexus.com/API-Specification-v1.0.0/Projects#API-method:-/project-xxxx/describe * @param projectId * @return */ public Map projectDescribe( String projectId, String... fields ) throws IOException { Objects.requireNonNull(projectId); Map inputs = new HashMap(); for( String item : fields ) { inputs.put(item, true); } JsonNode result = api(String.format("/%s/describe",projectId), objToJson(inputs)).call(); return jsonToObj(result); } /** * See https://wiki.dnanexus.com/API-Specification-v1.0.0/Projects#API-method:-/project-xxxx/destroy * @param projectId * @return */ public void projectDestroy(String projectId) throws IOException { api(String.format("/%s/destroy",projectId)).call(); } /** * Invoke DnaNexus API * * See https://wiki.dnanexus.com/API-Specification-v1.0.0/Search#API-method:-/system/findDataObjects * * @param args * @return */ public List systemFindDataObjects( Map args ) throws IOException { DxApi.DxMethod method = api("/system/findDataObjects", objToJson(args)); JsonNode result = method.call(); Map map = jsonToObj(result); return (List) map.get("results") ; } /** * Find a file in remote DnaNexus cloud storage * * @param projectId * @param fullyQualifiedFileName * @return */ public List fileFind(String projectId, String fullyQualifiedFileName) throws IOException { int p = fullyQualifiedFileName.lastIndexOf('/'); String folder; String fileName; if( p == -1 ) { folder = "/"; fileName = fullyQualifiedFileName; } else { fileName = fullyQualifiedFileName.substring(p+1); folder = fullyQualifiedFileName.substring(0,p); } Map<String,Object> scope = new HashMap<>(); scope.put("folder", folder); scope.put("project", projectId); Map<String,Object> args = new HashMap<>(); args.put("class","file"); args.put("name", fileName); args.put("scope", scope); return systemFindDataObjects(args); } /** * https://wiki.dnanexus.com/API-Specification-v1.0.0/Search#API-method:-/system/findProjects */ @SuppressWarnings("unchecked") public List<Map<String,Object>> projectFind(String name) throws IOException { Map<String,String> request = new HashMap<>(1); if( name != null && !"".equals(name)) { request.put("name", name); } JsonNode result = api("/system/findProjects", objToJson(request)).call(); return (List<Map<String, Object>>) ((Map) jsonToObj(result)).get("results"); } /** * ListCmd the content of a folder as define by the following API * * https://wiki.dnanexus.com/API-Specification-v1.0.0/Folders-and-Deletion#API-method:-/class-xxxx/listFolder * * @param contextId * @param path The folder to be listed. Note" must start with a / (slash) character * @param describe * @return */ public Map<String,Object> folderList(String contextId, String path, boolean describe) throws IOException { Map<String,Object> input = new HashMap<>(); input.put("folder", path); input.put("describe", describe); JsonNode result = api(String.format("/%s/listFolder",contextId), objToJson(input)).call() ; return jsonToObj(result); } /** * Wrap DnaNexus *newFolder* operation as define by * https://wiki.dnanexus.com/API-Specification-v1.0.0/Folders-and-Deletion#API-method:-/class-xxxx/newFolder * * @param path * @return the entity ID of the manipulated data container */ public String folderCreate(String containerId, String path, boolean create) throws IOException { /* * An example input JSON object * * '{"folder":"/alpha/beta/delta", "parents":true}' */ JsonNode obj = DxJson.getObjectBuilder() .put("folder", path) .put("parents", true) .build(); JsonNode result = api(String.format("/%s/newFolder", containerId), obj).call(); return result.get("id").textValue(); } /** * Create the {@code JsonNode} object to delete a folder. * <p> * Example object format: * * <pre> * '{"folder":"/folderName", "recurse": true}' * </pre> * * <p> * Read more https://wiki.dnanexus.com/API-Specification-v1.0.0/Folders-and-Deletion#API-method:-/class-xxxx/removeFolder * </p> * * @param path * * @return The entity ID of the manipulated data container */ public String folderDelete(String contextId, String path, boolean recurse) throws IOException { JsonNode node = DxJson.getObjectBuilder() .put("folder", path) .put("recurse", recurse) .build(); JsonNode result = api(String.format("/%s/removeFolder", contextId), node).call(); return result.get("id").textValue(); } /** * Creater the {@code JsonNode} input object required to delete one, or more, files * * <p> * Example JSON object format: * <pre> * '{"objects":["file-B86Q5B80j58B67zVXG3Q01K8"]}' * </pre> * </p> * * * * <p> * http://wiki.dnanexus.com/API-Specification-v1.0.0/Folders-and-Deletion#API-method:-/class-xxxx/removeObjects * </p> * * @param fileIds an array of strings representing IDs of the objects to be removed from the data * @return @return The entity ID of the manipulated data container */ public String fileDelete(String contextId, String... fileIds) throws IOException { ArrayNode container = new ArrayNode(JsonNodeFactory.instance); for( String item : fileIds ) { container.add( item ); } JsonNode input = DxJson.getObjectBuilder().put("objects", container).build(); JsonNode result = api(String.format("/%s/removeObjects", contextId), input).call(); return result.get("id").textValue(); } /** * Describe a file * * Read more https://wiki.dnanexus.com/API-Specification-v1.0.0/Files#API-method%3A-%2Ffile-xxxx%2Fdescribe * * * @param fileId * @return */ public Map<String,Object> fileDescribe(String fileId) throws IOException { JsonNode node = api(String.format("/%s/describe", fileId)).call(); return jsonToObj(node); } /** * https://wiki.dnanexus.com/API-Specification-v1.0.0/Name#API-method%3A-%2Fclass-xxxx%2Frename * * @param contextId * @param fileId * @param name * @return */ public String rename (String contextId, String fileId, String name) throws IOException { JsonNode input = DxJson.getObjectBuilder() .put("project", contextId) .put("name", name) .build(); JsonNode result = api(String.format("/%s/rename", fileId), input).call(); return result.get("id").textValue(); } /** * https://wiki.dnanexus.com/API-Specification-v1.0.0/Tags#API-method%3A-%2Fclass-xxxx%2FaddTags * * @param contextId * @param fileId * @param tags * @return */ public String addFileTags(String contextId, String fileId, String[] tags) throws IOException { ArrayNode container = new ArrayNode(JsonNodeFactory.instance); for( String item : tags ) { container.add( item ); } JsonNode input = DxJson.getObjectBuilder() .put("project", contextId) .put("tags", container) .build(); JsonNode result = api(String.format("/%s/addTags",fileId), input).call(); return result.get("id").textValue(); } /** * https://wiki.dnanexus.com/API-Specification-v1.0.0/Types#API-method%3A-%2Fclass-xxxx%2FaddTypes * * @param contextId * @param fileId * @param types * @return */ public String addFileTypes(String contextId, String fileId, String[] types) throws IOException { ArrayNode container = new ArrayNode(JsonNodeFactory.instance); for( String item : types ) { container.add( item ); } JsonNode input = DxJson.getObjectBuilder() .put("project", contextId) .put("types", container) .build(); JsonNode result = api(String.format("/%s/addTypes",fileId), input).call(); return result.get("id").textValue(); } /** * Given a file-id return the a URL and the authorization code to download it. * * See https://wiki.dnanexus.com/API-Specification-v1.0.0/Files#API-method%3A-%2Ffile-xxxx%2Fdownload * * @param fileId * @return A map object holding the */ public Map<String,Object> fileDownload(String fileId) throws IOException { JsonNode result = api(String.format("/%s/download",fileId)).call(); return jsonToObj(result); } /** * Create a new (empty) file ready * * Read more * https://wiki.dnanexus.com/API-Specification-v1.0.0/Files#API-method%3A-%2Ffile%2Fnew * * @param contextId * @param path * @return */ public String fileNew( String contextId, String path ) throws IOException { String name; String folder = null; int pos = path.lastIndexOf('/'); if( pos == -1 ) { name = path; } else { name = path.substring(pos+1); folder = path.substring(0,pos); } Map<String,Object> uploadInfo = new HashMap<>(); uploadInfo.put("project", contextId); uploadInfo.put("name", name); if( folder != null && folder.length()>0 ) { // set the parent folder where it must be stored uploadInfo.put("folder", folder); uploadInfo.put("parents", true); } return fileNew(uploadInfo); } /** * Create a new (empty) file ready * * Read more * https://wiki.dnanexus.com/API-Specification-v1.0.0/Files#API-method%3A-%2Ffile%2Fnew * * @param uploadInfo * @return */ public String fileNew( Map<String,Object> uploadInfo ) throws IOException { JsonNode result = api("/file/new", objToJson(uploadInfo)).call(); return result.get("id").textValue(); } /** * Upload a file (part) to the remote container * * See https://wiki.dnanexus.com/API-Specification-v1.0.0/Files#API-method:-/file-xxxx/upload * * @param fileId The file-id of the file being uploaded * @param index The chunk index of the filed being uploaded (1-based) * @return */ public Map fileUpload( String fileId, int index ) throws IOException { JsonNode input = DxJson.getObjectBuilder().put("index", index).build(); JsonNode result = api(String.format("/%s/upload",fileId), input).call(); return jsonToObj(result); } /** * Close the file when upload is complete * * See https://wiki.dnanexus.com/API-Specification-v1.0.0/Files#API-method:-/file-xxxx/close * * @param fileId The file-id of the file being uploaded * @return */ public Map fileClose( String fileId ) throws IOException { JsonNode result = api(String.format("/%s/close",fileId)).call(); return jsonToObj(result); } /** * Clone a file to the specified path * * @param sourceContainerId * @param sourceFileId * @param targetContainerId * @param targetPath * @return An {@code Map} containing the following elements: * <li>id: the entity ID of the source container * <li>project: the entity ID of the modified destination container * <li>exists: an array of strings representing the object IDs that could not be cloned because they already exist in the destination container */ public Map<String,Object> fileClone( String sourceContainerId, String sourceFileId, String targetContainerId, String targetPath ) throws IOException { List<String> wrap = new ArrayList<>(); wrap.add(sourceFileId); return fileClone(sourceContainerId, wrap, targetContainerId, targetPath); } /** * Clone a file to the specified path * * See https://wiki.dnanexus.com/API-Specification-v1.0.0/Cloning#API-method:-/class-xxxx/clone * * @param sourceContainerId * @param sourceFileId * @param targetContainerId * @param targetPath * @return An {@code Map} containing the following elements: * <li>id: the entity ID of the source container * <li>project: the entity ID of the modified destination container * <li>exists: an array of strings representing the object IDs that could not be cloned because they already exist in the destination container */ public Map<String,Object> fileClone( String sourceContainerId, List<String> sourceFileId, String targetContainerId, String targetPath ) throws IOException { if( sourceContainerId==null || sourceContainerId.isEmpty() ) throw new IllegalStateException("Argument `sourceContainerId` cannot be empty"); if( targetContainerId==null || targetContainerId.isEmpty() ) throw new IllegalStateException("Argument `targetContainerId` cannot be empty"); Map<String,Object> request = new HashMap<>(); request.put("objects", sourceFileId); // list of files to be copied request.put("project", targetContainerId); // target container ID request.put("destination", targetPath); // folder where store the file request.put("parents", true); // create the target folder if do not exist JsonNode result = api(String.format(String.format("/%s/clone", sourceContainerId)), objToJson(request)).call(); return jsonToObj(result); } /** * See https://wiki.dnanexus.com/API-Specification-v1.0.0/Applets%20and%20Entry%20Points#API-method:-/job/new * * @param inputs * @return */ public String jobNew(Map<String,Object> inputs) throws IOException { JsonNode result = api("/job/new", objToJson(inputs)).call(); return result.get("id").textValue(); } /** * See https://wiki.dnanexus.com/API-Specification-v1.0.0/Applets%20and%20Entry%20Points#API-method:-/job-xxxx/describe * * @param jobId */ public Map jobDescribe(String jobId, String... fields) throws IOException { Objects.requireNonNull(jobId); Map inputs = new HashMap(); for( String item : fields ) { inputs.put(item, true); } JsonNode result = api(String.format("/%s/describe",jobId), objToJson(inputs)).call(); return jsonToObj(result); } /** * See https://wiki.dnanexus.com/API-Specification-v1.0.0/Applets%20and%20Entry%20Points#API-method:-/job-xxxx/terminate * * @param jobId */ public void jobTerminate(String jobId) throws IOException { Objects.requireNonNull(jobId); api(String.format("/%s/terminate",jobId)).call(); } }