All Downloads are FREE. Search and download functionalities are using the official Maven repository.

cf.client.DefaultCloudController Maven / Gradle / Ivy

/*
 *   Copyright (c) 2013 Intellectual Reserve, Inc.  All rights reserved.
 *
 *   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 cf.client;

import java.io.IOException;
import java.net.URI;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.UUID;

import org.apache.http.HttpResponse;
import org.apache.http.StatusLine;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpDelete;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpPut;
import org.apache.http.client.utils.HttpClientUtils;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import cf.client.model.Application;
import cf.client.model.ApplicationInstance;
import cf.client.model.Info;
import cf.client.model.Organization;
import cf.client.model.PrivateDomain;
import cf.client.model.Route;
import cf.client.model.SecurityGroup;
import cf.client.model.Service;
import cf.client.model.ServiceAuthToken;
import cf.client.model.ServiceBinding;
import cf.client.model.ServiceInstance;
import cf.client.model.ServicePlan;
import cf.client.model.Space;
import cf.client.model.User;

import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;

/**
 * @author Mike Heath 
 */
public class DefaultCloudController implements CloudController {

	private static final Logger LOGGER = LoggerFactory.getLogger(DefaultCloudController.class);

	private static final String APP_INSTANCES = "/instances";
	private static final String V2_APPS = "/v2/apps";
	private static final String V2_PRIVATE_DOMAINS = "/v2/private_domains";
	private static final String V2_ROUTES = "/v2/routes";
	private static final String V2_SERVICES = "/v2/services";
	private static final String V2_SERVICE_AUTH_TOKENS = "/v2/service_auth_tokens";
	private static final String V2_SERVICE_BINDINGS = "/v2/service_bindings";
	private static final String V2_SERVICE_INSTANCES = "/v2/service_instances";
	private static final String V2_SERVICE_PLANS = "/v2/service_plans";
	private static final String V2_SPACES = "/v2/spaces";
	private static final String V2_ORGANIZATIONS = "/v2/organizations";

	private final HttpClient httpClient;
	private final URI target;

	private final ObjectMapper mapper;

	private final Object lock = new Object();

	// Access to the following fields needs to be done holding the #lock monitor
	private Info info;
	private Uaa uaa;

	public DefaultCloudController(HttpClient httpClient, URI target) {
		this.httpClient = httpClient;
		this.target = target;

		mapper = new ObjectMapper();
		mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
	}

	public DefaultCloudController(HttpClient httpClient, String uri) {
		this(httpClient, URI.create(uri));
	}
	
	@Override
	public URI getTarget() {
		return target;
	}

	@Override
	public Info getInfo() {
		synchronized (lock) {
			if (info == null) {
				fetchInfo();
			}
			return info;
		}
	}

	@Override
	public Uaa getUaa() {
		synchronized (lock) {
			if (uaa == null) {
				uaa = new DefaultUaa(httpClient, getInfo().getAuthorizationEndpoint());
			}
			return uaa;
		}
	}

	@Override
	public Map getApplicationInstances(Token token, UUID applicationGuid) {
		final JsonNode jsonNode = fetchResource(token, V2_APPS + "/" + applicationGuid.toString() + APP_INSTANCES);
		final Iterator> fields = jsonNode.fields();
		final Map instances = new HashMap<>();
		while (fields.hasNext()) {
			final Map.Entry field = fields.next();
			try {
				instances.put(field.getKey(), mapper.readValue(field.getValue().traverse(), ApplicationInstance.class));
			} catch (IOException e) {
				throw new RuntimeException(e);
			}
		}
		return instances;
	}
	
	@Override
	public Application getApplication(Token token, UUID applicationGuid) {
		JsonNode jsonNode = fetchResource(token, V2_APPS +"/"+ applicationGuid.toString());
		try {
			return mapper.readValue(jsonNode.get("entity").traverse(), Application.class);
		} catch (IOException e) {
			throw new RuntimeException(e);
		}
	}
	
	@Override
	public Application updateApplication(Token token, UUID applicationGuid, Application application) {
		JsonNode jsonNode = putJsonToUri(token, application, V2_APPS, applicationGuid);
		try {
			return mapper.readValue(jsonNode.get("entity").traverse(), Application.class);
		} catch (IOException e) {
			throw new RuntimeException(e);
		}
	}
	
