Add 'apps/arv-web/' from commit 'f9732ad8460d013c2f28363655d0d1b91894dca5'
[arvados.git] / sdk / java / src / main / java / org / arvados / sdk / java / Arvados.java
1 package org.arvados.sdk.java;
2
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.HttpContent;
7 import com.google.api.client.http.HttpRequest;
8 import com.google.api.client.http.HttpRequestFactory;
9 import com.google.api.client.http.HttpTransport;
10 import com.google.api.client.http.UriTemplate;
11 import com.google.api.client.json.JsonFactory;
12 import com.google.api.client.json.jackson2.JacksonFactory;
13 import com.google.api.client.util.Maps;
14 import com.google.api.services.discovery.Discovery;
15 import com.google.api.services.discovery.model.JsonSchema;
16 import com.google.api.services.discovery.model.RestDescription;
17 import com.google.api.services.discovery.model.RestMethod;
18 import com.google.api.services.discovery.model.RestMethod.Request;
19 import com.google.api.services.discovery.model.RestResource;
20
21 import java.math.BigDecimal;
22 import java.math.BigInteger;
23 import java.util.ArrayList;
24 import java.util.Arrays;
25 import java.util.HashMap;
26 import java.util.List;
27 import java.util.Map;
28 import java.util.Set;
29
30 import org.apache.log4j.Logger;
31 import org.json.simple.JSONArray;
32 import org.json.simple.JSONObject;
33
34 /**
35  * This class provides a java SDK interface to Arvados API server.
36  * 
37  * Please refer to http://doc.arvados.org/api/ to learn about the
38  *  various resources and methods exposed by the API server.
39  *  
40  * @author radhika
41  */
42 public class Arvados {
43   // HttpTransport and JsonFactory are thread-safe. So, use global instances.
44   private HttpTransport httpTransport;
45   private final JsonFactory jsonFactory = JacksonFactory.getDefaultInstance();
46
47   private String arvadosApiToken;
48   private String arvadosApiHost;
49   private boolean arvadosApiHostInsecure;
50
51   private String arvadosRootUrl;
52
53   private static final Logger logger = Logger.getLogger(Arvados.class);
54
55   // Get it once and reuse on the call requests
56   RestDescription restDescription = null;
57   String apiName = null;
58   String apiVersion = null;
59
60   public Arvados (String apiName, String apiVersion) throws Exception {
61     this (apiName, apiVersion, null, null, null);
62   }
63
64   public Arvados (String apiName, String apiVersion, String token,
65       String host, String hostInsecure) throws Exception {
66     this.apiName = apiName;
67     this.apiVersion = apiVersion;
68
69     // Read needed environmental variables if they are not passed
70     if (token != null) {
71       arvadosApiToken = token;
72     } else {
73       arvadosApiToken = System.getenv().get("ARVADOS_API_TOKEN");
74       if (arvadosApiToken == null) {
75         throw new Exception("Missing environment variable: ARVADOS_API_TOKEN");
76       }
77     }
78
79     if (host != null) {
80       arvadosApiHost = host;
81     } else {
82       arvadosApiHost = System.getenv().get("ARVADOS_API_HOST");      
83       if (arvadosApiHost == null) {
84         throw new Exception("Missing environment variable: ARVADOS_API_HOST");
85       }
86     }
87     arvadosRootUrl = "https://" + arvadosApiHost;
88     arvadosRootUrl += (arvadosApiHost.endsWith("/")) ? "" : "/";
89
90     if (hostInsecure != null) {
91       arvadosApiHostInsecure = Boolean.valueOf(hostInsecure);
92     } else {
93       arvadosApiHostInsecure =
94           "true".equals(System.getenv().get("ARVADOS_API_HOST_INSECURE")) ? true : false;
95     }
96
97     // Create HTTP_TRANSPORT object
98     NetHttpTransport.Builder builder = new NetHttpTransport.Builder();
99     if (arvadosApiHostInsecure) {
100       builder.doNotValidateCertificate();
101     }
102     httpTransport = builder.build();
103
104     // initialize rest description
105     restDescription = loadArvadosApi();
106   }
107
108   /**
109    * Make a call to API server with the provide call information.
110    * @param resourceName
111    * @param methodName
112    * @param paramsMap
113    * @return Map
114    * @throws Exception
115    */
116   public Map call(String resourceName, String methodName,
117       Map<String, Object> paramsMap) throws Exception {
118     RestMethod method = getMatchingMethod(resourceName, methodName);
119
120     HashMap<String, Object> parameters = loadParameters(paramsMap, method);
121
122     GenericUrl url = new GenericUrl(UriTemplate.expand(
123         arvadosRootUrl + restDescription.getBasePath() + method.getPath(), 
124         parameters, true));
125
126     try {
127       // construct the request
128       HttpRequestFactory requestFactory;
129       requestFactory = httpTransport.createRequestFactory();
130
131       // possibly required content
132       HttpContent content = null;
133
134       if (!method.getHttpMethod().equals("GET") &&
135           !method.getHttpMethod().equals("DELETE")) {
136         String objectName = resourceName.substring(0, resourceName.length()-1);
137         Object requestBody = paramsMap.get(objectName);
138         if (requestBody == null) {
139           error("POST method requires content object " + objectName);
140         }
141
142         content = new ByteArrayContent("application/json",((String)requestBody).getBytes());
143       }
144
145       HttpRequest request =
146           requestFactory.buildRequest(method.getHttpMethod(), url, content);
147
148       // make the request
149       List<String> authHeader = new ArrayList<String>();
150       authHeader.add("OAuth2 " + arvadosApiToken);
151       request.getHeaders().put("Authorization", authHeader);
152       String response = request.execute().parseAsString();
153
154       Map responseMap = jsonFactory.createJsonParser(response).parse(HashMap.class);
155
156       logger.debug(responseMap);
157
158       return responseMap;
159     } catch (Exception e) {
160       e.printStackTrace();
161       throw e;
162     }
163   }
164
165   /**
166    * Get all supported resources by the API
167    * @return Set
168    */
169   public Set<String> getAvailableResourses() {
170     return (restDescription.getResources().keySet());
171   }
172
173   /**
174    * Get all supported method names for the given resource
175    * @param resourceName
176    * @return Set
177    * @throws Exception
178    */
179   public Set<String> getAvailableMethodsForResourse(String resourceName)
180       throws Exception {
181     Map<String, RestMethod> methodMap = getMatchingMethodMap (resourceName);
182     return (methodMap.keySet());
183   }
184
185   /**
186    * Get the parameters for the method in the resource sought.
187    * @param resourceName
188    * @param methodName
189    * @return Set
190    * @throws Exception
191    */
192   public Map<String,List<String>> getAvailableParametersForMethod(String resourceName, String methodName)
193       throws Exception {
194     RestMethod method = getMatchingMethod(resourceName, methodName);
195     Map<String, List<String>> parameters = new HashMap<String, List<String>>();
196     List<String> requiredParameters = new ArrayList<String>();
197     List<String> optionalParameters = new ArrayList<String>();
198     parameters.put ("required", requiredParameters);
199     parameters.put("optional", optionalParameters);
200
201     try {
202       // get any request parameters
203       Request request = method.getRequest();
204       if (request != null) {
205         Object required = request.get("required");
206         Object requestProperties = request.get("properties");
207         if (requestProperties != null) {
208           if (requestProperties instanceof Map) {
209             Map properties = (Map)requestProperties;
210             Set<String> propertyKeys = properties.keySet();
211             for (String property : propertyKeys) {
212               if (Boolean.TRUE.equals(required)) {
213                 requiredParameters.add(property);
214               } else {
215                 optionalParameters.add(property);                
216               }
217             }
218           }
219         }
220       }
221
222       // get other listed parameters
223       Map<String,JsonSchema> methodParameters = method.getParameters();
224       for (Map.Entry<String, JsonSchema> entry : methodParameters.entrySet()) {
225         if (Boolean.TRUE.equals(entry.getValue().getRequired())) {
226           requiredParameters.add(entry.getKey());
227         } else {
228           optionalParameters.add(entry.getKey());
229         }
230       }
231     } catch (Exception e){
232       logger.error(e);
233     }
234
235     return parameters;
236   }
237
238   private HashMap<String, Object> loadParameters(Map<String, Object> paramsMap,
239       RestMethod method) throws Exception {
240     HashMap<String, Object> parameters = Maps.newHashMap();
241
242     // required parameters
243     if (method.getParameterOrder() != null) {
244       for (String parameterName : method.getParameterOrder()) {
245         JsonSchema parameter = method.getParameters().get(parameterName);
246         if (Boolean.TRUE.equals(parameter.getRequired())) {
247           Object parameterValue = paramsMap.get(parameterName);
248           if (parameterValue == null) {
249             error("missing required parameter: " + parameter);
250           } else {
251             putParameter(null, parameters, parameterName, parameter, parameterValue);
252           }
253         }
254       }
255     }
256
257     for (Map.Entry<String, Object> entry : paramsMap.entrySet()) {
258       String parameterName = entry.getKey();
259       Object parameterValue = entry.getValue();
260
261       if (parameterName.equals("contentType")) {
262         if (method.getHttpMethod().equals("GET") || method.getHttpMethod().equals("DELETE")) {
263           error("HTTP content type cannot be specified for this method: " + parameterName);
264         }
265       } else {
266         JsonSchema parameter = null;
267         if (restDescription.getParameters() != null) {
268           parameter = restDescription.getParameters().get(parameterName);
269         }
270         if (parameter == null && method.getParameters() != null) {
271           parameter = method.getParameters().get(parameterName);
272         }
273         putParameter(parameterName, parameters, parameterName, parameter, parameterValue);
274       }
275     }
276
277     return parameters;
278   }
279
280   private RestMethod getMatchingMethod(String resourceName, String methodName)
281       throws Exception {
282     Map<String, RestMethod> methodMap = getMatchingMethodMap(resourceName);
283
284     if (methodName == null) {
285       error("missing method name");      
286     }
287
288     RestMethod method =
289         methodMap == null ? null : methodMap.get(methodName);
290     if (method == null) {
291       error("method not found: ");
292     }
293
294     return method;
295   }
296
297   private Map<String, RestMethod> getMatchingMethodMap(String resourceName)
298       throws Exception {
299     if (resourceName == null) {
300       error("missing resource name");      
301     }
302
303     Map<String, RestMethod> methodMap = null;
304     Map<String, RestResource> resources = restDescription.getResources();
305     RestResource resource = resources.get(resourceName);
306     if (resource == null) {
307       error("resource not found");
308     }
309     methodMap = resource.getMethods();
310     return methodMap;
311   }
312
313   /**
314    * Not thread-safe. So, create for each request.
315    * @param apiName
316    * @param apiVersion
317    * @return
318    * @throws Exception
319    */
320   private RestDescription loadArvadosApi()
321       throws Exception {
322     try {
323       Discovery discovery;
324
325       Discovery.Builder discoveryBuilder =
326           new Discovery.Builder(httpTransport, jsonFactory, null);
327
328       discoveryBuilder.setRootUrl(arvadosRootUrl);
329       discoveryBuilder.setApplicationName(apiName);
330
331       discovery = discoveryBuilder.build();
332
333       return discovery.apis().getRest(apiName, apiVersion).execute();
334     } catch (Exception e) {
335       e.printStackTrace();
336       throw e;
337     }
338   }
339
340   /**
341    * Convert the input parameter into its equivalent json string.
342    * Add this json string value to the parameters map to be sent to server.
343    * @param argName
344    * @param parameters
345    * @param parameterName
346    * @param parameter
347    * @param parameterValue
348    * @throws Exception
349    */
350   private void putParameter(String argName, Map<String, Object> parameters,
351       String parameterName, JsonSchema parameter, Object parameterValue)
352           throws Exception {
353     Object value = parameterValue;
354     if (parameter != null) {
355       if ("boolean".equals(parameter.getType())) {
356         value = Boolean.valueOf(parameterValue.toString());
357       } else if ("number".equals(parameter.getType())) {
358         value = new BigDecimal(parameterValue.toString());
359       } else if ("integer".equals(parameter.getType())) {
360         value = new BigInteger(parameterValue.toString());
361       } else if ("float".equals(parameter.getType())) {
362         value = new BigDecimal(parameterValue.toString());
363       } else if ("Java.util.Calendar".equals(parameter.getType())) {
364         value = new BigDecimal(parameterValue.toString());
365       } else if (("array".equals(parameter.getType())) ||
366           ("Array".equals(parameter.getType()))) {
367         if (parameterValue.getClass().isArray()){
368           value = getJsonValueFromArrayType(parameterValue);
369         } else if (List.class.isAssignableFrom(parameterValue.getClass())) {
370           value = getJsonValueFromListType(parameterValue);
371         }
372       } else if (("Hash".equals(parameter.getType())) ||
373           ("hash".equals(parameter.getType()))) {
374         value = getJsonValueFromMapType(parameterValue);
375       } else {
376         if (parameterValue.getClass().isArray()){
377           value = getJsonValueFromArrayType(parameterValue);
378         } else if (List.class.isAssignableFrom(parameterValue.getClass())) {
379           value = getJsonValueFromListType(parameterValue);
380         } else if (Map.class.isAssignableFrom(parameterValue.getClass())) {
381           value = getJsonValueFromMapType(parameterValue);
382         }
383       }
384     }
385
386     parameters.put(parameterName, value);
387   }
388
389   /**
390    * Convert the given input array into json string before sending to server.
391    * @param parameterValue
392    * @return
393    */
394   private String getJsonValueFromArrayType (Object parameterValue) {
395     String arrayStr = Arrays.deepToString((Object[])parameterValue);
396
397     // we can expect either an array of array objects or an array of objects
398     if (arrayStr.startsWith("[[") && arrayStr.endsWith("]]")) {
399       Object[][] array = new Object[1][];
400       arrayStr = arrayStr.substring(2, arrayStr.length()-2);
401       String jsonStr = getJsonStringForArrayStr(arrayStr);
402       String value = "[" + jsonStr + "]";
403       return value;
404     } else {
405       arrayStr = arrayStr.substring(1, arrayStr.length()-1);
406       return (getJsonStringForArrayStr(arrayStr));
407     }
408   }
409
410   private String getJsonStringForArrayStr(String arrayStr) {
411     Object[] array = arrayStr.split(",");
412     Object[] trimmedArray = new Object[array.length];
413     for (int i=0; i<array.length; i++){
414       trimmedArray[i] = array[i].toString().trim();
415     }
416     String value = JSONArray.toJSONString(Arrays.asList(trimmedArray));
417     return value;
418   }
419
420   /**
421    * Convert the given input List into json string before sending to server.
422    * @param parameterValue
423    * @return
424    */
425   private String getJsonValueFromListType (Object parameterValue) {
426     List paramList = (List)parameterValue;
427     Object[] array = new Object[paramList.size()];
428     Arrays.deepToString(paramList.toArray(array));
429     return (getJsonValueFromArrayType(array));
430   }
431
432   /**
433    * Convert the given input map into json string before sending to server.
434    * @param parameterValue
435    * @return
436    */
437   private String getJsonValueFromMapType (Object parameterValue) {
438     JSONObject json = new JSONObject((Map)parameterValue);
439     return json.toString();
440   }
441
442   private static void error(String detail) throws Exception {
443     String errorDetail = "ERROR: " + detail;
444
445     logger.debug(errorDetail);
446     throw new Exception(errorDetail);
447   }
448
449   public static void main(String[] args){
450     System.out.println("Welcome to Arvados Java SDK.");
451     System.out.println("Please refer to http://doc.arvados.org/sdk/java/index.html to get started with the the SDK.");
452   }
453
454 }