Java tutorial
/* * Copyright 2015 SYSTRAN Software, Inc. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package net.systran.platform.geographic.client; import com.fasterxml.jackson.core.JsonGenerator.Feature; import com.fasterxml.jackson.databind.*; import com.fasterxml.jackson.annotation.*; import com.fasterxml.jackson.databind.annotation.JsonSerialize; import com.sun.jersey.api.client.Client; import com.sun.jersey.api.client.ClientResponse; import com.sun.jersey.api.client.config.ClientConfig; import com.sun.jersey.api.client.config.DefaultClientConfig; import com.sun.jersey.api.client.filter.LoggingFilter; import com.sun.jersey.api.client.WebResource.Builder; import com.sun.jersey.multipart.FormDataMultiPart; import javax.ws.rs.core.Response.Status.Family; import javax.ws.rs.core.MediaType; import java.io.*; import java.util.Collection; import java.util.Collections; import java.util.Map; import java.util.Map.Entry; import java.util.HashMap; import java.util.List; import java.util.Date; import java.util.TimeZone; import java.net.URLEncoder; import java.text.DateFormat; import java.text.SimpleDateFormat; import java.text.ParseException; import org.apache.james.mime4j.MimeException; import org.apache.james.mime4j.stream.EntityState; import org.apache.james.mime4j.stream.MimeTokenStream; import net.systran.platform.geographic.client.model.MultipartResponse; import net.systran.platform.geographic.client.auth.Authentication; import net.systran.platform.geographic.client.auth.HttpBasicAuth; import net.systran.platform.geographic.client.auth.ApiKeyAuth; import net.systran.platform.geographic.client.auth.OAuth; public class ApiClient { private Map<String, Client> hostMap = new HashMap<String, Client>(); private Map<String, String> defaultHeaderMap = new HashMap<String, String>(); private boolean debugging = false; private String basePath = "https://api-platform.systran.net"; private Map<String, Authentication> authentications; private DateFormat dateFormat; public ApiClient() { // Use ISO 8601 format for date and datetime. // See https://en.wikipedia.org/wiki/ISO_8601 this.dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ"); // Use UTC as the default time zone. this.dateFormat.setTimeZone(TimeZone.getTimeZone("UTC")); // Set default User-Agent. setUserAgent("Java-Systran"); // Setup authentications (key: authentication name, value: authentication). authentications = new HashMap<String, Authentication>(); authentications.put("apiKey", new ApiKeyAuth("query", "key")); authentications.put("accessToken", new ApiKeyAuth("header", "Authorization")); // Prevent the authentications from being modified. authentications = Collections.unmodifiableMap(authentications); } public String getBasePath() { return basePath; } public ApiClient setBasePath(String basePath) { this.basePath = basePath; return this; } /** * Get authentications (key: authentication name, value: authentication). */ public Map<String, Authentication> getAuthentications() { return authentications; } /** * Get authentication for the given name. * * @param authName The authentication name * @return The authentication, null if not found */ public Authentication getAuthentication(String authName) { return authentications.get(authName); } /** * Helper method to set username for the first HTTP basic authentication. */ public void setUsername(String username) { for (Authentication auth : authentications.values()) { if (auth instanceof HttpBasicAuth) { ((HttpBasicAuth) auth).setUsername(username); return; } } throw new RuntimeException("No HTTP basic authentication configured!"); } /** * Helper method to set password for the first HTTP basic authentication. */ public void setPassword(String password) { for (Authentication auth : authentications.values()) { if (auth instanceof HttpBasicAuth) { ((HttpBasicAuth) auth).setPassword(password); return; } } throw new RuntimeException("No HTTP basic authentication configured!"); } /** * Helper method to get the api key from a file location */ public static String LoadAPIKey(String filename) throws IOException { String apiKey; if (null == filename || 0 == filename.length()) throw new IllegalArgumentException("Empty API key file specified."); File file = new File(filename); FileInputStream fis = new FileInputStream(file); BufferedReader breader = new BufferedReader(new InputStreamReader(fis)); apiKey = breader.readLine().replaceAll("\\n", "").replaceAll("\\r", ""); fis.close(); breader.close(); if (null == apiKey || apiKey.length() < 10) throw new IllegalArgumentException("Too short API key."); return apiKey; } /** * Helper method to set API key value for the first API key authentication. */ public void setApiKey(String apiKey) { for (Authentication auth : authentications.values()) { if (auth instanceof ApiKeyAuth) { ((ApiKeyAuth) auth).setApiKey(apiKey); return; } } throw new RuntimeException("No API key authentication configured!"); } /** * Helper method to set API key prefix for the first API key authentication. */ public void setApiKeyPrefix(String apiKeyPrefix) { for (Authentication auth : authentications.values()) { if (auth instanceof ApiKeyAuth) { ((ApiKeyAuth) auth).setApiKeyPrefix(apiKeyPrefix); return; } } throw new RuntimeException("No API key authentication configured!"); } /** * Set the User-Agent header's value (by adding to the default header map). */ public ApiClient setUserAgent(String userAgent) { addDefaultHeader("User-Agent", userAgent); return this; } /** * Add a default header. * * @param key The header's key * @param value The header's value */ public ApiClient addDefaultHeader(String key, String value) { defaultHeaderMap.put(key, value); return this; } /** * Check that whether debugging is enabled for this API client. */ public boolean isDebugging() { return debugging; } /** * Enable/disable debugging for this API client. * * @param debugging To enable (true) or disable (false) debugging */ public ApiClient setDebugging(boolean debugging) { this.debugging = debugging; return this; } /** * Get the date format used to parse/format date parameters. */ public DateFormat getDateFormat() { return dateFormat; } /** * Set the date format used to parse/format date parameters. */ public ApiClient getDateFormat(DateFormat dateFormat) { this.dateFormat = dateFormat; return this; } /** * Parse the given string into Date object. */ public Date parseDate(String str) { try { return dateFormat.parse(str); } catch (java.text.ParseException e) { throw new RuntimeException(e); } } /** * Format the given Date object into string. */ public String formatDate(Date date) { return dateFormat.format(date); } /** * Format the given parameter object into string. */ public Object parameterToString(Object param) { if (param == null) { return ""; } else if (param instanceof Date) { return formatDate((Date) param); } else if (param instanceof Collection) { StringBuilder b = new StringBuilder(); for (Object o : (Collection) param) { if (o instanceof String) { Collection<String> pa = (Collection) param; return pa.toArray(new String[pa.size()]); } if (b.length() > 0) { b.append(","); } b.append(String.valueOf(o)); } return b.toString(); } else { return String.valueOf(param); } } /** * Select the Accept header's value from the given accepts array: * if JSON exists in the given array, use it; * otherwise use all of them (joining into a string) * * @param accepts The accepts array to select from * @return The Accept header to use. If the given array is empty, * null will be returned (not to set the Accept header explicitly). */ public String selectHeaderAccept(String[] accepts) { if (accepts.length == 0) return null; if (StringUtil.containsIgnoreCase(accepts, "application/json")) return "application/json"; return StringUtil.join(accepts, ","); } /** * Select the Content-Type header's value from the given array: * if JSON exists in the given array, use it; * otherwise use the first one of the array. * * @param contentTypes The Content-Type array to select from * @return The Content-Type header to use. If the given array is empty, * JSON will be used. */ public String selectHeaderContentType(String[] contentTypes) { if (contentTypes.length == 0) return "application/json"; if (StringUtil.containsIgnoreCase(contentTypes, "application/json")) return "application/json"; return contentTypes[0]; } /** * Escape the given string to be used as URL query value. */ public String escapeString(String str) { try { return URLEncoder.encode(str, "utf8").replaceAll("\\+", "%20"); } catch (UnsupportedEncodingException e) { return str; } } /** * Deserialize the given JSON string to Java object. * * @param httpResponse The ClientResponse * @param containerType The container type, one of "list", "array" or "" * @param cls The type of the Java object * @return The deserialized Java object */ public Object deserialize(ClientResponse httpResponse, String containerType, Class cls) throws ApiException { String contentType = ""; if (httpResponse.getHeaders() != null) { contentType = httpResponse.getHeaders().getFirst("Content-Type"); } if (contentType == null || (!contentType.contains("multipart/mixed") && !contentType.contains("application/json"))) { return httpResponse.getEntityInputStream(); } if (contentType.contains("multipart/mixed")) { // do multipart parser contentType = contentType.replace("Content-Type: ", ""); MimeTokenStream mts = new MimeTokenStream(); mts.parseHeadless(httpResponse.getEntityInputStream(), contentType); String partName = ""; MultipartResponse multipartResponse = new MultipartResponse(); try { for (EntityState state = mts.getState(); state != EntityState.T_END_OF_STREAM; state = mts.next()) { if (state == EntityState.T_FIELD) { // System.out.println("Header field detected: " + mts.getField().getName() + " : " + mts.getField().getBody()); if (mts.getField().getName().equals("part-name")) partName = mts.getField().getBody(); } else if (state == EntityState.T_BODY) { // System.out.println("Body detected " + partName); // System.out.println("Contents = " + mts.getInputStream()); // System.out.println("Header data = " + mts.getBodyDescriptor()); if (partName.equals("detectedLanguage")) { multipartResponse = JsonUtil.getJsonMapper().readValue(mts.getInputStream(), MultipartResponse.class); } else if (partName.equals("source")) { multipartResponse.setSource(mts.getInputStream()); } else { multipartResponse.setOutput(mts.getInputStream()); } partName = ""; } } } catch (MimeException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } return multipartResponse; } if (null != containerType) { containerType = containerType.toLowerCase(); } String json = httpResponse.getEntity(String.class); try { if ("list".equals(containerType) || "array".equals(containerType)) { JavaType typeInfo = JsonUtil.getJsonMapper().getTypeFactory().constructCollectionType(List.class, cls); List response = (List<?>) JsonUtil.getJsonMapper().readValue(json, typeInfo); return response; } else if (String.class.equals(cls)) { if (json != null && json.startsWith("\"") && json.endsWith("\"") && json.length() > 1) return json.substring(1, json.length() - 2); else return json; } else { return JsonUtil.getJsonMapper().readValue(json, cls); } } catch (IOException e) { throw new ApiException(500, e.getMessage(), null, json); } } /** * Serialize the given Java object into JSON string. */ public String serialize(Object obj) throws ApiException { try { if (obj != null) return JsonUtil.getJsonMapper().writeValueAsString(obj); else return null; } catch (Exception e) { throw new ApiException(500, e.getMessage()); } } /** * Invoke API by sending HTTP request with the given options. * * @param path The sub-path of the HTTP URL * @param method The request method, one of "GET", "POST", "PUT", and "DELETE" * @param queryParams The query parameters * @param body The request body object * @param headerParams The header parameters * @param formParams The form parameters * @param accept The request's Accept header * @param contentType The request's Content-Type header * @param authNames The authentications to apply * @return The response body in type of string */ public ClientResponse invokeAPI(String path, String method, Map<String, Object> queryParams, Object body, Map<String, String> headerParams, Map<String, String> formParams, String accept, String contentType, String[] authNames) throws ApiException { updateParamsForAuth(authNames, queryParams, headerParams); Client client = getClient(); StringBuilder b = new StringBuilder(); for (String key : queryParams.keySet()) { Object value = queryParams.get(key); if (value != null) { if (value instanceof String[]) { for (String val : (String[]) value) { if (b.toString().length() == 0) b.append("?"); else b.append("&"); b.append(escapeString(key)).append("=").append(escapeString(val)); } } else { if (b.toString().length() == 0) b.append("?"); else b.append("&"); b.append(escapeString(key)).append("=").append(escapeString(value.toString())); } } } String querystring = b.toString(); Builder builder; if (accept == null) builder = client.resource(basePath + path + querystring).getRequestBuilder(); else builder = client.resource(basePath + path + querystring).accept(accept); for (String key : headerParams.keySet()) { builder = builder.header(key, headerParams.get(key)); } for (String key : defaultHeaderMap.keySet()) { if (!headerParams.containsKey(key)) { builder = builder.header(key, defaultHeaderMap.get(key)); } } ClientResponse response = null; if ("GET".equals(method) && body == null) { response = (ClientResponse) builder.get(ClientResponse.class); } else if ("POST".equals(method) || ("GET".equals(method) && body != null)) { if ("GET".equals(method)) { builder = builder.header("X-HTTP-Method-Override", "GET"); } if (contentType.startsWith("application/x-www-form-urlencoded")) { String encodedFormParams = this.getXWWWFormUrlencodedParams(formParams); response = builder.type(contentType).post(ClientResponse.class, encodedFormParams); } else if (body == null) { response = builder.post(ClientResponse.class, null); } else if (body instanceof FormDataMultiPart) { response = builder.type(contentType).post(ClientResponse.class, body); } else response = builder.type(contentType).post(ClientResponse.class, serialize(body)); } else if ("PUT".equals(method)) { if ("application/x-www-form-urlencoded".equals(contentType)) { String encodedFormParams = this.getXWWWFormUrlencodedParams(formParams); response = builder.type(contentType).put(ClientResponse.class, encodedFormParams); } else if (body == null) { response = builder.put(ClientResponse.class, null); } else { response = builder.type(contentType).put(ClientResponse.class, serialize(body)); } } else if ("DELETE".equals(method)) { if ("application/x-www-form-urlencoded".equals(contentType)) { String encodedFormParams = this.getXWWWFormUrlencodedParams(formParams); response = builder.type(contentType).delete(ClientResponse.class, encodedFormParams); } else if (body == null) { response = builder.delete(ClientResponse.class); } else { response = builder.type(contentType).delete(ClientResponse.class, serialize(body)); } } else { throw new ApiException(500, "unknown method type " + method); } if (response.getClientResponseStatus() == ClientResponse.Status.NO_CONTENT) { return null; } else if (response.getClientResponseStatus().getFamily() == Family.SUCCESSFUL) { return response; } else { String message = "error"; String respBody = null; if (response.hasEntity()) { try { respBody = String.valueOf(response.getEntity(String.class)); message = respBody; } catch (RuntimeException e) { // e.printStackTrace(); } } throw new ApiException(response.getClientResponseStatus().getStatusCode(), message, response.getHeaders(), respBody); } } /** * Update query and header parameters based on authentication settings. * * @param authNames The authentications to apply */ private void updateParamsForAuth(String[] authNames, Map<String, Object> queryParams, Map<String, String> headerParams) { for (String authName : authNames) { Authentication auth = authentications.get(authName); if (auth == null) throw new RuntimeException("Authentication undefined: " + authName); auth.applyToParams(queryParams, headerParams); } } /** * Encode the given form parameters as request body. */ private String getXWWWFormUrlencodedParams(Map<String, String> formParams) { StringBuilder formParamBuilder = new StringBuilder(); for (Entry<String, String> param : formParams.entrySet()) { String keyStr = parameterToString(param.getKey()).toString(); String valueStr = parameterToString(param.getValue()).toString(); try { formParamBuilder.append(URLEncoder.encode(keyStr, "utf8")).append("=") .append(URLEncoder.encode(valueStr, "utf8")); formParamBuilder.append("&"); } catch (UnsupportedEncodingException e) { // move on to next } } String encodedFormParams = formParamBuilder.toString(); if (encodedFormParams.endsWith("&")) { encodedFormParams = encodedFormParams.substring(0, encodedFormParams.length() - 1); } return encodedFormParams; } /** * Get an existing client or create a new client to handle HTTP request. */ private Client getClient() { if (!hostMap.containsKey(basePath)) { Client client = Client.create(); if (debugging) client.addFilter(new LoggingFilter()); hostMap.put(basePath, client); } return hostMap.get(basePath); } }