Java tutorial
/******************************************************************************* * Copyright 2009-2012 Amazon Services. 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://aws.amazon.com/apache2.0 * This file 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. ******************************************************************************* * Marketplace Web Service Runtime Client Library */ package com.amazon.mws.shared; import java.io.Closeable; import java.net.URI; import java.net.URISyntaxException; import java.util.Collections; import java.util.HashMap; import java.util.Map; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.Callable; import java.util.concurrent.ExecutorService; import java.util.concurrent.Future; import java.util.concurrent.RejectedExecutionException; import java.util.concurrent.RejectedExecutionHandler; import java.util.concurrent.ThreadFactory; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.http.HttpHost; import org.apache.http.auth.AuthScope; import org.apache.http.auth.Credentials; import org.apache.http.auth.UsernamePasswordCredentials; import org.apache.http.client.CredentialsProvider; import org.apache.http.client.HttpClient; import org.apache.http.client.protocol.ClientContext; import org.apache.http.conn.ClientConnectionManager; import org.apache.http.conn.params.ConnRoutePNames; import org.apache.http.impl.client.BasicCredentialsProvider; import org.apache.http.impl.client.DefaultHttpClient; import org.apache.http.impl.conn.PoolingClientConnectionManager; import org.apache.http.params.BasicHttpParams; import org.apache.http.params.CoreProtocolPNames; import org.apache.http.params.HttpConnectionParams; import org.apache.http.protocol.BasicHttpContext; import org.apache.http.protocol.HttpContext; /** * An connection to a server to send requests to for processing. * <p> * Parameters that describe a MwsConnection are Server address and port, caller * credentials and network connection configuration. * <p> * Simplest use case is: * * <pre> * 1) Create an MwsConnection instance. * 2) Configure the connection using setter methods. * 3) Create and initialize a Request object. * 4) Call response = connection.execute(requestObject) * 5) Use the response object or handle exceptions. * 6) Repeat steps 3-5 as necessary. * 7) Close the connection object. * </pre> * * @author mayerj */ public class MwsConnection implements Cloneable, Closeable { /** * Immutable service and version URI for an endPoint. * * @author mayerj */ static class ServiceEndpoint { /** The service name. */ final String service; /** The service and version name as service/version. */ final String servicePath; /** The combined uri of the connection, service name, and version. */ final URI uri; /** The service version. */ final String version; /** * Create a service endPoint. * * @param baseUri * @param servicePath */ ServiceEndpoint(URI baseUri, String servicePath) { this.servicePath = servicePath; int j = servicePath.lastIndexOf('/'); this.service = servicePath.substring(0, j); this.version = servicePath.substring(j + 1); this.uri = baseUri.resolve("/" + servicePath); } } /** Commons logging. */ private static final Log log = LogFactory.getLog(MwsConnection.class); /** The global shared connection manager (lazy created). */ private static ClientConnectionManager sharedCM; /** The global shared executor service (lazy created). */ private static ExecutorService sharedES; /** The client application name. */ private String applicationName; /** The client application version. */ private String applicationVersion; /** The AWS access key id. */ private String awsAccessKeyId; /** The AWS secret key id. */ private String awsSecretKeyId; /** Connection manager to use for httpClient. */ private ClientConnectionManager connectionManager; /** The connection timeout in milliseconds. */ private int connectionTimeout = 50000; /** The socket timeout. */ private int socketTimeout = 50000; /** Max async threads to run concurrently. */ private int maxAsyncThreads = 30; /** Max async queue size before thread stealing. */ private int maxAsyncQueueSize = 300; /** Use endPoint URI. */ private URI endpoint = null; /** Custom executor service for async calls . */ private ExecutorService executorService; /** Set to true when httpClient is created and properties are frozen. */ private volatile boolean frozen; /** Created http client instance. */ private HttpClient httpClient; /** Context to use for httpClient. */ private HttpContext httpContext; /** Max number of connections. */ private int maxConnections = 100; /** Max retry count. */ private int maxErrorRetry = 3; /** The MWS Client library version being used. */ private String libraryVersion = "1.0.0"; /** HTTP proxy host name. */ private String proxyHost = null; /** HTTP proxy password. */ private String proxyPassword = null; /** HTTP proxy port number. */ private int proxyPort = -1; /** HTTP proxy username. */ private String proxyUsername = null; /** A map from servicePath to cached ServiceEndpoint. */ private Map<String, ServiceEndpoint> serviceMap; /** Method to use to create signature. */ private String signatureMethod = "HmacSHA256"; /** Use signature version. */ private String signatureVersion = "2"; /** Send user-agent. */ private String userAgent = null; /** Additional headers to set on requests */ private Map<String, String> headers = new HashMap<String, String>(); /** * Ensure that the connection has not been used and the properties are still * updatable. */ private void checkUpdatable() { if (frozen) { throw new IllegalStateException("Cannot change MwsConnection properties after connected."); } } /** * Initialize the connection. */ synchronized void freeze() { if (frozen) { return; } if (userAgent == null) { setDefaultUserAgent(); } serviceMap = new HashMap<String, ServiceEndpoint>(); if (log.isDebugEnabled()) { StringBuilder buf = new StringBuilder(); buf.append("Creating MwsConnection {"); buf.append("applicationName:"); buf.append(applicationName); buf.append(",applicationVersion:"); buf.append(applicationVersion); buf.append(",awsAccessKeyId:"); buf.append(awsAccessKeyId); buf.append(",uri:"); buf.append(endpoint.toString()); buf.append(",userAgent:"); buf.append(userAgent); buf.append(",connectionTimeout:"); buf.append(connectionTimeout); if (proxyHost != null && proxyPort != 0) { buf.append(",proxyUsername:"); buf.append(proxyUsername); buf.append(",proxyHost:"); buf.append(proxyHost); buf.append(",proxyPort:"); buf.append(proxyPort); } buf.append("}"); log.debug(buf); } BasicHttpParams httpParams = new BasicHttpParams(); httpParams.setParameter(CoreProtocolPNames.USER_AGENT, userAgent); HttpConnectionParams.setConnectionTimeout(httpParams, connectionTimeout); HttpConnectionParams.setSoTimeout(httpParams, socketTimeout); HttpConnectionParams.setStaleCheckingEnabled(httpParams, true); HttpConnectionParams.setTcpNoDelay(httpParams, true); connectionManager = getConnectionManager(); httpClient = new DefaultHttpClient(connectionManager, httpParams); httpContext = new BasicHttpContext(); if (proxyHost != null && proxyPort != 0) { String scheme = MwsUtl.usesHttps(endpoint) ? "https" : "http"; HttpHost hostConfiguration = new HttpHost(proxyHost, proxyPort, scheme); httpClient.getParams().setParameter(ConnRoutePNames.DEFAULT_PROXY, hostConfiguration); if (proxyUsername != null && proxyPassword != null) { Credentials credentials = new UsernamePasswordCredentials(proxyUsername, proxyPassword); CredentialsProvider cprovider = new BasicCredentialsProvider(); cprovider.setCredentials(new AuthScope(proxyHost, proxyPort), credentials); httpContext.setAttribute(ClientContext.CREDS_PROVIDER, cprovider); } } headers = Collections.unmodifiableMap(headers); frozen = true; } /** * Get a connection manager to use for this connection. * <p> * Called late in initialization. * <p> * Default implementation uses a shared PoolingClientConnectionManager. * * @return The connection manager to use. */ private ClientConnectionManager getConnectionManager() { synchronized (this.getClass()) { if (sharedCM == null) { PoolingClientConnectionManager cm = new PoolingClientConnectionManager(); cm.setMaxTotal(maxConnections); cm.setDefaultMaxPerRoute(maxConnections); sharedCM = cm; } return sharedCM; } } /** * Get the shared executor service that is used by async calls if no * executor is supplied. * * @return The shared executor service. */ private ExecutorService getSharedES() { synchronized (this.getClass()) { if (sharedES != null) { return sharedES; } sharedES = new ThreadPoolExecutor(maxAsyncThreads / 10, maxAsyncThreads, 60L, TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(maxAsyncQueueSize), new ThreadFactory() { private final AtomicInteger threadNumber = new AtomicInteger(1); public Thread newThread(Runnable task) { Thread thread = new Thread(task, "MWSClient-" + threadNumber.getAndIncrement()); thread.setDaemon(true); thread.setPriority(Thread.NORM_PRIORITY); return thread; } }, new RejectedExecutionHandler() { public void rejectedExecution(Runnable task, ThreadPoolExecutor executor) { if (!executor.isShutdown()) { log.warn("MWSClient async queue full, running on calling thread."); task.run(); } else { throw new RejectedExecutionException(); } } }); return sharedES; } } /** * Set the default user agent string. Called when connection first opened if * user agent is not set explicitly. */ private void setDefaultUserAgent() { setUserAgent(MwsUtl.escapeAppName(applicationName), MwsUtl.escapeAppVersion(applicationVersion), MwsUtl.escapeAttributeValue("Java/" + System.getProperty("java.version") + "/" + System.getProperty("java.class.version") + "/" + System.getProperty("java.vendor")), MwsUtl.escapeAttributeName("Platform"), MwsUtl.escapeAttributeValue("" + System.getProperty("os.name") + "/" + System.getProperty("os.arch") + "/" + System.getProperty("os.version")), MwsUtl.escapeAttributeName("MWSClientVersion"), MwsUtl.escapeAttributeValue(libraryVersion)); } /** * Get secret key is package protected to keep the secret. * * @return The secret key. */ String getAwsSecretKeyId() { return awsSecretKeyId; } /** * Get HttpClient to use to make requests. * <p> * First call to this method applies defaults and freezes the connections * configuration properties. * * @return The httpClient for this connection. */ HttpClient getHttpClient() { return httpClient; } /** * Get the HttpContext for this connection. * * @return The HttpContext. */ HttpContext getHttpContext() { return httpContext; } /** * Get a MwsServiceUri for the servicePath. * * @param servicePath * The service name and version as name/version, no leading or * trailing slash. * * @return The service endPoint instance. */ ServiceEndpoint getServiceEndpoint(String servicePath) { synchronized (serviceMap) { ServiceEndpoint sep = serviceMap.get(servicePath); if (sep == null) { sep = new ServiceEndpoint(endpoint, servicePath); serviceMap.put(servicePath, sep); } return sep; } } /** * Call an operation and return the response data. * * @param requestData * The request data. * * @return The response data. */ @SuppressWarnings("unchecked") public <T extends MwsObject> T call(MwsRequestType type, MwsObject requestData) { MwsReader responseReader = null; try { String servicePath = type.getServicePath(); String operationName = type.getOperationName(); MwsCall mc = newCall(servicePath, operationName); requestData.writeFragmentTo(mc); responseReader = mc.invoke(); MwsResponseHeaderMetadata rhmd = mc.getResponseHeaderMetadata(); MwsObject response = MwsUtl.newInstance(type.getResponseClass()); type.setRHMD(response, rhmd); response.readFragmentFrom(responseReader); return (T) response; } catch (Exception e) { throw type.wrapException(e); } finally { MwsUtl.close(responseReader); } } /** * Call a request async, return a future on the response data. * * @param requestData * * @return A future on the response data. */ public <T extends MwsObject> Future<T> callAsync(final MwsRequestType type, final MwsObject requestData) { return getExecutorService().submit(new Callable<T>() { public T call() { return MwsConnection.this.call(type, requestData); } }); } /** * Clone this connection, creates an unconnected connection with all the * same settings. But in a state as if it were never used. */ @Override public synchronized MwsConnection clone() { try { MwsConnection a = (MwsConnection) super.clone(); a.serviceMap = null; a.connectionManager = null; a.httpClient = null; a.httpContext = null; a.frozen = false; return a; } catch (Exception e) { throw MwsUtl.wrap(e); } } public void close() { if (connectionManager != null) { this.connectionManager.closeIdleConnections(0, TimeUnit.MILLISECONDS); } } /** * Get the client application name. * * @return The client application name. */ public String getApplicationName() { return applicationName; } /** * Get the client application version. * * @return The client application version. */ public String getApplicationVersion() { return applicationVersion; } /** * Get the configured AWS access key id. * * @return The AWS access key id. */ public String getAwsAccessKeyId() { return awsAccessKeyId; } /** * Get the connection timeout. * * @return The connection timeout in milliseconds. */ public int getConnectionTimeout() { return connectionTimeout; } /** * Gets end point property. * * @return Service end point URI */ public URI getEndpoint() { return endpoint; } /** * Get the executor service that will be used for async requests. * * @return The service. */ public synchronized ExecutorService getExecutorService() { if (executorService == null) { executorService = getSharedES(); } return executorService; } /** * Get the client library version. * * @return The client library version. */ public String getLibraryVersion() { return libraryVersion; } /** * Get the MaxAsyncQueueSize property. * * @return MaxAsyncQueueSize. */ public int getMaxAsyncQueueSize() { return maxAsyncQueueSize; } /** * Get MaxAsyncThreads property. * * @return Max number of threads to be used for async operations */ public int getMaxAsyncThreads() { return maxAsyncThreads; } /** * Gets MaxConnections property * * @return Max number of http connections */ public int getMaxConnections() { return maxConnections; } /** * Gets MaxErrorRetry property * * @return Max number of retries on 500th errors */ public int getMaxErrorRetry() { return maxErrorRetry; } /** * Gets ProxyHost property * * @return Proxy Host for connection */ public String getProxyHost() { return proxyHost; } /** * Gets ProxyPassword property * * @return Proxy Password */ public String getProxyPassword() { return proxyPassword; } /** * Gets ProxyPort property. * * @return Proxy Port for connection. */ public int getProxyPort() { return proxyPort; } /** * Gets the proxy username property. * * @return Proxy username. */ public String getProxyUsername() { return proxyUsername; } /** * Gets SignatureMethod property * * @return Signature Method for signing requests */ public String getSignatureMethod() { return signatureMethod; } /** * Gets SignatureVersion property * * @return Signature Version for signing requests */ public String getSignatureVersion() { return signatureVersion; } /** * Gets SocketTimeout property * * @return Socket Timeout for waiting for data */ public int getSocketTimeout() { return socketTimeout; } /** * Gets UserAgent property * * @return User Agent String to use when sending request */ public String getUserAgent() { return userAgent; } /** * Gets the currently set request headers * * @return Map of all set request headers */ protected Map<String, String> getRequestHeaders() { return headers; } /** * Gets the currently set value of a request header * * @param name the name of the header to get * @return value of specified header, or null if not defined */ public String getRequestHeader(String name) { return headers.get(name); } /** * Create a new request. * <p> * After first call to this method connection parameters can no longer be * updated. * * @param servicePath * @param operationName * * @return A new request. */ public MwsCall newCall(String servicePath, String operationName) { if (!frozen) { freeze(); } ServiceEndpoint sep = getServiceEndpoint(servicePath); // in future use sep+config to determine MwsCall implementation. return new MwsAQCall(this, sep, operationName); } /** * Set the application name. * * @param name */ public synchronized void setApplicationName(String name) { checkUpdatable(); applicationName = name; } /** * Set the application version. * * @param version */ public synchronized void setApplicationVersion(String version) { checkUpdatable(); applicationVersion = version; } /** * Set the access key id. * * @param awsAccessKeyId */ public synchronized void setAwsAccessKeyId(String awsAccessKeyId) { checkUpdatable(); this.awsAccessKeyId = awsAccessKeyId; } /** * Set the secret key. * * @param awsSecretKeyId */ public synchronized void setAwsSecretKeyId(String awsSecretKeyId) { checkUpdatable(); this.awsSecretKeyId = awsSecretKeyId; } /** * Set the connection timeout. * * @param timeout * The connection timeout in milliseconds; */ public synchronized void setConnectionTimeout(int timeout) { checkUpdatable(); this.connectionTimeout = timeout; } /** * Sets service endPoint property. * * @param endpoint * The service end point URI. * */ public synchronized void setEndpoint(URI endpoint) { checkUpdatable(); int port = endpoint.getPort(); if (port == 80 || port == 443) { if (MwsUtl.usesStandardPort(endpoint)) { try { //some versions of apache http client cause signature errors when //standard port is explicitly used, so remove that case. endpoint = new URI(endpoint.getScheme(), endpoint.getHost(), endpoint.getPath(), endpoint.getFragment()); } catch (URISyntaxException e) { throw new IllegalArgumentException(e); } } } this.endpoint = endpoint; } /** * Set the executor service to use for async requests. * * @param executor */ public synchronized void setExecutorService(ExecutorService executor) { checkUpdatable(); this.executorService = executor; } /** * Set the client library version. * <p> * Used to create default user agent string. * * @param libraryVersion * */ public synchronized void setLibraryVersion(String libraryVersion) { checkUpdatable(); this.libraryVersion = libraryVersion; } /** * Set MaxAsyncQueueSize property. * <p> * When the async queue is full further async calls will execute on the * calling thread. * * see {@link #setMaxAsyncThreads(int)} * * @param maxAsyncQueueSize */ public synchronized void setMaxAsyncQueueSize(int maxAsyncQueueSize) { checkUpdatable(); this.maxAsyncQueueSize = maxAsyncQueueSize; } /** * Set MaxAsyncThreads property. * <p> * This is the maximum number of threads to spawn for async operations. * <p> * When Max number of threads is reached, requests will be stored in the * queue. When queue becomes full, request will be executed on the calling * thread. * <p> * Only used to configure default shared executor service when it is first * created. If caller supplies an executor service for a connection or the * shared executor already exists then this parameter is ignored. * * @param maxAsyncThreads */ public synchronized void setMaxAsyncThreads(int maxAsyncThreads) { checkUpdatable(); this.maxAsyncThreads = maxAsyncThreads; } /** * Set MaxConnections property. * <p> * All MwsConnection instances share the same connection pool. Only the * first MwsConnection configures the pool size. * * @param maxConnections * Max number of http connections * */ public synchronized void setMaxConnections(int maxConnections) { checkUpdatable(); this.maxConnections = maxConnections; } /** * Sets MaxErrorRetry property * * @param maxErrorRetry * Max number of retries on 50x errors * */ public synchronized void setMaxErrorRetry(int maxErrorRetry) { checkUpdatable(); this.maxErrorRetry = maxErrorRetry; } /** * Sets ProxyHost property * * @param proxyHost * Proxy Host for connection * */ public synchronized void setProxyHost(String proxyHost) { checkUpdatable(); this.proxyHost = proxyHost; } /** * Sets ProxyPassword property * * @param proxyPassword * Proxy Password for connection * */ public synchronized void setProxyPassword(String proxyPassword) { checkUpdatable(); this.proxyPassword = proxyPassword; } /** * Sets ProxyPort property. * * @param proxyPort * Proxy Port for connection. * */ public synchronized void setProxyPort(int proxyPort) { checkUpdatable(); this.proxyPort = proxyPort; } /** * Sets ProxyUsername property. * * @param proxyUsername * Proxy username for connection. * */ public synchronized void setProxyUsername(String proxyUsername) { checkUpdatable(); this.proxyUsername = proxyUsername; } /** * Sets SignatureMethod property * * @param signatureMethod * Signature Method for signing requests */ public synchronized void setSignatureMethod(String signatureMethod) { checkUpdatable(); this.signatureMethod = signatureMethod; } /** * Sets SignatureVersion property * * @param signatureVersion * Signature Version for signing requests */ public synchronized void setSignatureVersion(String signatureVersion) { checkUpdatable(); this.signatureVersion = signatureVersion; } /** * Sets SocketTimeout property * * @param socketTimeout * Timeout for waiting for data */ public synchronized void setSocketTimeout(int socketTimeout) { checkUpdatable(); this.socketTimeout = socketTimeout; } /** * Sets UserAgent property * * @param applicationName * @param applicationVersion * @param programmingLanguage * @param additionalNameValuePairs * */ public synchronized void setUserAgent(String applicationName, String applicationVersion, String programmingLanguage, String... additionalNameValuePairs) { checkUpdatable(); StringBuilder b = new StringBuilder(); b.append(applicationName); b.append("/"); b.append(applicationVersion); b.append(" (Language="); b.append(programmingLanguage); int i = 0; while (i < additionalNameValuePairs.length) { String name = additionalNameValuePairs[i]; String value = additionalNameValuePairs[i + 1]; b.append("; "); b.append(name); b.append("="); b.append(value); i += 2; } b.append(")"); this.userAgent = b.toString(); } /** * Sets the value of a request header to be included on every request * * @param name the name of the header to set * @param value value to send with header */ public void includeRequestHeader(String name, String value) { checkUpdatable(); this.headers.put(name, value); } /** * Create with defaults. * */ public MwsConnection() { } /** * Create a connection. * * @param endpoint * The endPoint URI. * * @param applicationName * The calling applications name. * * @param applicationVersion * The calling applications version. * * @param awsAccessKeyId * The developer access key id. * * @param awsSecretKeyId * The developers secret key id. */ public MwsConnection(URI endpoint, String applicationName, String applicationVersion, String awsAccessKeyId, String awsSecretKeyId) { this(); setEndpoint(endpoint); this.applicationName = applicationName; this.applicationVersion = applicationVersion; this.awsAccessKeyId = awsAccessKeyId; this.awsSecretKeyId = awsSecretKeyId; } }