Package com.amazon.s3shell

Source Code of com.amazon.s3shell.S3Store

// This software code is made available "AS IS" without warranties of any
// kind.  You may copy, display, modify and redistribute the software
// code either by itself or as incorporated into your code; provided that
// you do not remove any proprietary notices.  Your use of this software
// code is at your own risk and you waive any claim against Amazon
// Digital Services, Inc. or its affiliates with respect to your use of
// this software code. (c) 2006 Amazon Digital Services, Inc. or its
// affiliates.

package com.amazon.s3shell;

import java.io.DataInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLEncoder;
import java.security.MessageDigest;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.TimeZone;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;

import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;

/**
* Simple interface class for using S3 in a basic fashion.  An instance of
* this class is intended for a single thread to interact with a single
* bucket in S3.
* <p>
* In general, methods of this class have three ways of failing.  They will
* throw IllegalArgumentException on usage errors, IOException on network
* errors or other errors that may be reasonably expected to succeed on
* retry, and return false for conditions where retry will not succeed (e.g.
* permission errors, buckets and items not existing, etc).
* <p>
* Instances of this class are <b>NOT</b> safe for concurrent use among multiple
* threads.
*
* @author Grant Emery (c) 2006 Amazon.com
**/
public class S3Store {
    private static S3Store instance;
    private static final Logger ourLogger = Logger.getLogger(S3Store.class.getName());
    /** S3 host to connect to */
    private final String m_host;
    /** AWS Access Key ID to connect as */
    private final String m_username;
    /** AWS Secret Access Key to connect with */
    private final String m_password;

    /** Bucket to perform operations with */
    private String m_bucket;

    // Some operations return XML messages that we need to parse.  We don't
    // need to construct a new parser for each request.
    /** Parser for S3 response XML messages */
    private final SAXParser m_parser;

    /** Signature algorithm used for S3 authentication: {@value} */
    private static final String SIGNATURE_ALGORITHM = "HmacSHA1";

    /** Number of millis to wait for S3 to respond to requests */
    private static final int READ_TIMEOUT = 30 * 1000;

    /**
     * Copy constructor.
     *
     * @param copy The S3Store instance to copy [may not be null]
     **/
    public S3Store(final S3Store copy) {
        this(copy.m_host, copy.m_username, copy.m_password, copy.m_bucket);
    }

    /**
     * Standard constructor which takes basic connection parameters.  No
     * connection is made to the server until a method is called on the
     * constructed instance, so if any connection parameters are bad,
     * it won't be known until then.
     *
     * @param host The S3 storage host to connect to [may not be null]
     * @param username The Amazon Web Services Access Key ID to use to connect
     *      [may not be null]
     * @param password The Amazon Web Services Secret Access Key to use to connect
     *      [may not be null]
     **/
    public S3Store(final String host, final String username, final String password) {
        if(host == null) throw new IllegalArgumentException("host may not be null");
        if(username == null) throw new IllegalArgumentException("username may not be null");
        if(password == null) throw new IllegalArgumentException("password may not be null");

        m_host = host;
        m_username = username;
        m_password = password;

        try {
            SAXParserFactory parserfactory = SAXParserFactory.newInstance();
            parserfactory.setNamespaceAware(false);
            parserfactory.setValidating(false);

            m_parser = parserfactory.newSAXParser();
        }
        catch(Exception e) {
            throw new IllegalArgumentException("parser creation failed", e);
        }
    }

    /**
     * Standard constructor taking connection parameters and a default
     * bucket to use.  No connection is made until an instance method of
     * this class is called, so if these parameters are incorrect it
     * won't be known until then.
     *
     * @param host The S3 storage host to connect to [may not be null]
     * @param username The Amazon Web Services Access Key ID to use to connect
     *      [may not be null]
     * @param password The Amazon Web Services Secret Access Key to use to
     *      connect [may not be null]
     * @param bucket The S3 bucket to use [may be null, although some operations will fail]
     **/
    public S3Store(final String host, final String username, final String password, final String bucket) {
        this(host, username, password);
        m_bucket = bucket;
    }

    /**
     * Sets the bucket to use for operations.
     *
     * @param bucket The bucket to use [may be null, although some operations
     *      will fail]
     **/
    public void setBucket(final String bucket) {
        m_bucket = bucket;
    }

    /**
     * Gets the bucket currently in use
     *
     * @return The bucket currently in use [may be null]
     **/
    public String getBucket() {
        return m_bucket;
    }
   
    /**
     * Gets the host currently used.
     */
    public String getHost() {
        return m_host;
    }

    /**
     * Creates the current bucket.
     *
     * @return True if the operation succeeded, false if it failed.
     * @throws IllegalArgumentException If there is no bucket set
     * @throws IOException From underlying network problems or if S3 returned
     * an internal server error.
     **/
    public boolean createBucket() throws IOException {
        final HttpURLConnection bucketConn = getBucketURLConnection("PUT");

        bucketConn.connect();

        return checkResponse("createBucket", bucketConn);
    }

    /**
     * Deletes the current bucket.
     *
     * @return True if the operation succeeded, false if it failed.
     * @throws IllegalArgumentException If there is no bucket set
     * @throws IOException From underlying network problems or if S3 returned
     * an internal server error.
     **/
    public boolean deleteBucket() throws IOException {
        final HttpURLConnection bucketConn = getBucketURLConnection("DELETE");

        bucketConn.connect();

        return checkResponse("deleteBucket", bucketConn);
    }

    /**
     * Lists the buckets owned by the current user.
     *
     * @return A List of Strings of item ids in this bucket or null if there
     * was an error.
     * @throws IOException From underlying network problems or if S3 returned
     * an internal server error.
     **/
    public List<String> listBuckets() throws IOException {
        final HttpURLConnection rootConn = getRootURLConnection("GET");

        rootConn.connect();
        if(!checkResponse("listBuckets()", rootConn)) {
            return null;
        }

        final ObjectListParser olp = new ObjectListParser("name");

        final InputStream responseData = rootConn.getInputStream();
        try {
            m_parser.parse(responseData, olp);
        }
        catch(SAXException e) {
            throw new IllegalArgumentException("SAX parser failed", e);
        }
        finally {
            responseData.close();
        }

        return olp.getList();
    }

    /**
     * Stores item data into S3.  No metadata headers are added.
     *
     * @param id The ID to store the item to [may not be null]
     * @param data The binary data to store [may not be null]
     * @return True if the operation succeeded, false if it failed.
     * @throws IllegalArgumentException If there is no bucket set
     * @throws IOException From underlying network problems or if S3 returned
     * an internal server error.
     **/      
    public boolean storeItem(final String id, final byte[] data) throws IOException {
      return storeItem(id, data, (Map<String, List<String>>) null);
    }

    private void addAclHeader(final Map<String, List<String>> headers, final String acl) {
      headers.put("x-amz-acl", Collections.singletonList(acl));
    }
   
    /**
     * Stores item data into S3.  No metadata headers are added.
     *
     * @param id The ID to store the item to [may not be null]
     * @param data The binary data to store [may not be null]
     * @param acl convenience param to specify an acl.  equivalent to including a header of "x-amz-acl" with this value.
     * Must be one of public-read, public-write, authenticated-read, or private (the default).    See:
     * http://docs.amazonwebservices.com/AmazonS3/latest/index.html?S3_ACLs.html for more info.
     * @param headers other headers to send.  may be null or empty.  useful for setting content-type, acls, or other user
     * meta-data.  see http://docs.amazonwebservices.com/AmazonS3/latest/index.html?UsingMetadata.html for more info.
     * @return True if the operation succeeded, false if it failed.
     * @throws IllegalArgumentException If there is no bucket set
     * @throws IOException From underlying network problems or if S3 returned
     * an internal server error.
     **/   
    public boolean storeItem(final String id, final byte[] data, final String acl) throws IOException {
      final Map<String, List<String>> headers = new HashMap<String, List<String>>(1);
      addAclHeader(headers, acl);
     
      return storeItem(id, data, headers);
    }
   
    /**
     * Stores item data into S3.  No metadata headers are added.
     *
     * @param id The ID to store the item to [may not be null]
     * @param data The binary data to store [may not be null]
     * @param acl convenience param to specify an acl.  equivalent to including a header of "x-amz-acl" with this value.
     * Must be one of public-read, public-write, authenticated-read, or private (the default).  See:
     * http://docs.amazonwebservices.com/AmazonS3/latest/index.html?S3_ACLs.html for more info.
     * @param headers other headers to send.  may be null or empty.  useful for setting content-type, acls, or other user
     * meta-data.  see http://docs.amazonwebservices.com/AmazonS3/latest/index.html?UsingMetadata.html for more info.
     * @return True if the operation succeeded, false if it failed.
     * @throws IllegalArgumentException If there is no bucket set
     * @throws IOException From underlying network problems or if S3 returned
     * an internal server error.
     **/
    public boolean storeItem(final String id, final byte[] data, final String acl, final Map<String, List<String>> _headers) throws IOException {
      final Map<String, List<String>> headers = new HashMap<String, List<String>>();
     
      if (_headers != null) {
        headers.putAll(_headers);
      }
     
      addAclHeader(headers, acl);
     
      return storeItem(id, data, headers);
    }

    /**
     * Stores item data into S3.  No metadata headers are added.
     *
     * @param id The ID to store the item to [may not be null]
     * @param data The binary data to store [may not be null]
     * @param headers other headers to send.  may be null or empty.  useful for setting content-type, acls, or other user
     * meta-data.  see http://docs.amazonwebservices.com/AmazonS3/latest/index.html?UsingMetadata.html for more info.
     * @return True if the operation succeeded, false if it failed.
     * @throws IllegalArgumentException If there is no bucket set
     * @throws IOException From underlying network problems or if S3 returned
     * an internal server error.
     **/
      public boolean storeItem(final String id, final byte[] data, final Map<String, List<String>> headers) throws IOException {     
        if(id == null) throw new IllegalArgumentException("id may not be null");
        if(data == null) throw new IllegalArgumentException("data may not be null");
       
        final HttpURLConnection itemConn = getItemURLConnection("PUT", id, data, headers);
       
        itemConn.setDoOutput(true);

        itemConn.connect();
        OutputStream dataout = itemConn.getOutputStream();
        dataout.write(data);
        dataout.close();

        return checkResponse("storeItem", itemConn);
    }
     
      public boolean copyItem(final String fromKey, final String toKey, final String acl)
      throws IOException {
        if (fromKey == null || toKey == null) {
          throw new IllegalArgumentException("neither fromKey or toKey can be null");
        }
       
        final Map<String, List<String>> headers = new HashMap<String, List<String>>(3);
       
        if (acl != null) {
          addAclHeader(headers, acl);
        }
       
        final String fullDst = String.format("%s/%s", this.m_bucket, fromKey);
       
        headers.put("x-amz-copy-source", Collections.singletonList(fullDst));
       
        final HttpURLConnection itemConn = getItemURLConnection("PUT", toKey, null, headers);
        itemConn.connect();
       
        return checkResponse("copyItem", itemConn);
      }
     
      public Map<String, List<String>> getMeta(final String key) throws IOException {
        if (key == null) {
          throw new IllegalArgumentException("key must not be null");
        }
       
        final HttpURLConnection itemConn = getItemURLConnection("HEAD", key, null, null);
       
        if (! checkResponse("getMeta", itemConn)) return null;
       
        return itemConn.getHeaderFields();
      }

