/**
   * Implemented by subclasses. The responsibility of this method is to contact the service provider
   * at the given endpoint URL and fetch a request or access token. What kind of token is retrieved
   * solely depends on the URL being used.
   *
   * <p>Correct implementations of this method must guarantee the following post-conditions:
   *
   * <ul>
   *   <li>the {@link OAuthConsumer} passed to this method must have a valid {@link
   *       OAuth#OAUTH_TOKEN} and {@link OAuth#OAUTH_TOKEN_SECRET} set by calling {@link
   *       OAuthConsumer#setTokenWithSecret(String, String)}
   *   <li>{@link #getResponseParameters()} must return the set of query parameters served by the
   *       service provider in the token response, with all OAuth specific parameters being removed
   * </ul>
   *
   * @param consumer the {@link OAuthConsumer} that should be used to sign the request
   * @param endpointUrl the URL at which the service provider serves the OAuth token that is to be
   *     fetched
   * @param additionalParameters you can pass parameters here (typically OAuth parameters such as
   *     oauth_callback or oauth_verifier) which will go directly into the signer, i.e. you don't
   *     have to put them into the request first, just so the consumer pull them out again. Pass
   *     them sequentially in key/value order.
   * @throws OAuthMessageSignerException if signing the token request fails
   * @throws OAuthCommunicationException if a network communication error occurs
   * @throws OAuthNotAuthorizedException if the server replies 401 - Unauthorized
   * @throws OAuthExpectationFailedException if an expectation has failed, e.g. because the server
   *     didn't reply in the expected format
   */
  protected void retrieveToken(
      OAuthConsumer consumer, String endpointUrl, String... additionalParameters)
      throws OAuthMessageSignerException, OAuthCommunicationException, OAuthNotAuthorizedException,
          OAuthExpectationFailedException {
    Map<String, String> defaultHeaders = getRequestHeaders();

    if (consumer.getConsumerKey() == null || consumer.getConsumerSecret() == null) {
      throw new OAuthExpectationFailedException("Consumer key or secret not set");
    }

    HttpRequest request = null;
    HttpResponse response = null;
    try {
      request = createRequest(endpointUrl);
      for (String header : defaultHeaders.keySet()) {
        request.setHeader(header, defaultHeaders.get(header));
      }
      if (additionalParameters != null) {
        HttpParameters httpParams = new HttpParameters();
        httpParams.putAll(additionalParameters, true);
        consumer.setAdditionalParameters(httpParams);
      }

      if (this.listener != null) {
        this.listener.prepareRequest(request);
      }

      consumer.sign(request);

      if (this.listener != null) {
        this.listener.prepareSubmission(request);
      }

      response = sendRequest(request);
      int statusCode = response.getStatusCode();

      boolean requestHandled = false;
      if (this.listener != null) {
        requestHandled = this.listener.onResponseReceived(request, response);
      }
      if (requestHandled) {
        return;
      }

      if (statusCode >= 300) {
        handleUnexpectedResponse(statusCode, response);
      }

      HttpParameters responseParams = OAuth.decodeForm(response.getContent());

      String token = responseParams.getFirst(OAuth.OAUTH_TOKEN);
      String secret = responseParams.getFirst(OAuth.OAUTH_TOKEN_SECRET);
      responseParams.remove(OAuth.OAUTH_TOKEN);
      responseParams.remove(OAuth.OAUTH_TOKEN_SECRET);

      setResponseParameters(responseParams);

      if (token == null || secret == null) {
        throw new OAuthExpectationFailedException(
            "Request token or token secret not set in server reply. "
                + "The service provider you use is probably buggy.");
      }

      consumer.setTokenWithSecret(token, secret);

    } catch (OAuthNotAuthorizedException e) {
      throw e;
    } catch (OAuthExpectationFailedException e) {
      throw e;
    } catch (Exception e) {
      throw new OAuthCommunicationException(e);
    } finally {
      try {
        closeConnection(request, response);
      } catch (Exception e) {
        throw new OAuthCommunicationException(e);
      }
    }
  }