/**
   * Send a webservice request with a single command
   *
   * @param service The webserivce client instance
   * @param cmd The command that we're sending (see the 'org.kie.remote.jaxb.gen package' in
   *     kie-remote-client)
   * @param respClass The class that we expect as a response
   * @param deploymentId The id of the deployment that we will interact with
   * @return the response object
   * @throws CommandWebServiceException if the webservice operation fails
   */
  private static <T> T doWebserviceSingleCommandRequest(
      CommandWebService service, Command<?> cmd, Class<T> respClass, String deploymentId)
      throws CommandWebServiceException {
    // Send request and get response from the WebService
    JaxbCommandsRequest req = new JaxbCommandsRequest(deploymentId, cmd);
    JaxbCommandsResponse response = service.execute(req);

    // Unwrap response
    JaxbCommandResponse<?> cmdResp = response.getResponses().get(0);

    return (T) cmdResp;
  }
  /**
   * Start a simple process, and retrieve the task information and content via the webservice
   *
   * @param applicationUrl Something like "http://localhost:8080/kie-wb/" or
   *     "http://localhost:8080/business-central/"
   * @param user The user doing the webservice requests
   * @param password The user's password
   * @param deploymentId The deployment id that the request will interact with
   * @param processId The id of the process we want to start
   * @throws Exception if something goes wrong
   */
  public static void startSimpleProcess(
      URL applicationUrl, String user, String password, String deploymentId, String processId)
      throws Exception {

    CommandWebService commandWebService =
        createWebserviceClient(applicationUrl, user, password, deploymentId);

    // Create start process command
    StartProcessCommand spc = new StartProcessCommand();
    spc.setProcessId(processId);
    JaxbStringObjectPairArray map = new JaxbStringObjectPairArray();
    JaxbStringObjectPair keyValue = new JaxbStringObjectPair();
    keyValue.setKey("myobject");
    keyValue.setValue(new MyType("variable", 29));
    map.getItems().add(keyValue);
    spc.setParameter(map);

    // Do webService request
    JaxbProcessInstanceResponse jpir =
        doWebserviceSingleCommandRequest(
            commandWebService, spc, JaxbProcessInstanceResponse.class, deploymentId);
    long procInstId = ((JaxbProcessInstanceResponse) jpir).getId();

    // Create command
    GetTasksByProcessInstanceIdCommand gtbic = new GetTasksByProcessInstanceIdCommand();
    gtbic.setProcessInstanceId(procInstId);

    // Do webservice request
    JaxbLongListResponse jllr =
        doWebserviceSingleCommandRequest(
            commandWebService, gtbic, JaxbLongListResponse.class, deploymentId);
    List<Long> taskIds = jllr.getResult();
    long taskId = taskIds.get(0);

    // Commands for task and task content
    GetTaskCommand gtc = new GetTaskCommand();
    gtc.setTaskId(taskId);
    GetTaskContentCommand gtcc = new GetTaskContentCommand();
    gtcc.setTaskId(taskId);

    // Do webservice request (with both commands)
    JaxbCommandsRequest req = new JaxbCommandsRequest(deploymentId, gtc);
    req.getCommands().add(gtcc); // <3>
    JaxbCommandsResponse response = commandWebService.execute(req);

    // Get task and content response
    Task task = (Task) response.getResponses().get(0).getResult();
    Map<String, Object> contentMap =
        (Map<String, Object>) response.getResponses().get(1).getResult();
  }
  /**
   * Method to communicate with the backend via REST.
   *
   * @param command The {@link Command} object to be executed.
   * @return The result of the {@link Command} object execution.
   */
  private <T> T executeRestCommand(Command command) {
    String cmdName = command.getClass().getSimpleName();

    JaxbCommandsRequest jaxbRequest =
        prepareCommandRequest(
            command,
            config.getUserName(),
            config.getDeploymentId(),
            config.getProcessInstanceId(),
            config.getCorrelationProperties());
    KieRemoteHttpRequest httpRequest = config.createHttpRequest().relativeRequest("/execute");

    // necessary for deserialization
    String deploymentId = config.getDeploymentId();
    if (!emptyDeploymentId(deploymentId)) {
      httpRequest.header(JaxbSerializationProvider.EXECUTE_DEPLOYMENT_ID_HEADER, deploymentId);
    }

    String jaxbRequestString = config.getJaxbSerializationProvider().serialize(jaxbRequest);
    if (logger.isTraceEnabled()) {
      try {
        logger.trace(
            "Sending {} via POST to {}", command.getClass().getSimpleName(), httpRequest.getUri());
      } catch (Exception e) {
        // do nothing because this should never happen..
      }
      logger.trace("Serialized JaxbCommandsRequest:\n {}", jaxbRequestString);
    }

    KieRemoteHttpResponse httpResponse = null;
    try {
      logger.debug(
          "Sending POST request with "
              + command.getClass().getSimpleName()
              + " to "
              + httpRequest.getUri());
      httpRequest.contentType(MediaType.APPLICATION_XML);
      httpRequest.accept(MediaType.APPLICATION_XML);
      httpRequest.body(jaxbRequestString);
      httpRequest.post();
      httpResponse = httpRequest.response();
    } catch (Exception e) {
      httpRequest.disconnect();
      throw new RemoteCommunicationException("Unable to send HTTP POST request", e);
    }

    // Get response
    boolean htmlException = false;
    JaxbExceptionResponse exceptionResponse = null;
    JaxbCommandsResponse cmdResponse = null;
    int responseStatus;
    try {
      responseStatus = httpResponse.code();
      String content = httpResponse.body();
      if (responseStatus < 300) {
        cmdResponse = deserializeResponseContent(content, JaxbCommandsResponse.class);

        // check version
        String version = cmdResponse.getVersion();
        if (version == null) {
          version = "pre-6.0.3";
        }
        if (!version.equals(VERSION)) {
          logger.info(
              "Response received from server version [{}] while client is version [{}]! This may cause problems.",
              version,
              VERSION);
        }
      } else {
        String contentType = httpResponse.contentType();
        if (contentType.equals(MediaType.APPLICATION_XML)) {
          Object response = deserializeResponseContent(content, JaxbExceptionResponse.class);
          if (response instanceof JaxbRestRequestException) {
            JaxbRestRequestException exception = (JaxbRestRequestException) response;
            exceptionResponse =
                new JaxbExceptionResponse(
                    exception.getUrl(), exception.getCause(), exception.getStatus());
            exceptionResponse.setCommandName(cmdName);
            exceptionResponse.setIndex(0);
            exceptionResponse.setMessage(exception.getMessage());
          } else if (response instanceof JaxbExceptionResponse) {
            exceptionResponse = (JaxbExceptionResponse) response;
          }
        } else if (contentType.startsWith(MediaType.TEXT_HTML)) {
          htmlException = true;
          exceptionResponse = new JaxbExceptionResponse();
          Document doc = Jsoup.parse(content);
          String body = doc.body().text();
          exceptionResponse.setMessage(body);
          exceptionResponse.setUrl(httpRequest.getUri().toString());
          exceptionResponse.setStackTrace("");
        } else {
          throw new RemoteCommunicationException(
              "Unable to deserialize response with content type '" + contentType + "'");
        }
      }
    } catch (Exception e) {
      logger.error(
          "Unable to retrieve response content from request with status {}: {}", e.getMessage(), e);
      throw new RemoteCommunicationException("Unable to retrieve content from response", e);
    } finally {
      httpRequest.disconnect();
    }

    if (cmdResponse != null) {
      List<JaxbCommandResponse<?>> responses = cmdResponse.getResponses();
      if (responses.size() == 0) {
        return null;
      } else if (responses.size() == 1) {
        // The type information *should* come from the Command class -- but it's a jaxb-gen class,
        // which means that it has lost it's type information..
        // TODO: fix this?
        JaxbCommandResponse<T> responseObject = (JaxbCommandResponse<T>) responses.get(0);
        if (responseObject instanceof JaxbExceptionResponse) {
          exceptionResponse = (JaxbExceptionResponse) responseObject;
        } else {
          return responseObject.getResult();
        }
      } else {
        throw new RemoteCommunicationException(
            "Unexpected number of results from "
                + command.getClass().getSimpleName()
                + ":"
                + responses.size()
                + " results instead of only 1");
      }
    }

    logger.error("Response with status {} returned.", responseStatus);
    // Process exception response
    switch (responseStatus) {
      case 409:
        throw new RemoteTaskException(
            exceptionResponse.getMessage(), exceptionResponse.getStackTrace());
      default:
        if (exceptionResponse != null) {
          if (!htmlException) {
            throw new RemoteApiException(
                exceptionResponse.getMessage(), exceptionResponse.getStackTrace());
          } else {
            throw new RemoteCommunicationException(
                exceptionResponse.getMessage(), exceptionResponse.getStackTrace());
          }
        } else {
          throw new RemoteCommunicationException(
              "Unable to communicate with remote API via URL "
                  + "'"
                  + httpRequest.getUri().toString()
                  + "'");
        }
    }
  }