Java tutorial
// // typica - A client library for Amazon Web Services // Copyright (C) 2007 Xerox Corporation // // 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.xerox.amazonws.common; import java.io.ByteArrayInputStream; import java.io.InputStream; import java.io.InputStreamReader; import java.io.IOException; import java.io.StringWriter; import java.io.UnsupportedEncodingException; import java.net.HttpURLConnection; import java.net.MalformedURLException; import java.net.SocketException; import java.net.URL; import java.net.URLEncoder; import java.text.Collator; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Collections; import java.util.Date; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Properties; import java.util.SortedMap; import java.util.TimeZone; import java.util.TreeMap; import javax.xml.bind.JAXBException; import javax.xml.bind.UnmarshalException; import org.apache.commons.httpclient.Header; import org.apache.commons.httpclient.HostConfiguration; import org.apache.commons.httpclient.HttpClient; import org.apache.commons.httpclient.HttpException; import org.apache.commons.httpclient.HttpMethodBase; import org.apache.commons.httpclient.MultiThreadedHttpConnectionManager; import org.apache.commons.httpclient.NTCredentials; import org.apache.commons.httpclient.URI; import org.apache.commons.httpclient.UsernamePasswordCredentials; import org.apache.commons.httpclient.auth.AuthScope; import org.apache.commons.httpclient.params.HttpConnectionManagerParams; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import com.xerox.amazonws.typica.jaxb.Response; import com.xerox.amazonws.typica.sqs2.jaxb.Error; import com.xerox.amazonws.typica.sqs2.jaxb.ErrorResponse; /** * This class provides an interface with the Amazon SQS service. It provides high level * methods for listing and creating message queues. * * @author D. Kavanagh * @author developer@dotech.com */ public class AWSQueryConnection extends AWSConnection { private static final Log log = LogFactory.getLog(AWSQueryConnection.class); // this is the number of automatic retries private int maxRetries = 5; private String userAgent = "typica/"; private int sigVersion = 1; private HttpClient hc = null; private int maxConnections = 100; private String proxyHost = null; private int proxyPort; private String proxyUser; private String proxyPassword; private String proxyDomain; // for ntlm authentication /** * Initializes the queue service with your AWS login information. * * @param awsAccessId The your user key into AWS * @param awsSecretKey The secret string used to generate signatures for authentication. * @param isSecure True if the data should be encrypted on the wire on the way to or from SQS. * @param server Which host to connect to. Usually, this will be s3.amazonaws.com * @param port Which port to use. */ public AWSQueryConnection(String awsAccessId, String awsSecretKey, boolean isSecure, String server, int port) { super(awsAccessId, awsSecretKey, isSecure, server, port); String version = "?"; try { Properties props = new Properties(); props.load(this.getClass().getClassLoader().getResourceAsStream("version.properties")); version = props.getProperty("version"); } catch (Exception ex) { } userAgent = userAgent + version + " (" + System.getProperty("os.arch") + "; " + System.getProperty("os.name") + ")"; } /** * This method returns the number of connections that can be open at once. * * @return the number of connections */ public int getMaxConnections() { return maxConnections; } /** * This method sets the number of connections that can be open at once. * * @param connections the number of connections */ public void setMaxConnections(int connections) { maxConnections = connections; hc = null; } /** * This method returns the number of times to retry when a recoverable error occurs. * * @return the number of times to retry on recoverable error */ public int getMaxRetries() { return maxRetries; } /** * This method sets the number of times to retry when a recoverable error occurs. * * @param retries the number of times to retry on recoverable error */ public void setMaxRetries(int retries) { maxRetries = retries; } /** * This method returns the signature version * * @return the version */ public int getSignatureVersion() { return sigVersion; } /** * This method sets the proxy host and port * * @param host the proxy host * @param port the proxy port */ public void setProxyValues(String host, int port) { this.proxyHost = host; this.proxyPort = port; hc = null; } /** * This method sets the proxy host, port, user and password (for authenticating proxies) * * @param host the proxy host * @param port the proxy port * @param user the proxy user * @param password the proxy password */ public void setProxyValues(String host, int port, String user, String password) { this.proxyHost = host; this.proxyPort = port; this.proxyUser = user; this.proxyPassword = password; hc = null; } /** * This method sets the proxy host, port, user, password and domain (for NTLM authentication) * * @param host the proxy host * @param port the proxy port * @param user the proxy user * @param password the proxy password * @param domain the proxy domain */ public void setProxyValues(String host, int port, String user, String password, String domain) { this.proxyHost = host; this.proxyPort = port; this.proxyUser = user; this.proxyPassword = password; this.proxyDomain = domain; hc = null; } /** * This method indicates the system properties should be used for proxy settings. These * properties are http.proxyHost, http.proxyPort, http.proxyUser and http.proxyPassword */ public void useSystemProxy() { this.proxyHost = System.getProperty("http.proxyHost"); if (this.proxyHost != null && this.proxyHost.trim().equals("")) { proxyHost = null; } this.proxyPort = getPort(); try { this.proxyPort = Integer.parseInt(System.getProperty("http.proxyPort")); } catch (NumberFormatException ex) { /* use default */ } this.proxyUser = System.getProperty("http.proxyUser"); this.proxyPassword = System.getProperty("http.proxyPassword"); this.proxyDomain = System.getProperty("http.proxyDomain"); hc = null; } /** * This method returns the map of headers for this connection * * @return map of headers (modifiable) */ public Map<String, List<String>> getHeaders() { return headers; } /** * This method sets the signature version used to sign requests (0 or 1). * NOTE: This value defaults to 1, so passing 0 is the most likely use case. * * @param version signature version */ public void setSignatureVersion(int version) { if (version != 0 && version != 1) { throw new IllegalArgumentException("Only signature versions 0 and 1 supported"); } sigVersion = version; } protected HttpClient getHttpClient() { if (hc == null) { configureHttpClient(); } return hc; } public void setHttpClient(HttpClient hc) { this.hc = hc; } /** * Make a http request and process the response. This method also performs automatic retries. * * @param method The HTTP method to use (GET, POST, DELETE, etc) * @param action the name of the action for this query request * @param params map of request params * @param respType the class that represents the desired/expected return type */ protected <T> T makeRequest(HttpMethodBase method, String action, Map<String, String> params, Class<T> respType) throws HttpException, IOException, JAXBException { // add auth params, and protocol specific headers Map<String, String> qParams = new HashMap<String, String>(params); qParams.put("Action", action); qParams.put("AWSAccessKeyId", getAwsAccessKeyId()); qParams.put("SignatureVersion", "" + sigVersion); qParams.put("Timestamp", httpDate()); if (headers != null) { for (Iterator<String> i = headers.keySet().iterator(); i.hasNext();) { String key = i.next(); for (Iterator<String> j = headers.get(key).iterator(); j.hasNext();) { qParams.put(key, j.next()); } } } // sort params by key ArrayList<String> keys = new ArrayList<String>(qParams.keySet()); Collator stringCollator = Collator.getInstance(); stringCollator.setStrength(Collator.PRIMARY); Collections.sort(keys, stringCollator); // build param string StringBuilder resource = new StringBuilder(); if (sigVersion == 0) { // ensure Action, Timestamp come first! resource.append(qParams.get("Action")); resource.append(qParams.get("Timestamp")); } else { for (String key : keys) { resource.append(key); resource.append(qParams.get(key)); } } // calculate signature String encoded = urlencode(encode(getSecretAccessKey(), resource.toString(), false)); // build param string, encoding values and adding request signature resource = new StringBuilder(); for (String key : keys) { resource.append("&"); resource.append(key); resource.append("="); resource.append(urlencode(qParams.get(key))); } resource.setCharAt(0, '?'); // set first param delimeter resource.append("&Signature="); resource.append(encoded); // finally, build request object URL url = makeURL(resource.toString()); method.setURI(new URI(url.toString(), true)); method.setRequestHeader(new Header("User-Agent", userAgent)); if (sigVersion == 0) { method.setRequestHeader(new Header("Content-Type", "application/x-www-form-urlencoded; charset=utf-8")); } Object response = null; boolean done = false; int retries = 0; boolean doRetry = false; String errorMsg = ""; do { int responseCode = 600; // default to high value, so we don't think it is valid try { responseCode = getHttpClient().executeMethod(method); } catch (SocketException ex) { // these can generally be retried. Treat it like a 500 error doRetry = true; errorMsg = ex.getMessage(); } // 100's are these are handled by httpclient if (responseCode < 300) { // 200's : parse normal response into requested object if (respType != null) { InputStream iStr = method.getResponseBodyAsStream(); response = JAXBuddy.deserializeXMLStream(respType, iStr); } done = true; } else if (responseCode < 400) { // 300's : what to do? throw new HttpException("redirect error : " + responseCode); } else if (responseCode < 500) { // 400's : parse client error message String body = getStringFromStream(method.getResponseBodyAsStream()); throw new HttpException("Client error : " + getErrorDetails(body)); } else if (responseCode < 600) { // 500's : retry... doRetry = true; String body = getStringFromStream(method.getResponseBodyAsStream()); errorMsg = getErrorDetails(body); } if (doRetry) { retries++; if (retries > maxRetries) { throw new HttpException("Number of retries exceeded : " + action + ", " + errorMsg); } doRetry = false; try { Thread.sleep((int) Math.pow(2.0, retries) * 1000); } catch (InterruptedException ex) { } } } while (!done); return (T) response; } private void configureHttpClient() { MultiThreadedHttpConnectionManager connMgr = new MultiThreadedHttpConnectionManager(); HttpConnectionManagerParams connParams = connMgr.getParams(); connParams.setMaxTotalConnections(maxConnections); connParams.setMaxConnectionsPerHost(HostConfiguration.ANY_HOST_CONFIGURATION, maxConnections); connMgr.setParams(connParams); hc = new HttpClient(connMgr); // NOTE: These didn't seem to help in my initial testing // hc.getParams().setParameter("http.tcp.nodelay", true); // hc.getParams().setParameter("http.connection.stalecheck", false); if (proxyHost != null) { HostConfiguration hostConfig = new HostConfiguration(); hostConfig.setProxy(proxyHost, proxyPort); hc.setHostConfiguration(hostConfig); log.info("Proxy Host set to " + proxyHost + ":" + proxyPort); if (proxyUser != null && !proxyUser.trim().equals("")) { if (proxyDomain != null) { hc.getState().setProxyCredentials(new AuthScope(proxyHost, proxyPort), new NTCredentials(proxyUser, proxyPassword, proxyHost, proxyDomain)); } else { hc.getState().setProxyCredentials(new AuthScope(proxyHost, proxyPort), new UsernamePasswordCredentials(proxyUser, proxyPassword)); } } } } private String getStringFromStream(InputStream iStr) throws IOException { InputStreamReader rdr = new InputStreamReader(iStr, "UTF-8"); StringWriter wtr = new StringWriter(); char[] buf = new char[1024]; int bytes; while ((bytes = rdr.read(buf)) > -1) { if (bytes > 0) { wtr.write(buf, 0, bytes); } } iStr.close(); return wtr.toString(); } private String getErrorDetails(String errorResponse) throws JAXBException { ByteArrayInputStream bais = new ByteArrayInputStream(errorResponse.getBytes()); if (errorResponse.indexOf("<ErrorResponse") > -1) { try { ErrorResponse resp = JAXBuddy.deserializeXMLStream(ErrorResponse.class, bais); Error err = resp.getErrors().get(0); return "(" + err.getCode() + ") " + err.getMessage(); } catch (UnmarshalException ex) { bais = new ByteArrayInputStream(errorResponse.getBytes()); com.xerox.amazonws.typica.jaxb.ErrorResponse resp = JAXBuddy .deserializeXMLStream(com.xerox.amazonws.typica.jaxb.ErrorResponse.class, bais); com.xerox.amazonws.typica.jaxb.Error err = resp.getErrors().get(0); return "(" + err.getCode() + ") " + err.getMessage(); } } else { Response resp = JAXBuddy.deserializeXMLStream(Response.class, bais); return resp.getErrors().getError().getMessage(); } } /** * Generate an rfc822 date for use in the Date HTTP header. */ private static String httpDate() { final String DateFormat = "yyyy-MM-dd'T'HH:mm:ss'Z'"; SimpleDateFormat format = new SimpleDateFormat(DateFormat, Locale.US); format.setTimeZone(TimeZone.getTimeZone("GMT")); return format.format(new Date()); } }