1 package org.arvados.sdk;
3 import com.google.api.client.http.javanet.*;
4 import com.google.api.client.http.ByteArrayContent;
5 import com.google.api.client.http.GenericUrl;
6 import com.google.api.client.http.HttpBackOffIOExceptionHandler;
7 import com.google.api.client.http.HttpContent;
8 import com.google.api.client.http.HttpRequest;
9 import com.google.api.client.http.HttpRequestFactory;
10 import com.google.api.client.http.HttpTransport;
11 import com.google.api.client.http.UriTemplate;
12 import com.google.api.client.json.JsonFactory;
13 import com.google.api.client.json.jackson2.JacksonFactory;
14 import com.google.api.client.util.ExponentialBackOff;
15 import com.google.api.client.util.Maps;
16 import com.google.api.services.discovery.Discovery;
17 import com.google.api.services.discovery.model.JsonSchema;
18 import com.google.api.services.discovery.model.RestDescription;
19 import com.google.api.services.discovery.model.RestMethod;
20 import com.google.api.services.discovery.model.RestMethod.Request;
21 import com.google.api.services.discovery.model.RestResource;
23 import java.math.BigDecimal;
24 import java.math.BigInteger;
25 import java.util.ArrayList;
26 import java.util.Arrays;
27 import java.util.HashMap;
28 import java.util.List;
32 import org.apache.log4j.Logger;
33 import org.json.simple.JSONArray;
34 import org.json.simple.JSONObject;
37 * This class provides a java SDK interface to Arvados API server.
39 * Please refer to http://doc.arvados.org/api/ to learn about the
40 * various resources and methods exposed by the API server.
44 public class Arvados {
45 // HttpTransport and JsonFactory are thread-safe. So, use global instances.
46 private HttpTransport httpTransport;
47 private final JsonFactory jsonFactory = JacksonFactory.getDefaultInstance();
49 private String arvadosApiToken;
50 private String arvadosApiHost;
51 private boolean arvadosApiHostInsecure;
53 private String arvadosRootUrl;
55 private static final Logger logger = Logger.getLogger(Arvados.class);
57 // Get it once and reuse on the call requests
58 RestDescription restDescription = null;
59 String apiName = null;
60 String apiVersion = null;
62 public Arvados (String apiName, String apiVersion) throws Exception {
63 this (apiName, apiVersion, null, null, null);
66 public Arvados (String apiName, String apiVersion, String token,
67 String host, String hostInsecure) throws Exception {
68 this.apiName = apiName;
69 this.apiVersion = apiVersion;
71 // Read needed environmental variables if they are not passed
73 arvadosApiToken = token;
75 arvadosApiToken = System.getenv().get("ARVADOS_API_TOKEN");
76 if (arvadosApiToken == null) {
77 throw new Exception("Missing environment variable: ARVADOS_API_TOKEN");
82 arvadosApiHost = host;
84 arvadosApiHost = System.getenv().get("ARVADOS_API_HOST");
85 if (arvadosApiHost == null) {
86 throw new Exception("Missing environment variable: ARVADOS_API_HOST");
89 arvadosRootUrl = "https://" + arvadosApiHost;
90 arvadosRootUrl += (arvadosApiHost.endsWith("/")) ? "" : "/";
92 if (hostInsecure != null) {
93 arvadosApiHostInsecure = Boolean.valueOf(hostInsecure);
95 arvadosApiHostInsecure =
96 "true".equals(System.getenv().get("ARVADOS_API_HOST_INSECURE")) ? true : false;
99 // Create HTTP_TRANSPORT object
100 NetHttpTransport.Builder builder = new NetHttpTransport.Builder();
101 if (arvadosApiHostInsecure) {
102 builder.doNotValidateCertificate();
104 httpTransport = builder.build();
106 // initialize rest description
107 restDescription = loadArvadosApi();
111 * Make a call to API server with the provide call information.
112 * @param resourceName
118 public Map call(String resourceName, String methodName,
119 Map<String, Object> paramsMap) throws Exception {
120 RestMethod method = getMatchingMethod(resourceName, methodName);
122 HashMap<String, Object> parameters = loadParameters(paramsMap, method);
124 GenericUrl url = new GenericUrl(UriTemplate.expand(
125 arvadosRootUrl + restDescription.getBasePath() + method.getPath(),
129 // construct the request
130 HttpRequestFactory requestFactory;
131 requestFactory = httpTransport.createRequestFactory();
133 // possibly required content
134 HttpContent content = null;
136 if (!method.getHttpMethod().equals("GET") &&
137 !method.getHttpMethod().equals("DELETE")) {
138 String objectName = resourceName.substring(0, resourceName.length()-1);
139 Object requestBody = paramsMap.get(objectName);
140 if (requestBody == null) {
141 error("POST method requires content object " + objectName);
144 content = new ByteArrayContent("application/json",((String)requestBody).getBytes());
147 HttpRequest request =
148 requestFactory.buildRequest(method.getHttpMethod(), url, content);
150 // Set read timeout to 120 seconds (up from default of 20 seconds)
151 request.setReadTimeout(120 * 1000);
153 // Add retry behavior
154 request.setIOExceptionHandler(new HttpBackOffIOExceptionHandler(new ExponentialBackOff()));
157 List<String> authHeader = new ArrayList<String>();
158 authHeader.add("OAuth2 " + arvadosApiToken);
159 request.getHeaders().put("Authorization", authHeader);
160 String response = request.execute().parseAsString();
162 Map responseMap = jsonFactory.createJsonParser(response).parse(HashMap.class);
164 logger.debug(responseMap);
167 } catch (Exception e) {
174 * Get all supported resources by the API
177 public Set<String> getAvailableResourses() {
178 return (restDescription.getResources().keySet());
182 * Get all supported method names for the given resource
183 * @param resourceName
187 public Set<String> getAvailableMethodsForResourse(String resourceName)
189 Map<String, RestMethod> methodMap = getMatchingMethodMap (resourceName);
190 return (methodMap.keySet());
194 * Get the parameters for the method in the resource sought.
195 * @param resourceName
200 public Map<String,List<String>> getAvailableParametersForMethod(String resourceName, String methodName)
202 RestMethod method = getMatchingMethod(resourceName, methodName);
203 Map<String, List<String>> parameters = new HashMap<String, List<String>>();
204 List<String> requiredParameters = new ArrayList<String>();
205 List<String> optionalParameters = new ArrayList<String>();
206 parameters.put ("required", requiredParameters);
207 parameters.put("optional", optionalParameters);
210 // get any request parameters
211 Request request = method.getRequest();
212 if (request != null) {
213 Object required = request.get("required");
214 Object requestProperties = request.get("properties");
215 if (requestProperties != null) {
216 if (requestProperties instanceof Map) {
217 Map properties = (Map)requestProperties;
218 Set<String> propertyKeys = properties.keySet();
219 for (String property : propertyKeys) {
220 if (Boolean.TRUE.equals(required)) {
221 requiredParameters.add(property);
223 optionalParameters.add(property);
230 // get other listed parameters
231 Map<String,JsonSchema> methodParameters = method.getParameters();
232 for (Map.Entry<String, JsonSchema> entry : methodParameters.entrySet()) {
233 if (Boolean.TRUE.equals(entry.getValue().getRequired())) {
234 requiredParameters.add(entry.getKey());
236 optionalParameters.add(entry.getKey());
239 } catch (Exception e){
246 private HashMap<String, Object> loadParameters(Map<String, Object> paramsMap,
247 RestMethod method) throws Exception {
248 HashMap<String, Object> parameters = Maps.newHashMap();
250 // required parameters
251 if (method.getParameterOrder() != null) {
252 for (String parameterName : method.getParameterOrder()) {
253 JsonSchema parameter = method.getParameters().get(parameterName);
254 if (Boolean.TRUE.equals(parameter.getRequired())) {
255 Object parameterValue = paramsMap.get(parameterName);
256 if (parameterValue == null) {
257 error("missing required parameter: " + parameter);
259 putParameter(null, parameters, parameterName, parameter, parameterValue);
265 for (Map.Entry<String, Object> entry : paramsMap.entrySet()) {
266 String parameterName = entry.getKey();
267 Object parameterValue = entry.getValue();
269 if (parameterName.equals("contentType")) {
270 if (method.getHttpMethod().equals("GET") || method.getHttpMethod().equals("DELETE")) {
271 error("HTTP content type cannot be specified for this method: " + parameterName);
274 JsonSchema parameter = null;
275 if (restDescription.getParameters() != null) {
276 parameter = restDescription.getParameters().get(parameterName);
278 if (parameter == null && method.getParameters() != null) {
279 parameter = method.getParameters().get(parameterName);
281 putParameter(parameterName, parameters, parameterName, parameter, parameterValue);
288 private RestMethod getMatchingMethod(String resourceName, String methodName)
290 Map<String, RestMethod> methodMap = getMatchingMethodMap(resourceName);
292 if (methodName == null) {
293 error("missing method name");
297 methodMap == null ? null : methodMap.get(methodName);
298 if (method == null) {
299 error("method not found: ");
305 private Map<String, RestMethod> getMatchingMethodMap(String resourceName)
307 if (resourceName == null) {
308 error("missing resource name");
311 Map<String, RestMethod> methodMap = null;
312 Map<String, RestResource> resources = restDescription.getResources();
313 RestResource resource = resources.get(resourceName);
314 if (resource == null) {
315 error("resource not found");
317 methodMap = resource.getMethods();
322 * Not thread-safe. So, create for each request.
328 private RestDescription loadArvadosApi()
333 Discovery.Builder discoveryBuilder =
334 new Discovery.Builder(httpTransport, jsonFactory, null);
336 discoveryBuilder.setRootUrl(arvadosRootUrl);
337 discoveryBuilder.setApplicationName(apiName);
339 discovery = discoveryBuilder.build();
341 return discovery.apis().getRest(apiName, apiVersion).execute();
342 } catch (Exception e) {
349 * Convert the input parameter into its equivalent json string.
350 * Add this json string value to the parameters map to be sent to server.
353 * @param parameterName
355 * @param parameterValue
358 private void putParameter(String argName, Map<String, Object> parameters,
359 String parameterName, JsonSchema parameter, Object parameterValue)
361 Object value = parameterValue;
362 if (parameter != null) {
363 if ("boolean".equals(parameter.getType())) {
364 value = Boolean.valueOf(parameterValue.toString());
365 } else if ("number".equals(parameter.getType())) {
366 value = new BigDecimal(parameterValue.toString());
367 } else if ("integer".equals(parameter.getType())) {
368 value = new BigInteger(parameterValue.toString());
369 } else if ("float".equals(parameter.getType())) {
370 value = new BigDecimal(parameterValue.toString());
371 } else if ("Java.util.Calendar".equals(parameter.getType())) {
372 value = new BigDecimal(parameterValue.toString());
373 } else if (("array".equals(parameter.getType())) ||
374 ("Array".equals(parameter.getType()))) {
375 if (parameterValue.getClass().isArray()){
376 value = getJsonValueFromArrayType(parameterValue);
377 } else if (List.class.isAssignableFrom(parameterValue.getClass())) {
378 value = getJsonValueFromListType(parameterValue);
380 } else if (("Hash".equals(parameter.getType())) ||
381 ("hash".equals(parameter.getType()))) {
382 value = getJsonValueFromMapType(parameterValue);
384 if (parameterValue.getClass().isArray()){
385 value = getJsonValueFromArrayType(parameterValue);
386 } else if (List.class.isAssignableFrom(parameterValue.getClass())) {
387 value = getJsonValueFromListType(parameterValue);
388 } else if (Map.class.isAssignableFrom(parameterValue.getClass())) {
389 value = getJsonValueFromMapType(parameterValue);
394 parameters.put(parameterName, value);
398 * Convert the given input array into json string before sending to server.
399 * @param parameterValue
402 private String getJsonValueFromArrayType (Object parameterValue) {
403 String arrayStr = Arrays.deepToString((Object[])parameterValue);
405 // we can expect either an array of array objects or an array of objects
406 if (arrayStr.startsWith("[[") && arrayStr.endsWith("]]")) {
407 Object[][] array = new Object[1][];
408 arrayStr = arrayStr.substring(2, arrayStr.length()-2);
409 String jsonStr = getJsonStringForArrayStr(arrayStr);
410 String value = "[" + jsonStr + "]";
413 arrayStr = arrayStr.substring(1, arrayStr.length()-1);
414 return (getJsonStringForArrayStr(arrayStr));
418 private String getJsonStringForArrayStr(String arrayStr) {
419 Object[] array = arrayStr.split(",");
420 Object[] trimmedArray = new Object[array.length];
421 for (int i=0; i<array.length; i++){
422 trimmedArray[i] = array[i].toString().trim();
424 String value = JSONArray.toJSONString(Arrays.asList(trimmedArray));
429 * Convert the given input List into json string before sending to server.
430 * @param parameterValue
433 private String getJsonValueFromListType (Object parameterValue) {
434 List paramList = (List)parameterValue;
435 Object[] array = new Object[paramList.size()];
436 Arrays.deepToString(paramList.toArray(array));
437 return (getJsonValueFromArrayType(array));
441 * Convert the given input map into json string before sending to server.
442 * @param parameterValue
445 private String getJsonValueFromMapType (Object parameterValue) {
446 JSONObject json = new JSONObject((Map)parameterValue);
447 return json.toString();
450 private static void error(String detail) throws Exception {
451 String errorDetail = "ERROR: " + detail;
453 logger.debug(errorDetail);
454 throw new Exception(errorDetail);
457 public static void main(String[] args){
458 System.out.println("Welcome to Arvados Java SDK.");
459 System.out.println("Please refer to http://doc.arvados.org/sdk/java/index.html to get started with the the SDK.");