From: radhika Date: Sat, 3 May 2014 12:46:52 +0000 (-0400) Subject: Merge branch 'master' into 2525-java-sdk X-Git-Tag: 1.1.0~2663 X-Git-Url: https://git.arvados.org/arvados.git/commitdiff_plain/c436da861845723ab18f56dba96052cf2569e539?hp=6c8ba53502c29dc9174291c04f3c7bc84777f9cc Merge branch 'master' into 2525-java-sdk --- diff --git a/.gitignore b/.gitignore index 2156fdf7e7..602e1b9e40 100644 --- a/.gitignore +++ b/.gitignore @@ -14,3 +14,5 @@ sdk/perl/pm_to_blib services/keep/bin services/keep/pkg services/keep/src/github.com +sdk/java/target +*.class diff --git a/sdk/java/.classpath b/sdk/java/.classpath new file mode 100644 index 0000000000..5613077a3a --- /dev/null +++ b/sdk/java/.classpath @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/sdk/java/.project b/sdk/java/.project new file mode 100644 index 0000000000..40c2bdf1ba --- /dev/null +++ b/sdk/java/.project @@ -0,0 +1,14 @@ + + + java + NO_M2ECLIPSE_SUPPORT: Project files created with the maven-eclipse-plugin are not supported in M2Eclipse. + + + + org.eclipse.jdt.core.javabuilder + + + + org.eclipse.jdt.core.javanature + + \ No newline at end of file diff --git a/sdk/java/.settings/org.eclipse.jdt.core.prefs b/sdk/java/.settings/org.eclipse.jdt.core.prefs new file mode 100644 index 0000000000..f4f19ea0e6 --- /dev/null +++ b/sdk/java/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1,5 @@ +#Mon Apr 28 10:33:40 EDT 2014 +org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6 +eclipse.preferences.version=1 +org.eclipse.jdt.core.compiler.source=1.6 +org.eclipse.jdt.core.compiler.compliance=1.6 diff --git a/sdk/java/ArvadosSDKJavaUser.java b/sdk/java/ArvadosSDKJavaUser.java new file mode 100644 index 0000000000..289d0c6a20 --- /dev/null +++ b/sdk/java/ArvadosSDKJavaUser.java @@ -0,0 +1,103 @@ +/** + * This Sample test program is useful in getting started with working with Arvados Java SDK. + * Please also see arvadso + * @author radhika + * + */ + +import java.io.File; +import java.io.BufferedWriter; +import java.io.FileWriter; + +import java.util.ArrayList; +import java.util.List; + +import org.arvados.sdk.java.Arvados; +import org.json.simple.JSONObject; +import org.json.simple.parser.JSONParser; + +import com.google.api.services.discovery.model.RestDescription; + +public class ArvadosSDKJavaUser { + /** Make sure the following environment variables are set before using Arvados: + * ARVADOS_API_TOKEN, ARVADOS_API_HOST, ARVADOS_API_HOST_INSECURE + */ + public static void main(String[] args) throws Exception { + String apiName = "arvados"; + String apiVersion = "v1"; + + Arvados arv = new Arvados(apiName); + + // Make a discover request. + System.out.println("Making an arvados discovery api request"); + List params = new ArrayList(); + params.add("discover"); + params.add("arvados"); + params.add("v1"); + + RestDescription restDescription = arv.discover(params); + System.out.println("Arvados discovery docuemnt:\n" + restDescription); + + // Make a users.list call + System.out.println("Making an arvados users.list api call"); + + params = new ArrayList(); + params.add("call"); + params.add("arvados"); + params.add("v1"); + params.add("users.list"); + + String response = arv.call(params); + System.out.println("Arvados users.list:\n" + response); + + // get uuid of the first user from the response + JSONParser parser = new JSONParser(); + Object obj = parser.parse(response); + JSONObject jsonObject = (JSONObject) obj; + List items = (List)jsonObject.get("items"); + + JSONObject firstUser = (JSONObject)items.get(0); + String userUuid = (String)firstUser.get("uuid"); + + // Make a users.get call on the uuid obtained above + System.out.println("Making a users.get for " + userUuid); + params = new ArrayList(); + params.add("call"); + params.add("arvados"); + params.add("v1"); + params.add("users.get"); + params.add(userUuid); + response = arv.call(params); + System.out.println("Arvados users.get:\n" + response); + + // Make a users.create call + System.out.println("Making a users.create call."); + + File file = new File("/tmp/arvados_test.json"); + BufferedWriter output = new BufferedWriter(new FileWriter(file)); + output.write("{}"); + output.close(); + String filePath = file.getPath(); + + params = new ArrayList(); + params.add("call"); + params.add("arvados"); + params.add("v1"); + params.add("users.create"); + params.add(filePath); + response = arv.call(params); + System.out.println("Arvados users.create:\n" + response); + + // Make a pipeline_templates.list call + System.out.println("Making a pipeline_templates.list call."); + + params = new ArrayList(); + params.add("call"); + params.add("arvados"); + params.add("v1"); + params.add("pipeline_templates.list"); + response = arv.call(params); + + System.out.println("Arvados pipelinetempates.list:\n" + response); + } +} diff --git a/sdk/java/README b/sdk/java/README new file mode 100644 index 0000000000..423cf97eaa --- /dev/null +++ b/sdk/java/README @@ -0,0 +1,131 @@ +== Arvados Java SDK + + - Using the Arvados Java SDK, you can access API server from a java program. + + - This document highlights the details as to how to use the SDK. + + - The Java SDK is used as a maven project. Hence, you would need a working + maven environment to be able to build the source code. + + If you do not have maven setup, you may find the following link useful. + + http://maven.apache.org/guides/getting-started/maven-in-five-minutes.html + + - In this document is used to refer to the directory where + arvados code is cloned in your system. + For ex: = $HOME/arvados + + +== Setting up the environment + + - The SDK requires a running Arvados API server. The information about the + API server needs to be passed to the SDK using environment variables. + + - The following two environment variables are required by the SDK + + ARVADOS_API_TOKEN, ARVADOS_API_HOST + + Below are examples using a .bashrc file: + + export ARVADOS_API_TOKEN=z40gplmla6i58rsg96jhg5u41ewdl5rj4g1py2xg6s6e2lsc3 + export ARVADOS_API_HOST=localhost:3001 + + - You can also set ARVADOS_API_HOST_INSECURE to true if you are using + self-signed certificates and want to bypass certificate validations. + + Below is an example using a .bashrc file: + + export ARVADOS_API_HOST_INSECURE=true + + +== Building the Arvados SDK + + - cd + + java -cp target/java-1.0-SNAPSHOT-jar-with-dependencies.jar org.arvados.sdk.java.Arvados call arvados v1 users.create + + java -cp target/java-1.0-SNAPSHOT-jar-with-dependencies.jar org.arvados.sdk.java.Arvados call arvados v1 pipeline_templates.list + + java -cp target/java-1.0-SNAPSHOT-jar-with-dependencies.jar org.arvados.sdk.java.Arvados call arvados v1 pipeline_templates.get + + java -cp target/java-1.0-SNAPSHOT-jar-with-dependencies.jar org.arvados.sdk.java.Arvados call arvados v1 pipeline_templates.create + + +== Implementing your code to use SDK + + - /sdk/java/ArvadosSDKJavaUser.java serves as an example + implementation using the java SDK. Please use this file to see how + you would want use the SDK from your java program. + The steps below use this java class name. + + - ArvadosSDKJavaUser.java creates an instance of Arvados SDK class and + uses it to make various "call" requests. + + - To compile ArvadosSDKJavaUser.java + + javac -cp /sdk/java/target/java-1.0-SNAPSHOT-jar-with-dependencies.jar ArvadosSDKJavaUser.java + + This results in the generation of the ArvadosSDKJavaUser.class file + in the same dir as the java file + + - To run the class file + + java -cp .:/sdk/java/target/java-1.0-SNAPSHOT-jar-with-dependencies.jar ArvadosSDKJavaUser + + +== Viewing and managing SDK logging + + - SDK uses log4j logging + + - The default location of the log file is + /sdk/java/log/arvados_sdk_java.log + + - Update log4j.properties file to change name and location of the log file. + + Modify the "log4j.appender.fileAppender.File" property in log4j.properties + file located at /sdk/java/src/main/resources + + Rebuild by running "mvn clean package" + + +== Using the SDK in eclipse + + - To develop in eclipse, you can use the SDK eclipse project. + + - Install "m2eclipse" plugin in your eclipse + + - Open the SDK project. + + File -> Import -> Existing Projects into Workspace -> Next -> Browse + and select /sdk/java diff --git a/sdk/java/pom.xml b/sdk/java/pom.xml new file mode 100644 index 0000000000..ed0e4839ea --- /dev/null +++ b/sdk/java/pom.xml @@ -0,0 +1,105 @@ + + 4.0.0 + org.arvados.sdk.java + java + jar + 1.0-SNAPSHOT + java + http://maven.apache.org + + + + com.google.apis + google-api-services-discovery + v1-rev42-1.18.0-rc + + + com.google.api-client + google-api-client + 1.18.0-rc + + + com.google.http-client + google-http-client-jackson2 + 1.18.0-rc + + + com.google.guava + guava + r05 + + + log4j + log4j + 1.2.16 + + + + junit + junit + 4.8.1 + + + com.googlecode.json-simple + json-simple + 1.1.1 + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.1 + + 1.6 + 1.6 + + + + org.apache.maven.plugins + maven-assembly-plugin + + + + attached + + package + + + jar-with-dependencies + + + + org.arvados.sdk.Arvados + + + + true + true + + + + + + + + + + src/main/resources + ${basedir}/target/classes + + log4j.properties + + true + + + src/test/resources + true + + + + diff --git a/sdk/java/src/main/java/org/arvados/sdk/java/Arvados.java b/sdk/java/src/main/java/org/arvados/sdk/java/Arvados.java new file mode 100644 index 0000000000..260b68d69b --- /dev/null +++ b/sdk/java/src/main/java/org/arvados/sdk/java/Arvados.java @@ -0,0 +1,361 @@ +package org.arvados.sdk.java; + +import com.google.api.client.http.javanet.*; +import com.google.api.client.http.FileContent; +import com.google.api.client.http.GenericUrl; +import com.google.api.client.http.HttpContent; +import com.google.api.client.http.HttpRequest; +import com.google.api.client.http.HttpRequestFactory; +import com.google.api.client.http.HttpTransport; +import com.google.api.client.http.UriTemplate; +import com.google.api.client.json.JsonFactory; +import com.google.api.client.json.jackson2.JacksonFactory; +import com.google.api.client.util.Lists; +import com.google.api.client.util.Maps; +import com.google.api.services.discovery.Discovery; +import com.google.api.services.discovery.model.JsonSchema; +import com.google.api.services.discovery.model.RestDescription; +import com.google.api.services.discovery.model.RestMethod; +import com.google.api.services.discovery.model.RestResource; + +import java.io.File; +import java.math.BigDecimal; +import java.math.BigInteger; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import org.apache.log4j.Logger; + +public class Arvados { + // HttpTransport and JsonFactory are thread-safe. So, use global instances. + private static HttpTransport HTTP_TRANSPORT; + private static final JsonFactory JSON_FACTORY = JacksonFactory.getDefaultInstance(); + + private static final Pattern METHOD_PATTERN = Pattern.compile("((\\w+)\\.)*(\\w+)"); + + private static String ARVADOS_API_TOKEN; + private static String ARVADOS_API_HOST; + private static boolean ARVADOS_API_HOST_INSECURE; + + private static String ARVADOS_ROOT_URL; + + private static final Logger logger = Logger.getLogger(Arvados.class); + + // Get it on a discover call and reuse on the call requests + RestDescription restDescription = null; + String apiName = null; + String apiVersion = null; + + public Arvados (String apiName, String apiVersion){ + try { + this.apiName = apiName; + this.apiVersion = apiVersion; + + // Read needed environmental variables + ARVADOS_API_TOKEN = System.getenv().get("ARVADOS_API_TOKEN"); + if (ARVADOS_API_TOKEN == null) { + throw new Exception("Missing environment variable: ARVADOS_API_TOKEN"); + } + + ARVADOS_API_HOST = System.getenv().get("ARVADOS_API_HOST"); + if (ARVADOS_API_HOST == null) { + throw new Exception("Missing environment variable: ARVADOS_API_HOST"); + } + + ARVADOS_ROOT_URL = "https://" + ARVADOS_API_HOST; + ARVADOS_ROOT_URL += (ARVADOS_API_HOST.endsWith("/")) ? "" : "/"; + + ARVADOS_API_HOST_INSECURE = "true".equals(System.getenv().get("ARVADOS_API_HOST_INSECURE")) ? true : false; + + // Create HTTP_TRANSPORT object + NetHttpTransport.Builder builder = new NetHttpTransport.Builder(); + if (ARVADOS_API_HOST_INSECURE) { + builder.doNotValidateCertificate(); + } + HTTP_TRANSPORT = builder.build(); + } catch (Throwable t) { + t.printStackTrace(); + } + } + + /** + * Make a discover call and cache the response in-memory. Reload the document on each invocation. + * @param params + * @return + * @throws Exception + */ + public RestDescription discover() throws Exception { + restDescription = loadArvadosApi(apiName, apiVersion); + + // compute method details + ArrayList result = Lists.newArrayList(); + String resourceName = ""; + processResources(result, resourceName, restDescription.getResources()); + + // display method details + Collections.sort(result); + StringBuffer buffer = new StringBuffer(); + for (MethodDetails methodDetail : result) { + buffer.append("\nArvados call " + apiName + " " + apiVersion + " " + methodDetail.name); + for (String param : methodDetail.requiredParameters) { + buffer.append(" <" + param + ">"); + } + if (methodDetail.hasContent) { + buffer.append(" contentFile"); + } + if (methodDetail.optionalParameters.isEmpty() && !methodDetail.hasContent) { + buffer.append("\n"); + } else { + buffer.append("\n [optional parameters...]"); + buffer.append("\n --contentType (default is \"application/json\")"); + for (String param : methodDetail.optionalParameters) { + buffer.append("\n --" + param + " "); + } + } + } + logger.debug(buffer.toString()); + + return (restDescription); + } + + public String call(List callParams) throws Exception { + if (callParams.size() == 1) { + error("call", "missing api name"); + } else if (callParams.size() == 2) { + error("call", "missing api version"); + } else if (callParams.size() == 3) { + error("call", "missing method name"); + } + + String fullMethodName = callParams.get(3); + Matcher m = METHOD_PATTERN.matcher(fullMethodName); + if (!m.matches()) { + error("call", "invalid method name: " + fullMethodName); + } + + // initialize rest description if not already + if (restDescription == null) { + restDescription = loadArvadosApi(callParams.get(1), callParams.get(2)); + } + + Map methodMap = null; + int curIndex = 0; + int nextIndex = fullMethodName.indexOf('.'); + if (nextIndex == -1) { + methodMap = restDescription.getMethods(); + } else { + Map resources = restDescription.getResources(); + while (true) { + RestResource resource = resources.get(fullMethodName.substring(curIndex, nextIndex)); + if (resource == null) { + break; + } + curIndex = nextIndex + 1; + nextIndex = fullMethodName.indexOf(curIndex + 1, '.'); + if (nextIndex == -1) { + methodMap = resource.getMethods(); + break; + } + resources = resource.getResources(); + } + } + + RestMethod method = + methodMap == null ? null : methodMap.get(fullMethodName.substring(curIndex)); + if (method == null) { + error("call", "method not found: " + fullMethodName); + } + + HashMap parameters = Maps.newHashMap(); + File requestBodyFile = null; + String contentType = "application/json"; + + // Start looking for params at index 4. The first 4 were: call arvados v1 + int i = 4; + // required parameters + if (method.getParameterOrder() != null) { + for (String parameterName : method.getParameterOrder()) { + JsonSchema parameter = method.getParameters().get(parameterName); + if (Boolean.TRUE.equals(parameter.getRequired())) { + if (i == callParams.size()) { + error("call", "missing required parameter: " + parameter); + } else { + putParameter(null, parameters, parameterName, parameter, callParams.get(i++)); + } + } + } + } + + // possibly required content + if (!method.getHttpMethod().equals("GET") && !method.getHttpMethod().equals("DELETE")) { + String fileName = callParams.get(i++); + requestBodyFile = new File(fileName); + if (!requestBodyFile.canRead()) { + error("call", "POST method requires input file. Unable to read file: " + fileName); + } + } + + while (i < callParams.size()) { + String argName = callParams.get(i++); + if (!argName.startsWith("--")) { + error("call", "optional parameters must start with \"--\": " + argName); + } + String parameterName = argName.substring(2); + if (i == callParams.size()) { + error("call", "missing parameter value for: " + argName); + } + String parameterValue = callParams.get(i++); + if (parameterName.equals("contentType")) { + contentType = parameterValue; + if (method.getHttpMethod().equals("GET") || method.getHttpMethod().equals("DELETE")) { + error("call", "HTTP content type cannot be specified for this method: " + argName); + } + } else { + JsonSchema parameter = null; + if (restDescription.getParameters() != null) { + parameter = restDescription.getParameters().get(parameterName); + } + if (parameter == null && method.getParameters() == null) { + parameter = method.getParameters().get(parameterName); + } + putParameter(argName, parameters, parameterName, parameter, parameterValue); + } + } + + GenericUrl url = new GenericUrl(UriTemplate.expand( + ARVADOS_ROOT_URL + restDescription.getBasePath() + method.getPath(), parameters, + true)); + + HttpContent content = null; + if (requestBodyFile != null) { + content = new FileContent(contentType, requestBodyFile); + } + + try { + HttpRequestFactory requestFactory; + requestFactory = HTTP_TRANSPORT.createRequestFactory(); + + HttpRequest request = requestFactory.buildRequest(method.getHttpMethod(), url, content); + + List authHeader = new ArrayList(); + authHeader.add("OAuth2 " + ARVADOS_API_TOKEN); + request.getHeaders().put("Authorization", authHeader); + String response = request.execute().parseAsString(); + + logger.debug(response); + + return response; + } catch (Exception e) { + e.printStackTrace(); + throw e; + } + } + + /** + * Not thread-safe. So, create for each request. + * @param apiName + * @param apiVersion + * @return + * @throws Exception + */ + private RestDescription loadArvadosApi(String apiName, String apiVersion) + throws Exception { + try { + Discovery discovery; + + Discovery.Builder discoveryBuilder = new Discovery.Builder(HTTP_TRANSPORT, JSON_FACTORY, null); + + discoveryBuilder.setRootUrl(ARVADOS_ROOT_URL); + discoveryBuilder.setApplicationName(apiName); + + discovery = discoveryBuilder.build(); + + return discovery.apis().getRest(apiName, apiVersion).execute(); + } catch (Exception e) { + e.printStackTrace(); + throw e; + } + } + + private void processMethods( + ArrayList result, String resourceName, Map methodMap) { + if (methodMap == null) { + return; + } + for (Map.Entry methodEntry : methodMap.entrySet()) { + MethodDetails details = new MethodDetails(); + String methodName = methodEntry.getKey(); + RestMethod method = methodEntry.getValue(); + details.name = (resourceName.isEmpty() ? "" : resourceName + ".") + methodName; + details.hasContent = + !method.getHttpMethod().equals("GET") && !method.getHttpMethod().equals("DELETE"); + // required parameters + if (method.getParameterOrder() != null) { + for (String parameterName : method.getParameterOrder()) { + JsonSchema parameter = method.getParameters().get(parameterName); + if (Boolean.TRUE.equals(parameter.getRequired())) { + details.requiredParameters.add(parameterName); + } + } + } + // optional parameters + Map parameters = method.getParameters(); + if (parameters != null) { + for (Map.Entry parameterEntry : parameters.entrySet()) { + String parameterName = parameterEntry.getKey(); + JsonSchema parameter = parameterEntry.getValue(); + if (!Boolean.TRUE.equals(parameter.getRequired())) { + details.optionalParameters.add(parameterName); + } + } + } + result.add(details); + } + } + + private void processResources( + ArrayList result, String resourceName, Map resourceMap) { + if (resourceMap == null) { + return; + } + for (Map.Entry entry : resourceMap.entrySet()) { + RestResource resource = entry.getValue(); + String curResourceName = (resourceName.isEmpty() ? "" : resourceName + ".") + entry.getKey(); + processMethods(result, curResourceName, resource.getMethods()); + processResources(result, curResourceName, resource.getResources()); + } + } + + private void putParameter(String argName, Map parameters, + String parameterName, JsonSchema parameter, String parameterValue) throws Exception { + Object value = parameterValue; + if (parameter != null) { + if ("boolean".equals(parameter.getType())) { + value = Boolean.valueOf(parameterValue); + } else if ("number".equals(parameter.getType())) { + value = new BigDecimal(parameterValue); + } else if ("integer".equals(parameter.getType())) { + value = new BigInteger(parameterValue); + } + } + Object oldValue = parameters.put(parameterName, value); + if (oldValue != null) { + error("call", "duplicate parameter: " + argName); + } + } + + private static void error(String command, String detail) throws Exception { + String errorDetail = "ERROR: " + detail + + "For help, type: Arvados" + (command == null ? "" : " help " + command); + + logger.debug(errorDetail); + throw new Exception(errorDetail); + } + + +} diff --git a/sdk/java/src/main/java/org/arvados/sdk/java/MethodDetails.java b/sdk/java/src/main/java/org/arvados/sdk/java/MethodDetails.java new file mode 100644 index 0000000000..247924663c --- /dev/null +++ b/sdk/java/src/main/java/org/arvados/sdk/java/MethodDetails.java @@ -0,0 +1,22 @@ +package org.arvados.sdk.java; + +import com.google.api.client.util.Lists; +import com.google.api.client.util.Sets; + +import java.util.ArrayList; +import java.util.SortedSet; + +public class MethodDetails implements Comparable { + String name; + ArrayList requiredParameters = Lists.newArrayList(); + SortedSet optionalParameters = Sets.newTreeSet(); + boolean hasContent; + + @Override + public int compareTo(MethodDetails o) { + if (o == this) { + return 0; + } + return name.compareTo(o.name); + } +} \ No newline at end of file diff --git a/sdk/java/src/main/resources/log4j.properties b/sdk/java/src/main/resources/log4j.properties new file mode 100644 index 0000000000..89a9b93c06 --- /dev/null +++ b/sdk/java/src/main/resources/log4j.properties @@ -0,0 +1,11 @@ +# To change log location, change log4j.appender.fileAppender.File + +log4j.rootLogger=DEBUG, fileAppender + +log4j.appender.fileAppender=org.apache.log4j.RollingFileAppender +log4j.appender.fileAppender.File=${basedir}/log/arvados_sdk_java.log +log4j.appender.fileAppender.Append=true +log4j.appender.file.MaxFileSize=10MB +log4j.appender.file.MaxBackupIndex=10 +log4j.appender.fileAppender.layout=org.apache.log4j.PatternLayout +log4j.appender.fileAppender.layout.ConversionPattern=[%d] %-5p %c %L %x - %m%n diff --git a/sdk/java/src/test/java/org/arvados/sdk/java/ArvadosTest.java b/sdk/java/src/test/java/org/arvados/sdk/java/ArvadosTest.java new file mode 100644 index 0000000000..77157e5c46 --- /dev/null +++ b/sdk/java/src/test/java/org/arvados/sdk/java/ArvadosTest.java @@ -0,0 +1,262 @@ +package org.arvados.sdk.java; + +import java.io.File; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import org.junit.Test; + +import static org.junit.Assert.*; + +import com.google.api.services.discovery.model.RestDescription; +import com.google.api.services.discovery.model.RestResource; + +import org.json.simple.JSONObject; +import org.json.simple.parser.JSONParser; + +/** + * Unit test for Arvados. + */ +public class ArvadosTest { + + /** + * test discover method + * @throws Exception + */ + @Test + public void testDiscover() throws Exception { + Arvados arv = new Arvados("arvados", "v1"); + + RestDescription restDescription = arv.discover(); + + // The discover method returns the supported methods + Map resources = restDescription.getResources(); + assertNotNull("Expected resources", resources); + + Object users = resources.get("users"); + assertNotNull ("Expected users.list method", users); + assertEquals("Exepcted users.list to be a RestResource type", RestResource.class, users.getClass()); + + assertTrue("Root URL expected to match ARVADOS_API_HOST env paramdeter", + restDescription.getRootUrl().contains(System.getenv().get("ARVADOS_API_HOST"))); + } + + /** + * Test users.list api + * @throws Exception + */ + @Test + public void testCallUsersList() throws Exception { + Arvados arv = new Arvados("arvados", "v1"); + + List params = new ArrayList(); + params.add("call"); + params.add("arvados"); + params.add("v1"); + params.add("users.list"); + + String response = arv.call(params); + assertTrue("Expected users.list in response", response.contains("arvados#userList")); + assertTrue("Expected users.list in response", response.contains("uuid")); + + JSONParser parser = new JSONParser(); + Object obj = parser.parse(response); + JSONObject jsonObject = (JSONObject) obj; + + assertEquals("Expected kind to be users.list", "arvados#userList", jsonObject.get("kind")); + + List items = (List)jsonObject.get("items"); + assertNotNull("expected users list items", items); + assertTrue("expected at least one item in users list", items.size()>0); + + JSONObject firstUser = (JSONObject)items.get(0); + assertNotNull ("Expcted at least one user", firstUser); + + assertEquals("Expected kind to be user", "arvados#user", firstUser.get("kind")); + assertNotNull("Expected uuid for first user", firstUser.get("uuid")); + } + + /** + * Test users.get api + * @throws Exception + */ + @Test + public void testCallUsersGet() throws Exception { + Arvados arv = new Arvados("arvados", "v1"); + + // call user.system and get uuid of this user + List params = new ArrayList(); + params.add("call"); + params.add("arvados"); + params.add("v1"); + params.add("users.list"); + + String response = arv.call(params); + JSONParser parser = new JSONParser(); + Object obj = parser.parse(response); + JSONObject jsonObject = (JSONObject) obj; + assertNotNull("expected users list", jsonObject); + List items = (List)jsonObject.get("items"); + assertNotNull("expected users list items", items); + + JSONObject firstUser = (JSONObject)items.get(0); + String userUuid = (String)firstUser.get("uuid"); + + // invoke users.get with the system user uuid + params = new ArrayList(); + params.add("call"); + params.add("arvados"); + params.add("v1"); + params.add("users.get"); + params.add(userUuid); + + response = arv.call(params); + + //JSONParser parser = new JSONParser(); + jsonObject = (JSONObject) parser.parse(response);; + assertNotNull("Expected uuid for first user", jsonObject.get("uuid")); + assertEquals("Expected system user uuid", userUuid, jsonObject.get("uuid")); + } + + /** + * Test users.create api + * @throws Exception + */ + @Test + public void testCreateUser() throws Exception { + Arvados arv = new Arvados("arvados", "v1"); + + File file = new File(getClass().getResource( "/create_user.json" ).toURI()); + String filePath = file.getPath(); + + List params = new ArrayList(); + params.add("call"); + params.add("arvados"); + params.add("v1"); + params.add("users.create"); + params.add(filePath); + String response = arv.call(params); + + JSONParser parser = new JSONParser(); + JSONObject jsonObject = (JSONObject) parser.parse(response); + assertEquals("Expected kind to be user", "arvados#user", jsonObject.get("kind")); + assertNotNull("Expected uuid for first user", jsonObject.get("uuid")); + } + + /** + * Test unsupported api version api + * @throws Exception + */ + @Test + public void testUnsupportedApiName() throws Exception { + Arvados arv = new Arvados("not_arvados", "v1"); + + List params = new ArrayList(); + params.add("call"); + params.add("not_arvados"); + params.add("v1"); + params.add("users.list"); + + Exception caught = null; + try { + arv.call(params); + } catch (Exception e) { + caught = e; + } + + assertNotNull ("expected exception", caught); + assertTrue ("Expected 404 when unsupported api is used", caught.getMessage().contains("404 Not Found")); + } + + /** + * Test unsupported api version api + * @throws Exception + */ + @Test + public void testUnsupportedVersion() throws Exception { + Arvados arv = new Arvados("arvados", "v1"); + + List params = new ArrayList(); + params.add("call"); + params.add("arvados"); + params.add("v2"); // no such version + params.add("users.list"); + + Exception caught = null; + try { + arv.call(params); + } catch (Exception e) { + caught = e; + } + + assertNotNull ("expected exception", caught); + assertTrue ("Expected 404 when unsupported version is used", caught.getMessage().contains("404 Not Found")); + } + + /** + * Test unsupported api version api + * @throws Exception + */ + @Test + public void testCallWithTooFewParams() throws Exception { + Arvados arv = new Arvados("arvados", "v1"); + + List params = new ArrayList(); + params.add("call"); + params.add("arvados"); + params.add("v1"); + + Exception caught = null; + try { + arv.call(params); + } catch (Exception e) { + caught = e; + } + + assertNotNull ("expected exception", caught); + assertTrue ("Expected ERROR: missing method name", caught.getMessage().contains("ERROR: missing method name")); + } + + /** + * Test pipeline_tempates.create api + * @throws Exception + */ + @Test + public void testCreateAndGetPipelineTemplate() throws Exception { + Arvados arv = new Arvados("arvados", "v1"); + + File file = new File(getClass().getResource( "/first_pipeline.json" ).toURI()); + String filePath = file.getPath(); + + List params = new ArrayList(); + params.add("call"); + params.add("arvados"); + params.add("v1"); + params.add("pipeline_templates.create"); + params.add(filePath); + String response = arv.call(params); + + JSONParser parser = new JSONParser(); + JSONObject jsonObject = (JSONObject) parser.parse(response); + assertEquals("Expected kind to be user", "arvados#pipelineTemplate", jsonObject.get("kind")); + String uuid = (String)jsonObject.get("uuid"); + assertNotNull("Expected uuid for pipeline template", uuid); + + // get the pipeline + params = new ArrayList(); + params.add("call"); + params.add("arvados"); + params.add("v1"); + params.add("pipeline_templates.get"); + params.add(uuid); + response = arv.call(params); + + parser = new JSONParser(); + jsonObject = (JSONObject) parser.parse(response); + assertEquals("Expected kind to be user", "arvados#pipelineTemplate", jsonObject.get("kind")); + assertEquals("Expected uuid for pipeline template", uuid, jsonObject.get("uuid")); + } + + +} \ No newline at end of file diff --git a/sdk/java/src/test/resources/create_user.json b/sdk/java/src/test/resources/create_user.json new file mode 100644 index 0000000000..0967ef424b --- /dev/null +++ b/sdk/java/src/test/resources/create_user.json @@ -0,0 +1 @@ +{} diff --git a/sdk/java/src/test/resources/first_pipeline.json b/sdk/java/src/test/resources/first_pipeline.json new file mode 100644 index 0000000000..3caa972466 --- /dev/null +++ b/sdk/java/src/test/resources/first_pipeline.json @@ -0,0 +1,16 @@ +{ + "name":"first pipeline", + "components":{ + "do_hash":{ + "script":"hash.py", + "script_parameters":{ + "input":{ + "required": true, + "dataclass": "Collection" + } + }, + "script_version":"master", + "output_is_persistent":true + } + } +} diff --git a/services/api/app/controllers/arvados/v1/schema_controller.rb b/services/api/app/controllers/arvados/v1/schema_controller.rb index 1db5eff259..3ac614d180 100644 --- a/services/api/app/controllers/arvados/v1/schema_controller.rb +++ b/services/api/app/controllers/arvados/v1/schema_controller.rb @@ -204,17 +204,17 @@ class Arvados::V1::SchemaController < ApplicationController limit: { type: "integer", description: "Maximum number of #{k.to_s.underscore.pluralize} to return.", - default: 100, + default: "100", format: "int32", - minimum: 0, + minimum: "0", location: "query", }, offset: { type: "integer", description: "Number of #{k.to_s.underscore.pluralize} to skip before first returned record.", - default: 0, + default: "0", format: "int32", - minimum: 0, + minimum: "0", location: "query", }, filters: { @@ -366,6 +366,9 @@ class Arvados::V1::SchemaController < ApplicationController else method[:parameters][k] = {} end + if !method[:parameters][k][:default].nil? + method[:parameters][k][:default] = 'string' + end method[:parameters][k][:type] ||= 'string' method[:parameters][k][:description] ||= '' method[:parameters][k][:location] = (route.segment_keys.include?(k) ? 'path' : 'query')