	@Override
	public UUID createService(Token token, Service service) {
		try {
			final String requestString = mapper.writeValueAsString(service);
			final HttpPost post = new HttpPost(target.resolve(V2_SERVICES));
			post.addHeader(token.toAuthorizationHeader());
			post.setEntity(new StringEntity(requestString, ContentType.APPLICATION_JSON));
			final HttpResponse response = httpClient.execute(post);
			try {
				validateResponse(response, 201);
				final JsonNode json = mapper.readTree(response.getEntity().getContent());
				return UUID.fromString(json.get("metadata").get("guid").asText());
			} finally {
				HttpClientUtils.closeQuietly(response);
			}
		} catch (IOException e) {
			throw new RuntimeException(e);
		}
	}

	@Override
	public RestCollection getServices(Token token) {
		return getServices(token ,null);
	}

	@Override
	public RestCollection getServices(Token token, UUID servicePlanGuid) {
		final ResultIterator iterator = new ResultIterator<>(
				token,
				V2_SERVICES,
				Service.class,
				servicePlanGuid == null ? null : ServiceQueryAttribute.SERVICE_PLAN_GUID,
				servicePlanGuid == null ? null : servicePlanGuid.toString());
		return new RestCollection<>(iterator.getSize(), iterator);
	}
	
	@Override
	public Service getService(Token token, UUID serviceGuid) {
		JsonNode jsonNode = fetchResource(token, V2_SERVICES +"/"+ serviceGuid.toString());
		try {
			return mapper.readValue(jsonNode.get("entity").traverse(), Service.class);
		} catch (IOException e) {
			throw new RuntimeException(e);
		}
	}

	@Override
	public ServicePlan getServicePlan(Token token, UUID servicePlanGuid) {
		JsonNode jsonNode = fetchResource(token, V2_SERVICE_PLANS +"/"+ servicePlanGuid.toString());
		try {
			return mapper.readValue(jsonNode.get("entity").traverse(), ServicePlan.class);
		} catch (IOException e) {
			throw new RuntimeException(e);
		}
	}
	
	@Override
	public RestCollection getServicePlans(Token token) {
		return getServicePlans(token, null, null);
	}

	@Override
	public RestCollection getServicePlans(Token token, ServicePlanQueryAttribute queryAttribute, String queryValue) {
		final ResultIterator iterator = new ResultIterator<>(
				token,
				V2_SERVICE_PLANS,
				ServicePlan.class,
				queryAttribute,
				queryValue);
		return new RestCollection<>(iterator.getSize(), iterator);
	}
	
	@Override
	public ServiceInstance getServiceInstance(Token token, UUID instanceGuid) {
		JsonNode jsonNode = fetchResource(token, V2_SERVICE_INSTANCES +"/"+ instanceGuid.toString());
		try {
			return mapper.readValue(jsonNode.get("entity").traverse(), ServiceInstance.class);
		} catch (IOException e) {
			throw new RuntimeException(e);
		}
	}
	
	
	@Override
	public Space getSpace(Token token, UUID spaceGuid) {
		JsonNode jsonNode = fetchResource(token, V2_SPACES +"/"+ spaceGuid.toString());
		try {
			return mapper.readValue(jsonNode.get("entity").traverse(), Space.class);
		} catch (IOException e) {
			throw new RuntimeException(e);
		}
	}
	
	@Override
	public RestCollection getManagersInSpace(Token token, UUID spaceGuid) {
		final ResultIterator iterator = new ResultIterator<>(
				token,
				V2_SPACES+"/"+spaceGuid.toString()+"/managers",
				User.class,
				null,
				null);
		return new RestCollection<>(iterator.getSize(), iterator);
	}

	@Override
	public RestCollection getSecurityGroupsForSpace(Token token, UUID spaceGuid) {
		//Hack until this issue is fixed: https://www.pivotaltracker.com/story/show/82055042
		JsonNode node = fetchResource(token, V2_SPACES+"/"+spaceGuid.toString()+"?inline-relations-depth=1");
		JsonNode entity = node.get("entity");
		final ResultIterator iterator = new ResultIterator<>(SecurityGroup.class, (ArrayNode)entity.get("security_groups"));
		return new RestCollection<>(iterator.getSize(), iterator);
	}

