1 // Copyright (C) The Arvados Authors. All rights reserved.
3 // SPDX-License-Identifier: Apache-2.0
5 package org.arvados.sdk;
7 import com.google.api.client.http.javanet.*;
8 import com.google.api.client.http.ByteArrayContent;
9 import com.google.api.client.http.GenericUrl;
10 import com.google.api.client.http.HttpBackOffIOExceptionHandler;
11 import com.google.api.client.http.HttpContent;
12 import com.google.api.client.http.HttpRequest;
13 import com.google.api.client.http.HttpRequestFactory;
14 import com.google.api.client.http.HttpTransport;
15 import com.google.api.client.http.UriTemplate;
16 import com.google.api.client.json.JsonFactory;
17 import com.google.api.client.json.jackson2.JacksonFactory;
18 import com.google.api.client.util.ExponentialBackOff;
19 import com.google.api.client.util.Maps;
20 import com.google.api.services.discovery.Discovery;
21 import com.google.api.services.discovery.model.JsonSchema;
22 import com.google.api.services.discovery.model.RestDescription;
23 import com.google.api.services.discovery.model.RestMethod;
24 import com.google.api.services.discovery.model.RestMethod.Request;
25 import com.google.api.services.discovery.model.RestResource;
27 import java.math.BigDecimal;
28 import java.math.BigInteger;
29 import java.util.ArrayList;
30 import java.util.Arrays;
31 import java.util.HashMap;
32 import java.util.List;
36 import org.apache.log4j.Logger;
37 import org.json.simple.JSONArray;
38 import org.json.simple.JSONObject;
41 * This class provides a java SDK interface to Arvados API server.
43 * Please refer to http://doc.arvados.org/api/ to learn about the
44 * various resources and methods exposed by the API server.
48 public class Arvados {
49 // HttpTransport and JsonFactory are thread-safe. So, use global instances.
50 private HttpTransport httpTransport;
51 private final JsonFactory jsonFactory = JacksonFactory.getDefaultInstance();
53 private String arvadosApiToken;
54 private String arvadosApiHost;
55 private boolean arvadosApiHostInsecure;
57 private String arvadosRootUrl;
59 private static final Logger logger = Logger.getLogger(Arvados.class);
61 // Get it once and reuse on the call requests
62 RestDescription restDescription = null;
63 String apiName = null;
64 String apiVersion = null;
66 public Arvados (String apiName, String apiVersion) throws Exception {
67 this (apiName, apiVersion, null, null, null);
70 public Arvados (String apiName, String apiVersion, String token,
71 String host, String hostInsecure) throws Exception {
72 this.apiName = apiName;
73 this.apiVersion = apiVersion;
75 // Read needed environmental variables if they are not passed
77 arvadosApiToken = token;
79 arvadosApiToken = System.getenv().get("ARVADOS_API_TOKEN");
80 if (arvadosApiToken == null) {
81 throw new Exception("Missing environment variable: ARVADOS_API_TOKEN");
86 arvadosApiHost = host;
88 arvadosApiHost = System.getenv().get("ARVADOS_API_HOST");
89 if (arvadosApiHost == null) {
90 throw new Exception("Missing environment variable: ARVADOS_API_HOST");
93 arvadosRootUrl = "https://" + arvadosApiHost;
95 if (hostInsecure != null) {
96 arvadosApiHostInsecure = Boolean.valueOf(hostInsecure);
98 arvadosApiHostInsecure =
99 "true".equals(System.getenv().get("ARVADOS_API_HOST_INSECURE")) ? true : false;
102 // Create HTTP_TRANSPORT object
103 NetHttpTransport.Builder builder = new NetHttpTransport.Builder();
104 if (arvadosApiHostInsecure) {
105 builder.doNotValidateCertificate();
107 httpTransport = builder.build();
109 // initialize rest description
110 restDescription = loadArvadosApi();
114 * Make a call to API server with the provide call information.
115 * @param resourceName
121 public Map call(String resourceName, String methodName,
122 Map<String, Object> paramsMap) throws Exception {
123 RestMethod method = getMatchingMethod(resourceName, methodName);
125 HashMap<String, Object> parameters = loadParameters(paramsMap, method);
127 GenericUrl url = new GenericUrl(UriTemplate.expand(
128 arvadosRootUrl + restDescription.getBasePath() + method.getPath(),
132 // construct the request
133 HttpRequestFactory requestFactory;
134 requestFactory = httpTransport.createRequestFactory();
136 // possibly required content
137 HttpContent content = null;
139 if (!method.getHttpMethod().equals("GET") &&
140 !method.getHttpMethod().equals("DELETE")) {
141 String objectName = resourceName.substring(0, resourceName.length()-1);
142 Object requestBody = paramsMap.get(objectName);
143 if (requestBody == null) {
144 error("POST method requires content object " + objectName);
147 content = new ByteArrayContent("application/json",((String)requestBody).getBytes());
150 HttpRequest request =
151 requestFactory.buildRequest(method.getHttpMethod(), url, content);
153 // Set read timeout to 120 seconds (up from default of 20 seconds)
154 request.setReadTimeout(120 * 1000);
156 // Add retry behavior
157 request.setIOExceptionHandler(new HttpBackOffIOExceptionHandler(new ExponentialBackOff()));
160 List<String> authHeader = new ArrayList<String>();
161 authHeader.add("OAuth2 " + arvadosApiToken);
162 request.getHeaders().put("Authorization", authHeader);
163 String response = request.execute().parseAsString();
165 Map responseMap = jsonFactory.createJsonParser(response).parse(HashMap.class);
167 logger.debug(responseMap);
170 } catch (Exception e) {
177 * Get all supported resources by the API
180 public Set<String> getAvailableResourses() {
181 return (restDescription.getResources().keySet());
185 * Get all supported method names for the given resource
186 * @param resourceName
190 public Set<String> getAvailableMethodsForResourse(String resourceName)
192 Map<String, RestMethod> methodMap = getMatchingMethodMap (resourceName);
193 return (methodMap.keySet());
197 * Get the parameters for the method in the resource sought.
198 * @param resourceName
203 public Map<String,List<String>> getAvailableParametersForMethod(String resourceName, String methodName)
205 RestMethod method = getMatchingMethod(resourceName, methodName);
206 Map<String, List<String>> parameters = new HashMap<String, List<String>>();
207 List<String> requiredParameters = new ArrayList<String>();
208 List<String> optionalParameters = new ArrayList<String>();
209 parameters.put ("required", requiredParameters);
210 parameters.put("optional", optionalParameters);
213 // get any request parameters
214 Request request = method.getRequest();
215 if (request != null) {
216 Object required = request.get("required");
217 Object requestProperties = request.get("properties");
218 if (requestProperties != null) {
219 if (requestProperties instanceof Map) {
220 Map properties = (Map)requestProperties;
221 Set<String> propertyKeys = properties.keySet();
222 for (String property : propertyKeys) {
223 if (Boolean.TRUE.equals(required)) {
224 requiredParameters.add(property);
226 optionalParameters.add(property);
233 // get other listed parameters
234 Map<String,JsonSchema> methodParameters = method.getParameters();
235 for (Map.Entry<String, JsonSchema> entry : methodParameters.entrySet()) {
236 if (Boolean.TRUE.equals(entry.getValue().getRequired())) {
237 requiredParameters.add(entry.getKey());
239 optionalParameters.add(entry.getKey());
242 } catch (Exception e){
249 private HashMap<String, Object> loadParameters(Map<String, Object> paramsMap,
250 RestMethod method) throws Exception {
251 HashMap<String, Object> parameters = Maps.newHashMap();
253 // required parameters
254 if (method.getParameterOrder() != null) {
255 for (String parameterName : method.getParameterOrder()) {
256 JsonSchema parameter = method.getParameters().get(parameterName);
257 if (Boolean.TRUE.equals(parameter.getRequired())) {
258 Object parameterValue = paramsMap.get(parameterName);
259 if (parameterValue == null) {
260 error("missing required parameter: " + parameter);
262 putParameter(null, parameters, parameterName, parameter, parameterValue);
268 for (Map.Entry<String, Object> entry : paramsMap.entrySet()) {
269 String parameterName = entry.getKey();
270 Object parameterValue = entry.getValue();
272 if (parameterName.equals("contentType")) {
273 if (method.getHttpMethod().equals("GET") || method.getHttpMethod().equals("DELETE")) {
274 error("HTTP content type cannot be specified for this method: " + parameterName);
277 JsonSchema parameter = null;
278 if (restDescription.getParameters() != null) {
279 parameter = restDescription.getParameters().get(parameterName);
281 if (parameter == null && method.getParameters() != null) {
282 parameter = method.getParameters().get(parameterName);
284 putParameter(parameterName, parameters, parameterName, parameter, parameterValue);
291 private RestMethod getMatchingMethod(String resourceName, String methodName)
293 Map<String, RestMethod> methodMap = getMatchingMethodMap(resourceName);
295 if (methodName == null) {
296 error("missing method name");
300 methodMap == null ? null : methodMap.get(methodName);
301 if (method == null) {
302 error("method not found: ");
308 private Map<String, RestMethod> getMatchingMethodMap(String resourceName)
310 if (resourceName == null) {
311 error("missing resource name");
314 Map<String, RestMethod> methodMap = null;
315 Map<String, RestResource> resources = restDescription.getResources();
316 RestResource resource = resources.get(resourceName);
317 if (resource == null) {
318 error("resource not found");
320 methodMap = resource.getMethods();
325 * Not thread-safe. So, create for each request.
331 private RestDescription loadArvadosApi()
336 Discovery.Builder discoveryBuilder =
337 new Discovery.Builder(httpTransport, jsonFactory, null);
339 discoveryBuilder.setRootUrl(arvadosRootUrl);
340 discoveryBuilder.setApplicationName(apiName);
342 discovery = discoveryBuilder.build();
344 return discovery.apis().getRest(apiName, apiVersion).execute();
345 } catch (Exception e) {
352 * Convert the input parameter into its equivalent json string.
353 * Add this json string value to the parameters map to be sent to server.
356 * @param parameterName
358 * @param parameterValue
361 private void putParameter(String argName, Map<String, Object> parameters,
362 String parameterName, JsonSchema parameter, Object parameterValue)
364 Object value = parameterValue;
365 if (parameter != null) {
366 if ("boolean".equals(parameter.getType())) {
367 value = Boolean.valueOf(parameterValue.toString());
368 } else if ("number".equals(parameter.getType())) {
369 value = new BigDecimal(parameterValue.toString());
370 } else if ("integer".equals(parameter.getType())) {
371 value = new BigInteger(parameterValue.toString());
372 } else if ("float".equals(parameter.getType())) {
373 value = new BigDecimal(parameterValue.toString());
374 } else if ("Java.util.Calendar".equals(parameter.getType())) {
375 value = new BigDecimal(parameterValue.toString());
376 } else if (("array".equals(parameter.getType())) ||
377 ("Array".equals(parameter.getType()))) {
378 if (parameterValue.getClass().isArray()){
379 value = getJsonValueFromArrayType(parameterValue);
380 } else if (List.class.isAssignableFrom(parameterValue.getClass())) {
381 value = getJsonValueFromListType(parameterValue);
383 } else if (("Hash".equals(parameter.getType())) ||
384 ("hash".equals(parameter.getType()))) {
385 value = getJsonValueFromMapType(parameterValue);
387 if (parameterValue.getClass().isArray()){
388 value = getJsonValueFromArrayType(parameterValue);
389 } else if (List.class.isAssignableFrom(parameterValue.getClass())) {
390 value = getJsonValueFromListType(parameterValue);
391 } else if (Map.class.isAssignableFrom(parameterValue.getClass())) {
392 value = getJsonValueFromMapType(parameterValue);
397 parameters.put(parameterName, value);
401 * Convert the given input array into json string before sending to server.
402 * @param parameterValue
405 private String getJsonValueFromArrayType (Object parameterValue) {
406 String arrayStr = Arrays.deepToString((Object[])parameterValue);
408 // we can expect either an array of array objects or an array of objects
409 if (arrayStr.startsWith("[[") && arrayStr.endsWith("]]")) {
410 Object[][] array = new Object[1][];
411 arrayStr = arrayStr.substring(2, arrayStr.length()-2);
412 String jsonStr = getJsonStringForArrayStr(arrayStr);
413 String value = "[" + jsonStr + "]";
416 arrayStr = arrayStr.substring(1, arrayStr.length()-1);
417 return (getJsonStringForArrayStr(arrayStr));
421 private String getJsonStringForArrayStr(String arrayStr) {
422 Object[] array = arrayStr.split(",");
423 Object[] trimmedArray = new Object[array.length];
424 for (int i=0; i<array.length; i++){
425 trimmedArray[i] = array[i].toString().trim();
427 String value = JSONArray.toJSONString(Arrays.asList(trimmedArray));
432 * Convert the given input List into json string before sending to server.
433 * @param parameterValue
436 private String getJsonValueFromListType (Object parameterValue) {
437 List paramList = (List)parameterValue;
438 Object[] array = new Object[paramList.size()];
439 Arrays.deepToString(paramList.toArray(array));
440 return (getJsonValueFromArrayType(array));
444 * Convert the given input map into json string before sending to server.
445 * @param parameterValue
448 private String getJsonValueFromMapType (Object parameterValue) {
449 JSONObject json = new JSONObject((Map)parameterValue);
450 return json.toString();
453 private static void error(String detail) throws Exception {
454 String errorDetail = "ERROR: " + detail;
456 logger.debug(errorDetail);
457 throw new Exception(errorDetail);
460 public static void main(String[] args){
461 System.out.println("Welcome to Arvados Java SDK.");
462 System.out.println("Please refer to http://doc.arvados.org/sdk/java/index.html to get started with the SDK.");