Package org.cloudfoundry.client.lib.rest

Source Code of org.cloudfoundry.client.lib.rest.CloudControllerClientImpl

/*
* Copyright 2009-2013 the original author or authors.
*
* Licensed 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.cloudfoundry.client.lib.rest;

import java.io.File;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.Socket;
import java.net.SocketException;
import java.net.URI;
import java.net.URL;
import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import java.util.UUID;
import java.util.zip.ZipFile;

import javax.websocket.ClientEndpointConfig;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.cloudfoundry.client.lib.ApplicationLogListener;
import org.cloudfoundry.client.lib.ClientHttpResponseCallback;
import org.cloudfoundry.client.lib.CloudCredentials;
import org.cloudfoundry.client.lib.CloudFoundryException;
import org.cloudfoundry.client.lib.RestLogCallback;
import org.cloudfoundry.client.lib.StartingInfo;
import org.cloudfoundry.client.lib.StreamingLogToken;
import org.cloudfoundry.client.lib.UploadStatusCallback;
import org.cloudfoundry.client.lib.archive.ApplicationArchive;
import org.cloudfoundry.client.lib.archive.DirectoryApplicationArchive;
import org.cloudfoundry.client.lib.archive.ZipApplicationArchive;
import org.cloudfoundry.client.lib.domain.ApplicationLog;
import org.cloudfoundry.client.lib.domain.ApplicationStats;
import org.cloudfoundry.client.lib.domain.CloudApplication;
import org.cloudfoundry.client.lib.domain.CloudDomain;
import org.cloudfoundry.client.lib.domain.CloudInfo;
import org.cloudfoundry.client.lib.domain.CloudOrganization;
import org.cloudfoundry.client.lib.domain.CloudQuota;
import org.cloudfoundry.client.lib.domain.CloudResource;
import org.cloudfoundry.client.lib.domain.CloudResources;
import org.cloudfoundry.client.lib.domain.CloudRoute;
import org.cloudfoundry.client.lib.domain.CloudService;
import org.cloudfoundry.client.lib.domain.CloudServiceBroker;
import org.cloudfoundry.client.lib.domain.CloudServiceOffering;
import org.cloudfoundry.client.lib.domain.CloudServicePlan;
import org.cloudfoundry.client.lib.domain.CloudSpace;
import org.cloudfoundry.client.lib.domain.CloudStack;
import org.cloudfoundry.client.lib.domain.CrashInfo;
import org.cloudfoundry.client.lib.domain.CrashesInfo;
import org.cloudfoundry.client.lib.domain.InstanceState;
import org.cloudfoundry.client.lib.domain.InstanceStats;
import org.cloudfoundry.client.lib.domain.InstancesInfo;
import org.cloudfoundry.client.lib.domain.Staging;
import org.cloudfoundry.client.lib.domain.UploadApplicationPayload;
import org.cloudfoundry.client.lib.oauth2.OauthClient;
import org.cloudfoundry.client.lib.util.CloudEntityResourceMapper;
import org.cloudfoundry.client.lib.util.CloudUtil;
import org.cloudfoundry.client.lib.util.JsonUtil;
import org.codehaus.jackson.map.ObjectMapper;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.http.client.ClientHttpRequest;
import org.springframework.http.client.ClientHttpRequestFactory;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.http.converter.FormHttpMessageConverter;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.util.Assert;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.RequestCallback;
import org.springframework.web.client.ResourceAccessException;
import org.springframework.web.client.ResponseErrorHandler;
import org.springframework.web.client.ResponseExtractor;
import org.springframework.web.client.RestTemplate;

/**
* Abstract implementation of the CloudControllerClient intended to serve as the base.
*
* @author Ramnivas Laddad
* @author A.B.Srinivasan
* @author Jennifer Hickey
* @author Dave Syer
* @author Thomas Risberg
* @author Alexander Orlov
*/
public class CloudControllerClientImpl implements CloudControllerClient {

  private static final String AUTHORIZATION_HEADER_KEY = "Authorization";
  private static final String PROXY_USER_HEADER_KEY = "Proxy-User";

  private static final String LOGS_LOCATION = "logs";
  private static final int JOB_POLLING_PERIOD = 5000; // matches that of gcf

  private OauthClient oauthClient;

  private CloudSpace sessionSpace;

  private CloudEntityResourceMapper resourceMapper = new CloudEntityResourceMapper();

  private RestTemplate restTemplate;

  private URL cloudControllerUrl;

  private LoggregatorClient loggregatorClient;

  protected CloudCredentials cloudCredentials;

  private final Log logger;

  /**
   * Only for unit tests. This works around the fact that the initialize method is called within the constructor and
   * hence can not be overloaded, making it impossible to write unit tests that don't trigger network calls.
   */
  protected CloudControllerClientImpl() {
    logger = LogFactory.getLog(getClass().getName());
  }

  public CloudControllerClientImpl(URL cloudControllerUrl, RestTemplate restTemplate,
                                   OauthClient oauthClient, LoggregatorClient loggregatorClient,
                                   CloudCredentials cloudCredentials, CloudSpace sessionSpace) {
    logger = LogFactory.getLog(getClass().getName());

    initialize(cloudControllerUrl, restTemplate, oauthClient, loggregatorClient, cloudCredentials);

    this.sessionSpace = sessionSpace;
  }

  public CloudControllerClientImpl(URL cloudControllerUrl, RestTemplate restTemplate,
                                   OauthClient oauthClient, LoggregatorClient loggregatorClient,
                                   CloudCredentials cloudCredentials, String orgName, String spaceName) {
    logger = LogFactory.getLog(getClass().getName());
    CloudControllerClientImpl tempClient =
        new CloudControllerClientImpl(cloudControllerUrl, restTemplate,
            oauthClient, loggregatorClient, cloudCredentials, null);

    initialize(cloudControllerUrl, restTemplate, oauthClient, loggregatorClient, cloudCredentials);

    this.sessionSpace = validateSpaceAndOrg(spaceName, orgName, tempClient);
  }

  private void initialize(URL cloudControllerUrl, RestTemplate restTemplate, OauthClient oauthClient,
                          LoggregatorClient loggregatorClient, CloudCredentials cloudCredentials) {
    Assert.notNull(cloudControllerUrl, "CloudControllerUrl cannot be null");
    Assert.notNull(restTemplate, "RestTemplate cannot be null");
    Assert.notNull(oauthClient, "OauthClient cannot be null");

    oauthClient.init(cloudCredentials);

    this.cloudCredentials = cloudCredentials;

    this.cloudControllerUrl = cloudControllerUrl;

    this.restTemplate = restTemplate;
    configureCloudFoundryRequestFactory(restTemplate);

    this.oauthClient = oauthClient;

    this.loggregatorClient = loggregatorClient;
  }

  private CloudSpace validateSpaceAndOrg(String spaceName, String orgName, CloudControllerClientImpl client) {
    List<CloudSpace> spaces = client.getSpaces();

    for (CloudSpace space : spaces) {
      if (space.getName().equals(spaceName)) {
        CloudOrganization org = space.getOrganization();
        if (orgName == null || org.getName().equals(orgName)) {
          return space;
        }
      }
    }

    throw new IllegalArgumentException("No matching organization and space found for org: " + orgName + " space: " + spaceName);
  }

  @Override
  public void setResponseErrorHandler(ResponseErrorHandler errorHandler) {
    this.restTemplate.setErrorHandler(errorHandler);
  }

  @Override
  public URL getCloudControllerUrl() {
    return this.cloudControllerUrl;
  }

  @Override
  public void updatePassword(String newPassword) {
    updatePassword(cloudCredentials, newPassword);
  }

  @Override
  public Map<String, String> getLogs(String appName) {
    String urlPath = getFileUrlPath();
    String instance = String.valueOf(0);
    return doGetLogs(urlPath, appName, instance);
  }

  @Override
  public List<ApplicationLog> getRecentLogs(String appName) {
    AccumulatingApplicationLogListener listener = new AccumulatingApplicationLogListener();
    streamLoggregatorLogs(appName, listener, true);
    synchronized (listener) {
      try {
        listener.wait();
      } catch (InterruptedException e) {
        // return any captured logs
      }
    }
    return listener.getLogs();
  }

  @Override
  public StreamingLogToken streamLogs(String appName, ApplicationLogListener listener) {
    return streamLoggregatorLogs(appName, listener, false);
  }

  @Override
  public Map<String, String> getCrashLogs(String appName) {
    String urlPath = getFileUrlPath();
    CrashesInfo crashes = getCrashes(appName);
    if (crashes.getCrashes().isEmpty()) {
      return Collections.emptyMap();
    }
    TreeMap<Date, String> crashInstances = new TreeMap<Date, String>();
    for (CrashInfo crash : crashes.getCrashes()) {
      crashInstances.put(crash.getSince(), crash.getInstance());
    }
    String instance = crashInstances.get(crashInstances.lastKey());
    return doGetLogs(urlPath, appName, instance);
  }

  @Override
  public String getFile(String appName, int instanceIndex, String filePath, int startPosition, int endPosition) {
    String urlPath = getFileUrlPath();
    Object appId = getFileAppId(appName);
    return doGetFile(urlPath, appId, instanceIndex, filePath, startPosition, endPosition);
  }


  @Override
  public void openFile(String appName, int instanceIndex, String filePath, ClientHttpResponseCallback callback) {
    String urlPath = getFileUrlPath();
    Object appId = getFileAppId(appName);
    doOpenFile(urlPath, appId, instanceIndex, filePath, callback);
  }

  @Override
  public void registerRestLogListener(RestLogCallback callBack) {
    if (getRestTemplate() instanceof LoggingRestTemplate) {
      ((LoggingRestTemplate)getRestTemplate()).registerRestLogListener(callBack);
    }
  }

  @Override
  public void unRegisterRestLogListener(RestLogCallback callBack) {
    if (getRestTemplate() instanceof LoggingRestTemplate) {
      ((LoggingRestTemplate)getRestTemplate()).unRegisterRestLogListener(callBack);
    }
  }

  /**
   * Returns null if no further content is available. Two errors that will
   * lead to a null value are 404 Bad Request errors, which are handled in the
   * implementation, meaning that no further log file contents are available,
   * or ResourceAccessException, also handled in the implementation,
   * indicating a possible timeout in the server serving the content. Note
   * that any other CloudFoundryException or RestClientException exception not
   * related to the two errors mentioned above may still be thrown (e.g. 500
   * level errors, Unauthorized or Forbidden exceptions, etc..)
   *
   * @return content if available, which may contain multiple lines, or null
   *         if no further content is available.
   *
   */
  @Override
  public String getStagingLogs(StartingInfo info, int offset) {
    String stagingFile = info.getStagingFile();
    if (stagingFile != null) {
      CloudFoundryClientHttpRequestFactory cfRequestFactory = null;
      try {
        HashMap<String, Object> logsRequest = new HashMap<String, Object>();
        logsRequest.put("offset", offset);

        cfRequestFactory = getRestTemplate().getRequestFactory() instanceof CloudFoundryClientHttpRequestFactory ? (CloudFoundryClientHttpRequestFactory) getRestTemplate()
            .getRequestFactory() : null;
        if (cfRequestFactory != null) {
          cfRequestFactory
              .increaseReadTimeoutForStreamedTailedLogs(5 * 60 * 1000);
        }
        return getRestTemplate().getForObject(
            stagingFile + "&tail&tail_offset={offset}",
            String.class, logsRequest);
      } catch (CloudFoundryException e) {
        if (e.getStatusCode().equals(HttpStatus.NOT_FOUND)) {
          // Content is no longer available
          return null;
        } else {
          throw e;
        }
      } catch (ResourceAccessException e) {
        // Likely read timeout, the directory server won't serve
        // the content again
        logger.debug("Caught exception while fetching staging logs. Aborting. Caught:" + e,
            e);
      } finally {
        if (cfRequestFactory != null) {
          cfRequestFactory
              .increaseReadTimeoutForStreamedTailedLogs(-1);
        }
      }
    }
    return null;
  }

  protected RestTemplate getRestTemplate() {
    return this.restTemplate;
  }

  protected String getUrl(String path) {
    return cloudControllerUrl + (path.startsWith("/") ? path : "/" + path);
  }

  protected void configureCloudFoundryRequestFactory(RestTemplate restTemplate) {
    ClientHttpRequestFactory requestFactory = restTemplate.getRequestFactory();
    if (!(requestFactory instanceof CloudFoundryClientHttpRequestFactory)) {
      restTemplate.setRequestFactory(
          new CloudFoundryClientHttpRequestFactory(requestFactory));
    }
  }

  private class CloudFoundryClientHttpRequestFactory implements ClientHttpRequestFactory {

    private ClientHttpRequestFactory delegate;
    private Integer defaultSocketTimeout = 0;

    public CloudFoundryClientHttpRequestFactory(ClientHttpRequestFactory delegate) {
      this.delegate = delegate;
      captureDefaultReadTimeout();
    }

    @Override
    public ClientHttpRequest createRequest(URI uri, HttpMethod httpMethod) throws IOException {
      ClientHttpRequest request = delegate.createRequest(uri, httpMethod);

      String authorizationHeader = oauthClient.getAuthorizationHeader();
      if (authorizationHeader != null) {
        request.getHeaders().add(AUTHORIZATION_HEADER_KEY, authorizationHeader);
      }

      if (cloudCredentials != null && cloudCredentials.getProxyUser() != null) {
        request.getHeaders().add(PROXY_USER_HEADER_KEY, cloudCredentials.getProxyUser());
      }

      return request;
    }

    private void captureDefaultReadTimeout() {
      if (delegate instanceof HttpComponentsClientHttpRequestFactory) {
        HttpComponentsClientHttpRequestFactory httpRequestFactory =
            (HttpComponentsClientHttpRequestFactory) delegate;
        defaultSocketTimeout = (Integer) httpRequestFactory
            .getHttpClient().getParams()
            .getParameter("http.socket.timeout");
        if (defaultSocketTimeout == null) {
          try {
            defaultSocketTimeout = new Socket().getSoTimeout();
          } catch (SocketException e) {
            defaultSocketTimeout = 0;
          }
        }
      }
    }

    public void increaseReadTimeoutForStreamedTailedLogs(int timeout) {
      // May temporary increase read timeout on other unrelated concurrent
      // threads, but per-request read timeout don't seem easily
      // accessible
      if (delegate instanceof HttpComponentsClientHttpRequestFactory) {
        HttpComponentsClientHttpRequestFactory httpRequestFactory =
            (HttpComponentsClientHttpRequestFactory) delegate;

        if (timeout > 0) {
          httpRequestFactory.setReadTimeout(timeout);
        } else {
          httpRequestFactory
              .setReadTimeout(defaultSocketTimeout);
        }
      }
    }
  }

  public static class CloudFoundryFormHttpMessageConverter extends FormHttpMessageConverter {
    @Override
    protected String getFilename(Object part) {
      if (part instanceof UploadApplicationPayload) {
        return ((UploadApplicationPayload) part).getArchive().getFilename();
      }
      return super.getFilename(part);
    }
  }

  protected Map<String, String> doGetLogs(String urlPath, String appName, String instance) {
    Object appId = getFileAppId(appName);
    String logFiles = doGetFile(urlPath, appId, instance, LOGS_LOCATION, -1, -1);
    String[] lines = logFiles.split("\n");
    List<String> fileNames = new ArrayList<String>();
    for (String line : lines) {
      String[] parts = line.split("\\s");
      if (parts.length > 0 && parts[0] != null) {
        fileNames.add(parts[0]);
      }
    }
    Map<String, String> logs = new HashMap<String, String>(fileNames.size());
    for(String fileName : fileNames) {
      String logFile = LOGS_LOCATION + "/" + fileName;
      logs.put(logFile, doGetFile(urlPath, appId, instance, logFile, -1, -1));
    }
    return logs;
  }

  @SuppressWarnings("unchecked")
  protected void doOpenFile(String urlPath, Object app, int instanceIndex, String filePath,
      ClientHttpResponseCallback callback) {
    getRestTemplate().execute(getUrl(urlPath), HttpMethod.GET, null, new ResponseExtractorWrapper(callback), app,
        String.valueOf(instanceIndex), filePath);
  }

  protected String doGetFile(String urlPath, Object app, int instanceIndex, String filePath, int startPosition, int endPosition) {
    return doGetFile(urlPath, app, String.valueOf(instanceIndex), filePath, startPosition, endPosition);
  }

  protected String doGetFile(String urlPath, Object app, String instance, String filePath, int startPosition, int endPosition) {
    Assert.isTrue(startPosition >= -1, "Invalid start position value: " + startPosition);
    Assert.isTrue(endPosition >= -1, "Invalid end position value: " + endPosition);
    Assert.isTrue(startPosition < 0 || endPosition < 0 || endPosition >= startPosition,
        "The end position (" + endPosition + ") can't be less than the start position (" + startPosition + ")");

    int start, end;
    if (startPosition == -1 && endPosition == -1) {
      start = 0;
      end = -1;
    } else {
      start = startPosition;
      end = endPosition;
    }

    final String range =
        "bytes=" + (start == -1 ? "" : start) + "-" + (end == -1 ? "" : end);

    return doGetFileByRange(urlPath, app, instance, filePath, start, end, range);
  }

  private String doGetFileByRange(String urlPath, Object app, String instance, String filePath, int start, int end,
                  String range) {

    boolean supportsRanges;
    try {
      supportsRanges = getRestTemplate().execute(getUrl(urlPath),
          HttpMethod.HEAD,
          new RequestCallback() {
            public void doWithRequest(ClientHttpRequest request) throws IOException {
              request.getHeaders().set("Range", "bytes=0-");
            }
          },
          new ResponseExtractor<Boolean>() {
            public Boolean extractData(ClientHttpResponse response) throws IOException {
              return response.getStatusCode().equals(HttpStatus.PARTIAL_CONTENT);
            }
          },
          app, instance, filePath);
    } catch (CloudFoundryException e) {
      if (e.getStatusCode().equals(HttpStatus.REQUESTED_RANGE_NOT_SATISFIABLE)) {
        // must be a 0 byte file
        return "";
      } else {
        throw e;
      }
    }
    HttpHeaders headers = new HttpHeaders();
    if (supportsRanges) {
      headers.set("Range", range);
    }
    HttpEntity<Object> requestEntity = new HttpEntity<Object>(headers);
    ResponseEntity<String> responseEntity = getRestTemplate().exchange(getUrl(urlPath),
        HttpMethod.GET, requestEntity, String.class, app, instance, filePath);
    String response = responseEntity.getBody();
    boolean partialFile = false;
    if (responseEntity.getStatusCode().equals(HttpStatus.PARTIAL_CONTENT)) {
      partialFile = true;
    }
    if (!partialFile && response != null) {
      if (start == -1) {
        return response.substring(response.length() - end);
      } else {
        if (start >= response.length()) {
          if (response.length() == 0) {
            return "";
          }
          throw new CloudFoundryException(HttpStatus.REQUESTED_RANGE_NOT_SATISFIABLE,
              "The starting position " + start + " is past the end of the file content.");
        }
        if (end != -1) {
          if (end >= response.length()) {
            end = response.length() - 1;
          }
          return response.substring(start, end + 1);
        } else {
          return response.substring(start);
        }
      }
    }
    return response;
  }

  @SuppressWarnings("unchecked")
  @Override
  public CloudInfo getInfo() {
    // info comes from two end points: /info and /v2/info

    String infoV2Json = getRestTemplate().getForObject(getUrl("/v2/info"), String.class);
    Map<String, Object> infoV2Map = JsonUtil.convertJsonToMap(infoV2Json);

    Map<String, Object> userMap = getUserInfo((String) infoV2Map.get("user"));

    String infoJson = getRestTemplate().getForObject(getUrl("/info"), String.class);
    Map<String, Object> infoMap = JsonUtil.convertJsonToMap(infoJson);
    Map<String, Object> limitMap = (Map<String, Object>) infoMap.get("limits");
    Map<String, Object> usageMap = (Map<String, Object>) infoMap.get("usage");

    String name = CloudUtil.parse(String.class, infoV2Map.get("name"));
    String support = CloudUtil.parse(String.class, infoV2Map.get("support"));
    String authorizationEndpoint = CloudUtil.parse(String.class, infoV2Map.get("authorization_endpoint"));
    String build = CloudUtil.parse(String.class, infoV2Map.get("build"));
    String version = "" + CloudUtil.parse(Number.class, infoV2Map.get("version"));
    String description = CloudUtil.parse(String.class, infoV2Map.get("description"));

    CloudInfo.Limits limits = null;
    CloudInfo.Usage usage = null;
    boolean debug = false;
    if (oauthClient.getToken() != null) {
      limits = new CloudInfo.Limits(limitMap);
      usage = new CloudInfo.Usage(usageMap);
      debug = CloudUtil.parse(Boolean.class, infoMap.get("allow_debug"));
    }

    String loggregatorEndpoint = CloudUtil.parse(String.class, infoV2Map.get("logging_endpoint"));

    return new CloudInfo(name, support, authorizationEndpoint, build, version, (String)userMap.get("user_name"),
        description, limits, usage, debug, loggregatorEndpoint);
  }

