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

com.google.maps.GeoApiContext Maven / Gradle / Ivy

Go to download

Use the Google Maps API Web Services in Java! https://developers.google.com/maps/documentation/webservices/

There is a newer version: 2.2.0
Show newest version
/*
 * 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.errors.ApiException;
import com.google.maps.errors.OverQueryLimitException;
import com.google.maps.internal.ApiConfig;
import com.google.maps.internal.ApiResponse;
import com.google.maps.internal.ExceptionsAllowedToRetry;
import com.google.maps.internal.HttpHeaders;
import com.google.maps.internal.StringJoin;
import com.google.maps.internal.UrlSigner;
import com.google.maps.metrics.NoOpRequestMetricsReporter;
import com.google.maps.metrics.RequestMetrics;
import com.google.maps.metrics.RequestMetricsReporter;
import java.io.Closeable;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.Proxy;
import java.net.URLEncoder;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;

/**
 * The entry point for making requests against the Google Geo APIs.
 *
 * 

Construct this object by using the enclosed {@link GeoApiContext.Builder}. * *

GeoApiContexts should be shared

* * GeoApiContext works best when you create a single GeoApiContext instance, or one per API key, and * reuse it for all your Google Geo API queries. This is because each GeoApiContext manages its own * thread pool, back-end client, and other resources. * *

When you are finished with a GeoApiContext object, you must call {@link #shutdown()} on it to * release its resources. */ public class GeoApiContext implements Closeable { private static final String VERSION = "@VERSION@"; // Populated by the build script private static final String USER_AGENT = "GoogleGeoApiClientJava/" + VERSION; private static final int DEFAULT_BACKOFF_TIMEOUT_MILLIS = 60 * 1000; // 60s private final RequestHandler requestHandler; private final String apiKey; private final String baseUrlOverride; private final String channel; private final String clientId; private final long errorTimeout; private final ExceptionsAllowedToRetry exceptionsAllowedToRetry; private final Integer maxRetries; private final UrlSigner urlSigner; private String experienceIdHeaderValue; private final RequestMetricsReporter requestMetricsReporter; /* package */ GeoApiContext( RequestHandler requestHandler, String apiKey, String baseUrlOverride, String channel, String clientId, long errorTimeout, ExceptionsAllowedToRetry exceptionsAllowedToRetry, Integer maxRetries, UrlSigner urlSigner, RequestMetricsReporter requestMetricsReporter, String... experienceIdHeaderValue) { this.requestHandler = requestHandler; this.apiKey = apiKey; this.baseUrlOverride = baseUrlOverride; this.channel = channel; this.clientId = clientId; this.errorTimeout = errorTimeout; this.exceptionsAllowedToRetry = exceptionsAllowedToRetry; this.maxRetries = maxRetries; this.urlSigner = urlSigner; this.requestMetricsReporter = requestMetricsReporter; setExperienceId(experienceIdHeaderValue); } /** * standard Java API to reclaim resources * * @throws IOException */ @Override public void close() throws IOException { shutdown(); } /** * The service provider interface that enables requests to be handled via switchable back ends. * There are supplied implementations of this interface for both OkHttp and Google App Engine's * URL Fetch API. * * @see OkHttpRequestHandler * @see GaeRequestHandler */ public interface RequestHandler { > PendingResult handle( String hostName, String url, String userAgent, String experienceIdHeaderValue, Class clazz, FieldNamingPolicy fieldNamingPolicy, long errorTimeout, Integer maxRetries, ExceptionsAllowedToRetry exceptionsAllowedToRetry, RequestMetrics metrics); > PendingResult handlePost( String hostName, String url, String payload, String userAgent, String experienceIdHeaderValue, Class clazz, FieldNamingPolicy fieldNamingPolicy, long errorTimeout, Integer maxRetries, ExceptionsAllowedToRetry exceptionsAllowedToRetry, RequestMetrics metrics); void shutdown(); /** Builder pattern for {@code GeoApiContext.RequestHandler}. */ interface Builder { Builder connectTimeout(long timeout, TimeUnit unit); Builder readTimeout(long timeout, TimeUnit unit); Builder writeTimeout(long timeout, TimeUnit unit); Builder queriesPerSecond(int maxQps); Builder proxy(Proxy proxy); Builder proxyAuthentication(String proxyUserName, String proxyUserPassword); RequestHandler build(); } } /** * Sets the value for the HTTP header field name {@link HttpHeaders#X_GOOG_MAPS_EXPERIENCE_ID} to * be used on subsequent API calls. Calling this method with {@code null} is equivalent to calling * {@link #clearExperienceId()}. * * @param experienceId The experience ID if set, otherwise null */ public void setExperienceId(String... experienceId) { if (experienceId == null || experienceId.length == 0) { experienceIdHeaderValue = null; return; } experienceIdHeaderValue = StringJoin.join(",", experienceId); } /** @return Returns the experience ID if set, otherwise, null */ public String getExperienceId() { return experienceIdHeaderValue; } /** * Clears the experience ID if set the HTTP header field {@link * HttpHeaders#X_GOOG_MAPS_EXPERIENCE_ID} will be omitted from subsequent calls. */ public void clearExperienceId() { experienceIdHeaderValue = null; } /** * Shut down this GeoApiContext instance, reclaiming resources. After shutdown() has been called, * no further queries may be done against this instance. */ public void shutdown() { requestHandler.shutdown(); } > PendingResult get( ApiConfig config, Class clazz, Map> params) { if (channel != null && !channel.isEmpty() && !params.containsKey("channel")) { params.put("channel", Collections.singletonList(channel)); } StringBuilder query = new StringBuilder(); for (Map.Entry> param : params.entrySet()) { List values = param.getValue(); for (String value : values) { query.append('&').append(param.getKey()).append("="); try { query.append(URLEncoder.encode(value, "UTF-8")); } catch (UnsupportedEncodingException e) { // This should never happen. UTF-8 support is required for every Java implementation. throw new IllegalStateException(e); } } } return getWithPath( clazz, config.fieldNamingPolicy, config.hostName, config.path, config.supportsClientId, query.toString(), requestMetricsReporter.newRequest(config.path)); } > PendingResult get( ApiConfig config, Class clazz, String... params) { if (params.length % 2 != 0) { throw new IllegalArgumentException("Params must be matching key/value pairs."); } StringBuilder query = new StringBuilder(); boolean channelSet = false; for (int i = 0; i < params.length; i += 2) { if (params[i].equals("channel")) { channelSet = true; } query.append('&').append(params[i]).append('='); // URL-encode the parameter. try { query.append(URLEncoder.encode(params[i + 1], "UTF-8")); } catch (UnsupportedEncodingException e) { // This should never happen. UTF-8 support is required for every Java implementation. throw new IllegalStateException(e); } } // Channel can be supplied per-request or per-context. We prioritize it from the request, // so if it's not provided there, provide it here if (!channelSet && channel != null && !channel.isEmpty()) { query.append("&channel=").append(channel); } return getWithPath( clazz, config.fieldNamingPolicy, config.hostName, config.path, config.supportsClientId, query.toString(), requestMetricsReporter.newRequest(config.path)); } > PendingResult post( ApiConfig config, Class clazz, Map> params) { checkContext(config.supportsClientId); StringBuilder url = new StringBuilder(config.path); if (config.supportsClientId && clientId != null) { url.append("?client=").append(clientId); } else { url.append("?key=").append(apiKey); } if (config.supportsClientId && urlSigner != null) { String signature = urlSigner.getSignature(url.toString()); url.append("&signature=").append(signature); } String hostName = config.hostName; if (baseUrlOverride != null) { hostName = baseUrlOverride; } return requestHandler.handlePost( hostName, url.toString(), params.get("_payload").get(0), USER_AGENT, experienceIdHeaderValue, clazz, config.fieldNamingPolicy, errorTimeout, maxRetries, exceptionsAllowedToRetry, requestMetricsReporter.newRequest(config.path)); } private > PendingResult getWithPath( Class clazz, FieldNamingPolicy fieldNamingPolicy, String hostName, String path, boolean canUseClientId, String encodedPath, RequestMetrics metrics) { checkContext(canUseClientId); if (!encodedPath.startsWith("&")) { throw new IllegalArgumentException("encodedPath must start with &"); } StringBuilder url = new StringBuilder(path); if (canUseClientId && clientId != null) { url.append("?client=").append(clientId); } else { url.append("?key=").append(apiKey); } url.append(encodedPath); if (canUseClientId && urlSigner != null) { String signature = urlSigner.getSignature(url.toString()); url.append("&signature=").append(signature); } if (baseUrlOverride != null) { hostName = baseUrlOverride; } return requestHandler.handle( hostName, url.toString(), USER_AGENT, experienceIdHeaderValue, clazz, fieldNamingPolicy, errorTimeout, maxRetries, exceptionsAllowedToRetry, metrics); } private void checkContext(boolean canUseClientId) { if (urlSigner == null && apiKey == null) { throw new IllegalStateException("Must provide either API key or Maps for Work credentials."); } else if (!canUseClientId && apiKey == null) { throw new IllegalStateException( "API does not support client ID & secret - you must provide a key"); } if (urlSigner == null && !apiKey.startsWith("AIza")) { throw new IllegalStateException("Invalid API key."); } } /** The Builder for {@code GeoApiContext}. */ public static class Builder { private RequestHandler.Builder builder; private String apiKey; private String baseUrlOverride; private String channel; private String clientId; private long errorTimeout = DEFAULT_BACKOFF_TIMEOUT_MILLIS; private ExceptionsAllowedToRetry exceptionsAllowedToRetry = new ExceptionsAllowedToRetry(); private Integer maxRetries; private UrlSigner urlSigner; private RequestMetricsReporter requestMetricsReporter = new NoOpRequestMetricsReporter(); private String[] experienceIdHeaderValue; /** Builder pattern for the enclosing {@code GeoApiContext}. */ public Builder() { requestHandlerBuilder(new OkHttpRequestHandler.Builder()); } public Builder(RequestHandler.Builder builder) { requestHandlerBuilder(builder); } /** * Changes the RequestHandler.Builder strategy to change between the {@code * OkHttpRequestHandler} and the {@code GaeRequestHandler}. * * @param builder The {@code RequestHandler.Builder} to use for {@link #build()} * @return Returns this builder for call chaining. * @see OkHttpRequestHandler * @see GaeRequestHandler */ public Builder requestHandlerBuilder(RequestHandler.Builder builder) { this.builder = builder; this.exceptionsAllowedToRetry.add(OverQueryLimitException.class); return this; } /** * Overrides the base URL of the API endpoint. Useful for testing or certain international usage * scenarios. * * @param baseUrl The URL to use, without a trailing slash, e.g. https://maps.googleapis.com * @return Returns this builder for call chaining. */ Builder baseUrlOverride(String baseUrl) { baseUrlOverride = baseUrl; return this; } /** * Older name for {@link #baseUrlOverride(String)}. This was used back when testing was the only * use case foreseen for this. * * @deprecated Use baseUrlOverride(String) instead. * @param baseUrl The URL to use, without a trailing slash, e.g. https://maps.googleapis.com * @return Returns this builder for call chaining. */ @Deprecated Builder baseUrlForTesting(String baseUrl) { return baseUrlOverride(baseUrl); } /** * Sets the API Key to use for authorizing requests. * * @param apiKey The API Key to use. * @return Returns this builder for call chaining. */ public Builder apiKey(String apiKey) { this.apiKey = apiKey; return this; } /** * Sets the ClientID/Secret pair to use for authorizing requests. Most users should use {@link * #apiKey(String)} instead. * * @param clientId The Client ID to use. * @param cryptographicSecret The Secret to use. * @return Returns this builder for call chaining. */ public Builder enterpriseCredentials(String clientId, String cryptographicSecret) { this.clientId = clientId; try { this.urlSigner = new UrlSigner(cryptographicSecret); } catch (NoSuchAlgorithmException | InvalidKeyException e) { throw new IllegalStateException(e); } return this; } /** * Sets the default channel for requests (can be overridden by requests). Only useful for Google * Maps for Work clients. * * @param channel The channel to use for analytics * @return Returns this builder for call chaining. */ public Builder channel(String channel) { this.channel = channel; return this; } /** * Sets the default connect timeout for new connections. A value of 0 means no timeout. * * @see java.net.URLConnection#setConnectTimeout(int) * @param timeout The connect timeout period in {@code unit}s. * @param unit The connect timeout time unit. * @return Returns this builder for call chaining. */ public Builder connectTimeout(long timeout, TimeUnit unit) { builder.connectTimeout(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) * @param timeout The read timeout period in {@code unit}s. * @param unit The read timeout time unit. * @return Returns this builder for call chaining. */ public Builder readTimeout(long timeout, TimeUnit unit) { builder.readTimeout(timeout, unit); return this; } /** * Sets the default write timeout for new connections. A value of 0 means no timeout. * * @param timeout The write timeout period in {@code unit}s. * @param unit The write timeout time unit. * @return Returns this builder for call chaining. */ public Builder writeTimeout(long timeout, TimeUnit unit) { builder.writeTimeout(timeout, unit); return this; } /** * Sets the cumulative time limit for which retry-able errors will be retried. Defaults to 60 * seconds. Set to zero to retry requests forever. * *

This operates separately from the count-based {@link #maxRetries(Integer)}. * * @param timeout The retry timeout period in {@code unit}s. * @param unit The retry timeout time unit. * @return Returns this builder for call chaining. */ public Builder retryTimeout(long timeout, TimeUnit unit) { this.errorTimeout = unit.toMillis(timeout); return this; } /** * Sets the maximum number of times each retry-able errors will be retried. Set this to null to * not have a max number. Set this to zero to disable retries. * *

This operates separately from the time-based {@link #retryTimeout(long, TimeUnit)}. * * @param maxRetries The maximum number of times to retry. * @return Returns this builder for call chaining. */ public Builder maxRetries(Integer maxRetries) { this.maxRetries = maxRetries; return this; } /** * Disables retries completely, by setting max retries to 0 and retry timeout to 0. * * @return Returns this builder for call chaining. */ public Builder disableRetries() { maxRetries(0); retryTimeout(0, TimeUnit.MILLISECONDS); return this; } /** * Sets the maximum number of queries that will be executed during a 1 second interval. The * default is 50. A minimum interval between requests will also be enforced, set to 1/(2 * * {@code maxQps}). * * @param maxQps The maximum queries per second. * @return Returns this builder for call chaining. */ public Builder queryRateLimit(int maxQps) { builder.queriesPerSecond(maxQps); return this; } /** * Allows specific API exceptions to be retried or not retried. * * @param exception The {@code ApiException} to allow or deny being re-tried. * @param allowedToRetry Whether to allow or deny re-trying {@code exception}. * @return Returns this builder for call chaining. */ public Builder setIfExceptionIsAllowedToRetry( Class exception, boolean allowedToRetry) { if (allowedToRetry) { exceptionsAllowedToRetry.add(exception); } else { exceptionsAllowedToRetry.remove(exception); } return this; } /** * Sets the proxy for new connections. * * @param proxy The proxy to be used by the underlying HTTP client. * @return Returns this builder for call chaining. */ public Builder proxy(Proxy proxy) { builder.proxy(proxy == null ? Proxy.NO_PROXY : proxy); return this; } /** * set authentication for proxy * * @param proxyUserName username for proxy authentication * @param proxyUserPassword username for proxy authentication * @return Returns this builder for call chaining. */ public Builder proxyAuthentication(String proxyUserName, String proxyUserPassword) { builder.proxyAuthentication(proxyUserName, proxyUserPassword); return this; } /** * Sets the value for the HTTP header field name {@link HttpHeaders#X_GOOG_MAPS_EXPERIENCE_ID} * HTTP header value for the field name on subsequent API calls. * * @param experienceId The experience ID * @return Returns this builder for call chaining. */ public Builder experienceId(String... experienceId) { this.experienceIdHeaderValue = experienceId; return this; } public Builder requestMetricsReporter(RequestMetricsReporter requestMetricsReporter) { this.requestMetricsReporter = requestMetricsReporter; return this; } /** * Converts this builder into a {@code GeoApiContext}. * * @return Returns the built {@code GeoApiContext}. */ public GeoApiContext build() { return new GeoApiContext( builder.build(), apiKey, baseUrlOverride, channel, clientId, errorTimeout, exceptionsAllowedToRetry, maxRetries, urlSigner, requestMetricsReporter, experienceIdHeaderValue); } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy