content: //
  // Licensed to the Apache Software Foundation (ASF) under one
  // or more contributor license agreements.  See the NOTICE file
  // distributed with this work for additional information
  // regarding copyright ownership.  The ASF licenses this file
  // to you 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.
  //
  
  package com.cloud.network.nicira;
  
  import java.io.IOException;
  import java.util.LinkedHashMap;
  import java.util.Map;
  
  import org.apache.http.HttpEntity;
  import org.apache.http.StatusLine;
  import org.apache.http.client.methods.CloseableHttpResponse;
  import org.apache.http.client.methods.HttpUriRequest;
  import org.apache.http.client.protocol.HttpClientContext;
  import org.apache.http.impl.client.CloseableHttpClient;
  import org.apache.http.util.EntityUtils;
  
  import com.cloud.utils.rest.BasicRestClient;
  import com.cloud.utils.rest.CloudstackRESTException;
  import com.cloud.utils.rest.HttpConstants;
  import com.cloud.utils.rest.HttpMethods;
  import com.cloud.utils.rest.HttpStatusCodeHelper;
  import com.cloud.utils.rest.HttpUriRequestBuilder;
  
  public class NiciraRestClient extends BasicRestClient {
  
  
      private static final String CONTENT_TYPE = HttpConstants.CONTENT_TYPE;
      private static final String TEXT_HTML_CONTENT_TYPE = HttpConstants.TEXT_HTML_CONTENT_TYPE;
  
      private static final int DEFAULT_BODY_RESP_MAX_LEN = 1024;
      private static final int DEFAULT_EXECUTION_LIMIT = 5;
  
      private final ExecutionCounter counter;
      private final int maxResponseErrorMesageLength;
      private final int executionLimit;
  
      private final String username;
      private final String password;
      private final String loginUrl;
  
      private NiciraRestClient(final Builder builder) {
          super(builder.client, builder.clientContext, builder.hostname);
          executionLimit = builder.executionLimit;
          counter = new ExecutionCounter(executionLimit);
          maxResponseErrorMesageLength = builder.maxResponseErrorMesageLength;
          username = builder.username;
          password = builder.password;
          loginUrl = builder.loginUrl;
      }
  
      public static Builder create() {
          return new Builder();
      }
  
      @Override
      public CloseableHttpResponse execute(final HttpUriRequest request) throws CloudstackRESTException {
          return execute(request, 0);
      }
  
      CloseableHttpResponse execute(final HttpUriRequest request, final int previousStatusCode) throws CloudstackRESTException {
          if (counter.hasReachedExecutionLimit()) {
              throw new CloudstackRESTException("Reached max executions limit of " + executionLimit);
          }
          counter.incrementExecutionCounter();
          logger.debug("Executing " + request.getMethod() + " request [execution count = " + counter.getValue() + "]");
          final CloseableHttpResponse response = super.execute(request);
  
          final StatusLine statusLine = response.getStatusLine();
          final int statusCode = statusLine.getStatusCode();
          logger.debug("Status of last request: " + statusLine.toString());
          if (HttpStatusCodeHelper.isUnauthorized(statusCode)) {
              return handleUnauthorizedResponse(request, previousStatusCode, response, statusCode);
          } else if (HttpStatusCodeHelper.isSuccess(statusCode)) {
              return handleSuccessResponse(request, response);
          } else if (HttpStatusCodeHelper.isConflict(statusCode)) {
              throw new CloudstackRESTException("Conflict: " + statusLine.getReasonPhrase(), statusCode);
          } else {
              throw new CloudstackRESTException("Unexpected status code: " + statusCode, statusCode);
          }
      }
  
      private CloseableHttpResponse handleUnauthorizedResponse(final HttpUriRequest request, final int previousStatusCode, final CloseableHttpResponse response, final int statusCode)
                      throws CloudstackRESTException {
          super.closeResponse(response);
          if (HttpStatusCodeHelper.isUnauthorized(previousStatusCode)) {
              logger.error(responseToErrorMessage(response));
              throw new CloudstackRESTException("Two consecutive failed attempts to authenticate against REST server");
          }
          final HttpUriRequest authenticateRequest = createAuthenticationRequest();
          final CloseableHttpResponse loginResponse = execute(authenticateRequest, statusCode);
          final int loginStatusCode = loginResponse.getStatusLine().getStatusCode();
          super.closeResponse(loginResponse);
          return execute(request, loginStatusCode);
      }
  
      private CloseableHttpResponse handleSuccessResponse(final HttpUriRequest request, final CloseableHttpResponse response) {
          if (!request.getURI().getPath().contains(loginUrl)) {
              counter.resetExecutionCounter();
          }
          return response;
      }
  
      private HttpUriRequest createAuthenticationRequest() {
          final Map<String, String> parameters = new LinkedHashMap<>();
          parameters.put("username", username);
          parameters.put("password", password);
          return HttpUriRequestBuilder.create()
              .method(HttpMethods.POST)
              .methodParameters(parameters)
              .path(loginUrl)
              .build();
      }
  
      private String responseToErrorMessage(final CloseableHttpResponse response) {
          String errorMessage = response.getStatusLine().toString();
          if (response.containsHeader(CONTENT_TYPE) && TEXT_HTML_CONTENT_TYPE.equals(response.getFirstHeader(CONTENT_TYPE).getValue())) {
              try {
                  final HttpEntity entity = response.getEntity();
                  final String respobnseBody = EntityUtils.toString(entity);
                  errorMessage = respobnseBody.subSequence(0, maxResponseErrorMesageLength).toString();
              } catch (final IOException e) {
                  logger.debug("Could not read response body. Response: " + response, e);
              }
          }
  
          return errorMessage;
      }
  
      protected static class Builder extends BasicRestClient.Builder<Builder> {
          private CloseableHttpClient client;
          private HttpClientContext clientContext;
          private String hostname;
          private String username;
          private String password;
          private String loginUrl;
          private int executionLimit = DEFAULT_EXECUTION_LIMIT;
          private int maxResponseErrorMesageLength = DEFAULT_BODY_RESP_MAX_LEN;
  
          public Builder hostname(final String hostname) {
              this.hostname = hostname;
              return this;
          }
  
          public Builder username(final String username) {
              this.username = username;
              return this;
          }
  
          public Builder password(final String password) {
              this.password = password;
              return this;
          }
  
          public Builder loginUrl(final String loginUrl) {
              this.loginUrl = loginUrl;
              return this;
          }
  
          @Override
          public Builder client(final CloseableHttpClient client) {
              this.client = client;
              return this;
          }
  
          @Override
          public Builder clientContext(final HttpClientContext clientContext) {
              this.clientContext = clientContext;
              return this;
          }
  
          public Builder executionLimit(final int executionLimit) {
              this.executionLimit = executionLimit;
              return this;
          }
  
          public Builder maxResponseErrorMesageLength(final int maxResponseErrorMesageLength) {
              this.maxResponseErrorMesageLength = maxResponseErrorMesageLength;
              return this;
          }
  
          @Override
          public NiciraRestClient build() {
              return new NiciraRestClient(this);
          }
  
      }
  }
  