org.dasein.cloud.google.GoogleMethod.java Source code

Java tutorial

Introduction

Here is the source code for org.dasein.cloud.google.GoogleMethod.java

Source

/**
 * Copyright (C) 2012-2013 Dell, Inc
 * See annotations for authorship information
 *
 * ====================================================================
 * 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.dasein.cloud.google;

import org.apache.commons.codec.binary.Base64;
import org.apache.http.Header;
import org.apache.http.HttpEntity;
import org.apache.http.HttpHost;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.HttpVersion;
import org.apache.http.client.HttpClient;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpDelete;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPatch;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.conn.params.ConnRoutePNames;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.message.BasicHeader;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.params.BasicHttpParams;
import org.apache.http.params.HttpParams;
import org.apache.http.params.HttpProtocolParams;
import org.apache.http.protocol.HTTP;
import org.apache.http.util.EntityUtils;
import org.apache.log4j.Logger;
import org.dasein.cloud.CloudException;
import org.dasein.cloud.InternalException;
import org.dasein.cloud.ProviderContext;
import org.dasein.cloud.util.Cache;
import org.dasein.cloud.util.CacheLevel;
import org.dasein.util.CalendarWrapper;
import org.dasein.util.uom.time.Hour;
import org.dasein.util.uom.time.TimePeriod;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.security.KeyStore;
import java.security.PrivateKey;
import java.security.Signature;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Date;
import java.util.Enumeration;
import java.util.List;
import java.util.Properties;

/**
 * Represents the interaction point between Dasein Cloud and the underlying REST API.
 * @author INSERT NAME HERE
 * @version 2013.01 initial version
 * @since 2013.01
 */
public class GoogleMethod {
    static public final int OK = 200;
    static public final int CREATED = 201;
    static public final int ACCEPTED = 202;
    static public final int NOT_FOUND = 404;

    static public class Param {
        private String key;
        private String value;

        public Param(@Nonnull String key, @Nonnull String value) throws InternalException {
            this.key = key;
            try {
                this.value = URLEncoder.encode(value, "utf-8");
            } catch (UnsupportedEncodingException e) {
                logger.error("UTF-8 unsupported: " + e.getMessage());
                e.printStackTrace();
                throw new InternalException(e);
            }
        }

        public @Nonnull String getKey() {
            return key;
        }

        public @Nonnull String getValue() {
            return value;
        }
    }

    static public final String VOLUME = "/disks";
    static public final String FIREWALL = "/global/firewalls";
    static public final String IMAGE = "/global/images";
    static public final String SERVER = "/instances";
    static public final String KERNEL = "/global/kernels";
    static public final String MACHINE_TYPE = "/global/machineTypes";
    static public final String NETWORK = "/global/networks";
    static public final String SNAPSHOT = "/global/snapshots";
    static public final String ZONE = "/zones";
    static public final String GLOBAL_OPERATION = "/global/operations";
    static public final String OPERATION = "/operations";

    static public final String VERSION = "v1beta14";

    static private final Logger logger = Google.getLogger(GoogleMethod.class);
    static private final Logger wire = Google.getWireLogger(GoogleMethod.class);

    private Google provider;

    public GoogleMethod(@Nonnull Google provider) {
        this.provider = provider;
    }

    public @Nullable JSONArray get(@Nonnull String service, @Nullable Param... params)
            throws CloudException, InternalException {
        if (logger.isTraceEnabled()) {
            logger.trace(
                    "ENTER - " + Google.class.getName() + ".get(" + service + "," + Arrays.toString(params) + ")");
        }
        if (wire.isDebugEnabled()) {
            wire.debug("");
            wire.debug(">>> [GET (" + (new Date()) + ")] -> " + service
                    + " >--------------------------------------------------------------------------------------");
        }
        try {
            ProviderContext ctx = provider.getContext();

            if (ctx == null) {
                throw new CloudException("No context was set for this request");
            }

            String endpoint = getEndpoint(ctx, service);

            if (logger.isDebugEnabled()) {
                logger.debug("endpoint=" + endpoint);
                logger.debug("");
                logger.debug("Getting accessToken from cache");
            }

            Cache<String> cache = Cache.getInstance(provider, "accessToken", String.class, CacheLevel.CLOUD,
                    new TimePeriod<Hour>(1, TimePeriod.HOUR));
            Collection<String> accessToken = (Collection<String>) cache.get(ctx);

            if (accessToken == null) {
                if (logger.isDebugEnabled()) {
                    logger.debug("Getting the accessToken afresh");
                }
                accessToken = new ArrayList<String>();
                accessToken.add(getAccessToken(ctx));
                cache.put(ctx, accessToken);
            }

            //TODO: Logging access token is not good
            if (logger.isDebugEnabled()) {
                logger.debug("accessToken=" + accessToken);
            }

            String paramString = "?access_token=" + accessToken.iterator().next()
                    + "&token_type=Bearer&expires_in=3600";

            if (params != null && params.length > 0) {
                for (Param p : params) {
                    paramString = paramString + "&" + p.getKey() + "=" + p.getValue();
                }
            }
            if (logger.isDebugEnabled()) {
                logger.debug("Param string=" + paramString);
            }
            HttpGet get = new HttpGet(endpoint + paramString);
            HttpClient client = getClient(ctx, endpoint.startsWith("https"));

            if (wire.isDebugEnabled()) {
                wire.debug(get.getRequestLine().toString());
                for (Header header : get.getAllHeaders()) {
                    wire.debug(header.getName() + ": " + header.getValue());
                }
                wire.debug("");
            }
            HttpResponse response;

            try {
                response = client.execute(get);
                if (wire.isDebugEnabled()) {
                    wire.debug(response.getStatusLine().toString());
                }
            } catch (IOException e) {
                logger.error("I/O error from server communications: " + e.getMessage());
                e.printStackTrace();
                throw new InternalException(e);
            }
            int status = response.getStatusLine().getStatusCode();

            if (status == NOT_FOUND) {
                return null;
            }
            if (status == OK) {
                HttpEntity entity = response.getEntity();
                String json;

                if (entity == null) {
                    return null;
                }
                try {
                    json = EntityUtils.toString(entity);
                    if (wire.isDebugEnabled()) {
                        wire.debug(json);
                    }
                } catch (IOException e) {
                    logger.error("Failed to read JSON entity");
                    e.printStackTrace();
                    throw new CloudException(e);
                }
                try {
                    JSONObject r = new JSONObject(json);

                    if (r.has("items"))
                        return r.getJSONArray("items");
                    else if (r.has("name")) {
                        JSONArray array = new JSONArray();
                        array.put(r);
                        return array;
                    } else
                        return null;
                } catch (JSONException e) {
                    logger.error("Invalid JSON from cloud: " + e.getMessage());
                    e.printStackTrace();
                    throw new CloudException(e);
                }
            } else if (status == NOT_FOUND) {
                return null;
            } else if (status == 400 && service.endsWith("get")) {
                return null;
            }
            throw new GoogleException(new GoogleException.ParsedException(response));
        } finally {
            if (logger.isTraceEnabled()) {
                logger.trace("EXIT - " + GoogleMethod.class.getName() + ".get()");
            }
            if (wire.isDebugEnabled()) {
                wire.debug("<<< [GET (" + (new Date()) + ")] -> " + service
                        + " <--------------------------------------------------------------------------------------");
                wire.debug("");
            }
        }
    }

    public @Nullable JSONObject post(@Nonnull String service, @Nonnull JSONObject jsonPayload)
            throws CloudException, InternalException {
        if (logger.isTraceEnabled()) {
            logger.trace("ENTER - " + Google.class.getName() + ".post(" + service + ")");
        }
        if (wire.isDebugEnabled()) {
            wire.debug("");
            wire.debug(">>> [POST (" + (new Date()) + ")] -> " + service
                    + " >--------------------------------------------------------------------------------------");
        }
        try {
            ProviderContext ctx = provider.getContext();

            if (ctx == null) {
                throw new CloudException("No context was set for this request");
            }

            String endpoint = getEndpoint(ctx, service);

            if (logger.isDebugEnabled()) {
                logger.debug("endpoint=" + endpoint);
                logger.debug("");
                logger.debug("Getting accessToken from cache");
            }

            Cache<String> cache = Cache.getInstance(provider, "accessToken", String.class, CacheLevel.CLOUD,
                    new TimePeriod<Hour>(1, TimePeriod.HOUR));
            Collection<String> accessToken = (Collection<String>) cache.get(ctx);

            if (accessToken == null) {
                if (logger.isDebugEnabled()) {
                    logger.debug("Getting the accessToken afresh");
                }
                accessToken = new ArrayList<String>();
                accessToken.add(getAccessToken(ctx));
                cache.put(ctx, accessToken);
            }

            //TODO: Logging access token is not good
            if (logger.isDebugEnabled()) {
                logger.debug("accessToken=" + accessToken);
            }

            String paramString = "?access_token=" + accessToken.iterator().next()
                    + "&token_type=Bearer&expires_in=3600";

            if (logger.isDebugEnabled()) {
                logger.debug("Param string=" + paramString);
            }
            HttpPost post = new HttpPost(endpoint + paramString);
            HttpClient client = getClient(ctx, endpoint.startsWith("https"));

            StringEntity stringEntity;
            try {
                stringEntity = new StringEntity(jsonPayload.toString());
            } catch (UnsupportedEncodingException e1) {
                // TODO Auto-generated catch block
                return null;
            }
            stringEntity.setContentType(new BasicHeader(HTTP.CONTENT_TYPE, "application/json"));
            post.setEntity(stringEntity);

            if (wire.isDebugEnabled()) {
                wire.debug(post.getRequestLine().toString());
                for (Header header : post.getAllHeaders()) {
                    wire.debug(header.getName() + ": " + header.getValue());
                }
                wire.debug("");
            }
            HttpResponse response;

            try {
                response = client.execute(post);
                if (wire.isDebugEnabled()) {
                    wire.debug(response.getStatusLine().toString());
                }
            } catch (IOException e) {
                logger.error("I/O error from server communications: " + e.getMessage());
                e.printStackTrace();
                throw new InternalException(e);
            }
            int status = response.getStatusLine().getStatusCode();

            if (status == NOT_FOUND) {
                return null;
            }
            if (status == OK || status == ACCEPTED) {
                HttpEntity entity = response.getEntity();
                String json;

                if (entity == null) {
                    return null;
                }
                try {
                    json = EntityUtils.toString(entity);
                    if (wire.isDebugEnabled()) {
                        wire.debug(json);
                    }
                } catch (IOException e) {
                    logger.error("Failed to read JSON entity");
                    e.printStackTrace();
                    throw new CloudException(e);
                }
                try {
                    return new JSONObject(json);
                } catch (JSONException e) {
                    logger.error("Invalid JSON from cloud: " + e.getMessage());
                    e.printStackTrace();
                    throw new CloudException(e);
                }
            } else if (status == NOT_FOUND) {
                return null;
            } else if (status == 400 && service.endsWith("post")) {
                return null;
            }
            throw new GoogleException(new GoogleException.ParsedException(response));
        } finally {
            if (logger.isTraceEnabled()) {
                logger.trace("EXIT - " + GoogleMethod.class.getName() + ".post()");
            }
            if (wire.isDebugEnabled()) {
                wire.debug("<<< [POST (" + (new Date()) + ")] -> " + service
                        + " <--------------------------------------------------------------------------------------");
                wire.debug("");
            }
        }
    }

