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

com.amazonaws.services.s3.model.transform.XmlResponsesSaxParser Maven / Gradle / Ivy

Go to download

The Amazon Web Services SDK for Java provides Java APIs for building software on AWS' cost-effective, scalable, and reliable infrastructure products. The AWS Java SDK allows developers to code against APIs for all of Amazon's infrastructure web services (Amazon S3, Amazon EC2, Amazon SQS, Amazon Relational Database Service, Amazon AutoScaling, etc).

The newest version!
/*
 * Copyright 2010-2014 Amazon.com, Inc. or its affiliates. All Rights Reserved.
 *
 * Portions copyright 2006-2009 James Murty. Please see LICENSE.txt
 * for applicable license terms and NOTICE.txt for applicable notices.
 *
 * Licensed under the Apache License, Version 2.0 (the "License").
 * You may not use this file except in compliance with the License.
 * A copy of the License is located at
 *
 *  http://aws.amazon.com/apache2.0
 *
 * or in the "license" file accompanying this file. 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.
 */
package com.amazonaws.services.s3.model.transform;

import static com.amazonaws.util.StringUtils.UTF8;

import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
import java.util.Map;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.xml.sax.Attributes;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.XMLReader;
import org.xml.sax.helpers.DefaultHandler;
import org.xml.sax.helpers.XMLReaderFactory;

import com.amazonaws.AmazonClientException;
import com.amazonaws.services.s3.internal.Constants;
import com.amazonaws.services.s3.internal.DeleteObjectsResponse;
import com.amazonaws.services.s3.internal.ObjectExpirationResult;
import com.amazonaws.services.s3.internal.ServerSideEncryptionResult;
import com.amazonaws.services.s3.internal.ServiceUtils;
import com.amazonaws.services.s3.model.AccessControlList;
import com.amazonaws.services.s3.model.AmazonS3Exception;
import com.amazonaws.services.s3.model.Bucket;
import com.amazonaws.services.s3.model.BucketCrossOriginConfiguration;
import com.amazonaws.services.s3.model.BucketLifecycleConfiguration;
import com.amazonaws.services.s3.model.BucketLifecycleConfiguration.Rule;
import com.amazonaws.services.s3.model.BucketLifecycleConfiguration.Transition;
import com.amazonaws.services.s3.model.BucketLoggingConfiguration;
import com.amazonaws.services.s3.model.BucketNotificationConfiguration;
import com.amazonaws.services.s3.model.BucketNotificationConfiguration.TopicConfiguration;
import com.amazonaws.services.s3.model.BucketTaggingConfiguration;
import com.amazonaws.services.s3.model.BucketVersioningConfiguration;
import com.amazonaws.services.s3.model.BucketWebsiteConfiguration;
import com.amazonaws.services.s3.model.CORSRule;
import com.amazonaws.services.s3.model.CORSRule.AllowedMethods;
import com.amazonaws.services.s3.model.CanonicalGrantee;
import com.amazonaws.services.s3.model.CompleteMultipartUploadResult;
import com.amazonaws.services.s3.model.DeleteObjectsResult;
import com.amazonaws.services.s3.model.DeleteObjectsResult.DeletedObject;
import com.amazonaws.services.s3.model.EmailAddressGrantee;
import com.amazonaws.services.s3.model.Grantee;
import com.amazonaws.services.s3.model.GroupGrantee;
import com.amazonaws.services.s3.model.InitiateMultipartUploadResult;
import com.amazonaws.services.s3.model.MultiObjectDeleteException;
import com.amazonaws.services.s3.model.MultiObjectDeleteException.DeleteError;
import com.amazonaws.services.s3.model.MultipartUpload;
import com.amazonaws.services.s3.model.MultipartUploadListing;
import com.amazonaws.services.s3.model.ObjectListing;
import com.amazonaws.services.s3.model.Owner;
import com.amazonaws.services.s3.model.PartListing;
import com.amazonaws.services.s3.model.PartSummary;
import com.amazonaws.services.s3.model.Permission;
import com.amazonaws.services.s3.model.RedirectRule;
import com.amazonaws.services.s3.model.RoutingRule;
import com.amazonaws.services.s3.model.RoutingRuleCondition;
import com.amazonaws.services.s3.model.S3Object;
import com.amazonaws.services.s3.model.S3ObjectSummary;
import com.amazonaws.services.s3.model.S3VersionSummary;
import com.amazonaws.services.s3.model.StorageClass;
import com.amazonaws.services.s3.model.TagSet;
import com.amazonaws.services.s3.model.VersionListing;

/**
 * XML Sax parser to read XML documents returned by S3 via the REST interface,
 * converting these documents into objects.
 */
public class XmlResponsesSaxParser {
    private static final Log log = LogFactory.getLog(XmlResponsesSaxParser.class);

    private XMLReader xr = null;

    private boolean sanitizeXmlDocument = true;

    /**
     * Constructs the XML SAX parser.
     *
     * @throws AmazonClientException
     */
    public XmlResponsesSaxParser() throws AmazonClientException {
        // Ensure we can load the XML Reader.
        try {
            xr = XMLReaderFactory.createXMLReader();
        } catch (SAXException e) {
            throw new AmazonClientException("Couldn't initialize a SAX driver to create an XMLReader", e);
        }
    }

    /**
     * Parses an XML document from an input stream using a document handler.
     *
     * @param handler
     *            the handler for the XML document
     * @param inputStream
     *            an input stream containing the XML document to parse
     *
     * @throws AmazonClientException
     *             any parsing, IO or other exceptions are wrapped in an
     *             S3ServiceException.
     */
    protected void parseXmlInputStream(DefaultHandler handler, InputStream inputStream)
            throws AmazonClientException {
        try {
            if (log.isDebugEnabled()) {
                log.debug("Parsing XML response document with handler: " + handler.getClass());
            }

            BufferedReader breader = new BufferedReader(new InputStreamReader(inputStream,
                Constants.DEFAULT_ENCODING));
            xr.setContentHandler(handler);
            xr.setErrorHandler(handler);
            xr.parse(new InputSource(breader));
        } catch (Throwable t) {
            try {
                inputStream.close();
            } catch (IOException e) {
                if (log.isErrorEnabled()) {
                    log.error("Unable to close response InputStream up after XML parse failure", e);
                }
            }
            throw new AmazonClientException("Failed to parse XML document with handler "
                + handler.getClass(), t);
        }
    }

    protected InputStream sanitizeXmlDocument(DefaultHandler handler, InputStream inputStream)
            throws AmazonClientException {
        if (!sanitizeXmlDocument) {
            // No sanitizing will be performed, return the original input stream unchanged.
            return inputStream;
        } else {
            if (log.isDebugEnabled()) {
                log.debug("Sanitizing XML document destined for handler " + handler.getClass());
            }

            InputStream sanitizedInputStream = null;

            try {
                /*
                 * Read object listing XML document from input stream provided into a
                 * string buffer, so we can replace troublesome characters before
                 * sending the document to the XML parser.
                 */
                StringBuilder listingDocBuffer = new StringBuilder();
                BufferedReader br = new BufferedReader(
                    new InputStreamReader(inputStream, Constants.DEFAULT_ENCODING));

                char[] buf = new char[8192];
                int read = -1;
                while ((read = br.read(buf)) != -1) {
                    listingDocBuffer.append(buf, 0, read);
                }
                br.close();

                /*
                 * Replace any carriage return (\r) characters with explicit XML
                 * character entities, to prevent the SAX parser from
                 * misinterpreting 0x0D characters as 0x0A and being unable to
                 * parse the XML.
                 */
                String listingDoc = listingDocBuffer.toString().replaceAll("\r", "
");

                sanitizedInputStream = new ByteArrayInputStream(
                    listingDoc.getBytes(UTF8));
            } catch (Throwable t) {
                try {
                    inputStream.close();
                } catch (IOException e) {
                    if (log.isErrorEnabled()) {
                        log.error("Unable to close response InputStream after failure sanitizing XML document", e);
                    }
                }
                throw new AmazonClientException("Failed to sanitize XML document destined for handler "
                    + handler.getClass(), t);
            }
            return sanitizedInputStream;
        }
    }

    /**
     * Checks if the specified string is empty or null and if so, returns null.
     * Otherwise simply returns the string.
     *
     * @param s
     *            The string to check.
     * @return Null if the specified string was null, or empty, otherwise
     *         returns the string the caller passed in.
     */
    private String checkForEmptyString(String s) {
        if (s == null) return null;
        if (s.length() == 0) return null;

        return s;
    }

    /**
     * Safely parses the specified string as an integer and returns the value.
     * If a NumberFormatException occurs while parsing the integer, an error is
     * logged and -1 is returned.
     *
     * @param s
     *            The string to parse and return as an integer.
     *
     * @return The integer value of the specified string, otherwise -1 if there
     *         were any problems parsing the string as an integer.
     */
    private int parseInt(String s) {
        try {
            return Integer.parseInt(s);
        } catch (NumberFormatException nfe) {
            log.error("Unable to parse integer value '" + s + "'", nfe);
        }

        return -1;
    }

    /**
     * Safely parses the specified string as a long and returns the value. If a
     * NumberFormatException occurs while parsing the long, an error is logged
     * and -1 is returned.
     *
     * @param s
     *            The string to parse and return as a long.
     *
     * @return The long value of the specified string, otherwise -1 if there
     *         were any problems parsing the string as a long.
     */
    private long parseLong(String s) {
        try {
            return Long.parseLong(s);
        } catch (NumberFormatException nfe) {
            log.error("Unable to parse long value '" + s + "'", nfe);
        }

        return -1;
    }

    /**
     * Parses a ListBucket response XML document from an input stream.
     *
     * @param inputStream
     *            XML data input stream.
     * @return the XML handler object populated with data parsed from the XML
     *         stream.
     * @throws AmazonClientException
     */
    public ListBucketHandler parseListBucketObjectsResponse(InputStream inputStream)
            throws AmazonClientException {
        ListBucketHandler handler = new ListBucketHandler();
        parseXmlInputStream(handler, sanitizeXmlDocument(handler, inputStream));
        return handler;
    }

    /**
     * Parses a ListVersions response XML document from an input stream.
     *
     * @param inputStream
     *            XML data input stream.
     * @return the XML handler object populated with data parsed from the XML
     *         stream.
     * @throws AmazonClientException
     */
    public ListVersionsHandler parseListVersionsResponse(InputStream inputStream)
            throws AmazonClientException {
        ListVersionsHandler handler = new ListVersionsHandler();
        parseXmlInputStream(handler, sanitizeXmlDocument(handler, inputStream));
        return handler;
    }

    /**
     * Parses a ListAllMyBuckets response XML document from an input stream.
     *
     * @param inputStream
     *            XML data input stream.
     * @return the XML handler object populated with data parsed from the XML
     *         stream.
     * @throws AmazonClientException
     */
    public ListAllMyBucketsHandler parseListMyBucketsResponse(InputStream inputStream)
            throws AmazonClientException {
        ListAllMyBucketsHandler handler = new ListAllMyBucketsHandler();
        parseXmlInputStream(handler, sanitizeXmlDocument(handler, inputStream));
        return handler;
    }

    /**
     * Parses an AccessControlListHandler response XML document from an input
     * stream.
     *
     * @param inputStream
     *            XML data input stream.
     * @return the XML handler object populated with data parsed from the XML
     *         stream.
     *
     * @throws AmazonClientException
     */
    public AccessControlListHandler parseAccessControlListResponse(InputStream inputStream)
        throws AmazonClientException
    {
        AccessControlListHandler handler = new AccessControlListHandler();
        parseXmlInputStream(handler, inputStream);
        return handler;
    }

    /**
     * Parses a LoggingStatus response XML document for a bucket from an input
     * stream.
     *
     * @param inputStream
     *            XML data input stream.
     * @return the XML handler object populated with data parsed from the XML
     *         stream.
     *
     * @throws AmazonClientException
     */
    public BucketLoggingConfigurationHandler parseLoggingStatusResponse(InputStream inputStream)
        throws AmazonClientException
    {
        BucketLoggingConfigurationHandler handler = new BucketLoggingConfigurationHandler();
        parseXmlInputStream(handler, inputStream);
        return handler;
    }

    public BucketLifecycleConfigurationHandler parseBucketLifecycleConfigurationResponse(InputStream inputStream)
    {
        BucketLifecycleConfigurationHandler handler = new BucketLifecycleConfigurationHandler();
        parseXmlInputStream(handler, inputStream);
        return handler;
    }

    public BucketCrossOriginConfigurationHandler parseBucketCrossOriginConfigurationResponse(InputStream inputStream)
    {
        BucketCrossOriginConfigurationHandler handler = new BucketCrossOriginConfigurationHandler();
        parseXmlInputStream(handler, inputStream);
        return handler;
    }

    public String parseBucketLocationResponse(InputStream inputStream)
        throws AmazonClientException
    {
        BucketLocationHandler handler = new BucketLocationHandler();
        parseXmlInputStream(handler, inputStream);
        return handler.getLocation();
    }

    public BucketVersioningConfigurationHandler parseVersioningConfigurationResponse(InputStream inputStream)
        throws AmazonClientException
    {
        BucketVersioningConfigurationHandler handler = new BucketVersioningConfigurationHandler();
        parseXmlInputStream(handler, inputStream);
        return handler;
    }

    public BucketWebsiteConfigurationHandler parseWebsiteConfigurationResponse(InputStream inputStream)
        throws AmazonClientException
    {
        BucketWebsiteConfigurationHandler handler = new BucketWebsiteConfigurationHandler();
        parseXmlInputStream(handler, inputStream);
        return handler;
    }

    public BucketNotificationConfigurationHandler parseNotificationConfigurationResponse(InputStream inputStream)
        throws AmazonClientException
    {
        BucketNotificationConfigurationHandler handler = new BucketNotificationConfigurationHandler();
        parseXmlInputStream(handler, inputStream);
        return handler;
    }

    public BucketTaggingConfigurationHandler parseTaggingConfigurationResponse(InputStream inputStream)
            throws AmazonClientException
        {
            BucketTaggingConfigurationHandler handler = new BucketTaggingConfigurationHandler();
            parseXmlInputStream(handler, inputStream);
            return handler;
        }

    public DeleteObjectsHandler parseDeletedObjectsResult(InputStream inputStream) {
        DeleteObjectsHandler handler = new DeleteObjectsHandler();
        parseXmlInputStream(handler, inputStream);
        return handler;
    }

    public CopyObjectResultHandler parseCopyObjectResponse(InputStream inputStream)
        throws AmazonClientException
    {
        CopyObjectResultHandler handler = new CopyObjectResultHandler();
        parseXmlInputStream(handler, inputStream);
        return handler;
    }

    public CompleteMultipartUploadHandler parseCompleteMultipartUploadResponse(InputStream inputStream)
        throws AmazonClientException
    {
        CompleteMultipartUploadHandler handler = new CompleteMultipartUploadHandler();
        parseXmlInputStream(handler, inputStream);
        return handler;
    }

    public InitiateMultipartUploadHandler parseInitiateMultipartUploadResponse(InputStream inputStream)
        throws AmazonClientException
    {
        InitiateMultipartUploadHandler handler = new InitiateMultipartUploadHandler();
        parseXmlInputStream(handler, inputStream);
        return handler;
    }

    public ListMultipartUploadsHandler parseListMultipartUploadsResponse(InputStream inputStream)
        throws AmazonClientException
    {
        ListMultipartUploadsHandler handler = new ListMultipartUploadsHandler();
        parseXmlInputStream(handler, inputStream);
        return handler;
    }

    public ListPartsHandler parseListPartsResponse(InputStream inputStream)
        throws AmazonClientException
    {
        ListPartsHandler handler = new ListPartsHandler();
        parseXmlInputStream(handler, inputStream);
        return handler;
    }


    /**
     * @param inputStream
     *
     * @return true if the bucket's is configured as Requester Pays, false if it
     *         is configured as Owner pays.
     *
     * @throws AmazonClientException
     */
    public boolean parseRequestPaymentConfigurationResponse(InputStream inputStream)
        throws AmazonClientException
    {
        RequestPaymentConfigurationHandler handler = new RequestPaymentConfigurationHandler();
        parseXmlInputStream(handler, inputStream);
        return handler.isRequesterPays();
    }

    // ////////////
    // Handlers //
    // ////////////

    /**
     * Handler for ListBucket response XML documents. The document is parsed
     * into {@link S3Object}s available via the {@link #getObjects()} method.
     */
    public class ListBucketHandler extends DefaultHandler {
        private S3ObjectSummary currentObject = null;
        private Owner currentOwner = null;
        private StringBuilder currText = null;
        private boolean insideCommonPrefixes = false;

        private ObjectListing objectListing = new ObjectListing();
        private List commonPrefixes = new ArrayList();

        // Listing properties.
        private String bucketName = null;
        private String requestPrefix = null;
        private String requestMarker = null;
        private int requestMaxKeys = 0;
        private String requestDelimiter = null;
        private String requestEncodingType = null;
        private boolean listingTruncated = false;
        private String lastKey = null;
        private String nextMarker = null;

        public ListBucketHandler() {
            super();
            this.currText = new StringBuilder();
        }

        public ObjectListing getObjectListing() {
            objectListing.setBucketName(bucketName);
            objectListing.setCommonPrefixes(commonPrefixes);
            objectListing.setDelimiter(requestDelimiter);
            objectListing.setMarker(requestMarker);
            objectListing.setMaxKeys(requestMaxKeys);
            objectListing.setPrefix(requestPrefix);
            objectListing.setTruncated(listingTruncated);
            objectListing.setEncodingType(requestEncodingType);

            /*
             * S3 only includes the NextMarker XML element if the request
             * specified a delimiter, but for consistency we'd like to always
             * give easy access to the next marker if we're returning a list of
             * results that's truncated.
             */
            if (nextMarker != null) {
                objectListing.setNextMarker(nextMarker);
            } else if (listingTruncated) {
                String nextMarker = null;
                if (objectListing.getObjectSummaries().isEmpty() == false) {
                    nextMarker = objectListing.getObjectSummaries().get(objectListing.getObjectSummaries().size() - 1).getKey();
                } else if (objectListing.getCommonPrefixes().isEmpty() == false) {
                    nextMarker = objectListing.getCommonPrefixes().get(objectListing.getCommonPrefixes().size() - 1);
                } else {
                    log.error("S3 response indicates truncated results, but contains no object summaries or common prefixes.");
                }

                objectListing.setNextMarker(nextMarker);
            }

            return objectListing;
        }

        /**
         * If the listing is truncated this method will return the marker that
         * should be used in subsequent bucket list calls to complete the
         * listing.
         *
         * @return null if the listing is not truncated, otherwise the next
         *         marker if it's available or the last object key seen if the
         *         next marker isn't available.
         */
        public String getMarkerForNextListing() {
            if (listingTruncated) {
                if (nextMarker != null) {
                    return nextMarker;
                } else if (lastKey != null) {
                    return lastKey;
                } else {
                    if (log.isWarnEnabled()) {
                        log.warn("Unable to find Next Marker or Last Key for truncated listing");
                    }
                    return null;
                }
            } else {
                return null;
            }
        }

        /**
         * @return true if the listing document was truncated, and therefore
         *         only contained a subset of the available S3 objects.
         */
        public boolean isListingTruncated() {
            return listingTruncated;
        }

        /**
         * @return the S3 objects contained in the listing.
         */
        public List getObjects() {
            return objectListing.getObjectSummaries();
        }

        public String[] getCommonPrefixes() {
            return commonPrefixes.toArray(new String[commonPrefixes.size()]);
        }

        public String getRequestPrefix() {
            return requestPrefix;
        }

        public String getRequestMarker() {
            return requestMarker;
        }

        public String getNextMarker() {
            return nextMarker;
        }

        public long getRequestMaxKeys() {
            return requestMaxKeys;
        }

        @Override
        public void startDocument() {
        }

        @Override
        public void endDocument() {
        }

        @Override
        public void startElement(String uri, String name, String qName, Attributes attrs) {
            if (name.equals("Contents")) {
                currentObject = new S3ObjectSummary();
                currentObject.setBucketName(bucketName);
            } else if (name.equals("Owner")) {
                currentOwner = new Owner();
                currentObject.setOwner(currentOwner);
            } else if (name.equals("CommonPrefixes")) {
                insideCommonPrefixes = true;
            }
        }

        @Override
        public void endElement(String uri, String name, String qName) {
            String elementText = this.currText.toString();
            // Listing details
            if (name.equals("Name")) {
                bucketName = elementText;
                if (log.isDebugEnabled()) {
                    log.debug("Examining listing for bucket: " + bucketName);
                }
            } else if (!insideCommonPrefixes && name.equals("Prefix")) {
                requestPrefix = checkForEmptyString(elementText);
            } else if (name.equals("Marker")) {
                requestMarker = checkForEmptyString(elementText);
            } else if (name.equals("NextMarker")) {
                nextMarker = elementText;
            } else if (name.equals("MaxKeys")) {
                requestMaxKeys = parseInt(elementText);
            } else if (name.equals("Delimiter")) {
                requestDelimiter = checkForEmptyString(elementText);
            } else if (name.equals("EncodingType")) {
                requestEncodingType = checkForEmptyString(elementText);
            } else if (name.equals("IsTruncated")) {
                String isTruncatedStr = elementText.toLowerCase(Locale.getDefault());
                if (isTruncatedStr.startsWith("false")) {
                    listingTruncated = false;
                } else if (isTruncatedStr.startsWith("true")) {
                    listingTruncated = true;
                } else {
                    throw new RuntimeException("Invalid value for IsTruncated field: "
                        + isTruncatedStr);
                }
            }
            // Object details.
            else if (name.equals("Contents")) {
                objectListing.getObjectSummaries().add(currentObject);
            } else if (name.equals("Key")) {
                currentObject.setKey(elementText);
                lastKey = elementText;
            } else if (name.equals("LastModified")) {
                try {
                    currentObject.setLastModified(ServiceUtils.parseIso8601Date(elementText));
                } catch (ParseException e) {
                    throw new RuntimeException(
                        "Non-ISO8601 date for LastModified in bucket's object listing output: "
                        + elementText, e);
                }
            } else if (name.equals("ETag")) {
                currentObject.setETag(ServiceUtils.removeQuotes(elementText));
            } else if (name.equals("Size")) {
                currentObject.setSize(parseLong(elementText));
            } else if (name.equals("StorageClass")) {
                currentObject.setStorageClass(elementText);
            }
            // Owner details.
            else if (name.equals("ID")) {
                if (currentOwner == null) {
                    currentOwner = new Owner();
                    currentObject.setOwner(currentOwner);
                }

                currentOwner.setId(elementText);
            } else if (name.equals("DisplayName")) {
                currentOwner.setDisplayName(elementText);
            }
            // Common prefixes.
            else if (insideCommonPrefixes && name.equals("Prefix")) {
                commonPrefixes.add(elementText);
            } else if (name.equals("CommonPrefixes")) {
                insideCommonPrefixes = false;
            }

            this.currText = new StringBuilder();
        }

        @Override
        public void characters(char ch[], int start, int length) {
            this.currText.append(ch, start, length);
        }
    }

    /**
     * Handler for ListAllMyBuckets response XML documents. The document is
     * parsed into {@link Bucket}s available via the {@link #getBuckets()}
     * method.
     */
    public class ListAllMyBucketsHandler extends DefaultHandler {
        private Owner bucketsOwner = null;
        private Bucket currentBucket = null;
        private StringBuilder currText = null;

        private List buckets = null;

        public ListAllMyBucketsHandler() {
            super();
            buckets = new ArrayList();
            this.currText = new StringBuilder();
        }

        /**
         * @return the buckets listed in the document.
         */
        public List getBuckets() {
            return buckets;
        }

        /**
         * @return the owner of the buckets.
         */
        public Owner getOwner() {
            return bucketsOwner;
        }

        @Override
        public void startDocument() {
        }

        @Override
        public void endDocument() {
        }

        @Override
        public void startElement(String uri, String name, String qName, Attributes attrs) {
            if (name.equals("Bucket")) {
                currentBucket = new Bucket();
            } else if (name.equals("Owner")) {
                bucketsOwner = new Owner();
            }
        }

        @Override
        public void endElement(String uri, String name, String qName) {
            String elementText = this.currText.toString();
            // Listing details.
            if (name.equals("ID")) {
                bucketsOwner.setId(elementText);
            } else if (name.equals("DisplayName")) {
                bucketsOwner.setDisplayName(elementText);
            }
            // Bucket item details.
            else if (name.equals("Bucket")) {
                currentBucket.setOwner(bucketsOwner);
                buckets.add(currentBucket);
            } else if (name.equals("Name")) {
                currentBucket.setName(elementText);
            } else if (name.equals("CreationDate")) {
                elementText += ".000Z";
                try {
                    currentBucket.setCreationDate(ServiceUtils.parseIso8601Date(elementText));
                } catch (ParseException e) {
                    throw new RuntimeException(
                        "Non-ISO8601 date for CreationDate in list buckets output: "
                        + elementText, e);
                }
            }
            this.currText = new StringBuilder();
        }

        @Override
        public void characters(char ch[], int start, int length) {
            this.currText.append(ch, start, length);
        }
    }

    /**
     * Handler for AccessControlList response XML documents. The document is
     * parsed into an {@link AccessControlList} object available via the
     * {@link #getAccessControlList()} method.
     */
    public class AccessControlListHandler extends DefaultHandler {
        private AccessControlList accessControlList = null;

        private Owner owner = null;
        private Grantee currentGrantee = null;
        private Permission currentPermission = null;
        private StringBuilder currText = null;

        private boolean insideACL = false;

        public AccessControlListHandler() {
            super();
            this.currText = new StringBuilder();
        }

        /**
         * @return an object representing the ACL document.
         */
        public AccessControlList getAccessControlList() {
            return accessControlList;
        }

        @Override
        public void startDocument() {
        }

        @Override
        public void endDocument() {
        }

        @Override
        public void startElement(String uri, String name, String qName, Attributes attrs) {
            if (name.equals("Owner")) {
                owner = new Owner();
            } else if (name.equals("AccessControlList")) {
                accessControlList = new AccessControlList();
                accessControlList.setOwner(owner);
                insideACL = true;
            } else if (name.equals("Grantee")) {
                String type = XmlResponsesSaxParser.findAttributeValue( "xsi:type", attrs );
                if ("AmazonCustomerByEmail".equals(type)) {
                    currentGrantee = new EmailAddressGrantee(null);
                } else if ("CanonicalUser".equals(type)) {
                    currentGrantee = new CanonicalGrantee(null);
                } else if ("Group".equals(type)) {
                    /*
                     * Nothing to do for GroupGrantees here since we
                     * can't construct an empty enum value early.
                     */
                }
            }
        }

        @Override
        public void endElement(String uri, String name, String qName) {
            String elementText = this.currText.toString();

            // Owner details.
            if (name.equals("ID") && !insideACL) {
                owner.setId(elementText);
            } else if (name.equals("DisplayName") && !insideACL) {
                owner.setDisplayName(elementText);
            }
            // ACL details.
            else if (name.equals("ID")) {
                currentGrantee.setIdentifier(elementText);
            } else if (name.equals("EmailAddress")) {
                currentGrantee.setIdentifier(elementText);
            } else if (name.equals("URI")) {
                /*
                 * Only GroupGrantees contain an URI element in them, and we
                 * can't construct currentGrantee during startElement for a
                 * GroupGrantee since it's an enum.
                 */
                currentGrantee = GroupGrantee.parseGroupGrantee(elementText);
            } else if (name.equals("DisplayName")) {
                ((CanonicalGrantee) currentGrantee).setDisplayName(elementText);
            } else if (name.equals("Permission")) {
                currentPermission = Permission.parsePermission(elementText);
            } else if (name.equals("Grant")) {
                accessControlList.grantPermission(currentGrantee, currentPermission);
                currentGrantee = null;
                currentPermission = null;
            } else if (name.equals("AccessControlList")) {
                insideACL = false;
            }
            this.currText = new StringBuilder();
        }

        @Override
        public void characters(char ch[], int start, int length) {
            this.currText.append(ch, start, length);
        }
    }

    /**
     * Handler for LoggingStatus response XML documents for a bucket. The
     * document is parsed into an {@link BucketLoggingConfiguration} object available
     * via the {@link #getBucketLoggingConfiguration()} method.
     */
    public class BucketLoggingConfigurationHandler extends DefaultHandler {
        private BucketLoggingConfiguration bucketLoggingConfiguration = null;

        private String targetBucket = null;
        private String targetPrefix = null;
        private StringBuilder currText = null;

        public BucketLoggingConfigurationHandler() {
            this.currText = new StringBuilder();
        }

        /**
         * @return
         * an object representing the bucket's LoggingStatus document.
         */
        public BucketLoggingConfiguration getBucketLoggingConfiguration() {
            return bucketLoggingConfiguration;
        }

        @Override
        public void startDocument() {
        }

        @Override
        public void endDocument() {
        }

        @Override
        public void startElement(String uri, String name, String qName, Attributes attrs) {
            if (name.equals("BucketLoggingStatus")) {
                bucketLoggingConfiguration = new BucketLoggingConfiguration();
            }
        }

        @Override
        public void endElement(String uri, String name, String qName) {
            String elementText = this.currText.toString();
            if (name.equals("TargetBucket")) {
                targetBucket = elementText;
            } else if (name.equals("TargetPrefix")) {
                targetPrefix = elementText;
            } else if (name.equals("LoggingEnabled")) {
                bucketLoggingConfiguration.setDestinationBucketName(targetBucket);
                bucketLoggingConfiguration.setLogFilePrefix(targetPrefix);
            }
            this.currText = new StringBuilder();
        }

        @Override
        public void characters(char ch[], int start, int length) {
            this.currText.append(ch, start, length);
        }
    }

    /**
     * Handler for CreateBucketConfiguration response XML documents for a
     * bucket. The document is parsed into a String representing the bucket's
     * location, available via the {@link #getLocation()} method.
     */
    public class BucketLocationHandler extends DefaultHandler {
        private String location = null;

        private StringBuilder currText = null;

        public BucketLocationHandler() {
            super();
            this.currText = new StringBuilder();
        }

        /**
         * @return
         * the bucket's location.
         */
        public String getLocation() {
            return location;
        }

        @Override
        public void startDocument() {
        }

        @Override
        public void endDocument() {
        }

        @Override
        public void startElement(String uri, String name, String qName, Attributes attrs) {
            if (name.equals("CreateBucketConfiguration")) {
            }
        }

        @Override
        public void endElement(String uri, String name, String qName) {
            String elementText = this.currText.toString();
            if (name.equals("LocationConstraint")) {
                if (elementText.length() == 0) {
                    location = null;
                } else {
                    location = elementText;
                }
            }
            this.currText = new StringBuilder();
        }

        @Override
        public void characters(char ch[], int start, int length) {
            this.currText.append(ch, start, length);
        }
    }


    public class CopyObjectResultHandler extends DefaultHandler implements ServerSideEncryptionResult, ObjectExpirationResult {

        // Data items for successful copy
        private String etag = null;
        private Date lastModified = null;
        private String versionId = null;
        private String serverSideEncryption;
        private Date expirationTime;
        private String expirationTimeRuleId;

        // Data items for failed copy
        private String errorCode = null;
        private String errorMessage = null;
        private String errorRequestId = null;
        private String errorHostId = null;
        private boolean receivedErrorResponse = false;

        private StringBuilder currText = null;

        public CopyObjectResultHandler() {
            super();
            this.currText = new StringBuilder();
        }

        public Date getLastModified() {
            return lastModified;
        }

        public String getVersionId() {
            return versionId;
        }

        public void setVersionId(String versionId) {
            this.versionId = versionId;
        }

        @Override
        public String getServerSideEncryption() {
            return serverSideEncryption;
        }

        @Override
        public void setServerSideEncryption(String serverSideEncryption) {
            this.serverSideEncryption = serverSideEncryption;
        }

        @Override
        public Date getExpirationTime() {
            return expirationTime;
        }

        @Override
        public void setExpirationTime(Date expirationTime) {
            this.expirationTime = expirationTime;
        }

        @Override
        public String getExpirationTimeRuleId() {
            return expirationTimeRuleId;
        }

        @Override
        public void setExpirationTimeRuleId(String expirationTimeRuleId) {
            this.expirationTimeRuleId = expirationTimeRuleId;
        }

        public String getETag() {
            return etag;
        }

        public String getErrorCode() {
            return errorCode;
        }

        public String getErrorHostId() {
            return errorHostId;
        }

        public String getErrorMessage() {
            return errorMessage;
        }

        public String getErrorRequestId() {
            return errorRequestId;
        }

        public boolean isErrorResponse() {
            return receivedErrorResponse;
        }


        @Override
        public void startDocument() {
        }

        @Override
        public void endDocument() {
        }

        @Override
        public void startElement(String uri, String name, String qName, Attributes attrs) {
            if (name.equals("CopyObjectResult")) {
                receivedErrorResponse = false;
            } else if (name.equals("Error")) {
                receivedErrorResponse = true;
            }
        }

        @Override
        public void endElement(String uri, String name, String qName) {
            String elementText = this.currText.toString();

            if (name.equals("LastModified")) {
                try {
                    lastModified = ServiceUtils.parseIso8601Date(elementText);
                } catch (ParseException e) {
                    throw new RuntimeException(
                        "Non-ISO8601 date for LastModified in copy object output: "
                        + elementText, e);
                }
            } else if (name.equals("ETag")) {
                etag = ServiceUtils.removeQuotes(elementText);
            } else if (name.equals("Code")) {
                errorCode = elementText;
            } else if (name.equals("Message")) {
                errorMessage = elementText;
            } else if (name.equals("RequestId")) {
                errorRequestId = elementText;
            } else if (name.equals("HostId")) {
                errorHostId = elementText;
            }

            this.currText = new StringBuilder();
        }

        @Override
        public void characters(char ch[], int start, int length) {
            this.currText.append(ch, start, length);
        }
    }

    /**
     * Handler for RequestPaymentConfiguration response XML documents for a
     * bucket. The document is parsed into a boolean value: true if the bucket's
     * is configured as Requester Pays, false if it is configured as Owner pays.
     * This boolean value is available via the {@link #isRequesterPays()}
     * method.
     */
    public class RequestPaymentConfigurationHandler extends DefaultHandler {
        private String payer = null;

        private StringBuilder currText = null;

        public RequestPaymentConfigurationHandler() {
            super();
            this.currText = new StringBuilder();
        }

        /**
         * @return true if the bucket's is configured as Requester Pays, false
         *         if it is configured as Owner pays.
         */
        public boolean isRequesterPays() {
            return "Requester".equals(payer);
        }

        @Override
        public void startDocument() {
        }

        @Override
        public void endDocument() {
        }

        @Override
        public void startElement(String uri, String name, String qName, Attributes attrs) {
            if (name.equals("RequestPaymentConfiguration")) {
            }
        }

        @Override
        public void endElement(String uri, String name, String qName) {
            String elementText = this.currText.toString();
            if (name.equals("Payer")) {
                payer = elementText;
            }
            this.currText = new StringBuilder();
        }

        @Override
        public void characters(char ch[], int start, int length) {
            this.currText.append(ch, start, length);
        }
    }

    /**
     * Handler for ListVersionsResult XML document.
     */
    public class ListVersionsHandler extends DefaultHandler {
        private VersionListing versionListing;
        private List versionSummaries;
        private S3VersionSummary currentVersionSummary;
        private Owner owner;
        private StringBuilder text;
        private boolean insideCommonPrefixes = false;

        public VersionListing getListing() { return versionListing; }

        @Override
        public void startDocument() {
            versionListing = new VersionListing();
            versionSummaries = new ArrayList();
            text = new StringBuilder();
        }

        @Override
        public void startElement(String uri, String name, String qName, Attributes attrs) {
            if (name.equals("ListVersionsResult")) {
            } else if (name.equals("CommonPrefixes")) {
                insideCommonPrefixes = true;
            } else if (name.equals("Name")) {
            } else if (name.equals("Prefix")) {
            } else if (name.equals("Delimiter")) {
            } else if (name.equals("EncodingType")) {
            } else if (name.equals("KeyMarker")) {
            } else if (name.equals("VersionIdMarker")) {
            } else if (name.equals("MaxKeys")) {
            } else if (name.equals("NextKeyMarker")) {
            } else if (name.equals("NextVersionIdMarker")) {
            } else if (name.equals("IsTruncated")) {
            } else if (name.equals("Version")) {
                currentVersionSummary = new S3VersionSummary();
                currentVersionSummary.setBucketName(versionListing.getBucketName());
            } else if (name.equals("DeleteMarker")) {
                currentVersionSummary = new S3VersionSummary();
                currentVersionSummary.setBucketName(versionListing.getBucketName());
                currentVersionSummary.setIsDeleteMarker(true);
            } else if (name.equals("Key")) {
            } else if (name.equals("VersionId")) {
            } else if (name.equals("IsLatest")) {
            } else if (name.equals("LastModified")) {
            } else if (name.equals("ETag")) {
            } else if (name.equals("Size")) {
            } else if (name.equals("Owner")) {
                this.owner = new Owner();
            } else if (name.equals("StorageClass")) {
            } else if (name.equals("ID")) {
            } else if (name.equals("DisplayName")) {
            } else {
                log.warn("Ignoring unexpected tag <"+name+">");
            }
            text.setLength(0);
        }

        @Override
        public void endElement(String uri, String name, String qName) throws SAXException {
            if (name.equals("ListVersionsResult")) {
                versionListing.setVersionSummaries(versionSummaries);
            } else if (name.equals("Name")) {
                versionListing.setBucketName(text.toString());
            } else if (!insideCommonPrefixes && name.equals("Prefix")) {
                versionListing.setPrefix(checkForEmptyString(text.toString()));
            } else if (insideCommonPrefixes && name.equals("Prefix")) {
                versionListing.getCommonPrefixes().add(checkForEmptyString(text.toString()));
            } else if (name.equals("CommonPrefixes")) {
                insideCommonPrefixes = false;
            } else if (name.equals("KeyMarker")) {
                versionListing.setKeyMarker(checkForEmptyString(text.toString()));
            } else if (name.equals("VersionIdMarker")) {
                versionListing.setVersionIdMarker(checkForEmptyString(text.toString()));
            } else if (name.equals("MaxKeys")) {
                versionListing.setMaxKeys(Integer.parseInt(text.toString()));
            } else if (name.equals("Delimiter")) {
                versionListing.setDelimiter(checkForEmptyString(text.toString()));
            } else if (name.equals("EncodingType")) {
                versionListing.setEncodingType(checkForEmptyString(text.toString()));
            } else if (name.equals("NextKeyMarker")) {
                versionListing.setNextKeyMarker(text.toString());
            } else if (name.equals("NextVersionIdMarker")) {
                versionListing.setNextVersionIdMarker(text.toString());
            } else if (name.equals("IsTruncated")) {
                versionListing.setTruncated("true".equals(text.toString()));
            } else if (name.equals("Version")) {
                assert(currentVersionSummary != null);
                versionSummaries.add(currentVersionSummary);
                currentVersionSummary = null;
            } else if (name.equals("DeleteMarker")) {
                assert(currentVersionSummary != null);
                versionSummaries.add(currentVersionSummary);
                currentVersionSummary = null;
            } else if (name.equals("Key")) {
                assert(currentVersionSummary != null);
                currentVersionSummary.setKey(text.toString());
            } else if (name.equals("VersionId")) {
                assert(currentVersionSummary != null);
                currentVersionSummary.setVersionId(text.toString());
            } else if (name.equals("IsLatest")) {
                assert(currentVersionSummary != null);
                currentVersionSummary.setIsLatest("true".equals(text.toString()));
            } else if (name.equals("LastModified")) {
                assert(currentVersionSummary != null);
                try {
                    currentVersionSummary.setLastModified(ServiceUtils.parseIso8601Date(text.toString()));
                } catch (ParseException e) {
                    throw new SAXException(
                        "Non-ISO8601 date for LastModified in copy object output: "
                        + text.toString(), e);
                }
            } else if (name.equals("ETag")) {
                assert(currentVersionSummary != null);
                assert(!currentVersionSummary.isDeleteMarker());
                currentVersionSummary.setETag(ServiceUtils.removeQuotes(text.toString()));
            } else if (name.equals("Size")) {
                assert(currentVersionSummary != null);
                assert(!currentVersionSummary.isDeleteMarker());
                currentVersionSummary.setSize(Long.parseLong(text.toString()));
            } else if (name.equals("Owner")) {
                currentVersionSummary.setOwner(owner);
                owner = null;
            } else if (name.equals("StorageClass")) {
                assert(currentVersionSummary != null);
                assert(!currentVersionSummary.isDeleteMarker());
                currentVersionSummary.setStorageClass(text.toString());
            } else if (name.equals("ID")) {
                assert(owner != null);
                owner.setId(text.toString());
            } else if (name.equals("DisplayName")) {
                assert(owner != null);
                owner.setDisplayName(text.toString());
            } else {
                log.warn("Ignoring unexpected tag <"+name+">");
            }
            text.setLength(0);
        }

        @Override
        public void characters(char ch[], int start, int length) {
            this.text.append(ch, start, length);
        }
    }

    public class BucketWebsiteConfigurationHandler extends DefaultHandler {
        private BucketWebsiteConfiguration configuration = new BucketWebsiteConfiguration(null);
        private RoutingRuleCondition condition = null;
        private RedirectRule  redirect  = null;
        private List rules = null;
        private RedirectRule  redirectAllRequestsTo = null;
        RoutingRule rule = null;
        private StringBuilder text;

        boolean inIndexDocumentElement = false;
        boolean inErrorDocumentElement = false;
        boolean inRoutingRules = false;
        boolean inRoutingRule = false;
        boolean inCondition = false;
        boolean inRedirect = false;
        boolean inRedirectAllRequestsTo = false;

        public BucketWebsiteConfiguration getConfiguration() { return configuration; }

        @Override
        public void startDocument() {
            text = new StringBuilder();
        }

        @Override
        public void startElement(String uri, String name, String qName, Attributes attrs) {
            if (name.equals("WebsiteConfiguration")) {
            } else if (name.equals("IndexDocument")) {
                inIndexDocumentElement = true;
            } else if (name.equals("Suffix") && inIndexDocumentElement) {
            } else if (name.equals("ErrorDocument")) {
                inErrorDocumentElement = true;
            } else if (name.equals("Key") && inErrorDocumentElement) {
            } else if (name.equals("RedirectAllRequestsTo")) {
                redirectAllRequestsTo = new RedirectRule();
                inRedirectAllRequestsTo = true;
            } else if (name.equals("RoutingRules")) {
                rules = new LinkedList();
                inRoutingRules = true;
            } else if (name.equals("RoutingRule") && inRoutingRules) {
                rule = new RoutingRule();
                inRoutingRule = true;
            } else if (name.equals("Condition") && inRoutingRule) {
                condition = new RoutingRuleCondition();
                inCondition = true;
            } else if (name.equals("KeyPrefixEquals") && inCondition) {
            } else if (name.equals("HttpErrorCodeReturnedEquals") && inCondition) {
            } else if (name.equals("Redirect") && inRoutingRule) {
                redirect = new RedirectRule();
                inRedirect = true;
            } else if (name.equals("Protocol") && (inRedirect || inRedirectAllRequestsTo)) {
            } else if (name.equals("HostName") && (inRedirect || inRedirectAllRequestsTo)) {
            } else if (name.equals("ReplaceKeyPrefixWith") && (inRedirect || inRedirectAllRequestsTo)) {
            } else if (name.equals("ReplaceKeyWith") && (inRedirect || inRedirectAllRequestsTo)) {
            } else if (name.equals("HttpRedirectCode") && (inRedirect || inRedirectAllRequestsTo)) {
            } else {
                log.warn("Ignoring unexpected tag <"+name+">");
            }
        }

        @Override
        public void endElement(String uri, String name, String qName) throws SAXException {
            if (name.equals("WebsiteConfiguration")) {
            } else if (name.equals("IndexDocument")) {
                inIndexDocumentElement = false;
            } else if (name.equals("Suffix") && inIndexDocumentElement) {
                configuration.setIndexDocumentSuffix(text.toString());
            } else if (name.equals("ErrorDocument")) {
                inErrorDocumentElement = false;
            } else if (name.equals("Key") && inErrorDocumentElement) {
                configuration.setErrorDocument(text.toString());
            } else if (name.equals("KeyPrefixEquals") && inCondition) {
                condition.setKeyPrefixEquals(text.toString());
            } else if (name.equals("HttpErrorCodeReturnedEquals") && inCondition) {
                condition.setHttpErrorCodeReturnedEquals(text.toString());
            } else if (name.equals("Condition") && inRoutingRule) {
                rule.setCondition(condition);
                inCondition = false;
                condition = null;
            } else if (name.equals("Protocol") && inRedirect) {
                redirect.setProtocol(text.toString());
            } else if (name.equals("Protocol") && inRedirectAllRequestsTo) {
                redirectAllRequestsTo.setProtocol(text.toString());
            } else if (name.equals("HostName") && inRedirect) {
                redirect.setHostName(text.toString());
            } else if (name.equals("HostName") && inRedirectAllRequestsTo) {
                redirectAllRequestsTo.setHostName(text.toString());
            } else if (name.equals("ReplaceKeyPrefixWith") && inRedirect) {
                redirect.setReplaceKeyPrefixWith(text.toString());
            } else if (name.equals("ReplaceKeyPrefixWith") && inRedirectAllRequestsTo) {
                redirectAllRequestsTo.setReplaceKeyPrefixWith(text.toString());
            } else if (name.equals("ReplaceKeyWith") && inRedirect) {
                redirect.setReplaceKeyWith(text.toString());
            } else if (name.equals("ReplaceKeyWith") && inRedirectAllRequestsTo) {
                redirectAllRequestsTo.setReplaceKeyWith(text.toString());
            } else if (name.equals("HttpRedirectCode") && inRedirect) {
                redirect.setHttpRedirectCode(text.toString());
            } else if (name.equals("HttpRedirectCode") && inRedirectAllRequestsTo) {
                redirectAllRequestsTo.setHttpRedirectCode(text.toString());
            } else if (name.equals("Redirect") && inRoutingRule) {
                rule.setRedirect(redirect);
                inRedirect = false;
                redirect = null;
            } else if (name.equals("RoutingRule") && inRoutingRules) {
                rules.add(rule);
                rule = null;
                inRoutingRule = false;
            } else if (name.equals("RoutingRules")) {
                configuration.setRoutingRules(rules);
                rules = null;
                inRoutingRules = false;
            } else if (name.equals("RedirectAllRequestsTo")) {
                configuration.setRedirectAllRequestsTo(redirectAllRequestsTo);
                inRedirectAllRequestsTo = false;
                redirectAllRequestsTo = null;
            }
            text.setLength(0);
        }

        @Override
        public void characters(char ch[], int start, int length) {
            this.text.append(ch, start, length);
        }
    }

    public class BucketVersioningConfigurationHandler extends DefaultHandler {
        private BucketVersioningConfiguration configuration = new BucketVersioningConfiguration();
        private StringBuilder text;

        public BucketVersioningConfiguration getConfiguration() { return configuration; }

        @Override
        public void startDocument() {
            text = new StringBuilder();
        }

        @Override
        public void startElement(String uri, String name, String qName, Attributes attrs) {
            if (name.equals("VersioningConfiguration")) {
            } else if (name.equals("Status")) {
                text.setLength(0);
            } else if (name.equals("MfaDelete")) {
                text.setLength(0);
            } else {
                log.warn("Ignoring unexpected tag <"+name+">");
            }
        }

        @Override
        public void endElement(String uri, String name, String qName) throws SAXException {
            if (name.equals("Status")) {
                configuration.setStatus(text.toString());
            } else if (name.equals("MfaDelete")) {
                String mfaDeleteStatus = text.toString();
                if (mfaDeleteStatus.equals("Disabled")) {
                    configuration.setMfaDeleteEnabled(false);
                } else if (mfaDeleteStatus.equals("Enabled")) {
                    configuration.setMfaDeleteEnabled(true);
                } else {
                    configuration.setMfaDeleteEnabled(null);
                }
            }
            text.setLength(0);
        }

        @Override
        public void characters(char ch[], int start, int length) {
            this.text.append(ch, start, length);
        }
    }

    /*
     * 
     * 
     *     http://Example-Bucket.s3.amazonaws.com/Example-Object
     *     Example-Bucket
     *     Example-Object
     *     "3858f62230ac3c915f300c664312c11f-9"
     * 
     *
     * Or if an error occurred while completing:
     *
     * 
     * 
     *     InternalError
     *     We encountered an internal error. Please try again.
     *     656c76696e6727732072657175657374
     *     Uuag1LuByRx9e6j5Onimru9pO4ZVKnJ2Qz7/C1NPcfTWAtRPfTaOFg==
     * 
     */
    public class CompleteMultipartUploadHandler extends DefaultHandler implements ServerSideEncryptionResult,
            ObjectExpirationResult {

        private StringBuilder text;

        // Successful completion
        private CompleteMultipartUploadResult result;

        @Override
        public String getServerSideEncryption() {
            if ( result != null )
                return result.getServerSideEncryption();
            else
                return null;
        }

        @Override
        public void setServerSideEncryption(String serverSideEncryption) {
            if ( result != null )
                result.setServerSideEncryption(serverSideEncryption);
        }

        /**
         * @see com.amazonaws.services.s3.model.CompleteMultipartUploadResult#getExpirationTime()
         */
        @Override
        public Date getExpirationTime() {
            if ( result != null )
                return result.getExpirationTime();
            return null;
        }

        /**
         * @see com.amazonaws.services.s3.model.CompleteMultipartUploadResult#setExpirationTime(java.util.Date)
         */
        @Override
        public void setExpirationTime(Date expirationTime) {
            if ( result != null )
                result.setExpirationTime(expirationTime);
        }

        /**
         * @see com.amazonaws.services.s3.model.CompleteMultipartUploadResult#getExpirationTimeRuleId()
         */
        @Override
        public String getExpirationTimeRuleId() {
            if ( result != null )
                return result.getExpirationTimeRuleId();
            return null;
        }

        /**
         * @see com.amazonaws.services.s3.model.CompleteMultipartUploadResult#setExpirationTimeRuleId(java.lang.String)
         */
        @Override
        public void setExpirationTimeRuleId(String expirationTimeRuleId) {
            if ( result != null )
                result.setExpirationTimeRuleId(expirationTimeRuleId);
        }

        // Error during completion
        private AmazonS3Exception ase;
        private String hostId;
        private String requestId;
        private String errorCode;

        public CompleteMultipartUploadResult getCompleteMultipartUploadResult() {
            return result;
        }

        public AmazonS3Exception getAmazonS3Exception() {
            return ase;
        }

        @Override
        public void startDocument() {
            text = new StringBuilder();
        }

        @Override
        public void startElement(String uri, String name, String qName, Attributes attrs) {
            // Success response XML elements
            if (name.equals("CompleteMultipartUploadResult")) {
                result = new CompleteMultipartUploadResult();
            } else if (name.equals("Location")) {
            } else if (name.equals("Bucket")) {
            } else if (name.equals("Key")) {
            } else if (name.equals("ETag")) {
            }

            // Error response XML elements
            if (name.equals("Error")) {
            } else if (name.equals("Code")) {
            } else if (name.equals("Message")) {
            } else if (name.equals("RequestId")) {
            } else if (name.equals("HostId")) {
            }
            text.setLength(0);
        }

        @Override
        public void endElement(String uri, String name, String qName) throws SAXException {
            if (result != null) {
                // Success response XML elements
                if (name.equals("CompleteMultipartUploadResult")) {
                } else if (name.equals("Location")) {
                    result.setLocation(text.toString());
                } else if (name.equals("Bucket")) {
                    result.setBucketName(text.toString());
                } else if (name.equals("Key")) {
                    result.setKey(text.toString());
                } else if (name.equals("ETag")) {
                    result.setETag(ServiceUtils.removeQuotes(text.toString()));
                }
            } else {
                // Error response XML elements
                if (name.equals("Error")) {
                    ase.setErrorCode(errorCode);
                    ase.setRequestId(requestId);
                    ase.setExtendedRequestId(hostId);
                } else if (name.equals("Code")) {
                    errorCode = text.toString();
                } else if (name.equals("Message")) {
                    ase = new AmazonS3Exception(text.toString());
                } else if (name.equals("RequestId")) {
                    requestId = text.toString();
                } else if (name.equals("HostId")) {
                    hostId = text.toString();
                }
            }
        }

        @Override
        public void characters(char ch[], int start, int length) {
            this.text.append(ch, start, length);
        }
    }

    /*
     * 
     * 
     *     example-bucket
     *     example-object
     *     VXBsb2FkIElEIGZvciA2aWWpbmcncyBteS1tb3ZpZS5tMnRzIHVwbG9hZA
     * 
     */
    public class InitiateMultipartUploadHandler extends DefaultHandler {
        private StringBuilder text;

        private InitiateMultipartUploadResult result;

        public InitiateMultipartUploadResult getInitiateMultipartUploadResult() {
            return result;
        }

        @Override
        public void startDocument() {
            text = new StringBuilder();
        }

        @Override
        public void startElement(String uri, String name, String qName, Attributes attrs) {
            if (name.equals("InitiateMultipartUploadResult")) {
                result = new InitiateMultipartUploadResult();
            } else if (name.equals("Bucket")) {
            } else if (name.equals("Key")) {
            } else if (name.equals("UploadId")) {
            }
            text.setLength(0);
        }

        @Override
        public void endElement(String uri, String name, String qName) throws SAXException {
            if (name.equals("InitiateMultipartUploadResult")) {
            } else if (name.equals("Bucket")) {
                result.setBucketName(text.toString());
            } else if (name.equals("Key")) {
                result.setKey(text.toString());
            } else if (name.equals("UploadId")) {
                result.setUploadId(text.toString());
            }
        }

        @Override
        public void characters(char ch[], int start, int length) {
            this.text.append(ch, start, length);
        }
    }

    /*
     * HTTP/1.1 200 OK
     * x-amz-id-2: Uuag1LuByRx9e6j5Onimru9pO4ZVKnJ2Qz7/C1NPcfTWAtRPfTaOFg==
     * x-amz-request-id: 656c76696e6727732072657175657374
     * Date: Tue, 16 Feb 2010 20:34:56 GMT
     * Content-Length: 1330
     * Connection: keep-alive
     * Server: AmazonS3
     *
     * 
     * 
     *     bucket
     *     
     *     /
     *     
     *     
     *     my-movie.m2ts
     *     YW55IGlkZWEgd2h5IGVsdmluZydzIHVwbG9hZCBmYWlsZWQ
     *     3
     *     true
     *     
     *         my-divisor
     *         XMgbGlrZSBlbHZpbmcncyBub3QgaGF2aW5nIG11Y2ggbHVjaw
     *         
     *             b1d16700c70b0b05597d7acd6a3f92be
     *             delving
     *         
     *         STANDARD
     *         Tue, 26 Jan 2010 19:42:19 GMT
     *     
     *     
     *         my-movie.m2ts
     *         VXBsb2FkIElEIGZvciBlbHZpbmcncyBteS1tb3ZpZS5tMnRzIHVwbG9hZA
     *         
     *             b1d16700c70b0b05597d7acd6a3f92be
     *             delving
     *         
     *         STANDARD
     *         Tue, 16 Feb 2010 20:34:56 GMT
     *     
     *     
     *         my-movie.m2ts
     *         YW55IGlkZWEgd2h5IGVsdmluZydzIHVwbG9hZCBmYWlsZWQ
     *         
     *             b1d16700c70b0b05597d7acd6a3f92be
     *             delving
     *         
     *         STANDARD
     *         Wed, 27 Jan 2010 03:02:01 GMT
     *     
     *    
     *        photos/
     *    
     *    
     *        videos/
     *    
     * 
     */
    public class ListMultipartUploadsHandler extends DefaultHandler {
        private StringBuilder text;

        private MultipartUploadListing result;

        private MultipartUpload currentMultipartUpload;
        private Owner currentOwner;
        private Owner currentInitiator;

        boolean inCommonPrefixes = false;

        public MultipartUploadListing getListMultipartUploadsResult() {
            return result;
        }

        @Override
        public void startDocument() {
            text = new StringBuilder();
        }

        @Override
        public void startElement(String uri, String name, String qName, Attributes attrs) {
            if (name.equals("ListMultipartUploadsResult")) {
                result = new MultipartUploadListing();
            } else if (name.equals("Bucket")) {
            } else if (name.equals("KeyMarker")) {
            } else if (name.equals("Delimiter")) {
            } else if (name.equals("UploadIdMarker")) {
            } else if (name.equals("NextKeyMarker")) {
            } else if (name.equals("NextUploadIdMarker")) {
            } else if (name.equals("MaxUploads")) {
            } else if (name.equals("EncodingType")) {
            } else if (name.equals("IsTruncated")) {
            } else if (name.equals("Upload")) {
                currentMultipartUpload = new MultipartUpload();
            } else if (name.equals("Key")) {
            } else if (name.equals("UploadId")) {
            } else if (name.equals("Owner")) {
                currentOwner = new Owner();
            } else if (name.equals("Initiator")) {
                currentInitiator = new Owner();
            } else if (name.equals("ID")) {
            } else if (name.equals("DisplayName")) {
            } else if (name.equals("StorageClass")) {
            } else if (name.equals("Initiated")) {
            } else if (name.equals("CommonPrefixes")) {
                inCommonPrefixes = true;
            }
            text.setLength(0);
        }

        @Override
        public void endElement(String uri, String name, String qName) throws SAXException {
            if (name.equals("ListMultipartUploadsResult")) {
            } else if (name.equals("Bucket")) {
                result.setBucketName(text.toString());
            } else if (name.equals("KeyMarker")) {
                result.setKeyMarker(checkForEmptyString(text.toString()));
            } else if (name.equals("Delimiter")) {
                result.setDelimiter(checkForEmptyString(text.toString()));
            } else if (name.equals("Prefix") && inCommonPrefixes == false) {
                result.setPrefix(checkForEmptyString(text.toString()));
            } else if (name.equals("Prefix") && inCommonPrefixes == true) {
                result.getCommonPrefixes().add(text.toString());
            } else if (name.equals("UploadIdMarker")) {
                result.setUploadIdMarker(checkForEmptyString(text.toString()));
            } else if (name.equals("NextKeyMarker")) {
                result.setNextKeyMarker(checkForEmptyString(text.toString()));
            } else if (name.equals("NextUploadIdMarker")) {
                result.setNextUploadIdMarker(checkForEmptyString(text.toString()));
            } else if (name.equals("MaxUploads")) {
                result.setMaxUploads(Integer.parseInt(text.toString()));
            } else if (name.equals("EncodingType")) {
                result.setEncodingType(checkForEmptyString(text.toString()));
            } else if (name.equals("IsTruncated")) {
                result.setTruncated(Boolean.parseBoolean(text.toString()));
            } else if (name.equals("Upload")) {
                result.getMultipartUploads().add(currentMultipartUpload);
            } else if (name.equals("Key")) {
                currentMultipartUpload.setKey(text.toString());
            } else if (name.equals("UploadId")) {
                currentMultipartUpload.setUploadId(text.toString());
            } else if (name.equals("Owner")) {
                currentMultipartUpload.setOwner(currentOwner);
                currentOwner = null;
            } else if (name.equals("Initiator")) {
                currentMultipartUpload.setInitiator(currentInitiator);
                currentInitiator = null;
            } else if (name.equals("ID") && currentOwner != null) {
                currentOwner.setId(checkForEmptyString(text.toString()));
            } else if (name.equals("DisplayName") && currentOwner != null) {
                currentOwner.setDisplayName(checkForEmptyString(text.toString()));
            } else if (name.equals("ID") && currentInitiator != null) {
                currentInitiator.setId(checkForEmptyString(text.toString()));
            } else if (name.equals("DisplayName") && currentInitiator != null) {
                currentInitiator.setDisplayName(checkForEmptyString(text.toString()));
            } else if (name.equals("StorageClass")) {
                currentMultipartUpload.setStorageClass(text.toString());
            } else if (name.equals("Initiated")) {
                try {
                    currentMultipartUpload.setInitiated(ServiceUtils.parseIso8601Date(text.toString()));
                } catch (ParseException e) {
                    throw new SAXException(
                            "Non-ISO8601 date for Initiated in initiate multipart upload result: "
                            + text.toString(), e);
                }
            } else if (name.equals("CommonPrefixes")) {
                inCommonPrefixes = false;
            }
        }

        @Override
        public void characters(char ch[], int start, int length) {
            this.text.append(ch, start, length);
        }
    }

    /*
     * HTTP/1.1 200 OK
     * x-amz-id-2: Uuag1LuByRx9e6j5Onimru9pO4ZVKnJ2Qz7/C1NPcfTWAtRPfTaOFg==
     * x-amz-request-id: 656c76696e6727732072657175657374
     * Date: Tue, 16 Feb 2010 20:34:56 GMT
     * Content-Length: 985
     * Connection: keep-alive
     * Server: AmazonS3
     *
     * 
     * 
     *     example-bucket
     *     example-object
     *     XXBsb2FkIElEIGZvciBlbHZpbmcncyVcdS1tb3ZpZS5tMnRzEEEwbG9hZA
     *     
     *         x1x16700c70b0b05597d7ecd6a3f92be
     *         username
     *     
     *     
     *         x1x16700c70b0b05597d7ecd6a3f92be
     *         username
     *     
     *     STANDARD
     *     1
     *     3
     *     2
     *     true
     *     
     *         2
     *         Wed, 27 Jan 2010 03:02:03 GMT
     *         "7778aef83f66abc1fa1e8477f296d394"
     *         10485760
     *     
     *     
     *        3
     *        Wed, 27 Jan 2010 03:02:02 GMT
     *        "aaaa18db4cc2f85cedef654fccc4a4x8"
     *        10485760
     *     
     * 
     */
    public class ListPartsHandler extends DefaultHandler {
        private StringBuilder text;

        private PartListing result;
        private Owner currentOwner;
        private Owner currentInitiator;
        private PartSummary currentPart;

        public PartListing getListPartsResult() {
            return result;
        }

        @Override
        public void startDocument() {
            text = new StringBuilder();
        }

        @Override
        public void startElement(String uri, String name, String qName, Attributes attrs) {
            if (name.equals("ListPartsResult")) {
                result = new PartListing();
            } else if (name.equals("Bucket")) {
            } else if (name.equals("Key")) {
            } else if (name.equals("UploadId")) {
            } else if (name.equals("Owner")) {
                currentOwner = new Owner();
            } else if (name.equals("Initiator")) {
                currentInitiator = new Owner();
            } else if (name.equals("ID")) {
            } else if (name.equals("DisplayName")) {
            } else if (name.equals("StorageClass")) {
            } else if (name.equals("PartNumberMarker")) {
            } else if (name.equals("NextPartNumberMarker")) {
            } else if (name.equals("MaxParts")) {
            } else if (name.equals("EncodingType")) {
            } else if (name.equals("IsTruncated")) {
            } else if (name.equals("Part")) {
                currentPart = new PartSummary();
            } else if (name.equals("PartNumber")) {
            } else if (name.equals("LastModified")) {
            } else if (name.equals("ETag")) {
            } else if (name.equals("Size")) {
            }
            text.setLength(0);
        }

        private Integer parseInteger(String text) {
            text = checkForEmptyString(text.toString());
            if (text == null) return null;
            return Integer.parseInt(text);
        }

        @Override
        public void endElement(String uri, String name, String qName) throws SAXException {
            if (name.equals("ListPartsResult")) {
            } else if (name.equals("Bucket")) {
                result.setBucketName(text.toString());
            } else if (name.equals("Key")) {
                result.setKey(text.toString());
            } else if (name.equals("UploadId")) {
                result.setUploadId(text.toString());
            } else if (name.equals("Owner")) {
                result.setOwner(currentOwner);
                currentOwner = null;
            } else if (name.equals("Initiator")) {
                result.setInitiator(currentInitiator);
                currentInitiator = null;
            } else if (name.equals("ID") && currentOwner != null) {
                currentOwner.setId(checkForEmptyString(text.toString()));
            } else if (name.equals("DisplayName") && currentOwner != null) {
                currentOwner.setDisplayName(checkForEmptyString(text.toString()));
            } else if (name.equals("ID") && currentInitiator != null) {
                currentInitiator.setId(checkForEmptyString(text.toString()));
            } else if (name.equals("DisplayName") && currentInitiator != null) {
                currentInitiator.setDisplayName(checkForEmptyString(text.toString()));
            } else if (name.equals("StorageClass")) {
                result.setStorageClass(text.toString());
            } else if (name.equals("PartNumberMarker")) {
                result.setPartNumberMarker(parseInteger(text.toString()));
            } else if (name.equals("NextPartNumberMarker")) {
                result.setNextPartNumberMarker(parseInteger(text.toString()));
            } else if (name.equals("MaxParts")) {
                result.setMaxParts(parseInteger(text.toString()));
            } else if (name.equals("EncodingType")) {
                result.setEncodingType(checkForEmptyString(text.toString()));
            } else if (name.equals("IsTruncated")) {
                result.setTruncated(Boolean.parseBoolean(text.toString()));
            } else if (name.equals("Part")) {
                result.getParts().add(currentPart);
            } else if (name.equals("PartNumber")) {
                currentPart.setPartNumber(Integer.parseInt(text.toString()));
            } else if (name.equals("LastModified")) {
                try {
                    currentPart.setLastModified(ServiceUtils.parseIso8601Date(text.toString()));
                } catch (ParseException e) {
                    throw new SAXException(
                            "Non-ISO8601 date for LastModified in list parts result: "
                            + text.toString(), e);
                }
            } else if (name.equals("ETag")) {
                currentPart.setETag(ServiceUtils.removeQuotes(text.toString()));
            } else if (name.equals("Size")) {
                currentPart.setSize(Long.parseLong(text.toString()));
            }
        }

        @Override
        public void characters(char ch[], int start, int length) {
            this.text.append(ch, start, length);
        }
    }

    public class BucketNotificationConfigurationHandler extends DefaultHandler {
        private BucketNotificationConfiguration configuration = new BucketNotificationConfiguration();
        private StringBuilder text;
        private List topicConfigurations;
        private String topic;
        private String event;

        public BucketNotificationConfiguration getConfiguration() { return configuration; }

        @Override
        public void startDocument() {
            text = new StringBuilder();
        }

        @Override
        public void startElement(String uri, String name, String qName, Attributes attrs) {
            if (name.equals("NotificationConfiguration")) {
                topicConfigurations = new ArrayList(1);
            }
            else if (name.equals("TopicConfiguration")) {
                topic = null;
                event = null;
            } else if (name.equals("Topic")) {
                text.setLength(0);
            } else if (name.equals("Event")) {
                text.setLength(0);
            } else {
                log.warn("Ignoring unexpected tag <"+name+">");
            }
        }

        @Override
        public void endElement(String uri, String name, String qName) throws SAXException {
            if (name.equals("Topic")) {
                topic = text.toString();
            }
            else if (name.equals("Event")) {
                event = text.toString();
            }
            else if (name.equals("TopicConfiguration")) {
                if ( topic != null && event != null ) {
                    topicConfigurations.add( new TopicConfiguration( topic, event ) );
                }
            }
            else if (name.equals("NotificationConfiguration")) {
                configuration.setTopicConfigurations( topicConfigurations );
            }
            text.setLength(0);
        }

        @Override
        public void characters(char ch[], int start, int length) {
            this.text.append(ch, start, length);
        }
    }

    public class BucketTaggingConfigurationHandler extends DefaultHandler {
        private BucketTaggingConfiguration configuration = new BucketTaggingConfiguration();
        private StringBuilder text;
        private List tagSets;
        private Map tags;
        private String tagKey;
        private String tagValue;

        public BucketTaggingConfiguration getConfiguration() { return configuration; }

        @Override
        public void startDocument() {
            text = new StringBuilder();
        }

        @Override
        public void startElement(String uri, String name, String qName, Attributes attrs) {
            if (name.equals("Tagging")) {
                tagSets = new ArrayList(1);
            } else if (name.equals("TagSet")) {
                tags = new HashMap( 1 );
            } else if (name.equals("Tag")) {
                tagKey = null;
                tagValue = null;
            } else if (name.equals("Key")) {
                text.setLength(0);
            } else if (name.equals("Value")) {
                text.setLength(0);
            } else {
                log.warn("Ignoring unexpected tag <"+name+">");
            }
        }

        @Override
        public void endElement(String uri, String name, String qName) throws SAXException {
            if (name.equals("Key")) {
                tagKey = text.toString();
            } else if (name.equals("Value")) {
                tagValue = text.toString();
            } else if (name.equals("Tag")) {
                if (tagKey != null && tagValue != null) {
                    tags.put(tagKey, tagValue);
                }
            } else if (name.equals("TagSet")) {
                tagSets.add(new TagSet(tags));
            } else if (name.equals("Tagging")) {
                configuration.setTagSets(tagSets);
            }
            text.setLength(0);
        }

        @Override
        public void characters(char ch[], int start, int length) {
            this.text.append(ch, start, length);
        }
    }

    /*
        HTTP/1.1 200 OK
        x-amz-id-2: Uuag1LuByRx9e6j5Onimru9pO4ZVKnJ2Qz7/C1NPcfTWAtRPfTaOFg==
        x-amz-request-id: 656c76696e6727732072657175657374
        Date: Tue, 20 Sep 2012 20:34:56 GMT
        Content-Type: application/xml
        Transfer-Encoding: chunked
        Connection: keep-alive
        Server: AmazonS3

        
        
            
               Key
               Version
            
            
               Key
               Version
               Code
               Message
            
            
               Key
               true
               Version
            
        
     */
    public class DeleteObjectsHandler extends DefaultHandler {
        private StringBuilder text;

        private DeletedObject deletedObject = null;
        private DeleteError error = null;
        private List deletedObjects = new LinkedList();
        private List deleteErrors = new LinkedList();

        public DeleteObjectsResponse getDeleteObjectResult() {
            return new DeleteObjectsResponse(deletedObjects, deleteErrors);
        }

        @Override
        public void startDocument() {
            text = new StringBuilder();
        }

        @Override
        public void startElement(String uri, String name, String qName, Attributes attrs) {
            if ( name.equals("Deleted") ) {
                deletedObject = new DeletedObject();
            } else if ( name.equals("Error") ) {
                error = new DeleteError();
            } else if ( name.equals("Key") ) {
            } else if ( name.equals("VersionId") ) {
            } else if ( name.equals("Code") ) {
            } else if ( name.equals("Message") ) {
            } else if ( name.equals("DeleteMarker") ) {
            } else if ( name.equals("DeleteMarkerVersionId") ) {
            } else if ( name.equals("DeleteResult") ) {
            } else {
                log.warn("Unexpected tag: " + name);
            }
            text.setLength(0);
        }

        @Override
        public void endElement(String uri, String name, String qName) throws SAXException {
            if ( name.equals("Deleted") ) {
                deletedObjects.add(deletedObject);
                deletedObject = null;
            } else if ( name.equals("Error") ) {
                deleteErrors.add(error);
                error = null;
            } else if ( name.equals("Key") ) {
                if ( deletedObject != null ) {
                    deletedObject.setKey(text.toString());
                } else if ( error != null ) {
                    error.setKey(text.toString());
                }
            } else if ( name.equals("VersionId") ) {
                if ( deletedObject != null ) {
                    deletedObject.setVersionId(text.toString());
                } else if ( error != null ) {
                    error.setVersionId(text.toString());
                }
            } else if ( name.equals("Code") ) {
                if ( error != null ) {
                    error.setCode(text.toString());
                }
            } else if ( name.equals("Message") ) {
                if ( error != null ) {
                    error.setMessage(text.toString());
                }
            } else if ( name.equals("DeleteMarker") ) {
                if ( deletedObject != null ) {
                    deletedObject.setDeleteMarker(text.toString().equals("true"));
                }
            } else if ( name.equals("DeleteMarkerVersionId") ) {
                if ( deletedObject != null ) {
                    deletedObject.setDeleteMarkerVersionId(text.toString());
                }
            }
        }

        @Override
        public void characters(char ch[], int start, int length) {
            this.text.append(ch, start, length);
        }
    }

    /*
    HTTP/1.1 200 OK
    x-amz-id-2: Uuag1LuByRx9e6j5Onimru9pO4ZVKnJ2Qz7/C1NPcfTWAtRPfTaOFg==
    x-amz-request-id: 656c76696e6727732072657175657374
    Date: Tue, 20 Sep 2012 20:34:56 GMT
    Content-Length: xxx
    Connection: keep-alive
    Server: AmazonS3

  
      
          logs-rule
          logs/
          Enabled
          
              30
              GLACIER
          
          
              365
          
     
     
         image-rule
         image/
         Enabled
         
             2012-12-31T00:00:00.000Z
             GLACIER
         
         
             2020-12-31T00:00:00.000Z
         
     
  
    */
    public class BucketLifecycleConfigurationHandler extends DefaultHandler {
        private StringBuilder text;
        private Rule rule;
        private List rules = new LinkedList();
        private Transition transition;
        boolean inTransition = false;

        public BucketLifecycleConfiguration getConfiguration() {
            return new BucketLifecycleConfiguration(rules);
        }

        @Override
        public void startDocument() {
            text = new StringBuilder();
        }

        @Override
        public void startElement(String uri, String name, String qName, Attributes attrs) {
            if (name.equals("LifecycleConfiguration")) {
            } else if (name.equals("Rule")) {
                rule = new Rule();
            } else if (name.equals("ID")) {
            } else if (name.equals("Prefix")) {
            } else if (name.equals("Status")) {
            } else if (name.equals("Transition")) {
                transition = new Transition();
                inTransition = true;
            } else if (name.equals("StorageClass")) {
            } else if (name.equals("Date")) {
            } else if (name.equals("Expiration")) {
            } else if (name.equals("Days")) {
            } else {
                log.warn("Unexpected tag: " + name);
            }
            text.setLength(0);
        }

        @Override
        public void endElement(String uri, String name, String qName) throws SAXException {
            if ( name.equals("LifecycleConfiguration") ) {
            } else if ( name.equals("Rule") ) {
                rules.add(rule);
                rule = null;
            } else if ( name.equals("ID") ) {
                rule.setId(text.toString());
            } else if ( name.equals("Prefix") ) {
                rule.setPrefix(text.toString());
            } else if ( name.equals("Status") ) {
                rule.setStatus(text.toString());
            } else if (name.equals("Transition")) {
                rule.setTransition(transition);
                inTransition = false;
            } else if (name.equals("StorageClass")) {
              transition.setStorageClass(StorageClass.fromValue(text.toString()));
            } else if (name.equals("Date")) {
                if (inTransition == false) {
                    try {
                        rule.setExpirationDate(ServiceUtils.parseIso8601Date(text.toString()));
                    } catch (ParseException e) {
                       rule.setExpirationDate(null);
                    }
                } else {
                    try {
                        transition.setDate(ServiceUtils.parseIso8601Date(text.toString()));
                    } catch (ParseException e) {
                        transition.setDate(null);
                    }
                }
            } else if (name.equals("Expiration")) {
            } else if (name.equals("Days")) {
                if (inTransition == false) {
                    rule.setExpirationInDays(Integer.parseInt(text.toString()));
                } else {
                    transition.setDays(Integer.parseInt(text.toString()));
                }
            } else {
                log.warn("Unexpected tag: " + name);
            }
        }

        @Override
        public void characters(char ch[], int start, int length) {
            this.text.append(ch, start, length);
        }
    }

    /*
    HTTP/1.1 200 OK
    x-amz-id-2: Uuag1LuByRx9e6j5Onimru9pO4ZVKnJ2Qz7/C1NPcfTWAtRPfTaOFg==
    x-amz-request-id: 656c76696e6727732072657175657374
    Date: Tue, 20 Sep 2011 20:34:56 GMT
    Content-Length: Some Length
    Connection: keep-alive
    Server: AmazonS3
    
       
         http://www.foobar.com
         GET
         3000
         x-amz-server-side-encryption
       
    
    */
    public class  BucketCrossOriginConfigurationHandler extends DefaultHandler {
        private StringBuilder text;
        private CORSRule rule;
        private List rules = new LinkedList();
        private List allowedMethods = null;
        private List allowedOrigins = null;
        private List exposedHeaders = null;
        private List allowedHeaders = null;

        public BucketCrossOriginConfiguration getConfiguration() {
            return new BucketCrossOriginConfiguration(rules);
        }

        @Override
        public void startDocument() {
            text = new StringBuilder();
        }

        @Override
        public void startElement(String uri, String name, String qName, Attributes attrs) {
            if ( name.equals("CORSConfiguration") ) {
            } else if ( name.equals("CORSRule") ) {
                rule = new CORSRule();
            } else if (name.equals("ID")) {
            } else if ( name.equals("AllowedOrigin") ) {
                if (allowedOrigins == null) {
                    allowedOrigins = new LinkedList();
                }
            } else if ( name.equals("AllowedMethod") ) {
                if(allowedMethods == null) {
                    allowedMethods = new LinkedList();
                }
            } else if ( name.equals("MaxAgeSeconds") ) {
            } else if ( name.equals("ExposeHeader") ) {
                if(exposedHeaders == null) {
                    exposedHeaders = new LinkedList();
                }
            } else if ( name.equals("AllowedHeader") ) {
                if(allowedHeaders == null) {
                allowedHeaders = new LinkedList();
                }
            } else {
                log.warn("Unexpected tag: " + name);
            }
            text.setLength(0);
        }

        @Override
        public void endElement(String uri, String name, String qName) throws SAXException {
            if ( name.equals("CORSConfiguration") ) {
            } else if ( name.equals("CORSRule") ) {
                rule.setAllowedHeaders(allowedHeaders);
                rule.setAllowedMethods(allowedMethods);
                rule.setAllowedOrigins(allowedOrigins);
                rule.setExposedHeaders(exposedHeaders);
                allowedHeaders = null;
                allowedMethods = null;
                allowedOrigins = null;
                exposedHeaders = null;
                rules.add(rule);
                rule = null;
            } else if ( name.equals("ID") ) {
                rule.setId(text.toString());
            } else if ( name.equals("AllowedOrigin") ) {
                allowedOrigins.add(text.toString());
            } else if ( name.equals("AllowedMethod") ) {
                 allowedMethods.add(AllowedMethods.fromValue(text.toString()));
            } else if ( name.equals("MaxAgeSeconds") ) {
                rule.setMaxAgeSeconds(Integer.parseInt(text.toString()));
            } else if ( name.equals("ExposeHeader") ) {
                exposedHeaders.add(text.toString());
            } else if (name.equals("AllowedHeader")) {
                allowedHeaders.add(text.toString());
            } else {
                log.warn("Unexpected tag: " + name);
            }
        }

        @Override
        public void characters(char ch[], int start, int length) {
            this.text.append(ch, start, length);
        }
    }

    private static String findAttributeValue( String qnameToFind, Attributes attrs ) {
        for ( int i = 0; i < attrs.getLength(); i++ ) {
            String qname = attrs.getQName( i );
            if ( qname.trim().equalsIgnoreCase( qnameToFind.trim() ) ) {
                return attrs.getValue( i );
            }
        }

        return null;
    }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy