Merge branch 'master' into 2525-java-sdk
authorradhika <radhika@curoverse.com>
Sat, 3 May 2014 12:46:52 +0000 (08:46 -0400)
committerradhika <radhika@curoverse.com>
Sat, 3 May 2014 12:46:52 +0000 (08:46 -0400)
14 files changed:
.gitignore
sdk/java/.classpath [new file with mode: 0644]
sdk/java/.project [new file with mode: 0644]
sdk/java/.settings/org.eclipse.jdt.core.prefs [new file with mode: 0644]
sdk/java/ArvadosSDKJavaUser.java [new file with mode: 0644]
sdk/java/README [new file with mode: 0644]
sdk/java/pom.xml [new file with mode: 0644]
sdk/java/src/main/java/org/arvados/sdk/java/Arvados.java [new file with mode: 0644]
sdk/java/src/main/java/org/arvados/sdk/java/MethodDetails.java [new file with mode: 0644]
sdk/java/src/main/resources/log4j.properties [new file with mode: 0644]
sdk/java/src/test/java/org/arvados/sdk/java/ArvadosTest.java [new file with mode: 0644]
sdk/java/src/test/resources/create_user.json [new file with mode: 0644]
sdk/java/src/test/resources/first_pipeline.json [new file with mode: 0644]
services/api/app/controllers/arvados/v1/schema_controller.rb

