All Downloads are FREE. Search and download functionalities are using the official Maven repository.

ai.api.AIDataService Maven / Gradle / Ivy

Go to download

API.AI Java SDK allows using voice commands and integration with dialog scenarios defined for a particular agent in API.AI.

There is a newer version: 1.6.12
Show newest version
package ai.api;

/***********************************************************************************************************************
 *
 * API.AI Java 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 com.google.gson.Gson;
import com.google.gson.JsonParseException;
import com.google.gson.JsonSyntaxException;

import ai.api.util.IOUtils;
import ai.api.util.StringUtils;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Type;
import java.net.*;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.Calendar;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

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 {

    private static final Logger Log = LogManager.getLogger(AIDataService.class);
    private static final Charset DEFAULT_CHARSET = StandardCharsets.UTF_8;
    private static final AIServiceContext UNDEFINED_SERVICE_CONTEXT = null;
    private static final String REQUEST_METHOD_POST = "POST";
    private static final String REQUEST_METHOD_DELETE = "DELETE";
    private static final String REQUEST_METHOD_GET = "GET";
    private static final String DEFAULT_REQUEST_METHOD = REQUEST_METHOD_POST;

    /**
     * Cannot be null
     */
    private final static Gson GSON = GsonFactory.getDefaultFactory().getGson();

    /**
	 *  Cannot be null
	 */
    private final AIConfiguration config;
    
    /**
     * Cannot be null
     */
    private final AIServiceContext defaultServiceContext;

    /**
     * Create new service for given configuration and some predefined service context 
     * @param config Service configuration data. Cannot be null
     * @param serviceContext Service context. If null then new context will be created
     * @throws IllegalArgumentException If config parameter is null
     */
    public AIDataService(final AIConfiguration config, final AIServiceContext serviceContext) {
    	if (config == null) {
    		throw new IllegalArgumentException("config should not be null");
    	}
        this.config = config.clone();
        if (serviceContext == null) {
        	this.defaultServiceContext = new AIServiceContextBuilder().generateSessionId().build();
        } else {
        	this.defaultServiceContext = serviceContext;
        }
    }
    
    /**
     * Create new service with unique context for given configuration 
     * @param config Service configuration data. Cannot be null
     * @throws IllegalArgumentException If config parameter is null
     */
    public AIDataService(final AIConfiguration config) {
    	this(config, null);
    }
    
    /**
     * @return Current context used in each request. Never null
     */
    public AIServiceContext getContext() {
    	return defaultServiceContext;
    }

    /**
     * Make request to the AI service.
     *
     * @param request request object to the service. Cannot be null
     * @return response object from service. Never null
     */
    public AIResponse request(final AIRequest request) throws AIServiceException {
        return request(request, (RequestExtras)null);
    }
    
    /**
     * Make request to the AI service.
     *
     * @param request request object to the service. Cannot be null
     * @param serviceContext custom service context that should be used instead of the default context
     * @return response object from service. Never null
     */
    public AIResponse request(final AIRequest request, final AIServiceContext serviceContext) throws AIServiceException {
        return request(request, (RequestExtras)null, serviceContext);
    }
        
    /**
     * Make request to the AI service.
     *
     * @param request request object to the service. Cannot be null
     * @param requestExtras object that can hold additional contexts and entities
     * @return response object from service. Never null
     */
    public AIResponse request(final AIRequest request, final RequestExtras requestExtras) throws AIServiceException {
    	return request(request, requestExtras, UNDEFINED_SERVICE_CONTEXT);
    }

    /**
     * Make request to the AI service.
     *
     * @param request request object to the service. Cannot be null
     * @param requestExtras object that can hold additional contexts and entities
     * @param serviceContext custom service context that should be used instead of the default context
     * @return response object from service. Never null
     */
    public AIResponse request(final AIRequest request, final RequestExtras requestExtras, final AIServiceContext serviceContext) throws AIServiceException {
        if (request == null) {
            throw new IllegalArgumentException("Request argument must not be null");
        }

        Log.debug("Start request");

        try {

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

            Map additionalHeaders = null;

            if (requestExtras != null) {
                fillRequest(request, requestExtras);
                additionalHeaders = requestExtras.getAdditionalHeaders();
            }

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

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

            Log.debug("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.error("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.
     *
     * @param voiceStream voice data stream for recognition.  Cannot be null
     * @return response object from service. Never null
     * @throws AIServiceException
     */
    public AIResponse voiceRequest(final InputStream voiceStream) throws AIServiceException {
        return voiceRequest(voiceStream, new RequestExtras());
    }

    /**
     * Make requests to the AI service with voice data.
     *
     * @param voiceStream voice data stream for recognition. Cannot be null
     * @param aiContexts additional contexts for request
     * @return response object from service. Never null
     * @throws AIServiceException
     */
    public AIResponse voiceRequest(final InputStream voiceStream, final List aiContexts) throws AIServiceException {
        return voiceRequest(voiceStream, new RequestExtras(aiContexts, null));
    }
    
    /**
     * Make requests to the AI service with voice data.
     *
     * @param voiceStream voice data stream for recognition. Cannot be null
     * @param requestExtras object that can hold additional contexts and entities
     * @return response object from service. Never null
     * @throws AIServiceException
     */
    public AIResponse voiceRequest(final InputStream voiceStream, final RequestExtras requestExtras) throws AIServiceException {
    	return voiceRequest(voiceStream, requestExtras, UNDEFINED_SERVICE_CONTEXT);
    }

    /**
     * Make requests to the AI service with voice data.
     *
     * @param voiceStream voice data stream for recognition. Cannot be null
     * @param requestExtras object that can hold additional contexts and entities
     * @param serviceContext custom service context that should be used instead of the default context
     * @return response object from service. Never null
     * @throws AIServiceException
     */
    public AIResponse voiceRequest(final InputStream voiceStream, final RequestExtras requestExtras, final AIServiceContext serviceContext) throws AIServiceException {
    	assert voiceStream != null;
        Log.debug("Start voice request");

        try {
            final AIRequest request = new AIRequest();

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

            Map additionalHeaders = null;

            if (requestExtras != null) {
                fillRequest(request, requestExtras);
                additionalHeaders = requestExtras.getAdditionalHeaders();
            }

            final String queryData = GSON.toJson(request);

            Log.debug("Request json: " + queryData);

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

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

            Log.debug("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.error("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.error("Exception while contexts clean.", e);
            return false;
        }
    }
    
    /**
     * Retrieves the list of all currently active contexts for a session
     * 
     * @return List of contexts, or empty list if there is no any active contexts
     * @throws AIServiceException
     */
    public List getActiveContexts() 
    		throws AIServiceException {
    	return getActiveContexts(UNDEFINED_SERVICE_CONTEXT);
    }
    
    /**
     * Retrieves the list of all currently active contexts for a session
     * @param serviceContext custom service context that should be used instead of the default context
     * @return List of contexts, or empty list if there is no any active contexts
     * @throws AIServiceException
     */
    public List getActiveContexts(final AIServiceContext serviceContext)
    		throws AIServiceException {
    	try {
			return doRequest(
					ApiActiveContextListResponse.class,
					config.getContextsUrl(getSessionId(serviceContext)),
					REQUEST_METHOD_GET);
		} catch (BadResponseStatusException e) {
			throw new AIServiceException(e.response);
		}
    }
    
    /**
     * Retrieves the specified context for a session
     * @param contextName The context name
     * @return null if context not found
     * @throws AIServiceException
     */
    public AIContext getActiveContext(final String contextName)
    		throws AIServiceException {
    	return getActiveContext(contextName, UNDEFINED_SERVICE_CONTEXT);
    }
    
    /**
     * Retrieves the specified context for a session
     * @param contextName The context name
     * @param serviceContext custom service context that should be used instead of the default context
     * @return null if context not found
     * @throws AIServiceException
     */
    public AIContext getActiveContext(final String contextName, final AIServiceContext serviceContext)
    		throws AIServiceException {
    	try {
			return doRequest(
					AIContext.class,
					config.getContextsUrl(getSessionId(serviceContext), contextName),
					REQUEST_METHOD_GET);
		} catch (BadResponseStatusException e) {
			if (e.response.getStatus().getCode() == 404) {
				return null;
			} else {
				throw new AIServiceException(e.response);
			}
		}
    }
    
    /**
     * Adds new active contexts for a session
     * @param contexts Iterable collection of contexts
     * @return List of added context names, or empty list if no contexts were added
     * @throws AIServiceException
     */
    public List addActiveContext(final Iterable contexts)
    		throws AIServiceException {
    	return addActiveContext(contexts, UNDEFINED_SERVICE_CONTEXT);
    }
    
    /**
     * Adds new active contexts for a session
     * @param contexts Iterable collection of contexts
     * @param serviceContext custom service context that should be used instead of the default context
     * @return List of added context names, or empty list if no contexts were added
     * @throws AIServiceException
     */
    public List addActiveContext(final Iterable contexts, final AIServiceContext serviceContext)
    		 throws AIServiceException {
    	ApiActiveContextNamesResponse response;
		try {
			response = doRequest(
					contexts,
					ApiActiveContextNamesResponse.class,
					config.getContextsUrl(getSessionId(serviceContext)),
					REQUEST_METHOD_POST);
			return response.names;
		} catch (BadResponseStatusException e) {
			throw new AIServiceException(e.response);
		}
    }
    
    /**
     * Adds new active context for a session
     * @param context New context
     * @return Name of added context
     * @throws AIServiceException
     */
    public String addActiveContext(final AIContext context)
    		throws AIServiceException {
    	return addActiveContext(context, UNDEFINED_SERVICE_CONTEXT);
    }
    
    /**
     * Adds new active context for a session
     * @param context New context
     * @param serviceContext custom service context that should be used instead of the default context
     * @return Name of added context
     * @throws AIServiceException
     */
    public String addActiveContext(final AIContext context, final AIServiceContext serviceContext)
    		throws AIServiceException {
    	ApiActiveContextNamesResponse response;
		try {
			response = doRequest(
					context,
					ApiActiveContextNamesResponse.class,
					config.getContextsUrl(getSessionId(serviceContext)),
					REQUEST_METHOD_POST);
			return response.names != null && response.names.size() > 0 ? response.names.get(0) : null;
		} catch (BadResponseStatusException e) {
			throw new AIServiceException(e.response);
		}
    }
    
    /**
     * Deletes all active contexts for a session
     * 
     * @throws AIServiceException
     */
    public void resetActiveContexts() 
    		throws AIServiceException {
    	resetActiveContexts(UNDEFINED_SERVICE_CONTEXT);
    }
    
    /**
     * Deletes all active contexts for a session
     * 
     * @param serviceContext custom service context that should be used instead of the default context
     * @throws AIServiceException
     */
    public void resetActiveContexts(final AIServiceContext serviceContext) 
    		throws AIServiceException {
    	try {
			doRequest(
					AIResponse.class,
					config.getContextsUrl(getSessionId(serviceContext)),
					REQUEST_METHOD_DELETE);
		} catch (BadResponseStatusException e) {
			throw new AIServiceException(e.response);
		}
    }
    
    /**
     * Deletes the specified context for a session
     * 
     * @param contextName The context name
     * @return false if context was not delete, true in otherwise case
     * @throws AIServiceException
     */
    public boolean removeActiveContext(final String contextName) 
    		throws AIServiceException {
    	return removeActiveContext(contextName, UNDEFINED_SERVICE_CONTEXT);
    }
    
    /**
     * Deletes the specified context for a session
     * 
     * @param contextName The context name
     * @param serviceContext custom service context that should be used instead of the default context
     * @return false if context was not delete, true in otherwise case
     * @throws AIServiceException
     */
    public boolean removeActiveContext(final String contextName, final AIServiceContext serviceContext) 
    		throws AIServiceException {
    	
		try {
			doRequest(
					AIResponse.class,
					config.getContextsUrl(getSessionId(serviceContext), contextName),
					REQUEST_METHOD_DELETE);
			return true;
		} catch (BadResponseStatusException e) {
			if (e.response.getStatus().getCode() == 404) {
				return false;
			} else {
				throw new AIServiceException(e.response);
			}
		}
    }

    /**
     * Add new entity to an agent entity list
     * 
     * @param userEntity new entity data
     * @return response object from service. Never null
     * @throws AIServiceException
     */
    public AIResponse uploadUserEntity(final Entity userEntity) throws AIServiceException {
        return uploadUserEntity(userEntity, UNDEFINED_SERVICE_CONTEXT);
    }
    
    /**
     * Add new entity to an agent entity list
     * 
     * @param userEntity new entity data
     * @param serviceContext custom service context that should be used instead of the default context
     * @return response object from service. Never null
     * @throws AIServiceException
     */
    public AIResponse uploadUserEntity(final Entity userEntity, AIServiceContext serviceContext) throws AIServiceException {
        return uploadUserEntities(Collections.singleton(userEntity), serviceContext);
    }
    
    /**
     * Add a bunch of new entity to an agent entity list
     * 
     * @param userEntities collection of a new entity data
     * @return response object from service. Never null
     * @throws AIServiceException
     */
    public AIResponse uploadUserEntities(final Collection userEntities) throws AIServiceException {
    	return uploadUserEntities(userEntities, UNDEFINED_SERVICE_CONTEXT);
    }

    /**
     * Add a bunch of new entity to an agent entity list
     * 
     * @param userEntities collection of a new entity data
     * @param serviceContext custom service context that should be used instead of the default context
     * @return response object from service. Never null
     * @throws AIServiceException
     */
    public AIResponse uploadUserEntities(final Collection userEntities, AIServiceContext serviceContext) 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(getSessionId(serviceContext)), requestData);
            if (StringUtils.isEmpty(response)) {
                throw new AIServiceException("Empty response from ai service. Please check configuration and Internet connection.");
            }
            Log.debug("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.error("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);
        }
    }
    
    /**
     * @param requestJson Cannot be null
     * @param serviceContext custom service context that should be used instead of the default context
     * @return Response string
     * @throws MalformedURLException
     * @throws AIServiceException
     */
    protected String doTextRequest(final String requestJson, AIServiceContext serviceContext) throws MalformedURLException, AIServiceException {
        return doTextRequest(config.getQuestionUrl(getSessionId(serviceContext)), requestJson);
    }

    /**
     * @param requestJson Cannot be null
     * @return Response string
     * @throws MalformedURLException
     * @throws AIServiceException
     */
    protected String doTextRequest(final String requestJson) throws MalformedURLException, AIServiceException {
        return doTextRequest(requestJson, UNDEFINED_SERVICE_CONTEXT);
    }

    /**
     * @param endpoint Cannot be null
     * @param requestJson Cannot be null
     * @return Response string
     * @throws MalformedURLException
     * @throws AIServiceException
     */
    protected String doTextRequest(final String endpoint, final String requestJson) throws MalformedURLException, AIServiceException {
        return doTextRequest(endpoint, requestJson, null);
    }
    
    /**
     * @param endpoint Cannot be null
     * @param requestJson Cannot be null
     * @param additionalHeaders
     * @return Response string
     * @throws MalformedURLException
     * @throws AIServiceException
     */
    protected String doTextRequest(final String endpoint,
                                   final String requestJson, 
                                   final Map additionalHeaders) throws MalformedURLException, AIServiceException {
    	// TODO call doRequest method
    	assert endpoint != null;
    	assert requestJson != null;
        HttpURLConnection connection = null;

        try {

            final URL url = new URL(endpoint);

            final String queryData = requestJson;

            Log.debug("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 entry : additionalHeaders.entrySet()) {
                    connection.addRequestProperty(entry.getKey(), entry.getValue());
                }
            }

            connection.connect();

            final BufferedOutputStream outputStream = new BufferedOutputStream(connection.getOutputStream());
            IOUtils.writeAll(queryData, outputStream, DEFAULT_CHARSET);
            outputStream.close();

            final InputStream inputStream = new BufferedInputStream(connection.getInputStream());
            final String response = IOUtils.readAll(inputStream, DEFAULT_CHARSET);
            inputStream.close();

            return response;
        } catch (final IOException e) {
            if (connection != null) {
                try {
                    final InputStream errorStream = connection.getErrorStream();
                    if (errorStream != null) {
                        final String errorString = IOUtils.readAll(errorStream, DEFAULT_CHARSET);
                        Log.debug(errorString);
                        return errorString;
                    }
                    else {
                        throw new AIServiceException("Can't connect to the api.ai service.", e);
                    }
                } catch (final IOException ex) {
                    Log.warn("Can't read error response", ex);
                }
            }
            Log.error("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();
            }
        }

    }

    /**
     * Method extracted for testing purposes
     * @param voiceStream Cannot be null
     * @param queryData Cannot be null
     */
    protected String doSoundRequest(final InputStream voiceStream, final String queryData) throws MalformedURLException, AIServiceException {
        return doSoundRequest(voiceStream, queryData, null, UNDEFINED_SERVICE_CONTEXT);
    }
    
    /**
     * Method extracted for testing purposes
     * @param voiceStream Cannot be null
     * @param queryData Cannot be null
     */
    protected String doSoundRequest(final InputStream voiceStream,
                                    final String queryData,
                                    final Map additionalHeaders) throws MalformedURLException, AIServiceException {
    	return doSoundRequest(voiceStream, queryData, additionalHeaders, UNDEFINED_SERVICE_CONTEXT);
    }

    /**
     * Method extracted for testing purposes
     * @param voiceStream Cannot be null
     * @param queryData Cannot be null
     */
    protected String doSoundRequest(final InputStream voiceStream,
                                    final String queryData,
                                    final Map additionalHeaders,
                                    final AIServiceContext serviceContext) throws MalformedURLException, AIServiceException {
    	
    	// TODO call doRequest method
    	assert voiceStream != null;
    	assert queryData != null;
        HttpURLConnection connection = null;
        HttpClient httpClient = null;

        try {
            final URL url = new URL(config.getQuestionUrl(getSessionId(serviceContext)));
            
            Log.debug("Connecting to {}", url);

            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 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.debug(errorString);
                if (!StringUtils.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.error("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();
            }
        }
    }
    
    protected  TResponse doRequest(
    		final Type responseType,
    		final String endpoint,
    		final String method)
    		throws AIServiceException, BadResponseStatusException {
    	return doRequest(responseType, endpoint, method, (Map)null);
    }
    
    protected  TResponse doRequest(
    		final TRequest request,
    		final Type responseType,
    		final String endpoint,
    		final String method)
    		throws AIServiceException, BadResponseStatusException {
    	return doRequest(request, responseType, endpoint, method, (Map)null);
    }
    
    protected  TResponse doRequest(
    		final Type responseType,
    		final String endpoint,
    		final String method,
    		final Map additionalHeaders)
    		throws AIServiceException, BadResponseStatusException {
    	return doRequest((Object)null, responseType, endpoint, method, additionalHeaders);
    }
    
    protected  TResponse doRequest(
    		final TRequest request,
    		final Type responseType,
    		final String endpoint,
    		final String method,
    		final Map additionalHeaders)
    		throws AIServiceException, BadResponseStatusException {

    	assert endpoint != null;
        HttpURLConnection connection = null;

        try {

            final URL url = new URL(endpoint);

            final String queryData = request != null ? GSON.toJson(request) : null;
            final String requestMethod = method != null ? method : DEFAULT_REQUEST_METHOD;


            Log.debug("Request json: " + queryData);

            if (config.getProxy() != null) {
                connection = (HttpURLConnection) url.openConnection(config.getProxy());
            } else {
                connection = (HttpURLConnection) url.openConnection();
            }
            
            if (queryData != null && ! REQUEST_METHOD_POST.equals(requestMethod)) {
            	throw new AIServiceException("Non-empty request should be sent using POST method");
            }

            connection.setRequestMethod(requestMethod);
            if (REQUEST_METHOD_POST.equals(requestMethod)) {
            	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 entry : additionalHeaders.entrySet()) {
                    connection.addRequestProperty(entry.getKey(), entry.getValue());
                }
            }

            connection.connect();

            if (queryData != null) {
	            final BufferedOutputStream outputStream = new BufferedOutputStream(connection.getOutputStream());
	            IOUtils.writeAll(queryData, outputStream, DEFAULT_CHARSET);
	            outputStream.close();
            }

            final InputStream inputStream = new BufferedInputStream(connection.getInputStream());
            final String response = IOUtils.readAll(inputStream, DEFAULT_CHARSET);
            inputStream.close();
            
            try {
	            AIResponse aiResponse = GSON.fromJson(response, AIResponse.class);
	            
	            if (aiResponse.getStatus() != null && aiResponse.getStatus().getCode() != 200) {
	            	throw new BadResponseStatusException(aiResponse);
	            }
            } catch (JsonParseException e) {
            	// response is not in a expected format
            }

            return GSON.fromJson(response, responseType);
        } catch (final IOException e) {
            if (connection != null) {
                try {
                    final InputStream errorStream = connection.getErrorStream();
                    if (errorStream != null) {
                        final String errorString = IOUtils.readAll(errorStream, DEFAULT_CHARSET);
                        Log.debug(errorString);
                        throw new AIServiceException(errorString, e);
                    }
                    else {
                        throw new AIServiceException("Can't connect to the api.ai service.", e);
                    }
                } catch (final IOException ex) {
                    Log.warn("Can't read error response", ex);
                }
            }
            Log.error("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();
            }
        }
    }

    private void fillRequest(final AIRequest request, final RequestExtras requestExtras) {
    	assert request != null;
    	assert requestExtras != null;
        if (requestExtras.hasContexts()) {
            request.setContexts(requestExtras.getContexts());
        }

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

        if (requestExtras.getLocation() != null) {
            request.setLocation(requestExtras.getLocation());
        }
    }
    
    private String getSessionId(AIServiceContext serviceContext) {
    	return serviceContext != null ? serviceContext.getSessionId() : defaultServiceContext.getSessionId();
    }
    
    private static class ApiActiveContextNamesResponse extends AIResponse {
    	
		private static final long serialVersionUID = 1L;
		
		public List names; 
    }
    
    private static interface ApiActiveContextListResponse extends List {
    }
    
    private static class BadResponseStatusException extends Exception {

    	private static final long serialVersionUID = 1L;
		
		private final AIResponse response;
    	
    	public BadResponseStatusException(final AIResponse response) {
    		this.response = response;
    	}
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy