ai.api.AIDataService.java Source code

Java tutorial

Introduction

Here is the source code for ai.api.AIDataService.java

Source

package ai.api;

/***********************************************************************************************************************
 *
 * API.AI Android SDK - client-side libraries for API.AI
 * =================================================
 *
 * Copyright (C) 2015 by Speaktoit, Inc. (https://www.speaktoit.com)
 * https://www.api.ai
 *
 * *********************************************************************************************************************
 *
 * 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.
 *
 ***********************************************************************************************************************/

import android.content.Context;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.text.TextUtils;
import android.util.Log;

import com.google.gson.Gson;
import com.google.gson.JsonSyntaxException;

import org.apache.commons.io.Charsets;
import org.apache.commons.io.IOUtils;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.*;
import java.util.Calendar;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;

import ai.api.http.HttpClient;
import ai.api.model.AIContext;
import ai.api.model.AIRequest;
import ai.api.model.AIResponse;
import ai.api.model.Entity;
import ai.api.model.Status;

/**
 * Do simple requests to the AI Service
 */
public class AIDataService {

    public static final String TAG = AIDataService.class.getName();

    @NonNull
    private final Context context;

    @NonNull
    private final AIConfiguration config;

    @NonNull
    private final String sessionId;

    @NonNull
    private final Gson gson = GsonFactory.getGson();

    public AIDataService(@NonNull final Context context, @NonNull final AIConfiguration config) {
        this.context = context;
        this.config = config;

        sessionId = SessionIdStorage.getSessionId(context);
    }

    public AIResponse request(@NonNull final AIRequest request) throws AIServiceException {
        return request(request, null);
    }

    /**
     * Make request to the ai service. This method must not be called in the UI Thread
     *
     * @param request request object to the service
     * @return response object from service
     */
    @NonNull
    public AIResponse request(@NonNull final AIRequest request, @Nullable final RequestExtras requestExtras)
            throws AIServiceException {
        if (request == null) {
            throw new IllegalArgumentException("Request argument must not be null");
        }

        Log.d(TAG, "Start request");

        try {

            request.setLanguage(config.getApiAiLanguage());
            request.setSessionId(sessionId);
            request.setTimezone(Calendar.getInstance().getTimeZone().getID());

            Map<String, String> additionalHeaders = null;

            if (requestExtras != null) {
                if (requestExtras.hasContexts()) {
                    request.setContexts(requestExtras.getContexts());
                }

                if (requestExtras.hasEntities()) {
                    request.setEntities(requestExtras.getEntities());
                }

                additionalHeaders = requestExtras.getAdditionalHeaders();
            }

            final String queryData = gson.toJson(request);
            final String response = doTextRequest(config.getQuestionUrl(), queryData, additionalHeaders);

            if (TextUtils.isEmpty(response)) {
                throw new AIServiceException(
                        "Empty response from ai service. Please check configuration and Internet connection.");
            }

            Log.d(TAG, "Response json: " + response.replaceAll("[\r\n]+", " "));

            final AIResponse aiResponse = gson.fromJson(response, AIResponse.class);

            if (aiResponse == null) {
                throw new AIServiceException("API.AI response parsed as null. Check debug log for details.");
            }

            if (aiResponse.isError()) {
                throw new AIServiceException(aiResponse);
            }

            aiResponse.cleanup();

            return aiResponse;

        } catch (final MalformedURLException e) {
            Log.e(TAG, "Malformed url should not be raised", e);
            throw new AIServiceException("Wrong configuration. Please, connect to API.AI Service support", e);
        } catch (final JsonSyntaxException je) {
            throw new AIServiceException("Wrong service answer format. Please, connect to API.AI Service support",
                    je);
        }

    }

    /**
     * Make requests to the ai service with voice data. This method must not be called in the UI Thread.
     *
     * @param voiceStream voice data stream for recognition
     * @return response object from service
     * @throws AIServiceException
     */
    @NonNull
    public AIResponse voiceRequest(@NonNull final InputStream voiceStream) throws AIServiceException {
        return voiceRequest(voiceStream, new RequestExtras());
    }

    /**
     * Make requests to the ai service with voice data. This method must not be called in the UI Thread.
     *
     * @param voiceStream voice data stream for recognition
     * @param aiContexts additional contexts for request
     * @return response object from service
     * @throws AIServiceException
     */
    @NonNull
    public AIResponse voiceRequest(@NonNull final InputStream voiceStream,
            @Nullable final List<AIContext> aiContexts) throws AIServiceException {
        return voiceRequest(voiceStream, new RequestExtras(aiContexts, null));
    }

    /**
     * Make requests to the ai service with voice data. This method must not be called in the UI Thread.
     *
     * @param voiceStream voice data stream for recognition
     * @param requestExtras object that can hold additional contexts and entities
     * @return response object from service
     * @throws AIServiceException
     */
    @NonNull
    public AIResponse voiceRequest(@NonNull final InputStream voiceStream,
            @Nullable final RequestExtras requestExtras) throws AIServiceException {
        Log.d(TAG, "Start voice request");

        try {
            final AIRequest request = new AIRequest();

            request.setLanguage(config.getApiAiLanguage());
            request.setSessionId(sessionId);
            request.setTimezone(Calendar.getInstance().getTimeZone().getID());

            Map<String, String> additionalHeaders = null;

            if (requestExtras != null) {
                if (requestExtras.hasContexts()) {
                    request.setContexts(requestExtras.getContexts());
                }
                if (requestExtras.hasEntities()) {
                    request.setEntities(requestExtras.getEntities());
                }

                additionalHeaders = requestExtras.getAdditionalHeaders();
            }

            final String queryData = gson.toJson(request);

            Log.d(TAG, "Request json: " + queryData);

            final String response = doSoundRequest(voiceStream, queryData, additionalHeaders);

            if (TextUtils.isEmpty(response)) {
                throw new AIServiceException("Empty response from ai service. Please check configuration.");
            }

            Log.d(TAG, "Response json: " + response);

            final AIResponse aiResponse = gson.fromJson(response, AIResponse.class);

            if (aiResponse == null) {
                throw new AIServiceException("API.AI response parsed as null. Check debug log for details.");
            }

            if (aiResponse.isError()) {
                throw new AIServiceException(aiResponse);
            }

            aiResponse.cleanup();

            return aiResponse;

        } catch (final MalformedURLException e) {
            Log.e(TAG, "Malformed url should not be raised", e);
            throw new AIServiceException("Wrong configuration. Please, connect to AI Service support", e);
        } catch (final JsonSyntaxException je) {
            throw new AIServiceException("Wrong service answer format. Please, connect to API.AI Service support",
                    je);
        }
    }

    /**
     * Forget all old contexts
     *
     * @return true if operation succeed, false otherwise
     */
    public boolean resetContexts() {
        final AIRequest cleanRequest = new AIRequest();
        cleanRequest.setQuery("empty_query_for_resetting_contexts"); // TODO remove it after protocol fix
        cleanRequest.setResetContexts(true);
        try {
            final AIResponse response = request(cleanRequest);
            return !response.isError();
        } catch (final AIServiceException e) {
            Log.e(TAG, "Exception while contexts clean.", e);
            return false;
        }
    }

    public AIResponse uploadUserEntity(final Entity userEntity) throws AIServiceException {
        return uploadUserEntities(Collections.singleton(userEntity));
    }

    public AIResponse uploadUserEntities(final Collection<Entity> userEntities) throws AIServiceException {
        if (userEntities == null || userEntities.size() == 0) {
            throw new AIServiceException("Empty entities list");
        }

        final String requestData = gson.toJson(userEntities);
        try {
            final String response = doTextRequest(config.getUserEntitiesEndpoint(sessionId), requestData);
            if (TextUtils.isEmpty(response)) {
                throw new AIServiceException(
                        "Empty response from ai service. Please check configuration and Internet connection.");
            }
            Log.d(TAG, "Response json: " + response);

            final AIResponse aiResponse = gson.fromJson(response, AIResponse.class);

            if (aiResponse == null) {
                throw new AIServiceException("API.AI response parsed as null. Check debug log for details.");
            }

            if (aiResponse.isError()) {
                throw new AIServiceException(aiResponse);
            }

            aiResponse.cleanup();
            return aiResponse;

        } catch (final MalformedURLException e) {
            Log.e(TAG, "Malformed url should not be raised", e);
            throw new AIServiceException("Wrong configuration. Please, connect to AI Service support", e);
        } catch (final JsonSyntaxException je) {
            throw new AIServiceException("Wrong service answer format. Please, connect to API.AI Service support",
                    je);
        }
    }

    protected String doTextRequest(final String requestJson) throws MalformedURLException, AIServiceException {
        return doTextRequest(config.getQuestionUrl(), requestJson);
    }

    protected String doTextRequest(final String endpoint, final String requestJson)
            throws MalformedURLException, AIServiceException {
        return doTextRequest(endpoint, requestJson, null);
    }