  @Override
  public List<CloudSpace> getSpaces() {
    String urlPath = "/v2/spaces?inline-relations-depth=1";
    List<Map<String, Object>> resourceList = getAllResources(urlPath, null);
    List<CloudSpace> spaces = new ArrayList<CloudSpace>();
    for (Map<String, Object> resource : resourceList) {
      spaces.add(resourceMapper.mapResource(resource, CloudSpace.class));
    }
    return spaces;
  }

  @Override
  public List<CloudOrganization> getOrganizations() {
    String urlPath = "/v2/organizations?inline-relations-depth=0";
    List<Map<String, Object>> resourceList = getAllResources(urlPath, null);
    List<CloudOrganization> orgs = new ArrayList<CloudOrganization>();
    for (Map<String, Object> resource : resourceList) {
      orgs.add(resourceMapper.mapResource(resource, CloudOrganization.class));
    }
    return orgs;
  }

  @Override
  public OAuth2AccessToken login() {
    oauthClient.init(cloudCredentials);
    return oauthClient.getToken();
  }

  @Override
  public void logout() {
    oauthClient.clear();
  }

  @Override
  public void register(String email, String password) {
    throw new UnsupportedOperationException("Feature is not yet implemented.");
  }

  @Override
  public void updatePassword(CloudCredentials credentials, String newPassword) {
    oauthClient.changePassword(credentials.getPassword(), newPassword);
    CloudCredentials newCloudCredentials = new CloudCredentials(credentials.getEmail(), newPassword);
    if (cloudCredentials.getProxyUser() != null) {
      cloudCredentials = newCloudCredentials.proxyForUser(cloudCredentials.getProxyUser());
    } else {
      cloudCredentials = newCloudCredentials;
    }
  }

  @Override
  public void unregister() {
    throw new UnsupportedOperationException("Feature is not yet implemented.");
  }

  @Override
  public List<CloudService> getServices() {
    Map<String, Object> urlVars = new HashMap<String, Object>();
    String urlPath = "/v2";
    if (sessionSpace != null) {
      urlVars.put("space", sessionSpace.getMeta().getGuid());
      urlPath = urlPath + "/spaces/{space}";
    }
    urlPath = urlPath + "/service_instances?inline-relations-depth=1&return_user_provided_service_instances=true";
    List<Map<String, Object>> resourceList = getAllResources(urlPath, urlVars);
    List<CloudService> services = new ArrayList<CloudService>();
    for (Map<String, Object> resource : resourceList) {
      if (hasEmbeddedResource(resource, "service_plan")) {
        fillInEmbeddedResource(resource, "service_plan", "service");
      }
      services.add(resourceMapper.mapResource(resource, CloudService.class));
    }
    return services;
  }

  @Override
  public void createService(CloudService service) {
    assertSpaceProvided("create service");
    Assert.notNull(service, "Service must not be null");
    Assert.notNull(service.getName(), "Service name must not be null");
    Assert.notNull(service.getLabel(), "Service label must not be null");
    Assert.notNull(service.getPlan(), "Service plan must not be null");

    CloudServicePlan cloudServicePlan = findPlanForService(service);

    HashMap<String, Object> serviceRequest = new HashMap<String, Object>();
    serviceRequest.put("space_guid", sessionSpace.getMeta().getGuid());
    serviceRequest.put("name", service.getName());
    serviceRequest.put("service_plan_guid", cloudServicePlan.getMeta().getGuid());
    getRestTemplate().postForObject(getUrl("/v2/service_instances"), serviceRequest, String.class);
  }

  private CloudServicePlan findPlanForService(CloudService service) {
    List<CloudServiceOffering> offerings = getServiceOfferings(service.getLabel());
    for (CloudServiceOffering offering : offerings) {
      if (service.getVersion() == null || service.getVersion().equals(offering.getVersion())) {
        for (CloudServicePlan plan : offering.getCloudServicePlans()) {
          if (service.getPlan() != null && service.getPlan().equals(plan.getName())) {
            return plan;
          }
        }
      }
    }
    throw new IllegalArgumentException("Service plan " + service.getPlan() + " not found");
  }

  @Override
  public void createUserProvidedService(CloudService service, Map<String, Object> credentials) {
    assertSpaceProvided("create service");
    Assert.notNull(credentials, "Service credentials must not be null");
    Assert.notNull(service, "Service must not be null");
    Assert.notNull(service.getName(), "Service name must not be null");
    Assert.isNull(service.getLabel(), "Service label is not valid for user-provided services");
    Assert.isNull(service.getProvider(), "Service provider is not valid for user-provided services");
    Assert.isNull(service.getVersion(), "Service version is not valid for user-provided services");
    Assert.isNull(service.getPlan(), "Service plan is not valid for user-provided services");

    HashMap<String, Object> serviceRequest = new HashMap<String, Object>();
    serviceRequest.put("space_guid", sessionSpace.getMeta().getGuid());
    serviceRequest.put("name", service.getName());
    serviceRequest.put("credentials", credentials);
    getRestTemplate().postForObject(getUrl("/v2/user_provided_service_instances"), serviceRequest, String.class);
  }

  @Override
  public CloudService getService(String serviceName) {
    String urlPath = "/v2";
    Map<String, Object> urlVars = new HashMap<String, Object>();
    if (sessionSpace != null) {
      urlVars.put("space", sessionSpace.getMeta().getGuid());
      urlPath = urlPath + "/spaces/{space}";
    }
    urlVars.put("q", "name:" + serviceName);
    urlPath = urlPath + "/service_instances?q={q}&return_user_provided_service_instances=true";
    List<Map<String, Object>> resourceList = getAllResources(urlPath, urlVars);
    CloudService cloudService = null;
    if (resourceList.size() > 0) {
      final Map<String, Object> resource = resourceList.get(0);
      if (hasEmbeddedResource(resource, "service_plan")) {
        fillInEmbeddedResource(resource, "service_plan", "service");
      }
      cloudService = resourceMapper.mapResource(resource, CloudService.class);
    }
    return cloudService;
  }

  @Override
  public void deleteService(String serviceName) {
    CloudService cloudService = getService(serviceName);
    doDeleteService(cloudService);
  }

  @Override
  public void deleteAllServices() {
    List<CloudService> cloudServices = getServices();
    for (CloudService cloudService : cloudServices) {
      doDeleteService(cloudService);
    }
  }

  @Override
  public List<CloudServiceOffering> getServiceOfferings() {
    String urlPath = "/v2/services?inline-relations-depth=1";
    List<Map<String, Object>> resourceList = getAllResources(urlPath, null);
    List<CloudServiceOffering> serviceOfferings = new ArrayList<CloudServiceOffering>();
    for (Map<String, Object> resource : resourceList) {
      CloudServiceOffering serviceOffering = resourceMapper.mapResource(resource, CloudServiceOffering.class);
      serviceOfferings.add(serviceOffering);
    }
    return serviceOfferings;
  }

  @Override
  public List<CloudServiceBroker> getServiceBrokers() {
    String urlPath = "/v2/service_brokers?inline-relations-depth=1";
    List<Map<String, Object>> resourceList = getAllResources(urlPath, null);
    List<CloudServiceBroker> serviceBrokers = new ArrayList<CloudServiceBroker>();
    for (Map<String, Object> resource : resourceList) {
      CloudServiceBroker broker = resourceMapper.mapResource(resource, CloudServiceBroker.class);
      serviceBrokers.add(broker);
    }
    return serviceBrokers;
  }

  @Override
  public CloudServiceBroker getServiceBroker(String name) {
    String urlPath = "/v2/service_brokers?q={q}";
    Map<String, Object> urlVars = new HashMap<>();
    urlVars.put("q", "name:" + name);
    List<Map<String, Object>> resourceList = getAllResources(urlPath, urlVars);
    CloudServiceBroker serviceBroker = null;
    if (resourceList.size() > 0) {
      final Map<String, Object> resource = resourceList.get(0);
      serviceBroker = resourceMapper.mapResource(resource, CloudServiceBroker.class);
    }
    return serviceBroker;
  }

  @Override
  public void createServiceBroker(CloudServiceBroker serviceBroker) {
    Assert.notNull(serviceBroker, "Service Broker must not be null");
    Assert.notNull(serviceBroker.getName(), "Service Broker name must not be null");
    Assert.notNull(serviceBroker.getUrl(), "Service Broker URL must not be null");
    Assert.notNull(serviceBroker.getUsername(), "Service Broker username must not be null");
    Assert.notNull(serviceBroker.getPassword(), "Service Broker password must not be null");

    HashMap<String, Object> serviceRequest = new HashMap<>();
    serviceRequest.put("name", serviceBroker.getName());
    serviceRequest.put("broker_url", serviceBroker.getUrl());
    serviceRequest.put("auth_username", serviceBroker.getUsername());
    serviceRequest.put("auth_password", serviceBroker.getPassword());
    getRestTemplate().postForObject(getUrl("/v2/service_brokers"), serviceRequest, String.class);
  }

  @Override
  public void updateServiceBroker(CloudServiceBroker serviceBroker) {
    Assert.notNull(serviceBroker, "Service Broker must not be null");
    Assert.notNull(serviceBroker.getName(), "Service Broker name must not be null");
    Assert.notNull(serviceBroker.getUrl(), "Service Broker URL must not be null");
    Assert.notNull(serviceBroker.getUsername(), "Service Broker username must not be null");
    Assert.notNull(serviceBroker.getPassword(), "Service Broker password must not be null");

    CloudServiceBroker existingBroker = getServiceBroker(serviceBroker.getName());
    Assert.notNull(existingBroker, "Cannot update broker if it does not first exist");

    HashMap<String, Object> serviceRequest = new HashMap<>();
    serviceRequest.put("name", serviceBroker.getName());
    serviceRequest.put("broker_url", serviceBroker.getUrl());
    serviceRequest.put("auth_username", serviceBroker.getUsername());
    serviceRequest.put("auth_password", serviceBroker.getPassword());
    getRestTemplate().put(getUrl("/v2/service_brokers/{guid}"), serviceRequest, existingBroker.getMeta().getGuid());
  }

  @Override
  public void deleteServiceBroker(String name) {
    CloudServiceBroker existingBroker = getServiceBroker(name);
    Assert.notNull(existingBroker, "Cannot update broker if it does not first exist");

    getRestTemplate().delete(getUrl("/v2/service_brokers/{guid}"), existingBroker.getMeta().getGuid());
  }

  @Override
  public void updateServicePlanVisibilityForBroker(String name, boolean visibility) {
    CloudServiceBroker broker = getServiceBroker(name);

    String urlPath = "/v2/services?q={q}";
    Map<String, Object> urlVars = new HashMap<>();
    urlVars.put("q", "service_broker_guid:" + broker.getMeta().getGuid());
    List<Map<String, Object>> serviceResourceList = getAllResources(urlPath, urlVars);

    for (Map<String, Object> serviceResource : serviceResourceList) {
      Map<String, Object> metadata = (Map<String, Object>) serviceResource.get("metadata");
      String serviceGuid = (String) metadata.get("guid");

      urlPath = "/v2/service_plans?q={q}";
      urlVars = new HashMap<>();
      urlVars.put("q", "service_guid:" + serviceGuid);
      List<Map<String, Object>> planResourceList = getAllResources(urlPath, urlVars);
      for (Map<String, Object> planResource : planResourceList) {
        metadata = (Map<String, Object>) planResource.get("metadata");
        String planGuid = (String) metadata.get("guid");

        HashMap<String, Object> planUpdateRequest = new HashMap<>();
        planUpdateRequest.put("public", visibility);
        getRestTemplate().put(getUrl("/v2/service_plans/{guid}"), planUpdateRequest, planGuid);
      }
    }
  }

  @Override
  public List<CloudApplication> getApplications() {
    Map<String, Object> urlVars = new HashMap<String, Object>();
    String urlPath = "/v2";
    if (sessionSpace != null) {
      urlVars.put("space", sessionSpace.getMeta().getGuid());
      urlPath = urlPath + "/spaces/{space}";
    }
    urlPath = urlPath + "/apps?inline-relations-depth=1";
    List<Map<String, Object>> resourceList = getAllResources(urlPath, urlVars);
    List<CloudApplication> apps = new ArrayList<CloudApplication>();
    for (Map<String, Object> resource : resourceList) {
      processApplicationResource(resource, true);
      apps.add(mapCloudApplication(resource));
    }
    return apps;
  }

  @Override
  public CloudApplication getApplication(String appName) {
    Map<String, Object> resource = findApplicationResource(appName, true);
    if (resource == null) {
      throw new CloudFoundryException(HttpStatus.NOT_FOUND, "Not Found", "Application not found");
    }
    return mapCloudApplication(resource);
  }

  @Override
  public CloudApplication getApplication(UUID appGuid) {
    Map<String, Object> resource = findApplicationResource(appGuid, true);
    if (resource == null) {
      throw new CloudFoundryException(HttpStatus.NOT_FOUND, "Not Found", "Application not found");
    }
    return mapCloudApplication(resource);
  }

  @SuppressWarnings("unchecked")
  private CloudApplication mapCloudApplication(Map<String, Object> resource) {
    UUID appId = resourceMapper.getGuidOfResource(resource);
    CloudApplication cloudApp = null;
    if (resource != null) {
      int running = getRunningInstances(appId,
          CloudApplication.AppState.valueOf(
              CloudEntityResourceMapper.getEntityAttribute(resource, "state", String.class)));
      ((Map<String, Object>)resource.get("entity")).put("running_instances", running);
      cloudApp = resourceMapper.mapResource(resource, CloudApplication.class);
      cloudApp.setUris(findApplicationUris(cloudApp.getMeta().getGuid()));
    }
    return cloudApp;
  }

  private int getRunningInstances(UUID appId, CloudApplication.AppState appState) {
    int running = 0;
    ApplicationStats appStats = doGetApplicationStats(appId, appState);
    if (appStats != null && appStats.getRecords() != null) {
      for (InstanceStats inst : appStats.getRecords()) {
        if (InstanceState.RUNNING == inst.getState()){
          running++;
        }
      }
    }
    return running;
  }

  @Override
  public ApplicationStats getApplicationStats(String appName) {
    CloudApplication app = getApplication(appName);
    return doGetApplicationStats(app.getMeta().getGuid(), app.getState());
  }

  @SuppressWarnings("unchecked")
  private ApplicationStats doGetApplicationStats(UUID appId, CloudApplication.AppState appState) {
    List<InstanceStats> instanceList = new ArrayList<InstanceStats>();
    if (appState.equals(CloudApplication.AppState.STARTED)) {
      Map<String, Object> respMap = getInstanceInfoForApp(appId, "stats");
      for (String instanceId : respMap.keySet()) {
        InstanceStats instanceStats =
            new InstanceStats(instanceId, (Map<String, Object>) respMap.get(instanceId));
        instanceList.add(instanceStats);
      }
    }
    return new ApplicationStats(instanceList);
  }

  private Map<String, Object> getInstanceInfoForApp(UUID appId, String path) {
    String url = getUrl("/v2/apps/{guid}/" + path);
    Map<String, Object> urlVars = new HashMap<String, Object>();
    urlVars.put("guid", appId);
    String resp = getRestTemplate().getForObject(url, String.class, urlVars);
    return JsonUtil.convertJsonToMap(resp);
  }

  @Override
  public void createApplication(String appName, Staging staging, Integer memory, List<String> uris,
                                List<String> serviceNames) {
    createApplication(appName, staging, null, memory, uris, serviceNames);
  }

  @Override
  public void createApplication(String appName, Staging staging, Integer disk, Integer memory,
                                List<String> uris, List<String> serviceNames) {
    HashMap<String, Object> appRequest = new HashMap<String, Object>();
    appRequest.put("space_guid", sessionSpace.getMeta().getGuid());
    appRequest.put("name", appName);
    appRequest.put("memory", memory);
    if (disk != null) {
      appRequest.put("disk_quota", disk);
    }
    appRequest.put("instances", 1);
    addStagingToRequest(staging, appRequest);
    appRequest.put("state", CloudApplication.AppState.STOPPED);

    String appResp = getRestTemplate().postForObject(getUrl("/v2/apps"), appRequest, String.class);
    Map<String, Object> appEntity = JsonUtil.convertJsonToMap(appResp);
    UUID newAppGuid = CloudEntityResourceMapper.getMeta(appEntity).getGuid();

    if (serviceNames != null && serviceNames.size() > 0) {
      updateApplicationServices(appName, serviceNames);
    }

    if (uris != null && uris.size() > 0) {
      addUris(uris, newAppGuid);
    }
  }

  private void addStagingToRequest(Staging staging, HashMap<String, Object> appRequest) {
    if (staging.getBuildpackUrl() != null) {
      appRequest.put("buildpack", staging.getBuildpackUrl());
    }
    if (staging.getCommand() != null) {
      appRequest.put("command", staging.getCommand());
    }
    if (staging.getStack() != null) {
      appRequest.put("stack_guid", getStack(staging.getStack()).getMeta().getGuid());
    }
    if (staging.getHealthCheckTimeout() != null) {
      appRequest.put("health_check_timeout", staging.getHealthCheckTimeout());
    }
  }

  @SuppressWarnings("unchecked")
  private List<Map<String, Object>> getAllResources(String urlPath, Map<String, Object> urlVars) {
    List<Map<String, Object>> allResources = new ArrayList<Map<String, Object>>();
    String resp;
    if (urlVars != null) {
      resp = getRestTemplate().getForObject(getUrl(urlPath), String.class, urlVars);
    } else {
      resp = getRestTemplate().getForObject(getUrl(urlPath), String.class);
    }
    Map<String, Object> respMap = JsonUtil.convertJsonToMap(resp);
    List<Map<String, Object>> newResources = (List<Map<String, Object>>) respMap.get("resources");
    if (newResources != null && newResources.size() > 0) {
      allResources.addAll(newResources);
    }
    String nextUrl = (String) respMap.get("next_url");
    while (nextUrl != null && nextUrl.length() > 0) {
      nextUrl = addPageOfResources(nextUrl, allResources);
    }
    return allResources;
  }

  @SuppressWarnings("unchecked")
  private String addPageOfResources(String nextUrl, List<Map<String, Object>> allResources) {
    String resp = getRestTemplate().getForObject(getUrl(nextUrl), String.class);
    Map<String, Object> respMap = JsonUtil.convertJsonToMap(resp);
    List<Map<String, Object>> newResources = (List<Map<String, Object>>) respMap.get("resources");
    if (newResources != null && newResources.size() > 0) {
      allResources.addAll(newResources);
    }
    return (String) respMap.get("next_url");
  }

  private void addUris(List<String> uris, UUID appGuid) {
    Map<String, UUID> domains = getDomainGuids();
    for (String uri : uris) {
      Map<String, String> uriInfo = new HashMap<String, String>(2);
      extractUriInfo(domains, uri, uriInfo);
      UUID domainGuid = domains.get(uriInfo.get("domainName"));
      bindRoute(uriInfo.get("host"), domainGuid, appGuid);
    }
  }

  private void removeUris(List<String> uris, UUID appGuid) {
    Map<String, UUID> domains = getDomainGuids();
    for (String uri : uris) {
      Map<String, String> uriInfo = new HashMap<String, String>(2);
      extractUriInfo(domains, uri, uriInfo);
      UUID domainGuid = domains.get(uriInfo.get("domainName"));
      unbindRoute(uriInfo.get("host"), domainGuid, appGuid);
    }
  }

  protected void extractUriInfo(Map<String, UUID> domains, String uri, Map<String, String> uriInfo) {
    URI newUri = URI.create(uri);
    String authority = newUri.getScheme() != null ? newUri.getAuthority(): newUri.getPath();
    for (String domain : domains.keySet()) {
      if (authority != null && authority.endsWith(domain)) {
        String previousDomain = uriInfo.get("domainName");
        if (previousDomain == null || domain.length() > previousDomain.length()) {
          //Favor most specific subdomains
          uriInfo.put("domainName", domain);
          if (domain.length() < authority.length()) {
            uriInfo.put("host", authority.substring(0, authority.indexOf(domain) - 1));
          } else if (domain.length() == authority.length()) {
            uriInfo.put("host", "");
          }
        }
      }
    }
    if (uriInfo.get("domainName") == null) {
      throw new IllegalArgumentException("Domain not found for URI " + uri);
    }
    if (uriInfo.get("host") == null) {
      throw new IllegalArgumentException("Invalid URI " + uri +
          " -- host not specified for domain " + uriInfo.get("domainName"));
    }
  }

  private Map<String, UUID> getDomainGuids() {
    Map<String, Object> urlVars = new HashMap<String, Object>();
    String urlPath = "/v2";
    if (sessionSpace != null) {
      urlVars.put("space", sessionSpace.getMeta().getGuid());
      urlPath = urlPath + "/spaces/{space}";
    }
    String domainPath = urlPath + "/domains?inline-relations-depth=1";
    List<Map<String, Object>> resourceList = getAllResources(domainPath, urlVars);
    Map<String, UUID> domains = new HashMap<String, UUID>(resourceList.size());
    for (Map<String, Object> d : resourceList) {
      domains.put(
          CloudEntityResourceMapper.getEntityAttribute(d, "name", String.class),
          CloudEntityResourceMapper.getMeta(d).getGuid());
    }
    return domains;
  }

  private UUID getDomainGuid(String domainName, boolean required) {
    Map<String, Object> urlVars = new HashMap<String, Object>();
    String urlPath = "/v2/domains?inline-relations-depth=1&q=name:{name}";
    urlVars.put("name", domainName);
    List<Map<String, Object>> resourceList = getAllResources(urlPath, urlVars);
    UUID domainGuid = null;
    if (resourceList.size() > 0) {
      Map<String, Object> resource = resourceList.get(0);
      domainGuid = resourceMapper.getGuidOfResource(resource);
    }
    if (domainGuid == null && required) {
      throw new IllegalArgumentException("Domain '" + domainName + "' not found.");
    }
    return domainGuid;
  }

  private void bindRoute(String host, UUID domainGuid, UUID appGuid) {
    UUID routeGuid = getRouteGuid(host, domainGuid);
    if (routeGuid == null) {
      routeGuid = doAddRoute(host, domainGuid);
    }
    String bindPath = "/v2/apps/{app}/routes/{route}";
    Map<String, Object> bindVars = new HashMap<String, Object>();
    bindVars.put("app", appGuid);
    bindVars.put("route", routeGuid);
    HashMap<String, Object> bindRequest = new HashMap<String, Object>();
    getRestTemplate().put(getUrl(bindPath), bindRequest, bindVars);
  }

  private void unbindRoute(String host, UUID domainGuid, UUID appGuid) {
    UUID routeGuid = getRouteGuid(host, domainGuid);
    if (routeGuid != null) {
      String bindPath = "/v2/apps/{app}/routes/{route}";
      Map<String, Object> bindVars = new HashMap<String, Object>();
      bindVars.put("app", appGuid);
      bindVars.put("route", routeGuid);
      getRestTemplate().delete(getUrl(bindPath), bindVars);
    }
  }

  private UUID getRouteGuid(String host, UUID domainGuid) {
    Map<String, Object> urlVars = new HashMap<String, Object>();
    String urlPath = "/v2";
    urlPath = urlPath + "/routes?inline-relations-depth=0&q=host:{host}";
    urlVars.put("host", host);
    List<Map<String, Object>> allRoutes = getAllResources(urlPath, urlVars);
    UUID routeGuid = null;
    for (Map<String, Object> route : allRoutes) {
      UUID routeSpace = CloudEntityResourceMapper.getEntityAttribute(route, "space_guid", UUID.class);
      UUID routeDomain = CloudEntityResourceMapper.getEntityAttribute(route, "domain_guid", UUID.class);
      if (sessionSpace.getMeta().getGuid().equals(routeSpace) &&
          domainGuid.equals(routeDomain)) {
        routeGuid = CloudEntityResourceMapper.getMeta(route).getGuid();
      }
    }
    return routeGuid;
  }

  private UUID doAddRoute(String host, UUID domainGuid) {
    assertSpaceProvided("add route");

    HashMap<String, Object> routeRequest = new HashMap<String, Object>();
    routeRequest.put("host", host);
    routeRequest.put("domain_guid", domainGuid);
    routeRequest.put("space_guid", sessionSpace.getMeta().getGuid());
    String routeResp = getRestTemplate().postForObject(getUrl("/v2/routes"), routeRequest, String.class);
    Map<String, Object> routeEntity = JsonUtil.convertJsonToMap(routeResp);
    return CloudEntityResourceMapper.getMeta(routeEntity).getGuid();
  }

  @Override
  public void uploadApplication(String appName, File file, UploadStatusCallback callback) throws IOException {
    Assert.notNull(file, "File must not be null");
    if (file.isDirectory()) {
      ApplicationArchive archive = new DirectoryApplicationArchive(file);
      uploadApplication(appName, archive, callback);
    } else {
      ZipFile zipFile = new ZipFile(file);
      try {
        ApplicationArchive archive = new ZipApplicationArchive(zipFile);
        uploadApplication(appName, archive, callback);
      } finally {
        zipFile.close();
      }
    }
  }

  @Override
  public void uploadApplication(String appName, ApplicationArchive archive, UploadStatusCallback callback)
      throws IOException {
    Assert.notNull(appName, "AppName must not be null");
    Assert.notNull(archive, "Archive must not be null");
    UUID appId = getAppId(appName);

    if (callback == null) {
      callback = UploadStatusCallback.NONE;
    }
    CloudResources knownRemoteResources = getKnownRemoteResources(archive);
    callback.onCheckResources();
    callback.onMatchedFileNames(knownRemoteResources.getFilenames());
    UploadApplicationPayload payload = new UploadApplicationPayload(archive, knownRemoteResources);
    callback.onProcessMatchedResources(payload.getTotalUncompressedSize());
    HttpEntity<?> entity = generatePartialResourceRequest(payload, knownRemoteResources);
    ResponseEntity<Map<String,Map<String,String>>> responseEntity =
                getRestTemplate().exchange(getUrl("/v2/apps/{guid}/bits?async=true"), HttpMethod.PUT, entity,
                                            new ParameterizedTypeReference<Map<String, Map<String,String>>>() {}, appId);
    processAsyncJob(responseEntity, callback);
  }

  private void processAsyncJob(ResponseEntity<Map<String,Map<String,String>>> jobCreationEntity, UploadStatusCallback callback) {
    Map<String, String> jobEntity = jobCreationEntity.getBody().get("entity");
    String jobStatus;
    do {
      jobStatus = jobEntity.get("status");
      boolean unsubscribe = callback.onProgress(jobStatus);
      if (unsubscribe) {
        return;
      } else {
        try {
          Thread.sleep(JOB_POLLING_PERIOD);
        } catch (InterruptedException ex) {
          return;
        }
      }
      String jobId = jobEntity.get("guid");
      ResponseEntity<Map<String, Map<String, String>>> jobProgressEntity =
          getRestTemplate().exchange(getUrl("/v2/jobs/{guid}"), HttpMethod.GET, HttpEntity.EMPTY,
              new ParameterizedTypeReference<Map<String, Map<String, String>>>() {
              }, jobId);
      jobEntity = jobProgressEntity.getBody().get("entity");
    } while (!jobStatus.equals("finished"));
  }

  private CloudResources getKnownRemoteResources(ApplicationArchive archive) throws IOException {
    CloudResources archiveResources = new CloudResources(archive);
    String json = JsonUtil.convertToJson(archiveResources);
    HttpHeaders headers = new HttpHeaders();
    headers.setContentType(JsonUtil.JSON_MEDIA_TYPE);
    HttpEntity<String> requestEntity = new HttpEntity<String>(json, headers);
    ResponseEntity<String> responseEntity =
      getRestTemplate().exchange(getUrl("/v2/resource_match"), HttpMethod.PUT, requestEntity, String.class);
    List<CloudResource> cloudResources = JsonUtil.convertJsonToCloudResourceList(responseEntity.getBody());
    return new CloudResources(cloudResources);
  }

  private HttpEntity<MultiValueMap<String, ?>> generatePartialResourceRequest(UploadApplicationPayload application,
      CloudResources knownRemoteResources) throws IOException {
    MultiValueMap<String, Object> body = new LinkedMultiValueMap<String, Object>(2);
    body.add("application", application);
    ObjectMapper mapper = new ObjectMapper();
    String knownRemoteResourcesPayload = mapper.writeValueAsString(knownRemoteResources);
    body.add("resources", knownRemoteResourcesPayload);
    HttpHeaders headers = new HttpHeaders();
    headers.setContentType(MediaType.MULTIPART_FORM_DATA);
    return new HttpEntity<MultiValueMap<String, ?>>(body, headers);
  }

  @Override
  public StartingInfo startApplication(String appName) {
    CloudApplication app = getApplication(appName);
    if (app.getState() != CloudApplication.AppState.STARTED) {
      HashMap<String, Object> appRequest = new HashMap<String, Object>();
      appRequest.put("state", CloudApplication.AppState.STARTED);

      HttpEntity<Object> requestEntity = new HttpEntity<Object>(
          appRequest);
      ResponseEntity<String> entity = getRestTemplate().exchange(
          getUrl("/v2/apps/{guid}?stage_async=true"), HttpMethod.PUT, requestEntity,
          String.class, app.getMeta().getGuid());

      HttpHeaders headers = entity.getHeaders();

      // Return a starting info, even with a null staging log value, as a non-null starting info
      // indicates that the response entity did have headers. The API contract is to return starting info
      // if there are headers in the response, null otherwise.
      if (headers != null && !headers.isEmpty()) {
        String stagingFile = headers.getFirst("x-app-staging-log");

        if (stagingFile != null) {
          try {
            stagingFile = URLDecoder.decode(stagingFile, "UTF-8");
          } catch (UnsupportedEncodingException e) {
            logger.error("unexpected inability to UTF-8 decode", e);
          }
        }
        // Return the starting info even if decoding failed or staging file is null
        return new StartingInfo(stagingFile);
      }
    }
    return null;
  }

  @Override
  public void debugApplication(String appName, CloudApplication.DebugMode mode) {
    throw new UnsupportedOperationException("Feature is not yet implemented.");
  }

  @Override
  public void stopApplication(String appName) {
    CloudApplication app = getApplication(appName);
    if (app.getState() != CloudApplication.AppState.STOPPED) {
      HashMap<String, Object> appRequest = new HashMap<String, Object>();
      appRequest.put("state", CloudApplication.AppState.STOPPED);
      getRestTemplate().put(getUrl("/v2/apps/{guid}"), appRequest, app.getMeta().getGuid());
    }
  }

  @Override
  public StartingInfo restartApplication(String appName) {
    stopApplication(appName);
    return startApplication(appName);
  }

  @Override
  public void deleteApplication(String appName) {
    UUID appId = getAppId(appName);
    doDeleteApplication(appId);
  }

  @Override
  public void deleteAllApplications() {
    List<CloudApplication> cloudApps = getApplications();
    for (CloudApplication cloudApp : cloudApps) {
      deleteApplication(cloudApp.getName());
    }
  }

  @Override
  public void updateApplicationDiskQuota(String appName, int disk) {
    UUID appId = getAppId(appName);
    HashMap<String, Object> appRequest = new HashMap<String, Object>();
    appRequest.put("disk_quota", disk);
    getRestTemplate().put(getUrl("/v2/apps/{guid}"), appRequest, appId);
  }

  @Override
  public void updateApplicationMemory(String appName, int memory) {
    UUID appId = getAppId(appName);
    HashMap<String, Object> appRequest = new HashMap<String, Object>();
    appRequest.put("memory", memory);
    getRestTemplate().put(getUrl("/v2/apps/{guid}"), appRequest, appId);
  }

  @Override
  public void updateApplicationInstances(String appName, int instances) {
    UUID appId = getAppId(appName);
    HashMap<String, Object> appRequest = new HashMap<String, Object>();
    appRequest.put("instances", instances);
    getRestTemplate().put(getUrl("/v2/apps/{guid}"), appRequest, appId);
  }

  @Override
  public void updateApplicationServices(String appName, List<String> services) {
    CloudApplication app = getApplication(appName);
    List<UUID> addServices = new ArrayList<UUID>();
    List<UUID> deleteServices = new ArrayList<UUID>();
    // services to add
    for (String serviceName : services) {
      if (!app.getServices().contains(serviceName)) {
        CloudService cloudService = getService(serviceName);
        if (cloudService != null) {
          addServices.add(cloudService.getMeta().getGuid());
        }
        else {
          throw new CloudFoundryException(HttpStatus.NOT_FOUND, "Service with name " + serviceName +
              " not found in current space " + sessionSpace.getName());
        }
      }
    }
    // services to delete
    for (String serviceName : app.getServices()) {
      if (!services.contains(serviceName)) {
        CloudService cloudService = getService(serviceName);
        if (cloudService != null) {
          deleteServices.add(cloudService.getMeta().getGuid());
        }
      }
    }
    for (UUID serviceId : addServices) {
      doBindService(app.getMeta().getGuid(), serviceId);
    }
    for (UUID serviceId : deleteServices) {
      doUnbindService(app.getMeta().getGuid(), serviceId);
    }
  }

  public List<CloudQuota> getQuotas() {
    String urlPath = "/v2/quota_definitions";
    List<Map<String, Object>> resourceList = getAllResources(urlPath, null);
    List<CloudQuota> quotas = new ArrayList<CloudQuota>();
    for (Map<String, Object> resource : resourceList) {
      quotas.add(resourceMapper.mapResource(resource, CloudQuota.class));
    }
    return quotas;
  }

  /**
   * Create quota from a CloudQuota instance (Quota Plan)
   *
   * @param quota
   */
  public void createQuota(CloudQuota quota) {
    String setPath = "/v2/quota_definitions";
    HashMap<String, Object> setRequest = new HashMap<String, Object>();
    setRequest.put("name", quota.getName());
    setRequest.put("memory_limit", quota.getMemoryLimit());
    setRequest.put("total_routes", quota.getTotalRoutes());
    setRequest.put("total_services", quota.getTotalServices());
    setRequest.put("non_basic_services_allowed", quota.isNonBasicServicesAllowed());
    getRestTemplate().postForObject(getUrl(setPath), setRequest, String.class);
  }

  public void updateQuota(CloudQuota quota, String name) {
    CloudQuota oldQuota = this.getQuotaByName(name, true);

    String setPath = "/v2/quota_definitions/{quotaGuid}";

    Map<String, Object> setVars = new HashMap<String, Object>();
    setVars.put("quotaGuid", oldQuota.getMeta().getGuid());

    HashMap<String, Object> setRequest = new HashMap<String, Object>();
    setRequest.put("name", quota.getName());
    setRequest.put("memory_limit", quota.getMemoryLimit());
    setRequest.put("total_routes", quota.getTotalRoutes());
    setRequest.put("total_services", quota.getTotalServices());
    setRequest.put("non_basic_services_allowed", quota.isNonBasicServicesAllowed());

    getRestTemplate().put(getUrl(setPath), setRequest, setVars);
  }

  public void deleteQuota(String quotaName) {
    CloudQuota quota = this.getQuotaByName(quotaName, true);
    String setPath = "/v2/quota_definitions/{quotaGuid}";
    Map<String, Object> setVars = new HashMap<String, Object>();
    setVars.put("quotaGuid", quota.getMeta().getGuid());
    getRestTemplate().delete(getUrl(setPath), setVars);
  }

  /**
   * Set quota to organization
   *
   * @param orgName
   * @param quotaName
   */
  public void setQuotaToOrg(String orgName, String quotaName) {
    CloudQuota quota = this.getQuotaByName(quotaName, true);
    CloudOrganization org = this.getOrgByName(orgName, true);

    doSetQuotaToOrg(org.getMeta().getGuid(), quota.getMeta().getGuid());
  }

  /**
   * Get organization by given name.
   *
   * @param orgName
   * @param required
   * @return CloudOrganization instance
   */
  public CloudOrganization getOrgByName(String orgName, boolean required) {
    Map<String, Object> urlVars = new HashMap<String, Object>();
    String urlPath = "/v2/organizations?inline-relations-depth=1&q=name:{name}";
    urlVars.put("name", orgName);
    CloudOrganization org = null;
    List<Map<String, Object>> resourceList = getAllResources(urlPath,
        urlVars);
    if (resourceList.size() > 0) {
      Map<String, Object> resource = resourceList.get(0);
      org = resourceMapper.mapResource(resource, CloudOrganization.class);
    }

    if (org == null && required) {
      throw new IllegalArgumentException("Organization '" + orgName
          + "' not found.");
    }

    return org;
  }

  /**
   * Get quota by given name.
   *
   * @param quotaName
   * @param required
   * @return CloudQuota instance
   */
  public CloudQuota getQuotaByName(String quotaName, boolean required) {
    Map<String, Object> urlVars = new HashMap<String, Object>();
    String urlPath = "/v2/quota_definitions?q=name:{name}";
    urlVars.put("name", quotaName);
    CloudQuota quota = null;
    List<Map<String, Object>> resourceList = getAllResources(urlPath, urlVars);
    if (resourceList.size() > 0) {
      Map<String, Object> resource = resourceList.get(0);
      quota = resourceMapper.mapResource(resource, CloudQuota.class);
    }

    if (quota == null && required) {
      throw new IllegalArgumentException("Quota '" + quotaName
          + "' not found.");
    }

    return quota;
  }

  private void doSetQuotaToOrg(UUID orgGuid, UUID quotaGuid) {
    String setPath = "/v2/organizations/{org}";
    Map<String, Object> setVars = new HashMap<String, Object>();
    setVars.put("org", orgGuid);
    HashMap<String, Object> setRequest = new HashMap<String, Object>();
    setRequest.put("quota_definition_guid", quotaGuid);

    getRestTemplate().put(getUrl(setPath), setRequest, setVars);
  }

  private void doBindService(UUID appId, UUID serviceId) {
    HashMap<String, Object> serviceRequest = new HashMap<String, Object>();
    serviceRequest.put("service_instance_guid", serviceId);
    serviceRequest.put("app_guid", appId);
    getRestTemplate().postForObject(getUrl("/v2/service_bindings"), serviceRequest, String.class);
  }

  private void doUnbindService(UUID appId, UUID serviceId) {
    UUID serviceBindingId = getServiceBindingId(appId, serviceId);
    getRestTemplate().delete(getUrl("/v2/service_bindings/{guid}"), serviceBindingId);
  }

  @Override
  public void updateApplicationStaging(String appName, Staging staging) {
    UUID appId = getAppId(appName);
    HashMap<String, Object> appRequest = new HashMap<String, Object>();
    addStagingToRequest(staging, appRequest);
    getRestTemplate().put(getUrl("/v2/apps/{guid}"), appRequest, appId);
  }

  @Override
  public void updateApplicationUris(String appName, List<String> uris) {
    CloudApplication app = getApplication(appName);
    List<String> newUris = new ArrayList<String>(uris);
    newUris.removeAll(app.getUris());
    List<String> removeUris = app.getUris();
    removeUris.removeAll(uris);
    removeUris(removeUris, app.getMeta().getGuid());
    addUris(newUris, app.getMeta().getGuid());
  }

  @Override
  public void updateApplicationEnv(String appName, Map<String, String> env) {
    UUID appId = getAppId(appName);
    HashMap<String, Object> appRequest = new HashMap<String, Object>();
    appRequest.put("environment_json", env);
    getRestTemplate().put(getUrl("/v2/apps/{guid}"), appRequest, appId);
  }

  @Override
  public void updateApplicationEnv(String appName, List<String> env) {
    Map<String, String> envHash = new HashMap<String, String>();
    for (String s : env) {
      if (!s.contains("=")) {
        throw new IllegalArgumentException("Environment setting without '=' is invalid: " + s);
      }
      String key = s.substring(0, s.indexOf('=')).trim();
      String value = s.substring(s.indexOf('=') + 1).trim();
      envHash.put(key, value);
    }
    updateApplicationEnv(appName, envHash);
  }

  @Override
  public void bindService(String appName, String serviceName) {
    CloudService cloudService = getService(serviceName);
    UUID appId = getAppId(appName);
    doBindService(appId, cloudService.getMeta().getGuid());
  }

  @Override
  public void unbindService(String appName, String serviceName) {
    CloudService cloudService = getService(serviceName);
    UUID appId = getAppId(appName);
    doUnbindService(appId, cloudService.getMeta().getGuid());
  }

  @Override
  public InstancesInfo getApplicationInstances(String appName) {
    CloudApplication app = getApplication(appName);
    return getApplicationInstances(app);
  }

  @Override
  public InstancesInfo getApplicationInstances(CloudApplication app) {
    if (app.getState().equals(CloudApplication.AppState.STARTED)) {
      return doGetApplicationInstances(app.getMeta().getGuid());
    }
    return null;
  }

  @SuppressWarnings("unchecked")
  private InstancesInfo doGetApplicationInstances(UUID appId) {
    try {
      List<Map<String, Object>> instanceList = new ArrayList<Map<String, Object>>();
      Map<String, Object> respMap = getInstanceInfoForApp(appId, "instances");
      List<String> keys = new ArrayList<String>(respMap.keySet());
      Collections.sort(keys);
      for (String instanceId : keys) {
        Integer index;
        try {
          index = Integer.valueOf(instanceId);
        } catch (NumberFormatException e) {
          index = -1;
        }
        Map<String, Object> instanceMap = (Map<String, Object>) respMap.get(instanceId);
        instanceMap.put("index", index);
        instanceList.add(instanceMap);
      }
      return new InstancesInfo(instanceList);
    } catch (CloudFoundryException e) {
      if (e.getStatusCode().equals(HttpStatus.BAD_REQUEST)) {
        return null;
      } else {
        throw e;
      }

    }
  }

  @SuppressWarnings("unchecked")
  @Override
  public CrashesInfo getCrashes(String appName) {
    UUID appId = getAppId(appName);
    if (appId == null) {
      throw new IllegalArgumentException("Application '" + appName + "' not found.");
    }
    Map<String, Object> urlVars = new HashMap<String, Object>();
    urlVars.put("guid", appId);
    String resp = getRestTemplate().getForObject(getUrl("/v2/apps/{guid}/crashes"), String.class, urlVars);
    Map<String, Object> respMap = JsonUtil.convertJsonToMap("{ \"crashes\" : " + resp + " }");
    List<Map<String, Object>> attributes = (List<Map<String, Object>>) respMap.get("crashes");
    return new CrashesInfo(attributes);
  }

  @Override
  public void rename(String appName, String newName) {
    UUID appId = getAppId(appName);
    HashMap<String, Object> appRequest = new HashMap<String, Object>();
    appRequest.put("name", newName);
    getRestTemplate().put(getUrl("/v2/apps/{guid}"), appRequest, appId);
  }

  @Override
  public List<CloudStack> getStacks() {
    String urlPath = "/v2/stacks";
    List<Map<String, Object>> resources = getAllResources(urlPath, null);
    List<CloudStack> stacks = new ArrayList<CloudStack>();
    for (Map<String, Object> resource : resources) {
      stacks.add(resourceMapper.mapResource(resource, CloudStack.class));
    }
    return stacks;
  }

  @Override
  public CloudStack getStack(String name) {
    String urlPath = "/v2/stacks?q={q}";
    Map<String, Object> urlVars = new HashMap<String, Object>();
    urlVars.put("q", "name:" + name);
    List<Map<String, Object>> resources = getAllResources(urlPath, urlVars);
    if (resources.size() > 0) {
      Map<String, Object> resource = resources.get(0);
      return resourceMapper.mapResource(resource, CloudStack.class);
    }
    return null;
  }

  @Override
  public List<CloudDomain> getDomainsForOrg() {
    assertSpaceProvided("access organization domains");
    return doGetDomains(sessionSpace.getOrganization());
  }

  @Override
  public List<CloudDomain> getDomains() {
    return doGetDomains((CloudOrganization) null);
  }

  @Override
  public List<CloudDomain> getPrivateDomains() {
    return doGetDomains("/v2/private_domains");
  }

  @Override
  public List<CloudDomain> getSharedDomains() {
    return doGetDomains("/v2/shared_domains");
  }

  @Override
  public CloudDomain getDefaultDomain() {
    List<CloudDomain> sharedDomains = getSharedDomains();
    if (sharedDomains.isEmpty()) {
      return null;
    } else {
      return sharedDomains.get(0);
    }
  }

  @Override
  public void addDomain(String domainName) {
    assertSpaceProvided("add domain");
    UUID domainGuid = getDomainGuid(domainName, false);
    if (domainGuid == null) {
      doCreateDomain(domainName);
    }
  }

  @Override
  public void deleteDomain(String domainName) {
    assertSpaceProvided("delete domain");
    UUID domainGuid = getDomainGuid(domainName, true);
    List<CloudRoute> routes = getRoutes(domainName);
    if (routes.size() > 0) {
      throw new IllegalStateException("Unable to remove domain that is in use --" +
          " it has " + routes.size() + " routes.");
    }
    doDeleteDomain(domainGuid);
  }

  @Override
  public void removeDomain(String domainName) {
    deleteDomain(domainName);
  }

  @Override
  public List<CloudRoute> getRoutes(String domainName) {
    assertSpaceProvided("get routes for domain");
    UUID domainGuid = getDomainGuid(domainName, true);
    return doGetRoutes(domainGuid);
  }

  @Override
  public void addRoute(String host, String domainName) {
    assertSpaceProvided("add route for domain");
    UUID domainGuid = getDomainGuid(domainName, true);
    doAddRoute(host, domainGuid);
  }

  @Override
  public void deleteRoute(String host, String domainName) {
    assertSpaceProvided("delete route for domain");
    UUID domainGuid = getDomainGuid(domainName, true);
    UUID routeGuid = getRouteGuid(host, domainGuid);
    if (routeGuid == null) {
      throw new IllegalArgumentException("Host '" + host + "' not found for domain '" + domainName + "'.");
    }
    doDeleteRoute(routeGuid);
  }

  protected String getFileUrlPath() {
    return "/v2/apps/{appId}/instances/{instance}/files/{filePath}";
  }

  protected Object getFileAppId(String appName) {
    return getAppId(appName);
  }

  private void assertSpaceProvided(String operation) {
    Assert.notNull(sessionSpace, "Unable to " + operation + " without specifying organization and space to use.");
  }

  private void doDeleteRoute(UUID routeGuid) {
    Map<String, Object> urlVars = new HashMap<String, Object>();
    String urlPath = "/v2/routes/{route}";
    urlVars.put("route", routeGuid);
    getRestTemplate().delete(getUrl(urlPath), urlVars);
  }

  /**
   * Delete routes that do not have any application which is assigned to them.
   *
   * @return deleted routes or an empty list if no routes have been found
   */
  @Override
  public List<CloudRoute> deleteOrphanedRoutes() {
    List<CloudRoute> orphanRoutes = new ArrayList<>();
    for (CloudDomain cloudDomain : getDomainsForOrg()) {
      orphanRoutes.addAll(fetchOrphanRoutes(cloudDomain.getName()));
    }

    List<CloudRoute> deletedCloudRoutes = new ArrayList<>();
    for (CloudRoute orphanRoute : orphanRoutes) {
      deleteRoute(orphanRoute.getHost(), orphanRoute.getDomain().getName());
      deletedCloudRoutes.add(orphanRoute);
    }

    return deletedCloudRoutes;
  }

  private List<CloudRoute> fetchOrphanRoutes(String domainName) {
    List<CloudRoute> orphanRoutes = new ArrayList<>();
    for (CloudRoute cloudRoute : getRoutes(domainName)) {
      if (isOrphanRoute(cloudRoute)) {
        orphanRoutes.add(cloudRoute);
      }
    }

    return orphanRoutes;
  }

  private boolean isOrphanRoute(CloudRoute cloudRoute) {
    return cloudRoute.getAppsUsingRoute() == 0;
  }

  private List<CloudDomain> doGetDomains(CloudOrganization org) {
    Map<String, Object> urlVars = new HashMap<String, Object>();
    String urlPath = "/v2";
    if (org != null) {
      urlVars.put("org", org.getMeta().getGuid());
      urlPath = urlPath + "/organizations/{org}";
    }
    urlPath = urlPath + "/domains";
    return doGetDomains(urlPath, urlVars);
  }

  private List<CloudDomain> doGetDomains(String urlPath) {
    return doGetDomains(urlPath, null);
  }

  private List<CloudDomain> doGetDomains(String urlPath, Map<String, Object> urlVars) {
    List<Map<String, Object>> domainResources = getAllResources(urlPath, urlVars);
    List<CloudDomain> domains = new ArrayList<CloudDomain>();
    for (Map<String, Object> resource : domainResources) {
      domains.add(resourceMapper.mapResource(resource, CloudDomain.class));
    }
    return domains;
  }

  private UUID doCreateDomain(String domainName) {
    String urlPath = "/v2/private_domains";
    HashMap<String, Object> domainRequest = new HashMap<String, Object>();
    domainRequest.put("owning_organization_guid", sessionSpace.getOrganization().getMeta().getGuid());
    domainRequest.put("name", domainName);
    domainRequest.put("wildcard", true);
    String resp = getRestTemplate().postForObject(getUrl(urlPath), domainRequest, String.class);
    Map<String, Object> respMap = JsonUtil.convertJsonToMap(resp);
    return resourceMapper.getGuidOfResource(respMap);
  }

  private void doDeleteDomain(UUID domainGuid) {
    Map<String, Object> urlVars = new HashMap<String, Object>();
    String urlPath = "/v2/private_domains/{domain}";
    urlVars.put("domain", domainGuid);
    getRestTemplate().delete(getUrl(urlPath), urlVars);
  }

  private List<CloudRoute> doGetRoutes(UUID domainGuid) {
    Map<String, Object> urlVars = new HashMap<String, Object>();
    String urlPath = "/v2";
//    TODO: NOT implemented ATM:
//    if (sessionSpace != null) {
//      urlVars.put("space", sessionSpace.getMeta().getGuid());
//      urlPath = urlPath + "/spaces/{space}";
//    }
    urlPath = urlPath + "/routes?inline-relations-depth=1";
    List<Map<String, Object>> allRoutes = getAllResources(urlPath, urlVars);
    List<CloudRoute> routes = new ArrayList<CloudRoute>();
    for (Map<String, Object> route : allRoutes) {
//      TODO: move space_guid to path once implemented (see above):
      UUID space = CloudEntityResourceMapper.getEntityAttribute(route, "space_guid", UUID.class);
      UUID domain = CloudEntityResourceMapper.getEntityAttribute(route, "domain_guid", UUID.class);
      if (sessionSpace.getMeta().getGuid().equals(space) && domainGuid.equals(domain)) {
        //routes.add(CloudEntityResourceMapper.getEntityAttribute(route, "host", String.class));
        routes.add(resourceMapper.mapResource(route, CloudRoute.class));
      }
    }
    return routes;
  }

  private void doDeleteService(CloudService cloudService) {
    List<UUID> appIds = getAppsBoundToService(cloudService);
    if (appIds.size() > 0) {
      for (UUID appId : appIds) {
        doUnbindService(appId, cloudService.getMeta().getGuid());
      }
    }
    getRestTemplate().delete(getUrl("/v2/service_instances/{guid}"), cloudService.getMeta().getGuid());
  }

