Package com.genesys.wsclient

Source Code of com.genesys.wsclient.GenesysRequest$GenesysResponse

package com.genesys.wsclient;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.nio.channels.CompletionHandler;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.Executor;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicReference;

import org.eclipse.jetty.client.ContentExchange;
import org.eclipse.jetty.client.HttpExchange;
import org.eclipse.jetty.http.HttpStatus;
import org.eclipse.jetty.io.EofException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.genesys.wsclient.impl.Authentication;
import com.genesys.wsclient.impl.CookieSession;
import com.genesys.wsclient.impl.ExecutorWrappedCompletionHandler;
import com.genesys.wsclient.impl.Jetty769HttpRequest;
import com.genesys.wsclient.impl.Jetty769Util;
import com.genesys.wsclient.impl.JsonUtil;
import com.genesys.wsclient.impl.StringUtil;
import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
import com.google.gson.JsonPrimitive;
import com.google.gson.JsonSyntaxException;

/**
* Use this class for setting up and execute Genesys Web Services requests.
*
* This class is not thread-safe. Therefore always set up and execute your request
* in the same thread, or use according multi-threading techniques.
*/
public class GenesysRequest {
 
  private static final Logger LOG_MESSAGE;
  private static final Logger LOG_MESSAGE_CONTENT_HEADERS;
  private static final Logger LOG_MESSAGE_CONTENT_RAW;
  private static final Logger LOG_MESSAGE_CONTENT_PRETTY;
 
  static {
    String packageName = GenesysRequest.class.getPackage().getName();
    LOG_MESSAGE = LoggerFactory.getLogger(packageName + ".MESSAGE");
    LOG_MESSAGE_CONTENT_HEADERS = LoggerFactory.getLogger(packageName + ".MESSAGE.CONTENT.HEADERS");
    LOG_MESSAGE_CONTENT_RAW = LoggerFactory.getLogger(packageName + ".MESSAGE.CONTENT.RAW");
    LOG_MESSAGE_CONTENT_PRETTY = LoggerFactory.getLogger(packageName + ".MESSAGE.CONTENT.PRETTY");
  }
 
  private final GenesysClient client;
  private final CookieSession cookieSession;
  private final Authentication authentication;
  private final String httpMethod;
  private final String uri;
  private Integer timeout; // null if timeout not set
  private boolean allFields;
  private final List<String> fields = new ArrayList<>();
  private boolean allSubresources;
  private final List<KeyValue> genericQueryParameters = new ArrayList<>();
  private final JsonObject jsonObject = new JsonObject();
  private HttpRequestSetup customSetup;
  private Executor asyncExecutor;
 
  protected GenesysRequest(
      GenesysClient client, String httpMethod, String uri,
      CookieSession cookieSession, Authentication authentication) {
    this.client = client;
    this.httpMethod = httpMethod;
    this.uri = uri;
    this.cookieSession = cookieSession;
    this.authentication = authentication;
    this.asyncExecutor = client.asyncExecutor;
  }
 
  /**
   * Sends this request and returns the response from the server.
   *
   * <p>This method is blocking.
   *
   * <p>A request can be executed more than once.
   *
   * @return
   *    The content (body) of the response.
   *
   * @throws GenesysMethodException
   *    If the server responds with statusCode != 0, which denotes a method error.
   *   This exception is unchecked.
   *
   * @throws TimeoutException
   *   The request timed out.
   *  
   * @throws ConnectionFailedException
   *   Connection to server failed. You may want to retry with an alternative server,
   *   or retry later.
   *   For your convenience, this exception extends {@link RequestIOException}.
   *
   * @throws RequestNotSentException
   *   The request failed, most probably it could not be sent to the server.
   *   You may want to retry the operation using the same server later.
   *   For your convenience, this exception extends {@link RequestIOException}.
   *
   * @throws ResponseNotReceivedException
   *   Trouble on receiving the response from the server from the network.
   *   You may want to retry the operation using the same server if the
   *   operation can be repeated without harm (as is the case of idempotent operations).
   *   For your convenience, this exception extends {@link RequestIOException}.
   *
   * @throws InvalidGenesysResponseException
   *   The response does not follow the Genesys Web Services conventions.
   *   This would indicate a defect in the server or in this client library,
   *   that is why this exception is unchecked.
   *
   * @throws HttpStatusException
   *   HTTP status code received which indicates non-success,
   *   and the response does not indicate a {@link GenesysMethodException}.
   *   You may react depending on the HTTP status code.
   *   This exception is unchecked.
   *
   * @throws InterruptedException
   *   If this methods is interrupted while blocked waiting for a response.
   */
  public String execute()
    throws
      GenesysMethodException,
      ConnectionFailedException,
      RequestNotSentException,
      ResponseNotReceivedException,
      InvalidGenesysResponseException,
      HttpStatusException,
      TimeoutException,
      InterruptedException {

    ExceptionCachingExchange exchange = new ExceptionCachingExchange();   
    setupAndSend(exchange);
    exchange.waitForDone();
    checkExchangeCompleted(exchange);
    cookieSession.handleResponse(exchange);
    return checkAndRetrieveResponse(exchange);
  }

  private void setupAndSend(ContentExchange exchange) {
    Jetty769HttpRequest request = new Jetty769HttpRequest(exchange);
    setupRequest(request);
    logRequest(exchange);

    try {
      client.httpClient.send(exchange);
    } catch (IOException e) {
      // This IOException is not a result of trying to write the request on the socket.
      // The Jetty HttpClient inappropriately throws it as a checked exception.
      // Browse the code for more details.
      throw new RuntimeException(e);
    }
  }
 
  private static class ExceptionCachingExchange extends ContentExchange {

    private final AtomicReference<Throwable> connectionFailedException = new AtomicReference<Throwable>();
    private final AtomicReference<Throwable> otherException = new AtomicReference<Throwable>();

    public ExceptionCachingExchange() {
      super(true); // true for caching headers
    }

    @Override
    protected void onConnectionFailed(Throwable e) {
      super.onConnectionFailed(e);
      connectionFailedException.set(e);
    }

    @Override
    protected void onException(Throwable e) {
      super.onException(e);
      otherException.set(e);
    }

    public Throwable getConnectionFailedException() {
      return connectionFailedException.get();
    }

    public Throwable getOtherException() {
      return otherException.get();
    }

  }
 
  /**
   * Asynchronous counterpart of {@link #execute()}.
   *
   * <p>Please expect the same exceptions to be passed to <code>completionHandler.failed()</code>
   * as defined in {@link #execute()}.
   *
   * @param attachment
   *   Contextual object to attach to the operation, which will be passed to the completion handler.
   *   Can be <code>null</code>.
   *  
   * @param completionHandler
   *   Callback interface for notifying completion and result.
   */
  public <A> void executeAsync(final A attachment, CompletionHandler<String, ? super A> completionHandler) {
    if (asyncExecutor == null) {
      throw new IllegalStateException("asyncExecutor is null");
    }
   
    final CompletionHandler<String, ? super A> asyncCompletionHandler =
        new ExecutorWrappedCompletionHandler<>(asyncExecutor, completionHandler);
    AsyncExchange<A> exchange = new AsyncExchange<A>(attachment, asyncCompletionHandler);   
    setupAndSend(exchange);
  }
 
  class AsyncExchange<A> extends ContentExchange {
    private final A attachment;
    private final CompletionHandler<String, ? super A> asyncCompletionHandler;

    /**
     * onRequestComplete and onResponseComplete must both be received to consider the exchange done.
     * @see HttpExchange#waitForDone
     */
    private boolean requestAndResponseCompleted = false;
    private boolean requestCompleted = false;
    private boolean responseCompleted = false;
   
    public AsyncExchange(A attachment, CompletionHandler<String, ? super A> asyncCompletionHandler) {
      super(true); // true for caching headers
      this.attachment = attachment;
      this.asyncCompletionHandler = asyncCompletionHandler;
    }

    @Override protected void onRequestComplete() throws IOException {
      super.onRequestComplete();
     
      synchronized (this) {
        requestCompleted = true;
        requestAndResponseCompleted = requestCompleted && responseCompleted;
      }
      onComplete();
    }
   
    @Override protected void onResponseComplete() throws IOException {
      super.onResponseComplete();
     
      synchronized (this) {
        responseCompleted = true;
        requestAndResponseCompleted = requestCompleted && responseCompleted;
      }
      onComplete();
    }
   
    private void onComplete() {
      if (requestAndResponseCompleted) {
        cookieSession.handleResponse(this);
        try {
          String result = checkAndRetrieveResponse(this);
          asyncCompletionHandler.completed(result, attachment);
        } catch (InvalidGenesysResponseException | GenesysMethodException | HttpStatusException e) {
          asyncCompletionHandler.failed(e, attachment);
        }
      }
    }
   
    @Override protected void onConnectionFailed(Throwable cause) {
      super.onConnectionFailed(cause);
     
      Throwable exc = new ConnectionFailedException(cause);
      asyncCompletionHandler.failed(exc, attachment);
    }
   
    @Override protected void onExpire() {
      super.onExpire();
     
      asyncCompletionHandler.failed(new TimeoutException(), attachment);
    }
   
    @Override protected void onException(Throwable cause) {
      super.onException(cause);
     
      Throwable e = onOtherException(cause, this.getStatus());
      asyncCompletionHandler.failed(e, attachment);
    }
   
  }