    public @Nullable JSONObject patch(@Nonnull String service, @Nonnull JSONObject jsonPayload)
            throws CloudException, InternalException {
        if (logger.isTraceEnabled()) {
            logger.trace("ENTER - " + Google.class.getName() + ".patch(" + service + ")");
        }
        if (wire.isDebugEnabled()) {
            wire.debug("");
            wire.debug(">>> [PATCH (" + (new Date()) + ")] -> " + service
                    + " >--------------------------------------------------------------------------------------");
        }
        try {
            ProviderContext ctx = provider.getContext();

            if (ctx == null) {
                throw new CloudException("No context was set for this request");
            }

            String endpoint = getEndpoint(ctx, service);

            if (logger.isDebugEnabled()) {
                logger.debug("endpoint=" + endpoint);
                logger.debug("");
                logger.debug("Getting accessToken from cache");
            }

            Cache<String> cache = Cache.getInstance(provider, "accessToken", String.class, CacheLevel.CLOUD,
                    new TimePeriod<Hour>(1, TimePeriod.HOUR));
            Collection<String> accessToken = (Collection<String>) cache.get(ctx);

            if (accessToken == null) {
                if (logger.isDebugEnabled()) {
                    logger.debug("Getting the accessToken afresh");
                }
                accessToken = new ArrayList<String>();
                accessToken.add(getAccessToken(ctx));
                cache.put(ctx, accessToken);
            }

            //TODO: Logging access token is not good
            if (logger.isDebugEnabled()) {
                logger.debug("accessToken=" + accessToken);
            }

            String paramString = "?access_token=" + accessToken.iterator().next()
                    + "&token_type=Bearer&expires_in=3600";

            if (logger.isDebugEnabled()) {
                logger.debug("Param string=" + paramString);
            }
            HttpPatch patch = new HttpPatch(endpoint + paramString);
            HttpClient client = getClient(ctx, endpoint.startsWith("https"));

            StringEntity stringEntity;
            try {
                stringEntity = new StringEntity(jsonPayload.toString());
            } catch (UnsupportedEncodingException e1) {
                // TODO Auto-generated catch block
                return null;
            }
            stringEntity.setContentType(new BasicHeader(HTTP.CONTENT_TYPE, "application/json"));
            patch.setEntity(stringEntity);

            if (wire.isDebugEnabled()) {
                wire.debug(patch.getRequestLine().toString());
                for (Header header : patch.getAllHeaders()) {
                    wire.debug(header.getName() + ": " + header.getValue());
                }
                wire.debug("");
            }
            HttpResponse response;

            try {
                response = client.execute(patch);
                if (wire.isDebugEnabled()) {
                    wire.debug(response.getStatusLine().toString());
                }
            } catch (IOException e) {
                logger.error("I/O error from server communications: " + e.getMessage());
                e.printStackTrace();
                throw new InternalException(e);
            }
            int status = response.getStatusLine().getStatusCode();

            if (status == NOT_FOUND) {
                return null;
            }
            if (status == OK || status == ACCEPTED) {
                HttpEntity entity = response.getEntity();
                String json;

                if (entity == null) {
                    return null;
                }
                try {
                    json = EntityUtils.toString(entity);
                    if (wire.isDebugEnabled()) {
                        wire.debug(json);
                    }
                } catch (IOException e) {
                    logger.error("Failed to read JSON entity");
                    e.printStackTrace();
                    throw new CloudException(e);
                }
                try {
                    return new JSONObject(json);
                } catch (JSONException e) {
                    logger.error("Invalid JSON from cloud: " + e.getMessage());
                    e.printStackTrace();
                    throw new CloudException(e);
                }
            } else if (status == NOT_FOUND) {
                return null;
            } else if (status == 400 && service.endsWith("patch")) {
                return null;
            }
            throw new GoogleException(new GoogleException.ParsedException(response));
        } finally {
            if (logger.isTraceEnabled()) {
                logger.trace("EXIT - " + GoogleMethod.class.getName() + ".patch()");
            }
            if (wire.isDebugEnabled()) {
                wire.debug("<<< [PATCH (" + (new Date()) + ")] -> " + service
                        + " <--------------------------------------------------------------------------------------");
                wire.debug("");
            }
        }
    }

    public @Nullable JSONObject delete(@Nonnull String service, @Nullable Param... params)
            throws CloudException, InternalException {
        if (logger.isTraceEnabled()) {
            logger.trace("ENTER - " + Google.class.getName() + ".delete(" + service + "," + Arrays.toString(params)
                    + ")");
        }
        if (wire.isDebugEnabled()) {
            wire.debug("");
            wire.debug(">>> [DELETE (" + (new Date()) + ")] -> " + service
                    + " >--------------------------------------------------------------------------------------");
        }
        try {
            ProviderContext ctx = provider.getContext();

            if (ctx == null) {
                throw new CloudException("No context was set for this request");
            }

            String endpoint = getEndpoint(ctx, service);

            if (logger.isDebugEnabled()) {
                logger.debug("endpoint=" + endpoint);
                logger.debug("");
                logger.debug("Getting accessToken from cache");
            }

            Cache<String> cache = Cache.getInstance(provider, "accessToken", String.class, CacheLevel.CLOUD,
                    new TimePeriod<Hour>(1, TimePeriod.HOUR));
            Collection<String> accessToken = (Collection<String>) cache.get(ctx);

            if (accessToken == null) {
                if (logger.isDebugEnabled()) {
                    logger.debug("Getting the accessToken afresh");
                }
                accessToken = new ArrayList<String>();
                accessToken.add(getAccessToken(ctx));
                cache.put(ctx, accessToken);
            }

            //TODO: Logging access token is not good
            if (logger.isDebugEnabled()) {
                logger.debug("accessToken=" + accessToken);
            }

            String paramString = "?access_token=" + accessToken.iterator().next()
                    + "&token_type=Bearer&expires_in=3600";

            String id = "";

            if (params != null && params.length > 0) {
                for (Param p : params) {
                    if (p.getKey().equals("id"))
                        id = p.getValue();
                }

            }
            if (logger.isDebugEnabled()) {
                logger.debug("Param string=" + paramString);
            }

            HttpDelete delete = new HttpDelete(endpoint + "/" + id + paramString);
            HttpClient client = getClient(ctx, endpoint.startsWith("https"));

            if (wire.isDebugEnabled()) {
                wire.debug(delete.getRequestLine().toString());
                for (Header header : delete.getAllHeaders()) {
                    wire.debug(header.getName() + ": " + header.getValue());
                }
                wire.debug("");
            }
            HttpResponse response;

            try {
                response = client.execute(delete);
                if (wire.isDebugEnabled()) {
                    wire.debug(response.getStatusLine().toString());
                }
            } catch (IOException e) {
                logger.error("I/O error from server communications: " + e.getMessage());
                e.printStackTrace();
                throw new InternalException(e);
            }
            int status = response.getStatusLine().getStatusCode();

            if (status == NOT_FOUND) {
                return null;
            }
            if (status == OK || status == ACCEPTED) {
                HttpEntity entity = response.getEntity();
                String json;

                if (entity == null) {
                    return null;
                }
                try {
                    json = EntityUtils.toString(entity);
                    if (wire.isDebugEnabled()) {
                        wire.debug(json);
                    }
                } catch (IOException e) {
                    logger.error("Failed to read JSON entity");
                    e.printStackTrace();
                    throw new CloudException(e);
                }
                try {
                    return new JSONObject(json);
                } catch (JSONException e) {
                    logger.error("Invalid JSON from cloud: " + e.getMessage());
                    e.printStackTrace();
                    throw new CloudException(e);
                }
            } else if (status == NOT_FOUND) {
                return null;
            } else if (status == 400 && service.endsWith("delete")) {
                return null;
            }
            throw new GoogleException(new GoogleException.ParsedException(response));
        } finally {
            if (logger.isTraceEnabled()) {
                logger.trace("EXIT - " + GoogleMethod.class.getName() + ".delete()");
            }
            if (wire.isDebugEnabled()) {
                wire.debug("<<< [DELETE (" + (new Date()) + ")] -> " + service
                        + " <--------------------------------------------------------------------------------------");
                wire.debug("");
            }
        }
    }

    public @Nonnull String getProjectId(@Nonnull ProviderContext ctx) {
        return ctx.getAccountNumber().toString();
    }

    public static @Nonnull String getIss(@Nonnull ProviderContext ctx) {
        return System.getProperty("apiSharedKey").toString();
    }

    public static @Nonnull String getPrivateKey(@Nonnull ProviderContext ctx) {
        return System.getProperty("apiSecretKey").toString();
    }

    //TODO: rewrite/review the module
    static @Nonnull String getToken(@Nonnull String iss, @Nonnull String p12File) throws CloudException {
        if (logger.isDebugEnabled()) {
            logger.debug("iss: " + iss);
            logger.debug("p12File: " + p12File);
        }

        String header = "{\"alg\":\"RS256\",\"typ\":\"JWT\"}";
        StringBuffer token = new StringBuffer();

        try {
            token.append(Base64.encodeBase64URLSafeString(header.getBytes("UTF-8")));

            token.append(".");

            String scope = "https://www.googleapis.com/auth/compute";
            String aud = "https://accounts.google.com/o/oauth2/token";
            String expiry = Long.toString((System.currentTimeMillis() / 1000) + 3600);
            String startTime = Long.toString((System.currentTimeMillis() / 1000));

            String payload = "{\"iss\": \"" + iss + "\", \"scope\": \"" + scope + "\", \"aud\": \"" + aud
                    + "\", \"exp\": \"" + expiry + "\", \"iat\": \"" + startTime + "\"}";

            token.append(Base64.encodeBase64URLSafeString(payload.getBytes("UTF-8")));

            // TODO: the password is hardcoded. This has to be read from the ctx or from the environment variable
            char[] password = "notasecret".toCharArray();
            FileInputStream iStream = new FileInputStream(new File(p12File));
            KeyStore store = KeyStore.getInstance("PKCS12");
            try {
                store.load(iStream, password);
            } finally {
                try {
                    iStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                    logger.error("Could not read the keystore file");
                    throw new CloudException(e);
                }
            }
            String alias = "";

            Enumeration<String> aliases = store.aliases();
            while (aliases.hasMoreElements()) {
                String keyStoreAlias = aliases.nextElement().toString();
                if (store.isKeyEntry(keyStoreAlias)) {
                    alias = keyStoreAlias;
                    break;
                }
            }

            PrivateKey privateKey = (PrivateKey) store.getKey(alias, password);

            Signature shaSignature = Signature.getInstance("SHA256withRSA");
            shaSignature.initSign(privateKey);
            shaSignature.update(token.toString().getBytes("UTF-8"));
            String signedToken = Base64.encodeBase64URLSafeString(shaSignature.sign());

            //Separate with a period
            token.append(".");

            //Add the encoded signature
            token.append(signedToken);
            return token.toString();

        } catch (Exception e) {
            e.printStackTrace();
            logger.error("Could not sign the payload with the private key");
            throw new CloudException(e);
        }
    }

