cf.client.DefaultCloudController.java Source code

Java tutorial

Introduction

Here is the source code for cf.client.DefaultCloudController.java

Source

/*
 *   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 cf.client.model.ApplicationInstance;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
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.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.Info;
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 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;

/**
 * @author Mike Heath <elcapo@gmail.com>
 */
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_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 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 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<String, ApplicationInstance> getApplicationInstances(Token token, UUID applicationGuid) {
        final JsonNode jsonNode = fetchResource(token, V2_APPS + "/" + applicationGuid.toString() + APP_INSTANCES);
        final Iterator<Map.Entry<String, JsonNode>> fields = jsonNode.fields();
        final Map<String, ApplicationInstance> instances = new HashMap<>();
        while (fields.hasNext()) {
            final Map.Entry<String, JsonNode> 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 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<Service> getServices(Token token) {
        return getServices(token, null);
    }

    @Override
    public RestCollection<Service> getServices(Token token, UUID servicePlanGuid) {
        final ResultIterator<Service> 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 RestCollection<ServicePlan> getServicePlans(Token token) {
        return getServicePlans(token, null, null);
    }

    @Override
    public RestCollection<ServicePlan> getServicePlans(Token token, ServicePlanQueryAttribute queryAttribute,
            String queryValue) {
        final ResultIterator<ServicePlan> iterator = new ResultIterator<>(token, V2_SERVICE_PLANS,
                ServicePlan.class, queryAttribute, queryValue);
        return new RestCollection<>(iterator.getSize(), iterator);
    }

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

    @Override
    public RestCollection<ServiceInstance> getServiceInstances(Token token,
            ServiceInstanceQueryAttribute queryAttribute, String queryValue) {
        final ResultIterator<ServiceInstance> iterator = new ResultIterator<>(token, V2_SERVICE_INSTANCES,
                ServiceInstance.class, queryAttribute, queryValue);
        return new RestCollection<>(iterator.getSize(), iterator);
    }

    @Override
    public RestCollection<ServiceBinding> getServiceBindings(Token token) {
        return getServiceBindings(token, null, null);
    }

    @Override
    public RestCollection<ServiceBinding> getServiceBindings(Token token,
            ServiceBindingQueryAttribute queryAttribute, String queryValue) {
        final ResultIterator<ServiceBinding> iterator = new ResultIterator<>(token, V2_SERVICE_BINDINGS,
                ServiceBinding.class, queryAttribute, queryValue);
        return new RestCollection<>(iterator.getSize(), iterator);
    }

    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 UUID createServicePlan(Token token, ServicePlan request) {
        return postJsonToUri(token, request, V2_SERVICE_PLANS);
    }

    @Override
    public RestCollection<ServiceAuthToken> getAuthTokens(Token token) {
        final ResultIterator<ServiceAuthToken> 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);
    }

    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 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<T> implements Iterator<Resource<T>> {

        private ThreadLocal<SimpleDateFormat> dateFormat = new ThreadLocal<SimpleDateFormat>() {
            @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<T> type;

        private String nextUri;
        private Iterator<Resource<T>> iterator;

        private ResultIterator(Token token, String uri, Class<T> 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();

            parseResources(jsonNode);
        }

        private void parseResources(JsonNode jsonNode) {
            final JsonNode nextUrlNode = jsonNode.get("next_url");
            nextUri = nextUrlNode.isNull() ? null : nextUrlNode.asText();
            final Iterator<JsonNode> resourceNodeIterator = jsonNode.get("resources").elements();
            final ArrayList<Resource<T>> resources = new ArrayList<>();
            while (resourceNodeIterator.hasNext()) {
                final JsonNode node = resourceNodeIterator.next();
                final JsonNode metadata = node.get("metadata");
                final UUID guid = UUID.fromString(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);
            parseResources(jsonNode);
            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<T> 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;
        }

    }
}