    /**
     * Gets an item from the current bucket.
     *
     * @param id The item to get [may not be null]
     * @return The item data, or null if there was an error (e.g. the item
     * doesn't exist)
     * @throws IllegalArgumentException If there is no bucket set
     * @throws IOException From underlying network problems or if S3 returned
     * an internal server error.
     **/
      public S3Object getItemWithHeaders(final String id) throws IOException {
        if(id == null) throw new IllegalArgumentException("id may not be null");

        final HttpURLConnection itemConn = getItemURLConnection("GET", id, null, null);

        itemConn.connect();

        if(!checkResponse("getItem", itemConn)) return null;

        final int responseBytes = itemConn.getContentLength();
        final byte[] retval = new byte[responseBytes];

        final DataInputStream datainput = new DataInputStream(itemConn.getInputStream());
        try {
            datainput.readFully(retval);
        }
        finally {
            datainput.close();
        }
       
        return new S3Object(retval, itemConn.getHeaderFields());
    }
     
      public byte[] getItem(final String id) throws IOException {
        final S3Object ret = getItemWithHeaders(id);
        return (ret == null ? null : ret.getData());
      }

    /**
     * Deletes an item from the current bucket.
     *
     * @param id The item to delete [may not be null]
     * @return True on success, false on failure.
     * @throws IllegalArgumentException If there is no bucket set
     * @throws IOException From underlying network problems or if S3 returned
     * an internal server error.
     **/
    public boolean deleteItem(final String id) throws IOException {
        if(id == null) throw new IllegalArgumentException("id may not be null");

        final HttpURLConnection itemConn = getItemURLConnection("DELETE", id, null, null);

        itemConn.connect();

        return checkResponse("deleteItem", itemConn);
    }

    /**
     * Lists the contents of the current bucket from the beginning.  The number
     * of items returned may be limited by the server.
     *
     * @return A list of ids or null if there was an error.
     * @throws IllegalArgumentException If there is no bucket set
     * @throws IOException From underlying network problems or if S3 returned
     * an internal server error.
     **/
    public List<String> listItems() throws IOException {
        return listItems(null, null, 0);
    }

    /**
     * Lists those contents of the current bucket with IDs starting with
     * the given prefix.
     *
     * @param prefix The prefix to limit searches to.  If null, no restriction
     * is applied.
     * @return A list of ids or null if there was an error.
     * @throws IllegalArgumentException If there is no bucket set
     * @throws IOException From underlying network problems or if S3 returned
     * an internal server error.
     **/
    public List<String> listItems(final String prefix) throws IOException {
        return listItems(prefix, null, 0);
    }

    /**
     * Lists those contents of the current bucket with IDs starting with
     * the given prefix that occur strictly lexicographically after the
     * the given marker.
     *
     * @param prefix The prefix to limit searches to.  If null, no restriction
     * is applied.
     * @param marker The marker indicating where to start returning results.
     * If null, no restriction is applied.
     * @throws IllegalArgumentException If there is no bucket set
     * @throws IOException From underlying network problems or if S3 returned
     * an internal server error.
     **/
    public List<String> listItems(final String prefix, final String marker) throws IOException {
        return listItems(prefix, marker, 0);
    }

    /**
     * Lists those contents of the current bucket with IDs starting
     * with the given prefix that occur strictly lexicographically after
     * the given marker, limiting the results to the given maximum number.
     *
     * @param prefix The prefix to limit searches to.  If null, no restriction
     * is applied.
     * @param marker The marker indicating where to start returning results.
     * If null, no restriction is applied.
     * @param max The maximum number of results to return.  If 0, no additional
     * restriction beyond the server default is applied.
     * @throws IllegalArgumentException If there is no bucket set
     * @throws IOException From underlying network problems or if S3 returned
     * an internal server error.
     **/
    public List<String> listItems(final String prefix, final String marker, final int max) throws IOException {
        if(max < 0) throw new IllegalArgumentException("max must be >= 0");

        final HttpURLConnection bucketConn = getBucketURLConnection("GET", prefix, marker, max);

        bucketConn.connect();

        if(!checkResponse("listItems("+prefix+","+marker+","+max+")", bucketConn)) {
            return null;
        }

        // the response comes as an XML document that we have to parse to
        // find the "key" fields we're interested in.  this helper SAX parser
        // just looks for these key tags and collects them into a list
        final ObjectListParser olp = new ObjectListParser("key");

        InputStream responseData = bucketConn.getInputStream();
        try {
            m_parser.parse(responseData, olp);
        }
        catch(SAXException e) {
            throw new IllegalArgumentException("SAX parser failed", e);
        }
        finally {
            responseData.close();
        }

        return olp.getList();
    }

    /**
     * Given an HttpURLConnection, this method determines whether the request
     * succeeded or not.  A request is a success if it returns a success
     * response code (generally "200 OK" or "204 No Content", depending on
     * the specific operation).  In most cases, this method returns true
     * on success, false on non-recoverable failure, or throws an IOException
     * in cases where a retry might be reasonably expected to succeed.  When
     * this method returns false, it will also print an error message to
     * the logger.
     **/
    private boolean checkResponse(final String operation, final HttpURLConnection conn) throws IOException {
        final int responseCode = conn.getResponseCode();
        //
        // When S3 is overloaded or having other problems of a transient
        // nature, it tends to return this error.  since this can probably
        // be fixed by retry, we throw it as an exception
        if(responseCode == HttpURLConnection.HTTP_INTERNAL_ERROR) {
            throw new IOException(operation+": internal server error");
        }
        if(responseCode == HttpURLConnection.HTTP_UNAVAILABLE) {
            throw new IOException(operation+": service unavailable");
        }
        if(responseCode == HttpURLConnection.HTTP_GATEWAY_TIMEOUT) {
            throw new IOException(operation+": gateway timeout");
        }
       
        if (responseCode == HttpURLConnection.HTTP_NOT_FOUND) {
          return false;
        }
       
        // 2xx response codes are ok, everything else is an error
        if(responseCode / 100 != 2) {
          ourLogger.log(Level.SEVERE, String.format("%s: response code %d", operation, responseCode));
            printError(conn);

            return false;
        }

        return true;
    }

    /**
     * If a connection to S3 returned an error response code, this method
     * will parse the error response XML and send the user-visible message
     * to the logger as SEVERE.
     **/
    private void printError(final HttpURLConnection conn) throws IOException {
        final InputStream errorData = conn.getErrorStream();
        // Some errors, like Service Unavailable, are produced by the
        // container and not S3, so they may not include an error stream.
        if(errorData == null) return;

        // Here we use our simple SAX parser to pull the message field
        // out of the error xml S3 returns to us
        final ObjectListParser olp = new ObjectListParser("message");

        try {
            m_parser.parse(errorData, olp);
        }
        catch(SAXException e) {
            throw new IllegalArgumentException("SAX parser failed", e);
        }
        finally {
            errorData.close();
        }

        for(String msg : olp.getList()) {
          ourLogger.log(Level.SEVERE, msg);
        }
    }

    /**
     * Creates a new HttpURLConnection that refers to the S3 root level for
     * the "list buckets" operation.
     **/
    private HttpURLConnection getRootURLConnection(final String method) throws IOException {
        final String url = "http://" + m_host + "/";
        final URL rootURL = new URL(url);

        final HttpURLConnection rootConn = (HttpURLConnection)rootURL.openConnection();
        rootConn.setRequestMethod(method);
        rootConn.setReadTimeout(READ_TIMEOUT);
        addAuthorization(rootConn, method, null);

        return rootConn;
    }

    /**
     * Creates a new HttpURLConnection that refers to the current bucket for
     * operations such as bucket creation, bucket deletion, and listing
     * bucket contents.
     **/
    private HttpURLConnection getBucketURLConnection(final String method) throws IOException {
        return getBucketURLConnection(method, null, null, 0);
    }

    /**
     * Creates a new HttpURLConnection that refers to the current bucket for
     * specialized listing of bucket contents.
     **/
    private HttpURLConnection getBucketURLConnection(final String method, final String prefix, final String marker, final int max) throws IOException {
        if(m_bucket == null) {
            throw new IllegalArgumentException("bucket is not set");
        }

        String url = "http://" + m_host + "/" + m_bucket;
        final StringBuilder query = new StringBuilder("");

        // Assemble the query string as individual clauses prefixed with
        // "&"'s.  After it's constructed, the first "&" will be changed to
        // the "?" that denotes the start of a query string.
        if(prefix != null) {
            query.append("&prefix=").append(URLEncoder.encode(prefix, "UTF-8"));
        }
        if(marker != null) {
            query.append("&marker=").append(URLEncoder.encode(marker, "UTF-8"));
        }
        if(max != 0) {
            query.append("&max-keys=").append(max);
        }

        if(query.length() > 0) {
            query.setCharAt(0, '?');

            url += query;
        }

        final URL bucketURL = new URL(url);

        final HttpURLConnection bucketConn = (HttpURLConnection)bucketURL.openConnection();
        bucketConn.setRequestMethod(method);
        bucketConn.setReadTimeout(READ_TIMEOUT);

        addAuthorization(bucketConn, method, null);

        return bucketConn;
    }

    /**
     * Gets an HttpURLConnection referring to a specific item for storing
     * and retrieving of data.
     **/
    private HttpURLConnection getItemURLConnection(final String method, final String id, final byte[] data, final Map<String, List<String>> headers) throws IOException {
        if(m_bucket == null) {
            throw new IllegalArgumentException("bucket is not set");
        }
       
        final URL itemURL = new URL("http://" + m_host + "/" + m_bucket + "/" + id);

        final HttpURLConnection urlConn = (HttpURLConnection)itemURL.openConnection();
        urlConn.setRequestMethod(method);
        urlConn.setReadTimeout(READ_TIMEOUT);
       
        if (headers != null) {
          for (final Map.Entry<String, List<String>> me : headers.entrySet()) {
            for (final String v : me.getValue()) {
              urlConn.setRequestProperty(me.getKey(), v);
            }
          }
        }

        addAuthorization(urlConn, method, data);

        return urlConn;
    }
   
    public String getContentType(final HttpURLConnection conn) {
      for (final Map.Entry<String, List<String>> me : conn.getRequestProperties().entrySet()) {
        if ("Content-Type".equalsIgnoreCase(me.getKey())) {
          return me.getValue().iterator().next();
        }
      }
      return "";
    }

    /**
     * Given an HttpURLConnection, this method adds the appropriate
     * authentication data to it to connect to S3.  If connection data
     * is provided, an MD5 digest is included for additional security,
     * but the data itself is not written to the connection.
     **/
    private void addAuthorization(final HttpURLConnection conn, final String method, final byte[] data) throws IOException {
      String contentType = getContentType(conn);
        // on these methods, the java.net classes will add this content type
        // automatically
//        if(method.equalsIgnoreCase("GET") || method.equalsIgnoreCase("DELETE")) {
//            contentType = "application/x-www-form-urlencoded";
//        }

        // for additional security, include a content-md5 tag with any
        // query that is supplying data to S3
        String contentMD5 = "";
        if(data != null) {
            try {
                MessageDigest md = MessageDigest.getInstance("MD5");
                md.update(data);

                // BASE64Encoder isn't technically a public class, but it
                // has been consistently available in Java releases to date
               
                contentMD5 = Base64.encodeBytes(md.digest());
            }
            catch(Exception e) {
                throw new IllegalArgumentException("unable to compute content-md5", e);
            }

            conn.addRequestProperty("Content-MD5", contentMD5);
        }

        // all requests must include a Date header to prevent replay of
        // requests.  this format is defined by RFC 2616 in reference to
        // RFC 1123 and RFC 822
        final String DateFormat = "EEE, dd MMM yyyy HH:mm:ss ";
        final SimpleDateFormat format = new SimpleDateFormat( DateFormat, Locale.US );
        format.setTimeZone( TimeZone.getTimeZone( "GMT" ) );
        final String date = format.format( new Date() ) + "GMT";

        conn.addRequestProperty("Date", date);

        // S3 authentication works as a SHA1 hash of the following information
        // in this precise order
        final StringBuilder buf = new StringBuilder();
        buf.append(method).append("\n");
        buf.append(contentMD5).append("\n");
        buf.append(contentType).append("\n");
        buf.append(date).append("\n");
       
        final String headers = getHeaders(conn);
       
        if (headers.length() > 0) {
          buf.append(headers);
        }
       
        buf.append(conn.getURL().getPath());
       
        String auth;
        try {
            final SecretKeySpec signingKey = new SecretKeySpec(m_password.getBytes(), SIGNATURE_ALGORITHM);

            final Mac mac = Mac.getInstance(SIGNATURE_ALGORITHM);
            mac.init(signingKey);

            // BASE64Encoder isn't technically a public class, but it
            // has been consistently available in Java releases to date
            auth = Base64.encodeBytes(mac.doFinal(buf.toString().getBytes()));
        }
        catch(Exception e) {
            throw new IllegalArgumentException("Unable to calculate digest", e);
        }

        conn.setRequestProperty("Authorization", "AWS "+m_username+":"+auth);
    }
   
    private String getHeaders(final HttpURLConnection conn) {
      final Map<String, List<String>> props = conn.getRequestProperties();
      final List<String> keys = new ArrayList<String>(props.keySet());
      Collections.sort(keys);
     
      final StringBuilder buf = new StringBuilder();
      for (final String k : keys) {
        if (k.toLowerCase().startsWith("x-amz-")) {
          final List<String> vals = props.get(k);
         
          if (vals.size() > 0) {
            buf.append(k.toLowerCase().trim());
            buf.append(":");
           
            for (final String v : vals) {
              buf.append(v.trim());
              buf.append(",");
            }
           
            buf.deleteCharAt(buf.length() -1);
            buf.append("\n");
          }
        }
      }
     
      return buf.toString();
    }

    /**
     * This is our simple SAX parser for handling response XML from
     * S3.  In every case where we parse an XML response, we're looking
     * for the contents of a single tag which may occur multiple times,
     * such as "key" tags when listing bucket contents or "message" tags
     * when parsing error responses.  This class assumes that the target
     * tags don't contain any subtags.
     **/
    private static class ObjectListParser extends DefaultHandler {
        private final String m_lookfor;
        private final List<String> m_ids = new ArrayList<String>();
        private boolean m_storeChars = false;
        private StringBuilder m_keyName;
       
        /**
         * Constructs an object list parser that gather the contents of
         * tags with the given name.
         *
         * @param lookfor The element name to gather character data from
         **/
        public ObjectListParser(final String lookfor) {
            m_lookfor = lookfor;
        }

        /**
         * Once parsing is complete, this retrieves the list of contents
         * of all the matching tags encountered.
         *
         * @return The list of contents of all matching tags [not null]
         **/
        public List<String> getList() {
            return m_ids;
        }

        @Override
        public void startElement(String uri, String localName, String qName, Attributes attributes) {
            if(qName.equalsIgnoreCase(m_lookfor)) {
                m_keyName = new StringBuilder();
                m_storeChars = true;
            }
        }

        @Override
        public void endElement(String url, String localName, String qName) {
            if(qName.equalsIgnoreCase(m_lookfor)) {
                m_ids.add(m_keyName.toString());
                m_storeChars = false;
            }
        }

        @Override
        public void characters(char[] ch, int start, int length) {
            if(m_storeChars) {
                m_keyName.append(ch, start, length);
            }
        }
    }
   
    public static S3Store getInstance() {
        if (instance == null) {
            String host = System.getProperty("org.ualr.cpsc.amazon.s3.host");
            String access_key = System.getProperty("org.ualr.cpsc.amazon.s3.access");
            String secret_key = System.getProperty("org.ualr.cpsc.amazon.s3.secret");
            String default_bucket = System.getProperty("org.ualr.cpsc.amazon.s3.bucket.default");
            instance = new S3Store(host, access_key, secret_key, default_bucket);
        }
        return instance;
    }
}
TOP

Related Classes of com.amazon.s3shell.S3Store

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.