    public static @Nonnull String getAccessToken(@Nonnull ProviderContext ctx) throws CloudException {
        if (logger.isTraceEnabled()) {
            logger.trace("ENTER - " + Google.class.getName() + ".getAccessToken()");
        }
        if (wire.isDebugEnabled()) {
            wire.debug("");
            wire.debug(">>> [GET ACCESS TOKEN (" + (new Date())
                    + ")] >--------------------------------------------------------------------------------------");
        }
        String iss = getIss(ctx);
        String p12File = getPrivateKey(ctx);
        String authUrl = "https://accounts.google.com/o/oauth2/token";

        try {
            HttpClient client = new DefaultHttpClient();
            List<BasicNameValuePair> formparams = new ArrayList<BasicNameValuePair>();
            formparams.add(new BasicNameValuePair("grant_type", "urn:ietf:params:oauth:grant-type:jwt-bearer"));
            formparams.add(new BasicNameValuePair("assertion", getToken(iss, p12File)));
            UrlEncodedFormEntity entity = new UrlEncodedFormEntity(formparams, "UTF-8");

            HttpPost httppost = new HttpPost(authUrl);
            httppost.addHeader("Content-Type", "application/x-www-form-urlencoded");
            httppost.setEntity(entity);

            // TODO: Logging header may not be good
            if (wire.isDebugEnabled()) {
                wire.debug(httppost.getRequestLine().toString());
                for (Header header : httppost.getAllHeaders()) {
                    wire.debug(header.getName() + ": " + header.getValue());
                }
                wire.debug("");
            }

            HttpResponse httpResponse = client.execute(httppost);

            if (wire.isDebugEnabled()) {
                wire.debug(httpResponse.getStatusLine().toString());
            }

            int statusCode = httpResponse.getStatusLine().getStatusCode();
            if (statusCode == HttpStatus.SC_OK) {

                InputStream iStream = httpResponse.getEntity().getContent();
                InputStreamReader iStreamReader = new InputStreamReader(iStream);
                StringBuilder sBuilder = new StringBuilder();
                BufferedReader bufferReader = new BufferedReader(iStreamReader);
                String read = bufferReader.readLine();

                while (read != null) {
                    sBuilder.append(read);
                    read = bufferReader.readLine();
                }

                JSONObject json = new JSONObject(sBuilder.toString());
                return json.getString("access_token");
            }
        } catch (Exception e) {
            e.printStackTrace();
            logger.error("Error while parsing the access token");
            throw new CloudException(e);
        } finally {
            if (logger.isTraceEnabled()) {
                logger.trace("EXIT - " + GoogleMethod.class.getName() + ".getAccessToken()");
            }
            if (wire.isDebugEnabled()) {
                wire.debug("<<< [GET ACCESS TOKEN (" + (new Date())
                        + ")] <--------------------------------------------------------------------------------------");
                wire.debug("");
            }
        }
        throw new CloudException("Could not obtain access token");
    }

    private @Nonnull HttpClient getClient(@Nonnull ProviderContext ctx, boolean ssl) {
        HttpParams params = new BasicHttpParams();

        HttpProtocolParams.setVersion(params, HttpVersion.HTTP_1_1);
        //noinspection deprecation
        HttpProtocolParams.setContentCharset(params, HTTP.UTF_8);
        HttpProtocolParams.setUserAgent(params, "Dasein Cloud");

        Properties p = ctx.getCustomProperties();

        if (p != null) {
            String proxyHost = p.getProperty("proxyHost");
            String proxyPort = p.getProperty("proxyPort");

            if (proxyHost != null) {
                int port = 0;

                if (proxyPort != null && proxyPort.length() > 0) {
                    port = Integer.parseInt(proxyPort);
                }
                params.setParameter(ConnRoutePNames.DEFAULT_PROXY,
                        new HttpHost(proxyHost, port, ssl ? "https" : "http"));
            }
        }
        return new DefaultHttpClient(params);
    }

    public @Nonnull String getEndpoint(@Nonnull ProviderContext ctx, @Nonnull String service) {
        String endpoint = ctx.getEndpoint();

        if (endpoint == null || endpoint.equals("")) {
            endpoint = "https://www.googleapis.com/compute/" + GoogleMethod.VERSION + "/projects/";
        }
        if (endpoint.endsWith("/") && service.startsWith("/")) {
            while (endpoint.endsWith("/") && !endpoint.equals("/")) {
                endpoint = endpoint.substring(0, endpoint.length() - 1);
            }
        }

        if (service.contains(GoogleMethod.IMAGE))
            endpoint = endpoint + "/google";
        else if (service.contains("global") || service.equals(GoogleMethod.ZONE))
            endpoint = endpoint + "/" + getProjectId(ctx);
        else
            endpoint = endpoint + "/" + getProjectId(ctx) + "/zones/" + ctx.getRegionId() + "-a";

        return endpoint + service;
    }

