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 org.apache.cloudstack.region;
  
  import java.io.IOException;
  import java.io.InputStream;
  import java.io.ObjectInputStream;
  import java.io.UnsupportedEncodingException;
  import java.net.URLEncoder;
  import java.util.ArrayList;
  import java.util.Collections;
  import java.util.Iterator;
  import java.util.List;
  import java.util.StringTokenizer;
  
  import javax.crypto.Mac;
  import javax.crypto.spec.SecretKeySpec;
  
  import org.apache.commons.codec.binary.Base64;
  import org.apache.commons.httpclient.HttpClient;
  import org.apache.commons.httpclient.HttpException;
  import org.apache.commons.httpclient.HttpMethod;
  import org.apache.commons.httpclient.NameValuePair;
  import org.apache.commons.httpclient.methods.GetMethod;
  import org.apache.logging.log4j.Logger;
  import org.apache.logging.log4j.LogManager;
  
  import com.thoughtworks.xstream.XStream;
  import com.thoughtworks.xstream.io.xml.DomDriver;
  
  import com.cloud.domain.DomainVO;
  import com.cloud.user.UserAccount;
  import com.cloud.user.UserAccountVO;
  
  /**
   * Utility class for making API calls between peer Regions
   *
   */
  public class RegionsApiUtil {
      protected static Logger LOGGER = LogManager.getLogger(RegionsApiUtil.class);
  
      /**
       * Makes an api call using region service end_point, api command and params
       * @param region
       * @param command
       * @param params
       * @return True, if api is successful
       */
      protected static boolean makeAPICall(Region region, String command, List<NameValuePair> params) {
          try {
              String apiParams = buildParams(command, params);
              String url = buildUrl(apiParams, region);
              HttpClient client = new HttpClient();
              HttpMethod method = new GetMethod(url);
              if (client.executeMethod(method) == 200) {
                  return true;
              } else {
                  return false;
              }
          } catch (HttpException e) {
              LOGGER.error(e.getMessage());
              return false;
          } catch (IOException e) {
              LOGGER.error(e.getMessage());
              return false;
          }
      }
  
      /**
       * Makes an api call using region service end_point, api command and params
       * Returns Account object on success
       * @param region
       * @param command
       * @param params
       * @return
       */
      protected static RegionAccount makeAccountAPICall(Region region, String command, List<NameValuePair> params) {
          try {
              String url = buildUrl(buildParams(command, params), region);
              HttpClient client = new HttpClient();
              HttpMethod method = new GetMethod(url);
              if (client.executeMethod(method) == 200) {
                  InputStream is = method.getResponseBodyAsStream();
                  //Translate response to Account object
                  XStream xstream = new XStream(new DomDriver());
                  xstream.alias("account", RegionAccount.class);
                  xstream.alias("user", RegionUser.class);
                  xstream.aliasField("id", RegionAccount.class, "uuid");
                  xstream.aliasField("name", RegionAccount.class, "accountName");
                  xstream.aliasField("accounttype", RegionAccount.class, "type");
                  xstream.aliasField("domainid", RegionAccount.class, "domainUuid");
                  xstream.aliasField("networkdomain", RegionAccount.class, "networkDomain");
                  xstream.aliasField("id", RegionUser.class, "uuid");
                  xstream.aliasField("accountId", RegionUser.class, "accountUuid");
                  try(ObjectInputStream in = xstream.createObjectInputStream(is);) {
                      return (RegionAccount) in.readObject();
                  }catch (IOException e) {
                      LOGGER.error(e.getMessage());
                      return null;
                  }
              } else {
                  return null;
              }
          } catch (HttpException e) {
              LOGGER.error(e.getMessage());
              return null;
          } catch (IOException e) {
              LOGGER.error(e.getMessage());
              return null;
          } catch (ClassNotFoundException e) {
              LOGGER.error(e.getMessage());
              return null;
          }
      }
  
      /**
       * Makes an api call using region service end_point, api command and params
       * Returns Domain object on success
       * @param region
       * @param command
       * @param params
       * @return
       */
      protected static RegionDomain makeDomainAPICall(Region region, String command, List<NameValuePair> params) {
          try {
              String url = buildUrl(buildParams(command, params), region);
              HttpClient client = new HttpClient();
              HttpMethod method = new GetMethod(url);
              if (client.executeMethod(method) == 200) {
                  InputStream is = method.getResponseBodyAsStream();
                  XStream xstream = new XStream(new DomDriver());
                  //Translate response to Domain object
                  xstream.alias("domain", RegionDomain.class);
                  xstream.aliasField("id", RegionDomain.class, "uuid");
                  xstream.aliasField("parentdomainid", RegionDomain.class, "parentUuid");
                  xstream.aliasField("networkdomain", DomainVO.class, "networkDomain");
                  try(ObjectInputStream in = xstream.createObjectInputStream(is);) {
                      return (RegionDomain) in.readObject();
                  }catch (IOException e) {
                      LOGGER.error(e.getMessage());
                      return null;
                  }
              } else {
                  return null;
              }
          } catch (HttpException e) {
              LOGGER.error(e.getMessage());
              return null;
          } catch (IOException e) {
              LOGGER.error(e.getMessage());
              return null;
          } catch (ClassNotFoundException e) {
              LOGGER.error(e.getMessage());
              return null;
          }
      }
  
      /**
       * Makes an api call using region service end_point, api command and params
       * Returns UserAccount object on success
       * @param region
       * @param command
       * @param params
       * @return
       */
      protected static UserAccount makeUserAccountAPICall(Region region, String command, List<NameValuePair> params) {
          try {
              String url = buildUrl(buildParams(command, params), region);
              HttpClient client = new HttpClient();
              HttpMethod method = new GetMethod(url);
              if (client.executeMethod(method) == 200) {
                  InputStream is = method.getResponseBodyAsStream();
                  XStream xstream = new XStream(new DomDriver());
                  xstream.alias("useraccount", UserAccountVO.class);
                  xstream.aliasField("id", UserAccountVO.class, "uuid");
                  try(ObjectInputStream in = xstream.createObjectInputStream(is);) {
                      return (UserAccountVO)in.readObject();
                  } catch (IOException e) {
                      LOGGER.error(e.getMessage());
                      return null;
                  }
              } else {
                  return null;
              }
          } catch (HttpException e) {
              LOGGER.error(e.getMessage());
              return null;
          } catch (IOException e) {
              LOGGER.error(e.getMessage());
              return null;
          } catch (ClassNotFoundException e) {
              LOGGER.error(e.getMessage());
              return null;
          }
      }
  
      /**
       * Builds parameters string with command and encoded param values
       * @param command
       * @param params
       * @return
       */
      protected static String buildParams(String command, List<NameValuePair> params) {
          StringBuffer paramString = new StringBuffer("command=" + command);
          Iterator<NameValuePair> iter = params.iterator();
          try {
              while (iter.hasNext()) {
                  NameValuePair param = iter.next();
                  if (param.getValue() != null && !(param.getValue().isEmpty())) {
                      paramString.append("&" + param.getName() + "=" + URLEncoder.encode(param.getValue(), "UTF-8"));
                  }
              }
          } catch (UnsupportedEncodingException e) {
              LOGGER.error(e.getMessage());
              return null;
          }
          return paramString.toString();
      }
  
      /**
       * Build URL for api call using region end_point
       * Parameters are sorted and signed using secret_key
       * @param apiParams
       * @param region
       * @return
       */
      private static String buildUrl(String apiParams, Region region) {
  
          String apiKey = "";
          String secretKey = "";
          String encodedApiKey;
          try {
              encodedApiKey = URLEncoder.encode(apiKey, "UTF-8");
  
              List<String> sortedParams = new ArrayList<String>();
              sortedParams.add("apikey=" + encodedApiKey.toLowerCase());
              StringTokenizer st = new StringTokenizer(apiParams, "&");
              String url = null;
              boolean first = true;
              while (st.hasMoreTokens()) {
                  String paramValue = st.nextToken();
                  String param = paramValue.substring(0, paramValue.indexOf("="));
                  String value = paramValue.substring(paramValue.indexOf("=") + 1, paramValue.length());
                  if (first) {
                      url = param + "=" + value;
                      first = false;
                  } else {
                      url = url + "&" + param + "=" + value;
                  }
                  sortedParams.add(param.toLowerCase() + "=" + value.toLowerCase());
              }
              Collections.sort(sortedParams);
  
              //Construct the sorted URL and sign and URL encode the sorted URL with your secret key
              String sortedUrl = null;
              first = true;
              for (String param : sortedParams) {
                  if (first) {
                      sortedUrl = param;
                      first = false;
                  } else {
                      sortedUrl = sortedUrl + "&" + param;
                  }
              }
              String encodedSignature = signRequest(sortedUrl, secretKey);
  
              String finalUrl = region.getEndPoint() + "?" + apiParams + "&apiKey=" + apiKey + "&signature=" + encodedSignature;
  
              return finalUrl;
  
          } catch (UnsupportedEncodingException e) {
              LOGGER.error(e.getMessage());
              return null;
          }
      }
  
      /**
       * 1. Signs a string with a secret key using SHA-1 2. Base64 encode the result 3. URL encode the final result
       *
       * @param request
       * @param key
       * @return
       */
      private static String signRequest(String request, String key) {
          try {
              Mac mac = Mac.getInstance("HmacSHA1");
              SecretKeySpec keySpec = new SecretKeySpec(key.getBytes(), "HmacSHA1");
              mac.init(keySpec);
              mac.update(request.getBytes());
              byte[] encryptedBytes = mac.doFinal();
              return URLEncoder.encode(Base64.encodeBase64String(encryptedBytes), "UTF-8");
          } catch (Exception ex) {
              LOGGER.error(ex.getMessage());
              return null;
          }
      }
  
  }
  