com.google.maps.GeoApiContext.java Source code

Java tutorial

Introduction

Here is the source code for com.google.maps.GeoApiContext.java

Source

/*
 * Copyright 2014 Google 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 com.google.maps;

import com.google.gson.FieldNamingPolicy;
import com.google.maps.internal.ApiResponse;
import com.google.maps.internal.ExceptionResult;
import com.google.maps.internal.OkHttpPendingResult;
import com.google.maps.internal.RateLimitExecutorService;
import com.google.maps.internal.UrlSigner;

import com.squareup.okhttp.Dispatcher;
import com.squareup.okhttp.OkHttpClient;
import com.squareup.okhttp.Request;

import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * The entry point for making requests against the Google Geo APIs.
 */
public class GeoApiContext {
    private static final String VERSION = "@VERSION@"; // Populated by the build script
    private static final String DEFAULT_HOST = "https://maps.googleapis.com";
    private static final String USER_AGENT = "GoogleGeoApiClientJava/" + VERSION;
    private static final int DEFAULT_BACKOFF_TIMEOUT_MILLIS = 60 * 1000; // 60s

    private String host = DEFAULT_HOST;
    private String apiKey;
    private String clientId;
    private UrlSigner urlSigner;
    private final OkHttpClient client = new OkHttpClient();
    private final RateLimitExecutorService rateLimitExecutorService;

    private static Logger log = Logger.getLogger(GeoApiContext.class.getName());
    private long errorTimeout = DEFAULT_BACKOFF_TIMEOUT_MILLIS;

    public GeoApiContext() {
        rateLimitExecutorService = new RateLimitExecutorService();
        client.setDispatcher(new Dispatcher(rateLimitExecutorService));
    }

    <T, R extends ApiResponse<T>> PendingResult<T> get(Class<R> clazz, String path, Map<String, String> params) {
        StringBuilder query = new StringBuilder();

        for (Map.Entry<String, String> param : params.entrySet()) {
            query.append('&').append(param.getKey()).append("=");
            try {
                query.append(URLEncoder.encode(param.getValue(), "UTF-8"));
            } catch (UnsupportedEncodingException e) {
                return new ExceptionResult<T>(e);
            }
        }
        return getWithPath(clazz, FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES, path, query.toString());
    }

    <T, R extends ApiResponse<T>> PendingResult<T> get(Class<R> clazz, String path, String... params) {
        return get(clazz, FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES, path, params);
    }

    <T, R extends ApiResponse<T>> PendingResult<T> get(Class<R> clazz, FieldNamingPolicy fieldNamingPolicy,
            String path, String... params) {
        if (params.length % 2 != 0) {
            throw new IllegalArgumentException("Params must be matching key/value pairs.");
        }

        StringBuilder query = new StringBuilder();

        for (int i = 0; i < params.length; i++) {
            query.append('&').append(params[i]).append('=');
            i++;

            // URL-encode the parameter.
            try {
                query.append(URLEncoder.encode(params[i], "UTF-8"));
            } catch (UnsupportedEncodingException e) {
                return new ExceptionResult<T>(e);
            }
        }

        return getWithPath(clazz, fieldNamingPolicy, path, query.toString());
    }

    private <T, R extends ApiResponse<T>> PendingResult<T> getWithPath(Class<R> clazz,
            FieldNamingPolicy fieldNamingPolicy, String path, String encodedPath) {
        checkContext();
        if (!encodedPath.startsWith("&")) {
            throw new IllegalArgumentException("encodedPath must start with &");
        }

        StringBuilder url = new StringBuilder(path);
        if (clientId != null) {
            url.append("?client=").append(clientId);
        } else {
            url.append("?key=").append(apiKey);
        }
        url.append(encodedPath);

        if (clientId != null) {
            try {
                String signature = urlSigner.getSignature(url.toString());
                url.append("&signature=").append(signature);
            } catch (Exception e) {
                return new ExceptionResult<T>(e);
            }
        }

        Request req = new Request.Builder().get().header("User-Agent", USER_AGENT).url(host + url).build();

        log.log(Level.INFO, "Request: {0}", host + url);

        return new OkHttpPendingResult<T, R>(req, client, clazz, fieldNamingPolicy, errorTimeout);
    }

    private void checkContext() {
        if (urlSigner == null && apiKey == null) {
            throw new IllegalStateException("Must provide either API key or Maps for Work credentials.");
        }
        if (urlSigner == null && !apiKey.startsWith("AIza")) {
            throw new IllegalStateException("Invalid API key.");
        }
    }

    /**
     * Override the base URL of the API endpoint. Useful only for testing.
     * @param baseUrl  The URL to use, without a trailing slash, e.g. https://maps.googleapis.com
     */
    GeoApiContext setBaseUrl(String baseUrl) {
        host = baseUrl;
        return this;
    }

    public GeoApiContext setApiKey(String apiKey) {
        this.apiKey = apiKey;
        return this;
    }

    public GeoApiContext setEnterpriseCredentials(String clientId, String cryptographicSecret) {
        this.clientId = clientId;
        this.urlSigner = new UrlSigner(cryptographicSecret);
        return this;
    }

    /**
     * Sets the default connect timeout for new connections. A value of 0 means no timeout.
     *
     * @see java.net.URLConnection#setConnectTimeout(int)
     */
    public GeoApiContext setConnectTimeout(long timeout, TimeUnit unit) {
        client.setConnectTimeout(timeout, unit);
        return this;
    }

    /**
     * Sets the default read timeout for new connections. A value of 0 means no timeout.
     *
     * @see java.net.URLConnection#setReadTimeout(int)
     */
    public GeoApiContext setReadTimeout(long timeout, TimeUnit unit) {
        client.setReadTimeout(timeout, unit);
        return this;
    }

    /**
     * Sets the default write timeout for new connections. A value of 0 means no timeout.
     */
    public GeoApiContext setWriteTimeout(long timeout, TimeUnit unit) {
        client.setWriteTimeout(timeout, unit);
        return this;
    }

    /**
     * Sets the time limit for which retry-able errors will be retried. Defaults to 60 seconds. Set
     * to zero to disable.
     */
    public GeoApiContext setRetryTimeout(long timeout, TimeUnit unit) {
        this.errorTimeout = unit.toMillis(timeout);
        return this;
    }

    /**
     * Sets the maximum number of queries that will be executed during a 1 second interval.
     * The default is 10. A minimum interval between requests will also be enforced,
     * set to 1/(2 * {@code maxQps}).
     */
    public GeoApiContext setQueryRateLimit(int maxQps) {
        rateLimitExecutorService.setQueriesPerSecond(maxQps);
        return this;
    }

    /**
     * Sets the rate at which queries are executed.
     *
     * @param maxQps The maximum number of queries to execute per second.
     * @param minimumInterval The minimum amount of time, in milliseconds, to pause between requests.
     * Note that this pause only occurs if the amount of time between requests has not elapsed
     * naturally.
     */
    public GeoApiContext setQueryRateLimit(int maxQps, int minimumInterval) {
        rateLimitExecutorService.setQueriesPerSecond(maxQps, minimumInterval);
        return this;
    }
}