    public static @Nonnull String getResourceName(String resourceLink, String resource) {
        int index = resourceLink.indexOf(resource);
        String resoureName = resourceLink.substring(index + resource.length() + 1);
        return resoureName;
    }

    public static void checkError(JSONObject jsonResponse) throws CloudException {
        String errString = null;
        try {
            if (jsonResponse.has("httpErrorStatusCode")) {

                errString = jsonResponse.getString("httpErrorStatusCode") + " "
                        + jsonResponse.getString("httpErrorMessage");

                if (jsonResponse.has("error")) {
                    JSONObject error = jsonResponse.getJSONObject("error");
                    if (error.has("errors")) {
                        JSONArray errorArray = error.getJSONArray("errors");
                        JSONObject errorObject = errorArray.getJSONObject(0);
                        if (errorObject.has("message"))
                            errString = errString + " Reason: " + errorObject.getString("message");
                    }
                }
            }
        } catch (JSONException e) {
            logger.error("Launches did not come back in the form of a valid list: " + e.getMessage());
            e.printStackTrace();
            throw new CloudException(e);
        }
        if (errString != null) {
            throw new CloudException(errString);
        }
    }

    public @Nonnull String getOperationStatus(String operationUrl, JSONObject response)
            throws CloudException, InternalException {
        if (logger.isTraceEnabled()) {
            logger.trace("ENTER - " + Google.class.getName() + ".getOperationStatus(" + operationUrl + ")");
        }
        if (wire.isDebugEnabled()) {
            wire.debug("");
            wire.debug(">>> [GET OPERATION STATUS (" + (new Date()) + ")] -> " + operationUrl
                    + " >--------------------------------------------------------------------------------------");
        }
        try {
            GoogleMethod method = new GoogleMethod(provider);
            String status = null;
            String operationId = null;

            try {
                if (response != null && response.has("status")) {

                    status = (String) response.getString("status");
                    if (response.has("name"))
                        operationId = response.getString("name");
                    if (logger.isDebugEnabled()) {
                        logger.debug("operationId=" + operationId);
                    }
                }
            } catch (JSONException e) {
                logger.error("Launches did not come back in the form of a valid list: " + e.getMessage());
                e.printStackTrace();
                throw new CloudException(e);
            }
            if (status.equals("DONE")) {
                GoogleMethod.checkError(response);
                return status;
            }

            long timeout = System.currentTimeMillis() + (CalendarWrapper.MINUTE * 15L);
            JSONObject operation = null;
            while (timeout > System.currentTimeMillis()) {
                try {
                    Thread.sleep(150L);
                } catch (InterruptedException ignore) {
                }
                if (!status.equals("DONE")) {
                    JSONArray jobArray = method.get(operationUrl + "/" + operationId);

                    if (jobArray != null) {
                        try {
                            operation = jobArray.getJSONObject(0);
                            status = operation.getString("status");
                        } catch (JSONException e) {
                            logger.error("Operation did not succeed: " + e.getMessage());
                            e.printStackTrace();
                            throw new CloudException(e);
                        }
                    }
                    if (status.equals("DONE")) {
                        GoogleMethod.checkError(response);
                        return status;
                    }
                }
            }
            throw new CloudException("System timed out waiting for Operation to complete");
        } finally {
            if (logger.isTraceEnabled()) {
                logger.trace("EXIT - " + GoogleMethod.class.getName() + ".getOperationStatus()");
            }
            if (wire.isDebugEnabled()) {
                wire.debug("<<< [GET OPERATION STATUS (" + (new Date())
                        + ")] <--------------------------------------------------------------------------------------");
                wire.debug("");
            }
        }
    }
}