  @SuppressWarnings("unchecked")
  private List<UUID> getAppsBoundToService(CloudService cloudService) {
    List<UUID> appGuids = new ArrayList<UUID>();
    String urlPath = "/v2";
    Map<String, Object> urlVars = new HashMap<String, Object>();
    if (sessionSpace != null) {
      urlVars.put("space", sessionSpace.getMeta().getGuid());
      urlPath = urlPath + "/spaces/{space}";
    }
    urlVars.put("q", "name:" + cloudService.getName());
    urlPath = urlPath + "/service_instances?q={q}";
    List<Map<String, Object>> resourceList = getAllResources(urlPath, urlVars);
    for (Map<String, Object> resource : resourceList) {
      fillInEmbeddedResource(resource, "service_bindings");
      List<Map<String, Object>> bindings =
          CloudEntityResourceMapper.getEntityAttribute(resource, "service_bindings", List.class);
      for (Map<String, Object> binding : bindings) {
        String appId = CloudEntityResourceMapper.getEntityAttribute(binding, "app_guid", String.class);
        if (appId != null) {
          appGuids.add(UUID.fromString(appId));
        }
      }
    }
    return appGuids;
  }

  private void doDeleteApplication(UUID appId) {
    getRestTemplate().delete(getUrl("/v2/apps/{guid}?recursive=true"), appId);
  }

  private List<CloudServiceOffering> getServiceOfferings(String label) {
    Assert.notNull(label, "Service label must not be null");
    List<Map<String, Object>> resourceList = getAllResources("/v2/services?inline-relations-depth=1", null);
    List<CloudServiceOffering> results = new ArrayList<CloudServiceOffering>();
    for (Map<String, Object> resource : resourceList) {
      CloudServiceOffering cloudServiceOffering =
          resourceMapper.mapResource(resource, CloudServiceOffering.class);
      if (cloudServiceOffering.getLabel() != null && label.equals(cloudServiceOffering.getLabel())) {
        results.add(cloudServiceOffering);
      }
    }
    return results;
  }

  @SuppressWarnings("unchecked")
  private UUID getServiceBindingId(UUID appId, UUID serviceId ) {
    Map<String, Object> urlVars = new HashMap<String, Object>();
    urlVars.put("guid", appId);
    List<Map<String, Object>> resourceList = getAllResources("/v2/apps/{guid}/service_bindings", urlVars);
    UUID serviceBindingId = null;
    if (resourceList != null && resourceList.size() > 0) {
      for (Map<String, Object> resource : resourceList) {
        Map<String, Object> bindingMeta = (Map<String, Object>) resource.get("metadata");
        Map<String, Object> bindingEntity = (Map<String, Object>) resource.get("entity");
        String serviceInstanceGuid = (String) bindingEntity.get("service_instance_guid");
        if (serviceInstanceGuid != null && serviceInstanceGuid.equals(serviceId.toString())) {
          String bindingGuid = (String) bindingMeta.get("guid");
          serviceBindingId = UUID.fromString(bindingGuid);
          break;
        }
      }
    }
    return serviceBindingId;
  }

  @SuppressWarnings("unchecked")
  private UUID getAppId(String appName) {
    Map<String, Object> resource = findApplicationResource(appName, false);
    UUID guid = null;
    if (resource != null) {
      Map<String, Object> appMeta = (Map<String, Object>) resource.get("metadata");
      guid = UUID.fromString(String.valueOf(appMeta.get("guid")));
    }
    return guid;
  }

  private StreamingLogToken streamLoggregatorLogs(String appName, ApplicationLogListener listener, boolean recent) {
    ClientEndpointConfig.Configurator configurator = new ClientEndpointConfig.Configurator() {
      public void beforeRequest(Map<String, List<String>> headers) {
        String authorizationHeader = oauthClient.getAuthorizationHeader();
        if (authorizationHeader != null) {
          headers.put(AUTHORIZATION_HEADER_KEY, Arrays.asList(authorizationHeader));
        }
      }
    };

    String endpoint = getInfo().getLoggregatorEndpoint();
    String mode = recent ? "dump" : "tail";
    UUID appId = getAppId(appName);
    return loggregatorClient.connectToLoggregator(endpoint, mode, appId, listener, configurator);
  }

  private class AccumulatingApplicationLogListener implements ApplicationLogListener {
    private List<ApplicationLog> logs = new ArrayList<ApplicationLog>();

    @Override
    public void onMessage(ApplicationLog log) {
      logs.add(log);
    }

    @Override
    public void onError(Throwable exception) {
      synchronized (this) {
        this.notify();
      }
    }

    @Override
    public void onComplete() {
      synchronized (this) {
        this.notify();
      }
    }

    public List<ApplicationLog> getLogs() {
      Collections.sort(logs);
      return logs;
    }
  }

  private Map<String, Object> findApplicationResource(UUID appGuid, boolean fetchServiceInfo) {
    Map<String, Object> urlVars = new HashMap<String, Object>();
    String urlPath = "/v2/apps/{app}?inline-relations-depth=1";
    urlVars.put("app", appGuid);
    String resp = getRestTemplate().getForObject(getUrl(urlPath), String.class, urlVars);

    return processApplicationResource(JsonUtil.convertJsonToMap(resp), fetchServiceInfo);
  }


  private Map<String, Object> findApplicationResource(String appName, boolean fetchServiceInfo) {
    Map<String, Object> urlVars = new HashMap<String, Object>();
    String urlPath = "/v2";
    if (sessionSpace != null) {
      urlVars.put("space", sessionSpace.getMeta().getGuid());
      urlPath = urlPath + "/spaces/{space}";
    }
    urlVars.put("q", "name:" + appName);
    urlPath = urlPath + "/apps?inline-relations-depth=1&q={q}";

    List<Map<String, Object>> allResources = getAllResources(urlPath, urlVars);
    if(!allResources.isEmpty()) {
      return processApplicationResource(allResources.get(0), fetchServiceInfo);
    }
    return null;
  }

  private Map<String, Object> processApplicationResource(Map<String, Object> resource, boolean fetchServiceInfo) {
    if (fetchServiceInfo) {
      fillInEmbeddedResource(resource, "service_bindings", "service_instance");
    }
    fillInEmbeddedResource(resource, "stack");
    return resource;
  }

  private List<String> findApplicationUris(UUID appGuid) {
    String urlPath = "/v2/apps/{app}/routes?inline-relations-depth=1";
    Map<String, Object> urlVars = new HashMap<String, Object>();
    urlVars.put("app", appGuid);
    List<Map<String, Object>> resourceList = getAllResources(urlPath, urlVars);
    List<String> uris =  new ArrayList<String>();
    for (Map<String, Object> resource : resourceList) {
      Map<String, Object> domainResource = CloudEntityResourceMapper.getEmbeddedResource(resource, "domain");
      String host = CloudEntityResourceMapper.getEntityAttribute(resource, "host", String.class);
      String domain = CloudEntityResourceMapper.getEntityAttribute(domainResource, "name", String.class);
      if (host != null && host.length() > 0)
        uris.add(host + "." + domain);
      else
        uris.add(domain);
    }
    return uris;
  }

  @SuppressWarnings("restriction")
  private Map<String, Object> getUserInfo(String user) {
//    String userJson = getRestTemplate().getForObject(getUrl("/v2/users/{guid}"), String.class, user);
//    Map<String, Object> userInfo = (Map<String, Object>) JsonUtil.convertJsonToMap(userJson);
//    return userInfo();
    //TODO: remove this temporary hack once the /v2/users/ uri can be accessed by mere mortals
    String userJson = "{}";
    OAuth2AccessToken accessToken = oauthClient.getToken();
    if (accessToken != null) {
      String tokenString = accessToken.getValue();
      int x = tokenString.indexOf('.');
      int y = tokenString.indexOf('.', x + 1);
      String encodedString = tokenString.substring(x + 1, y);
      try {
        byte[] decodedBytes = new sun.misc.BASE64Decoder().decodeBuffer(encodedString);
        userJson = new String(decodedBytes, 0, decodedBytes.length, "UTF-8");
      } catch (IOException e) {}
    }
    return(JsonUtil.convertJsonToMap(userJson));
  }

  @SuppressWarnings("unchecked")
  private void fillInEmbeddedResource(Map<String, Object> resource, String... resourcePath) {
    if (resourcePath.length == 0) {
      return;
    }
    Map<String, Object> entity = (Map<String, Object>) resource.get("entity");

    String headKey = resourcePath[0];
    String[] tailPath = Arrays.copyOfRange(resourcePath, 1, resourcePath.length);

    if (!entity.containsKey(headKey)) {
      String pathUrl = entity.get(headKey + "_url").toString();
      Object response = getRestTemplate().getForObject(getUrl(pathUrl), Object.class);
      if (response instanceof Map) {
        Map<String, Object> responseMap = (Map<String, Object>) response;
        if (responseMap.containsKey("resources")) {
          response = responseMap.get("resources");
        }
      }
      entity.put(headKey, response);
    }
    Object embeddedResource = entity.get(headKey);

    if (embeddedResource instanceof Map) {
      Map<String, Object> embeddedResourceMap = (Map<String, Object>) embeddedResource;
      //entity = (Map<String, Object>) embeddedResourceMap.get("entity");
      fillInEmbeddedResource(embeddedResourceMap, tailPath);
    } else if (embeddedResource instanceof List) {
      List<Object> embeddedResourcesList = (List<Object>) embeddedResource;
      for (Object r: embeddedResourcesList) {
        fillInEmbeddedResource((Map<String, Object>)r, tailPath);
      }
    } else {
      // no way to proceed
      return;
    }
  }

  @SuppressWarnings("unchecked")
  private boolean hasEmbeddedResource(Map<String, Object> resource, String resourceKey) {
    Map<String, Object> entity = (Map<String, Object>) resource.get("entity");
    return entity.containsKey(resourceKey) || entity.containsKey(resourceKey + "_url");
  }

  private static class ResponseExtractorWrapper implements ResponseExtractor {

    private ClientHttpResponseCallback callback;

    public ResponseExtractorWrapper(ClientHttpResponseCallback callback) {
      this.callback = callback;
    }

    @Override
    public Object extractData(ClientHttpResponse clientHttpResponse) throws IOException {
      callback.onClientHttpResponse(clientHttpResponse);
      return null;
    }

  }
}
TOP

Related Classes of org.cloudfoundry.client.lib.rest.CloudControllerClientImpl

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.