net.systran.platform.geographic.client.ApiClient.java Source code

Java tutorial

Introduction

Here is the source code for net.systran.platform.geographic.client.ApiClient.java

Source

/*
 * 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);
    }
}