  private void setupRequest(HttpRequest request) {
    request.setMethod(httpMethod);

    StringBuilder uriBuilder = new StringBuilder();
    uriBuilder.append(uri);
    appendUriQuery(uriBuilder);
    request.setUri(uriBuilder.toString());

    authentication.setupRequest(request);
    cookieSession.setupRequest(request);
    setRequestContent(request);
    request.setTimeout(timeout);
   
    if (customSetup != null)
      customSetup.setupRequest(request);
  }

  private void appendUriQuery(StringBuilder uriBuilder) {
    ArrayList<KeyValue> queryParams = new ArrayList<>();
   
    if (allFields)
      queryParams.add(new KeyValue("fields", "*"));
    else if (!fields.isEmpty())
      queryParams.add(new KeyValue("fields", StringUtil.join(fields, ",")));
   
    if (allSubresources)
      queryParams.add(new KeyValue("subresources", "*"));
   
    char separator = '?';
    for (KeyValue param : queryParams) {
      uriBuilder.append(separator);
      separator = '&';
      uriBuilder.append(urlEncode(param.key));
      if (param.value != null)
        uriBuilder.append('=' + urlEncode(param.value));
    }
  }
 
  private static String urlEncode(String s) {
    try {
      return URLEncoder.encode(s, "UTF-8");
    } catch (UnsupportedEncodingException e) {
      throw new RuntimeException(e);
    }
  }

  private void setRequestContent(HttpRequest request) {
    if (!jsonObject.entrySet().isEmpty()) {
      request.setContentType("application/json");
      String content = JsonUtil.toJson(jsonObject);
      request.setContent(content, "UTF-8");
    }
  }
 
  private static void checkExchangeCompleted(ExceptionCachingExchange exchange)
    throws
      ConnectionFailedException,
      RequestNotSentException,
      ResponseNotReceivedException,
      TimeoutException {

    if (exchange.getStatus() != HttpExchange.STATUS_COMPLETED) {
      if (exchange.getStatus() == HttpExchange.STATUS_EXPIRED) {
        throw new TimeoutException();
      } else if (exchange.getConnectionFailedException() != null) {
        throw new ConnectionFailedException(exchange.getConnectionFailedException());
      } else {
        Throwable exc = onOtherException(exchange.getOtherException(), exchange.getStatus());
        if (exc instanceof RequestNotSentException) {
          throw (RequestNotSentException)exc;
        } else if (exc instanceof ResponseNotReceivedException) {
          throw (ResponseNotReceivedException)exc;
        } else {
          throw new AssertionError();
        }
      }
    }
  }
 
  private static Throwable onOtherException(Throwable otherException, int exchangeStatus) {
    if (otherException != null) {
      if (otherException instanceof EofException || isRequestSent(exchangeStatus))
        return new ResponseNotReceivedException(otherException);
      else
        return new RequestNotSentException(otherException);
    } else {
      String message = "HttpExchange status: " + HttpExchange.toState(exchangeStatus);
      if (isRequestSent(exchangeStatus))
        return new ResponseNotReceivedException(message);
      else
        return new RequestNotSentException(message);
    }
  }

  private static boolean isRequestSent(int exchangeStatus) {
    return exchangeStatus >= HttpExchange.STATUS_WAITING_FOR_RESPONSE;
  }

  private static String checkAndRetrieveResponse(ContentExchange exchange)
    throws
      InvalidGenesysResponseException,
      GenesysMethodException,
      HttpStatusException {

    int httpStatus = exchange.getResponseStatus();
   
    if (HttpStatus.isRedirection(httpStatus) || HttpStatus.isInformational(httpStatus)) {
      // Informational responses will actually not get handled here, they will timeout.
      throw new HttpStatusException(httpStatus, exchange);
    }

    try {
      String responseContent = checkContentAndRetrieveResponse(exchange);
      if (HttpStatus.isSuccess(httpStatus)) {
        return responseContent;
      } else {
        throw new HttpStatusException(httpStatus, exchange);
      }
    } catch (InvalidGenesysResponseException e) {
      if (HttpStatus.isSuccess(httpStatus)) {
        throw e;
      } else {
        // If the HTTP status code indicates an error, ignore an invalid content.
        throw new HttpStatusException(httpStatus, exchange);
      }
    }
  }

  private static class GenesysResponse {
    public Integer statusCode;
    public String statusMessage;
  }