index 2156fdf7e7404d77dcd0f853428596304aa9398f..602e1b9e40dbb8eda8a20ca1a607c40fa7e0b525 100644 (file)
@@ -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 (file)
index 0000000..5613077
--- /dev/null
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+       <classpathentry including="**/*.java" kind="src" output="target/test-classes" path="src/test/java"/>
+       <classpathentry including="**/*.java" kind="src" path="src/main/java"/>
+       <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
+       <classpathentry kind="var" path="M2_REPO/com/google/apis/google-api-services-discovery/v1-rev42-1.18.0-rc/google-api-services-discovery-v1-rev42-1.18.0-rc.jar" sourcepath="/GOOGLE_SERVICE_DISCOVERY"/>
+       <classpathentry kind="var" path="M2_REPO/com/google/api-client/google-api-client/1.18.0-rc/google-api-client-1.18.0-rc.jar" sourcepath="/GOOGLE_API_CLIENT/google-api-client-1.18.0-rc-sources.jar"/>
+       <classpathentry kind="var" path="M2_REPO/com/google/http-client/google-http-client/1.18.0-rc/google-http-client-1.18.0-rc.jar"/>
+       <classpathentry kind="var" path="M2_REPO/com/google/code/findbugs/jsr305/1.3.9/jsr305-1.3.9.jar"/>
+       <classpathentry kind="var" path="M2_REPO/org/apache/httpcomponents/httpclient/4.0.1/httpclient-4.0.1.jar"/>
+       <classpathentry kind="var" path="M2_REPO/org/apache/httpcomponents/httpcore/4.0.1/httpcore-4.0.1.jar"/>
+       <classpathentry kind="var" path="M2_REPO/commons-logging/commons-logging/1.1.1/commons-logging-1.1.1.jar"/>
+       <classpathentry kind="var" path="M2_REPO/commons-codec/commons-codec/1.3/commons-codec-1.3.jar"/>
+       <classpathentry kind="var" path="M2_REPO/com/google/http-client/google-http-client-jackson2/1.18.0-rc/google-http-client-jackson2-1.18.0-rc.jar"/>
+       <classpathentry kind="var" path="M2_REPO/com/fasterxml/jackson/core/jackson-core/2.1.3/jackson-core-2.1.3.jar"/>
+       <classpathentry kind="var" path="M2_REPO/com/google/guava/guava/r05/guava-r05.jar"/>
+       <classpathentry kind="var" path="M2_REPO/log4j/log4j/1.2.16/log4j-1.2.16.jar"/>
+       <classpathentry kind="var" path="M2_REPO/com/googlecode/json-simple/json-simple/1.1.1/json-simple-1.1.1.jar"/>
+       <classpathentry kind="var" path="M2_REPO/junit/junit/4.8.1/junit-4.8.1.jar"/>
+       <classpathentry kind="output" path="target/classes"/>
+</classpath>
diff --git a/sdk/java/.project b/sdk/java/.project
new file mode 100644 (file)
index 0000000..40c2bdf
--- /dev/null
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+  <name>java</name>
+  <comment>NO_M2ECLIPSE_SUPPORT: Project files created with the maven-eclipse-plugin are not supported in M2Eclipse.</comment>
+  <projects/>
+  <buildSpec>
+    <buildCommand>
+      <name>org.eclipse.jdt.core.javabuilder</name>
+    </buildCommand>
+  </buildSpec>
+  <natures>
+    <nature>org.eclipse.jdt.core.javanature</nature>
+  </natures>
+</projectDescription>
\ 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 (file)
index 0000000..f4f19ea
--- /dev/null
@@ -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 (file)
index 0000000..289d0c6
--- /dev/null
@@ -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<String> params = new ArrayList<String>();
+    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<String>();
+    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<String>();
+    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<String>();
+    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<String>();
+    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 (file)
index 0000000..423cf97
--- /dev/null
@@ -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 <ARVADOS_HOME> is used to refer to the directory where
+      arvados code is cloned in your system.
+      For ex: <ARVADOS_HOME> = $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 <ARVADOS_HOME/sdk/java
+
+  - mvn clean package
+
+      This will generate arvados sdk jar file the target directory
+
+
+== Using Arvados SDK at command line
+
+  - This section helps understand how to use the SDK from command line
+
+  - cd <ARVADOS_HOME/sdk/java
+
+  - Getting help
+
+      java -cp target/java-1.0-SNAPSHOT-jar-with-dependencies.jar org.arvados.sdk.java.Arvados help
+
+      java -cp target/java-1.0-SNAPSHOT-jar-with-dependencies.jar org.arvados.sdk.java.Arvados help call
+
+      java -cp target/java-1.0-SNAPSHOT-jar-with-dependencies.jar org.arvados.sdk.java.Arvados help discover
+
+
+  - Getting the discovery document
+
+      java -cp target/java-1.0-SNAPSHOT-jar-with-dependencies.jar org.arvados.sdk.java.Arvados discover arvados v1
+
+
+  - Making a "call" to the API server
+
+      java -cp target/java-1.0-SNAPSHOT-jar-with-dependencies.jar org.arvados.sdk.java.Arvados call arvados v1 users.list
+
+      java -cp target/java-1.0-SNAPSHOT-jar-with-dependencies.jar org.arvados.sdk.java.Arvados call arvados v1 users.get <uuid>
+
+      java -cp target/java-1.0-SNAPSHOT-jar-with-dependencies.jar org.arvados.sdk.java.Arvados call arvados v1 users.create <filename>
+
+      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 <uuid>
+
+      java -cp target/java-1.0-SNAPSHOT-jar-with-dependencies.jar org.arvados.sdk.java.Arvados call arvados v1 pipeline_templates.create <filename>
+
+
+== Implementing your code to use SDK
+
+  - <ARVADOS_HOME>/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 <ARVADOS_HOME>/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 .:<ARVADOS_HOME>/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
+      <ARVADOS_HOME>/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 <ARVADOS_HOME>/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 <ARVADOS_HOME>/sdk/java
diff --git a/sdk/java/pom.xml b/sdk/java/pom.xml
new file mode 100644 (file)
index 0000000..ed0e483
--- /dev/null
@@ -0,0 +1,105 @@
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+  <modelVersion>4.0.0</modelVersion>
+  <groupId>org.arvados.sdk.java</groupId>
+  <artifactId>java</artifactId>
+  <packaging>jar</packaging>
+  <version>1.0-SNAPSHOT</version>
+  <name>java</name>
+  <url>http://maven.apache.org</url>
+  
+  <dependencies>
+    <dependency>
+       <groupId>com.google.apis</groupId>
+       <artifactId>google-api-services-discovery</artifactId>
+       <version>v1-rev42-1.18.0-rc</version>
+    </dependency>
+    <dependency>
+      <groupId>com.google.api-client</groupId>
+      <artifactId>google-api-client</artifactId>
+      <version>1.18.0-rc</version>
+    </dependency>
+    <dependency>
+       <groupId>com.google.http-client</groupId>
+       <artifactId>google-http-client-jackson2</artifactId>
+       <version>1.18.0-rc</version>
+    </dependency>
+    <dependency>
+       <groupId>com.google.guava</groupId>
+       <artifactId>guava</artifactId>
+       <version>r05</version>
+    </dependency>
+    <dependency>
+       <groupId>log4j</groupId>
+       <artifactId>log4j</artifactId>
+       <version>1.2.16</version>
+    </dependency>
+                    
+    <dependency>
+         <groupId>junit</groupId>
+         <artifactId>junit</artifactId>
+         <version>4.8.1</version>
+       </dependency>
+    <dependency>
+        <groupId>com.googlecode.json-simple</groupId>
+        <artifactId>json-simple</artifactId>
+        <version>1.1.1</version>
+       </dependency>
+  </dependencies>
+
+  <build>
+    <plugins>
+       <plugin>
+          <groupId>org.apache.maven.plugins</groupId>
+          <artifactId>maven-compiler-plugin</artifactId>
+          <version>3.1</version>
+          <configuration>
+            <source>1.6</source>
+            <target>1.6</target>
+          </configuration>
+       </plugin>
+        <plugin>
+          <groupId>org.apache.maven.plugins</groupId>
+          <artifactId>maven-assembly-plugin</artifactId>
+          <executions>
+            <execution>
+              <goals>
+                <goal>attached</goal>
+              </goals>
+              <phase>package</phase>
+              <configuration>
+                <descriptorRefs>
+                  <descriptorRef>jar-with-dependencies</descriptorRef>
+                </descriptorRefs>
+                <archive>
+                  <manifest>
+                    <mainClass>org.arvados.sdk.Arvados</mainClass>
+                  </manifest>
+                  <manifestEntries>
+                    <!--<Premain-Class>Your.agent.class</Premain-Class>
+                    <Agent-Class>Your.agent.class</Agent-Class>-->
+                    <Can-Redefine-Classes>true</Can-Redefine-Classes>
+                    <Can-Retransform-Classes>true</Can-Retransform-Classes>
+                  </manifestEntries>
+                </archive>
+              </configuration>
+            </execution>
+          </executions>
+        </plugin>
+      </plugins>
+      <resources>
+       <resource>
+          <directory>src/main/resources</directory>
+            <targetPath>${basedir}/target/classes</targetPath>
+            <includes>
+              <include>log4j.properties</include>
+            </includes>        
+          <filtering>true</filtering>      
+        </resource>
+       <resource>
+          <directory>src/test/resources</directory>      
+          <filtering>true</filtering>      
+        </resource>
+      </resources>
+    </build>
+</project>
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 (file)
index 0000000..260b68d
--- /dev/null
@@ -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<MethodDetails> 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 <value> (default is \"application/json\")");
+        for (String param : methodDetail.optionalParameters) {
+          buffer.append("\n  --" + param + " <value>");
+        }
+      }
+    }
+    logger.debug(buffer.toString());
+    
+    return (restDescription);
+  }
+
+  public String call(List<String> 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<String, RestMethod> methodMap = null;
+    int curIndex = 0;
+    int nextIndex = fullMethodName.indexOf('.');
+    if (nextIndex == -1) {
+      methodMap = restDescription.getMethods();
+    } else {
+      Map<String, RestResource> 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<String, Object> parameters = Maps.newHashMap();
+    File requestBodyFile = null;
+    String contentType = "application/json";
+
+    // Start looking for params at index 4. The first 4 were: call arvados v1 <method_name>
+    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<String> authHeader = new ArrayList<String>();
+      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<MethodDetails> result, String resourceName, Map<String, RestMethod> methodMap) {
+    if (methodMap == null) {
+      return;
+    }
+    for (Map.Entry<String, RestMethod> 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<String, JsonSchema> parameters = method.getParameters();
+      if (parameters != null) {
+        for (Map.Entry<String, JsonSchema> 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<MethodDetails> result, String resourceName, Map<String, RestResource> resourceMap) {
+    if (resourceMap == null) {
+      return;
+    }
+    for (Map.Entry<String, RestResource> 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<String, Object> 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 (file)
index 0000000..2479246
--- /dev/null
@@ -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<MethodDetails> {
+    String name;
+    ArrayList<String> requiredParameters = Lists.newArrayList();
+    SortedSet<String> 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 (file)
index 0000000..89a9b93
--- /dev/null
@@ -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 (file)
index 0000000..77157e5
--- /dev/null
@@ -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<String, RestResource> 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<String> params = new ArrayList<String>();
+    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 <uuid> 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<String> params = new ArrayList<String>();
+    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<String>();
+    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<String> params = new ArrayList<String>();
+    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<String> params = new ArrayList<String>();
+    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<String> params = new ArrayList<String>();
+    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<String> params = new ArrayList<String>();
+    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<String> params = new ArrayList<String>();
+    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<String>();
+    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 (file)
index 0000000..0967ef4
--- /dev/null
@@ -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 (file)
index 0000000..3caa972
--- /dev/null
@@ -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
+    }
+  }
+}
index 1db5eff2595792f9714cb3c812ffbe30c5b4023a..3ac614d1804d0fa69c2265dc38bc13dc14671e88 100644 (file)
@@ -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')