	@Override
	public Organization getOrganization(Token token, UUID organizationGuid) {
		JsonNode jsonNode = fetchResource(token, V2_ORGANIZATIONS +"/"+ organizationGuid.toString());
		try {
			return mapper.readValue(jsonNode.get("entity").traverse(), Organization.class);
		} catch (IOException e) {
			throw new RuntimeException(e);
		}
	}

	@Override
	public RestCollection getServiceInstances(Token token) {
		return getServiceInstances(token, null, null);
	}

	@Override
	public RestCollection getServiceInstances(Token token, ServiceInstanceQueryAttribute queryAttribute, String queryValue) {
		final ResultIterator iterator = new ResultIterator<>(
				token,
				V2_SERVICE_INSTANCES,
				ServiceInstance.class,
				queryAttribute,
				queryValue);
		return new RestCollection<>(iterator.getSize(), iterator);
	}
	
	@Override
	public RestCollection getServiceBindings(Token token) {
		return getServiceBindings(token, null, null);
	}
	
	@Override
	public RestCollection getServiceBindings(Token token, ServiceBindingQueryAttribute queryAttribute, String queryValue) {
		final ResultIterator iterator = new ResultIterator<>(
				token,
				V2_SERVICE_BINDINGS,
				ServiceBinding.class,
				queryAttribute,
				queryValue);
		return new RestCollection<>(iterator.getSize(), iterator);
	}
	
	@Override
	public ServiceBinding getServiceBinding(Token token, UUID serviceBindingGuid) {
		JsonNode jsonNode = fetchResource(token, V2_SERVICE_BINDINGS +"/"+ serviceBindingGuid.toString());
		try {
			return mapper.readValue(jsonNode.get("entity").traverse(), ServiceBinding.class);
		} catch (IOException e) {
			throw new RuntimeException(e);
		}
	}

	private void validateResponse(HttpResponse response, int... expectedStatusCodes) {
		final StatusLine statusLine = response.getStatusLine();
		final int statusCode = statusLine.getStatusCode();
		for (int code : expectedStatusCodes) {
			if (code == statusCode) {
				return;
			}
		}
		throw new UnexpectedResponseException(response);
	}

	@Override
	public void deleteService(Token token, UUID serviceGuid) {
		deleteUri(token, V2_SERVICES + "/" + serviceGuid);
	}
	
	@Override
	public Service updateService(Token token, UUID serviceGuid, Service service) {
		JsonNode jsonNode = putJsonToUri(token, service, V2_SERVICES, serviceGuid);
		try {
			return mapper.readValue(jsonNode.get("entity").traverse(), Service.class);
		} catch (IOException e) {
			throw new RuntimeException(e);
		}
	}

	@Override
	public UUID createServicePlan(Token token, ServicePlan request) {
		return postJsonToUri(token, request, V2_SERVICE_PLANS);
	}
	
	@Override
	public ServicePlan updateServicePlan(Token token, UUID servicePlanGuid, ServicePlan service) {
		JsonNode jsonNode = putJsonToUri(token, service, V2_SERVICE_PLANS, servicePlanGuid);
		try {
			return mapper.readValue(jsonNode.get("entity").traverse(), ServicePlan.class);
		} catch (IOException e) {
			throw new RuntimeException(e);
		}
	}

	@Override
	public RestCollection getAuthTokens(Token token) {
		final ResultIterator iterator = new ResultIterator<>(
				token,
				V2_SERVICE_AUTH_TOKENS,
				ServiceAuthToken.class,
				null,
				null);
		return new RestCollection<>(iterator.getSize(), iterator);
	}

	@Override
	public UUID createAuthToken(Token token, ServiceAuthToken request) {
		return postJsonToUri(token, request, V2_SERVICE_AUTH_TOKENS);
	}

	@Override
	public void deleteServiceAuthToken(Token token, UUID authTokenGuid) {
		deleteUri(token, V2_SERVICE_AUTH_TOKENS + "/" + authTokenGuid);
	}

	@Override
	public UUID createServiceInstance(Token token, String name, UUID planGuid, UUID spaceGuid) {
		try {
			final ObjectNode json = mapper.createObjectNode();
			json.put("name", name);
			json.put("service_plan_guid", planGuid.toString());
			json.put("space_guid", spaceGuid.toString());
			final HttpPost post = new HttpPost(target.resolve(V2_SERVICE_INSTANCES));
			post.addHeader(token.toAuthorizationHeader());
			post.setEntity(new StringEntity(json.toString(), ContentType.APPLICATION_JSON));
			final HttpResponse response = httpClient.execute(post);
			try {
				validateResponse(response, 201);
				final JsonNode jsonResponse = mapper.readTree(response.getEntity().getContent());
				return UUID.fromString(jsonResponse.get("metadata").get("guid").asText());
			} finally {
				HttpClientUtils.closeQuietly(response);
			}
		} catch (IOException e) {
			throw new RuntimeException(e);
		}
	}

	@Override
	public void deleteServiceInstance(Token token, UUID instanceGuid) {
		deleteUri(token, V2_SERVICE_INSTANCES + "/" + instanceGuid);
	}
	
	@Override
	public RestCollection getPrivateDomains(Token token) {
		final ResultIterator iterator = new ResultIterator<>(
				token,
				V2_PRIVATE_DOMAINS,
				PrivateDomain.class,
				null,
				null);
		return new RestCollection<>(iterator.getSize(), iterator);
	}

	@Override
	public RestCollection getRoutes(Token token) {
		final ResultIterator iterator = new ResultIterator<>(
				token,
				V2_ROUTES,
				Route.class,
				null,
				null);
		return new RestCollection<>(iterator.getSize(), iterator);
	}
	
	@Override
	public RestCollection getAppsForRoute(Token token, UUID routeGuid) {
		final ResultIterator iterator = new ResultIterator<>(
				token,
				V2_ROUTES+"/"+routeGuid+"/apps",
				Application.class,
				null,
				null);
		return new RestCollection<>(iterator.getSize(), iterator);
	}
	
	@Override
	public RestCollection getRoutes(Token token, RouteQueryAttribute queryAttribute, String queryValue) {
		final ResultIterator iterator = new ResultIterator<>(
				token,
				V2_ROUTES,
				Route.class,
				queryAttribute,
				queryValue);
		return new RestCollection<>(iterator.getSize(), iterator);
	}

	private UUID postJsonToUri(Token token, Object json, String uri) {
		try {
			final String requestString = mapper.writeValueAsString(json);
			final HttpPost post = new HttpPost(target.resolve(uri));
			post.addHeader(token.toAuthorizationHeader());
			post.setEntity(new StringEntity(requestString, ContentType.APPLICATION_JSON));
			final HttpResponse response = httpClient.execute(post);
			try {
				validateResponse(response, 201);
				final JsonNode responseJson = mapper.readTree(response.getEntity().getContent());
				return UUID.fromString(responseJson.get("metadata").get("guid").asText());
			} finally {
				HttpClientUtils.closeQuietly(response);
			}
		} catch (IOException e) {
			throw new RuntimeException(e);
		}
	}

	private JsonNode putJsonToUri(Token token, Object json, String uri, UUID guid) {
		try {
			final String requestString = mapper.writeValueAsString(json);
			final HttpPut put = new HttpPut(target.resolve(uri+"/"+ guid.toString()));
			put.addHeader(token.toAuthorizationHeader());
			put.setEntity(new StringEntity(requestString, ContentType.APPLICATION_JSON));
			final HttpResponse response = httpClient.execute(put);
			try {
				validateResponse(response, 201);
				return mapper.readTree(response.getEntity().getContent());
			} finally {
				HttpClientUtils.closeQuietly(response);
			}
		} catch (IOException e) {
			throw new RuntimeException(e);
		}
	}

	private void deleteUri(Token token, String uri) {
		try {
			final HttpDelete delete = new HttpDelete(target.resolve(uri));
			delete.addHeader(token.toAuthorizationHeader());
			final HttpResponse response = httpClient.execute(delete);
			try {
				validateResponse(response, 204);
			} finally {
				HttpClientUtils.closeQuietly(response);
			}
		} catch (IOException e) {
			throw new RuntimeException(e);
		}
	}