  private static String checkContentAndRetrieveResponse(ContentExchange exchange)
      throws
        InvalidGenesysResponseException,
        GenesysMethodException {
   
    String responseContent;
    try {
      responseContent = exchange.getResponseContent();
    } catch (UnsupportedEncodingException e) {
      throw new InvalidGenesysResponseException(e);
    }
   
    logResponse(exchange, responseContent);

    String contentTypeValue = exchange.getResponseFields().getStringField("Content-Type");
    if (contentTypeValue == null)
      throw new InvalidGenesysResponseException("No Content-Type header");
   
    String contentType = contentTypeValue.split(";", 2)[0].trim();
    if (!contentType.equals("application/json"))
      throw new InvalidGenesysResponseException("Content-Type of is not application/json");
 
    GenesysResponse response;
    try {
      response = JsonUtil.fromJson(responseContent, GenesysResponse.class);
    } catch (JsonSyntaxException e) {
      throw new InvalidGenesysResponseException(e);
    }
    if (response == null)
      throw new InvalidGenesysResponseException("Invalid JSON");
    if (response.statusCode == null)
      throw new InvalidGenesysResponseException("Missing statusCode");
    if (response.statusCode != 0)
      throw new GenesysMethodException(exchange.getResponseStatus(),
          response.statusCode, response.statusMessage);
   
    return responseContent;
  }
 
  private void logRequest(ContentExchange exchange) {
    LOG_MESSAGE.debug(
      "Sending request to server: " + exchange.getAddress() +
      ", request: " + exchange.getMethod() + " " + exchange.getRequestURI());
   
    Jetty769Util.logHeaders(LOG_MESSAGE_CONTENT_HEADERS, exchange.getRequestFields());
   
    if (LOG_MESSAGE_CONTENT_RAW.isDebugEnabled() || LOG_MESSAGE_CONTENT_PRETTY.isDebugEnabled() ) {
      if (exchange.getRequestContent() != null) {
        try {
          String content = new String(exchange.getRequestContent().array(), "UTF-8");
          LOG_MESSAGE_CONTENT_RAW.debug("Content: " + content);
          if (LOG_MESSAGE_CONTENT_PRETTY.isDebugEnabled())
            LOG_MESSAGE_CONTENT_PRETTY.debug("Content:\n" + JsonUtil.prettify(content));
        } catch (UnsupportedEncodingException e) {
          throw new RuntimeException(e);
        }
      }
    }
  }

  private static void logResponse(ContentExchange exchange, String content) {
    LOG_MESSAGE.debug("Received response: HTTP " + exchange.getResponseStatus());
    Jetty769Util.logHeaders(LOG_MESSAGE_CONTENT_HEADERS, exchange.getResponseFields());
    if (content == null)
      content = "";
    LOG_MESSAGE_CONTENT_RAW.debug("Content: " + content);
    if (LOG_MESSAGE_CONTENT_PRETTY.isDebugEnabled() )
      LOG_MESSAGE_CONTENT_PRETTY.debug("Content:\n" + JsonUtil.prettify(content));
  }
 
  private static class KeyValue {
    final String key;
    final String value;
   
    KeyValue(String key, String value) {
      this.key = key;
      this.value = value;
    }
  }

  public GenesysRequest timeout(int timeout) {
    this.timeout = timeout;
    return this;
  }
 
  public GenesysRequest asyncExecutor(Executor asyncExecutor) {
    this.asyncExecutor = asyncExecutor;
    return this;
  }
 
  public GenesysRequest allFields() {
    allFields = true;
    return this;
  }
 
  public GenesysRequest fields(String... fieldNames) {
    fields.addAll(Arrays.asList(fieldNames));
    return this;
  }
 
  public GenesysRequest allSubresources() {
    allSubresources = true;
    return this;
  }
 
  public GenesysRequest queryParameter(String name, String value) {
    this.genericQueryParameters.add(new KeyValue(name, value));
    return this;
  }

  public GenesysRequest jsonParameter(String name, String value) {
    this.jsonObject.addProperty(name, value);
    return this;
  }

  public GenesysRequest jsonParameter(String name, String[] value) {
    JsonArray jsonArray = new JsonArray();
    for (String element : value) {
      jsonArray.add(new JsonPrimitive(element));
    }
    this.jsonObject.add(name, jsonArray);
    return this;
  }
 
  public GenesysRequest jsonParameter(String parentName, String name, String value) {
    JsonObject parentObj = jsonObject.getAsJsonObject(parentName);
    if (parentObj == null) {
      parentObj = new JsonObject();
      this.jsonObject.add(parentName, parentObj);
    }
    parentObj.addProperty(name, value);
    return this;
  }

  public GenesysRequest customize(HttpRequestSetup httpRequestSetup) {
    this.customSetup = httpRequestSetup;
    return this;
  }

  public GenesysRequest operationName(String operationName) {
    return jsonParameter("operationName", operationName);
  }
 
  public GenesysRequest destinationPhoneNumber(String phoneNumber) {
    return jsonParameter("destination", "phoneNumber", phoneNumber);
  }
 
}
TOP

Related Classes of com.genesys.wsclient.GenesysRequest$GenesysResponse

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.
er.logger.LogFileValue
TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.