    protected String doTextRequest(@NonNull final String endpoint, @NonNull final String requestJson,
            @Nullable final Map<String, String> additionalHeaders)
            throws MalformedURLException, AIServiceException {

        HttpURLConnection connection = null;

        try {

            final URL url = new URL(endpoint);

            final String queryData = requestJson;

            Log.d(TAG, "Request json: " + queryData);

            if (config.getProxy() != null) {
                connection = (HttpURLConnection) url.openConnection(config.getProxy());
            } else {
                connection = (HttpURLConnection) url.openConnection();
            }

            connection.setRequestMethod("POST");
            connection.setDoOutput(true);
            connection.addRequestProperty("Authorization", "Bearer " + config.getApiKey());
            connection.addRequestProperty("Content-Type", "application/json; charset=utf-8");
            connection.addRequestProperty("Accept", "application/json");

            if (additionalHeaders != null) {
                for (final Map.Entry<String, String> entry : additionalHeaders.entrySet()) {
                    connection.addRequestProperty(entry.getKey(), entry.getValue());
                }
            }

            connection.connect();

            final BufferedOutputStream outputStream = new BufferedOutputStream(connection.getOutputStream());
            IOUtils.write(queryData, outputStream, Charsets.UTF_8);
            outputStream.close();

            final InputStream inputStream = new BufferedInputStream(connection.getInputStream());
            final String response = IOUtils.toString(inputStream, Charsets.UTF_8);
            inputStream.close();

            return response;
        } catch (final IOException e) {
            if (connection != null) {
                try {
                    final InputStream errorStream = connection.getErrorStream();
                    if (errorStream != null) {
                        final String errorString = IOUtils.toString(errorStream, Charsets.UTF_8);
                        Log.d(TAG, "" + errorString);
                        return errorString;
                    } else {
                        throw new AIServiceException("Can't connect to the api.ai service.", e);
                    }
                } catch (final IOException ex) {
                    Log.w(TAG, "Can't read error response", ex);
                }
            }
            Log.e(TAG,
                    "Can't make request to the API.AI service. Please, check connection settings and API access token.",
                    e);
            throw new AIServiceException(
                    "Can't make request to the API.AI service. Please, check connection settings and API access token.",
                    e);

        } finally {
            if (connection != null) {
                connection.disconnect();
            }
        }

    }

    protected String doSoundRequest(@NonNull final InputStream voiceStream, @NonNull final String queryData)
            throws MalformedURLException, AIServiceException {
        return doSoundRequest(voiceStream, queryData, null);
    }

    /**
     * Method extracted for testing purposes
     */
    protected String doSoundRequest(@NonNull final InputStream voiceStream, @NonNull final String queryData,
            @Nullable final Map<String, String> additionalHeaders)
            throws MalformedURLException, AIServiceException {

        HttpURLConnection connection = null;
        HttpClient httpClient = null;

        try {
            final URL url = new URL(config.getQuestionUrl());

            if (config.getProxy() != null) {
                connection = (HttpURLConnection) url.openConnection(config.getProxy());
            } else {
                connection = (HttpURLConnection) url.openConnection();
            }

            connection.addRequestProperty("Authorization", "Bearer " + config.getApiKey());
            connection.addRequestProperty("Accept", "application/json");

            if (additionalHeaders != null) {
                for (final Map.Entry<String, String> entry : additionalHeaders.entrySet()) {
                    connection.addRequestProperty(entry.getKey(), entry.getValue());
                }
            }

            connection.setRequestMethod("POST");
            connection.setDoInput(true);
            connection.setDoOutput(true);

            httpClient = new HttpClient(connection);
            httpClient.setWriteSoundLog(config.isWriteSoundLog());

            httpClient.connectForMultipart();
            httpClient.addFormPart("request", queryData);
            httpClient.addFilePart("voiceData", "voice.wav", voiceStream);
            httpClient.finishMultipart();

            final String response = httpClient.getResponse();
            return response;

        } catch (final IOException e) {

            if (httpClient != null) {
                final String errorString = httpClient.getErrorString();
                Log.d(TAG, "" + errorString);
                if (!TextUtils.isEmpty(errorString)) {
                    return errorString;
                } else if (e instanceof HttpRetryException) {
                    final AIResponse response = new AIResponse();
                    final int code = ((HttpRetryException) e).responseCode();
                    final Status status = Status.fromResponseCode(code);
                    status.setErrorDetails(((HttpRetryException) e).getReason());
                    response.setStatus(status);
                    throw new AIServiceException(response);
                }
            }

            Log.e(TAG,
                    "Can't make request to the API.AI service. Please, check connection settings and API.AI keys.",
                    e);
            throw new AIServiceException(
                    "Can't make request to the API.AI service. Please, check connection settings and API.AI keys.",
                    e);
        } finally {
            if (connection != null) {
                connection.disconnect();
            }
        }
    }

}