	private void fetchInfo() {
		try {
			final HttpGet get = new HttpGet(target.resolve("/info"));
			// TODO Standardize on error handling
			// TODO Throw exception if non version 2 Cloud Controller
			final HttpResponse response = httpClient.execute(get);
			try {
				synchronized (lock) {
					info = mapper.readValue(response.getEntity().getContent(), Info.class);
				}
			} finally {
				HttpClientUtils.closeQuietly(response);
			}
		} catch (IOException e) {
			throw new RuntimeException(e);
		}
	}

	private JsonNode fetchResource(Token token, String uri) {
		LOGGER.debug("GET {}", uri);
		try {
			final HttpGet httpGet = new HttpGet(target.resolve(uri));
			httpGet.setHeader(token.toAuthorizationHeader());
			final HttpResponse response = httpClient.execute(httpGet);
			try {
				validateResponse(response, 200);
				return mapper.readTree(response.getEntity().getContent());
			} finally {
				HttpClientUtils.closeQuietly(response);
			}
		} catch (IOException e) {
			throw new RuntimeException(e);
		}
	}
	
	private class ResultIterator implements Iterator> {

		private ThreadLocal dateFormat = new ThreadLocal() {
			@Override
			protected SimpleDateFormat initialValue() {
				return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss Z");
			}
		};

		private final Token token;

		private final int size;

		private final Class type;

		private String nextUri;
		private Iterator> iterator;

		private ResultIterator(Token token, String uri, Class type, QueryAttribute queryAttribute, String queryValue) {
			this.type = type;

			this.token = token;
			if (queryAttribute != null) {
				uri += "?q=" + queryAttribute + ":" + queryValue;
			}
			final JsonNode jsonNode = fetchResource(token, uri);

			size = jsonNode.get("total_results").asInt();

			final JsonNode nextUrlNode = jsonNode.get("next_url");
			nextUri = nextUrlNode.isNull() ? null : nextUrlNode.asText();

			parseResources((ArrayNode)jsonNode.get("resources"));
		}

		private ResultIterator(Class type, ArrayNode jsonNode) {
			this.type = type;

			this.token = null;

			this.size = jsonNode.size();

			parseResources(jsonNode);
		}

		private void parseResources(ArrayNode resourcesJsonNode) {
			final Iterator resourceNodeIterator = resourcesJsonNode.elements();
			final ArrayList> resources = new ArrayList<>();
			while (resourceNodeIterator.hasNext()) {
				final JsonNode node = resourceNodeIterator.next();
				final JsonNode metadata = node.get("metadata");
				final String guid = metadata.get("guid").asText();
				final URI uri = URI.create(metadata.get("url").asText());
				Date created;
				try {
					created = dateFormat.get().parse(metadata.get("created_at").asText());
				} catch (ParseException e) {
					created = null;
				}
				Date updated;
				final String updatedAt = metadata.get("updated_at").asText();
				try {
					updated = updatedAt == null ? null : dateFormat.get().parse(updatedAt);
				} catch (ParseException e) {
					updated = null;
				}
				final T entity;
				try {
					entity = mapper.readValue(node.get("entity").traverse(), type);
				} catch (IOException e) {
					throw new RuntimeException(e);
				}
				resources.add(new Resource<>(entity, guid, uri, created, updated));
			}
			iterator = resources.iterator();
		}

		public boolean fetchNextPage() {
			if (nextUri == null) {
				return false;
			}
			final JsonNode jsonNode = fetchResource(token, nextUri);
			final JsonNode nextUrlNode = jsonNode.get("next_url");
			nextUri = nextUrlNode.isNull() ? null : nextUrlNode.asText();
			parseResources((ArrayNode)jsonNode.get("resources"));
			return true;
		}

		@Override
		public boolean hasNext() {
			// Check if current iterator has an element, if not load the next page and check again.
			return iterator.hasNext() || (fetchNextPage() && iterator.hasNext());
		}

		@Override
		public Resource next() {
			if (iterator.hasNext()) {
				return iterator.next();
			}
			if (!fetchNextPage()) {
				return null;
			}
			return iterator.next();
		}

		@Override
		public void remove() {
			throw new UnsupportedOperationException();
		}

		private int getSize() {
			return size;
		}

	}
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy