org.jets3t.service.impl.soap.axis.SoapS3Service.java Source code

Java tutorial

Introduction

Here is the source code for org.jets3t.service.impl.soap.axis.SoapS3Service.java

Source

/*
 * jets3t : Java Extra-Tasty S3 Toolkit (for Amazon S3 online storage service)
 * This is a java.net project, see https://jets3t.dev.java.net/
 * 
 * Copyright 2006 James Murty
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *     http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License. 
 */
package org.jets3t.service.impl.soap.axis;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

import javax.activation.DataHandler;
import javax.xml.rpc.ServiceException;
import javax.xml.transform.stream.StreamSource;

import org.apache.axis.attachments.AttachmentPart;
import org.apache.axis.attachments.SourceDataSource;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.jets3t.service.Constants;
import org.jets3t.service.Jets3tProperties;
import org.jets3t.service.S3ObjectsChunk;
import org.jets3t.service.S3Service;
import org.jets3t.service.S3ServiceException;
import org.jets3t.service.acl.AccessControlList;
import org.jets3t.service.acl.CanonicalGrantee;
import org.jets3t.service.acl.EmailAddressGrantee;
import org.jets3t.service.acl.GrantAndPermission;
import org.jets3t.service.acl.GranteeInterface;
import org.jets3t.service.acl.GroupGrantee;
import org.jets3t.service.impl.rest.httpclient.RestS3Service;
import org.jets3t.service.impl.soap.axis._2006_03_01.AccessControlPolicy;
import org.jets3t.service.impl.soap.axis._2006_03_01.AmazonCustomerByEmail;
import org.jets3t.service.impl.soap.axis._2006_03_01.AmazonS3SoapBindingStub;
import org.jets3t.service.impl.soap.axis._2006_03_01.AmazonS3_ServiceLocator;
import org.jets3t.service.impl.soap.axis._2006_03_01.BucketLoggingStatus;
import org.jets3t.service.impl.soap.axis._2006_03_01.CanonicalUser;
import org.jets3t.service.impl.soap.axis._2006_03_01.CopyObjectResult;
import org.jets3t.service.impl.soap.axis._2006_03_01.GetObjectResult;
import org.jets3t.service.impl.soap.axis._2006_03_01.Grant;
import org.jets3t.service.impl.soap.axis._2006_03_01.Grantee;
import org.jets3t.service.impl.soap.axis._2006_03_01.Group;
import org.jets3t.service.impl.soap.axis._2006_03_01.ListAllMyBucketsEntry;
import org.jets3t.service.impl.soap.axis._2006_03_01.ListAllMyBucketsResult;
import org.jets3t.service.impl.soap.axis._2006_03_01.ListBucketResult;
import org.jets3t.service.impl.soap.axis._2006_03_01.ListEntry;
import org.jets3t.service.impl.soap.axis._2006_03_01.LoggingSettings;
import org.jets3t.service.impl.soap.axis._2006_03_01.MetadataDirective;
import org.jets3t.service.impl.soap.axis._2006_03_01.MetadataEntry;
import org.jets3t.service.impl.soap.axis._2006_03_01.Permission;
import org.jets3t.service.impl.soap.axis._2006_03_01.PrefixEntry;
import org.jets3t.service.impl.soap.axis._2006_03_01.PutObjectResult;
import org.jets3t.service.model.S3Bucket;
import org.jets3t.service.model.S3BucketLoggingStatus;
import org.jets3t.service.model.S3Object;
import org.jets3t.service.model.S3Owner;
import org.jets3t.service.security.AWSCredentials;
import org.jets3t.service.utils.Mimetypes;
import org.jets3t.service.utils.ServiceUtils;

/**
 * SOAP implementation of an S3Service based on the 
 * <a href="http://ws.apache.org/axis/">Apache Axis 1.4</a> library.  
 * <p>
 * <b>Note</b>: This SOAP implementation does <b>not</b> support IO streaming uploads to S3. Any 
 * documents uploaded by this implementation must fit inside memory allocated to the Java program
 * running this class if OutOfMemory errors are to be avoided. 
 * </p>
 * <p>
 * <b>Note 2</b>: The SOAP implementation does not perform retries when communication with s3 fails.
 * </p> 
 * <p>
 * The preferred S3Service implementation in JetS3t is {@link RestS3Service}. This SOAP 
 * implementation class is provided with JetS3t as a proof-of-concept, showing that alternative
 * service implementations are possible and what a SOAP service might look like. <b>We do not
 * recommend that this service be used to perform any real work.</b>
 * </p>
 * 
 * @author James Murty
 */
public class SoapS3Service extends S3Service {
    private static final long serialVersionUID = 6421138869712673819L;

    private static final Log log = LogFactory.getLog(SoapS3Service.class);
    private AmazonS3_ServiceLocator locator = null;

    /**
     * Constructs the SOAP service implementation and, based on the value of {@link S3Service#isHttpsOnly}
     * sets the SOAP endpoint to use HTTP or HTTPS protocols.
     * 
     * @param awsCredentials
     * @param invokingApplicationDescription
     * a short description of the application using the service, suitable for inclusion in a
     * user agent string for REST/HTTP requests. Ideally this would include the application's
     * version number, for example: <code>Cockpit/0.6.1</code> or <code>My App Name/1.0</code>
     * @param jets3tProperties
     * JetS3t properties that will be applied within this service.
     * 
     * @throws S3ServiceException
     */
    public SoapS3Service(AWSCredentials awsCredentials, String invokingApplicationDescription,
            Jets3tProperties jets3tProperties) throws S3ServiceException {
        super(awsCredentials, invokingApplicationDescription, jets3tProperties);

        locator = new AmazonS3_ServiceLocator();
        if (super.isHttpsOnly()) {
            // Use an SSL connection, to further secure the signature.
            if (log.isDebugEnabled()) {
                log.debug("SOAP service will use HTTPS for all communication");
            }
            locator.setAmazonS3EndpointAddress("https://" + Constants.S3_HOSTNAME + "/soap");
        } else {
            if (log.isDebugEnabled()) {
                log.debug("SOAP service will use HTTP for all communication");
            }
            locator.setAmazonS3EndpointAddress("http://" + Constants.S3_HOSTNAME + "/soap");
        }
        // Ensure we can get the stub.
        getSoapBinding();
    }

    /**
     * Constructs the SOAP service implementation and, based on the value of {@link S3Service#isHttpsOnly}
     * sets the SOAP endpoint to use HTTP or HTTPS protocols.
     * 
     * @param awsCredentials
     * @param invokingApplicationDescription
     * a short description of the application using the service, suitable for inclusion in a
     * user agent string for REST/HTTP requests. Ideally this would include the application's
     * version number, for example: <code>Cockpit/0.6.1</code> or <code>My App Name/1.0</code>
     * @throws S3ServiceException
     */
    public SoapS3Service(AWSCredentials awsCredentials, String invokingApplicationDescription)
            throws S3ServiceException {
        this(awsCredentials, invokingApplicationDescription,
                Jets3tProperties.getInstance(Constants.JETS3T_PROPERTIES_FILENAME));
    }

    /**
     * Constructs the SOAP service implementation and, based on the value of {@link S3Service#isHttpsOnly}
     * sets the SOAP endpoint to use HTTP or HTTPS protocols.
     * 
     * @param awsCredentials
     * @throws S3ServiceException
     */
    public SoapS3Service(AWSCredentials awsCredentials) throws S3ServiceException {
        this(awsCredentials, null);
    }

    private AmazonS3SoapBindingStub getSoapBinding() throws S3ServiceException {
        try {
            return (AmazonS3SoapBindingStub) locator.getAmazonS3();
        } catch (ServiceException e) {
            throw new S3ServiceException("Unable to initialise SOAP binding", e);
        }
    }

    private String getAWSAccessKey() {
        if (getAWSCredentials() == null) {
            return null;
        } else {
            return getAWSCredentials().getAccessKey();
        }
    }

    private String getAWSSecretKey() {
        if (getAWSCredentials() == null) {
            return null;
        } else {
            return getAWSCredentials().getSecretKey();
        }
    }

    private Calendar getTimeStamp(long timestamp) throws ParseException {
        if (getAWSCredentials() == null) {
            return null;
        }
        Calendar ts = new GregorianCalendar();
        Date date = ServiceUtils.parseIso8601Date(convertDateToString(ts));
        ts.setTime(date);
        return ts;
    }

    private String convertDateToString(Calendar cal) {
        if (cal != null) {
            return ServiceUtils.formatIso8601Date(cal.getTime());
        } else {
            return "";
        }
    }

    private S3Owner convertOwner(CanonicalUser user) {
        S3Owner owner = new S3Owner(user.getID(), user.getDisplayName());
        return owner;
    }

    /**
     * Converts a SOAP object AccessControlPolicy to a jets3t AccessControlList.
     * 
     * @param policy
     * @return
     * @throws S3ServiceException
     */
    private AccessControlList convertAccessControlTypes(AccessControlPolicy policy) throws S3ServiceException {
        AccessControlList acl = new AccessControlList();
        acl.setOwner(convertOwner(policy.getOwner()));

        Grant[] grants = policy.getAccessControlList();
        for (int i = 0; i < grants.length; i++) {
            Grant grant = (Grant) grants[i];
            org.jets3t.service.acl.Permission permission = org.jets3t.service.acl.Permission
                    .parsePermission(grant.getPermission().toString());

            Grantee grantee = grant.getGrantee();
            if (grantee instanceof Group) {
                GroupGrantee jets3tGrantee = new GroupGrantee();
                jets3tGrantee.setIdentifier(((Group) grantee).getURI());
                acl.grantPermission(jets3tGrantee, permission);
            } else if (grantee instanceof CanonicalUser) {
                CanonicalUser canonicalUser = (CanonicalUser) grantee;
                CanonicalGrantee jets3tGrantee = new CanonicalGrantee();
                jets3tGrantee.setIdentifier(canonicalUser.getID());
                jets3tGrantee.setDisplayName(canonicalUser.getDisplayName());
                acl.grantPermission(jets3tGrantee, permission);
            } else if (grantee instanceof AmazonCustomerByEmail) {
                AmazonCustomerByEmail customerByEmail = (AmazonCustomerByEmail) grantee;
                EmailAddressGrantee jets3tGrantee = new EmailAddressGrantee();
                jets3tGrantee.setIdentifier(customerByEmail.getEmailAddress());
                acl.grantPermission(jets3tGrantee, permission);
            } else {
                throw new S3ServiceException("Unrecognised grantee type: " + grantee.getClass());
            }
        }
        return acl;
    }

    /**
     * Converts a jets3t AccessControlList object to an array of SOAP Grant objects.
     * 
     * @param acl
     * @return
     * @throws S3ServiceException
     */
    private Grant[] convertACLtoGrants(AccessControlList acl) throws S3ServiceException {
        if (acl == null) {
            return null;
        }
        if (acl.isCannedRestACL()) {
            throw new S3ServiceException("Cannot use canned REST ACLs with SOAP service");
        }

        Grant[] grants = new Grant[acl.getGrants().size()];

        Iterator grantIter = acl.getGrants().iterator();
        int index = 0;
        while (grantIter.hasNext()) {
            GrantAndPermission jets3tGaP = (GrantAndPermission) grantIter.next();
            GranteeInterface jets3tGrantee = jets3tGaP.getGrantee();
            Grant grant = new Grant();

            if (jets3tGrantee instanceof GroupGrantee) {
                GroupGrantee groupGrantee = (GroupGrantee) jets3tGrantee;
                Group group = new Group();
                group.setURI(groupGrantee.getIdentifier());
                grant.setGrantee(group);
            } else if (jets3tGrantee instanceof CanonicalGrantee) {
                CanonicalGrantee canonicalGrantee = (CanonicalGrantee) jets3tGrantee;
                CanonicalUser canonicalUser = new CanonicalUser();
                canonicalUser.setID(canonicalGrantee.getIdentifier());
                canonicalUser.setDisplayName(canonicalGrantee.getDisplayName());
                grant.setGrantee(canonicalUser);
            } else if (jets3tGrantee instanceof EmailAddressGrantee) {
                EmailAddressGrantee emailGrantee = (EmailAddressGrantee) jets3tGrantee;
                AmazonCustomerByEmail customerByEmail = new AmazonCustomerByEmail();
                customerByEmail.setEmailAddress(emailGrantee.getIdentifier());
                grant.setGrantee(customerByEmail);
            } else {
                throw new S3ServiceException("Unrecognised jets3t grantee type: " + jets3tGrantee.getClass());
            }
            Permission permission = Permission.fromString(jets3tGaP.getPermission().toString());
            grant.setPermission(permission);
            grants[index++] = grant;
        }
        return grants;
    }

    /**
     * Converts metadata information from a standard map to SOAP objects.
     * 
     * @param metadataMap
     * @return
     */
    private MetadataEntry[] convertMetadata(Map metadataMap) {
        MetadataEntry[] metadata = new MetadataEntry[metadataMap.size()];
        Iterator metadataIter = metadataMap.entrySet().iterator();
        int index = 0;
        while (metadataIter.hasNext()) {
            Map.Entry entry = (Map.Entry) metadataIter.next();
            Object metadataName = entry.getKey();
            Object metadataValue = entry.getValue();
            if (log.isDebugEnabled()) {
                log.debug("Setting metadata: " + metadataName + "=" + metadataValue);
            }
            MetadataEntry mdEntry = new MetadataEntry();
            mdEntry.setName(metadataName.toString());
            mdEntry.setValue(metadataValue.toString());
            metadata[index++] = mdEntry;
        }
        return metadata;
    }

    ////////////////////////////////////////////////////////////////
    // Methods below this point implement S3Service abstract methods
    ////////////////////////////////////////////////////////////////    

    protected S3Bucket[] listAllBucketsImpl() throws S3ServiceException {
        if (log.isDebugEnabled()) {
            log.debug("Listing all buckets for AWS user: " + getAWSCredentials().getAccessKey());
        }

        S3Bucket[] buckets = null;
        try {
            AmazonS3SoapBindingStub s3SoapBinding = getSoapBinding();
            Calendar timestamp = getTimeStamp(System.currentTimeMillis());
            String signature = ServiceUtils.signWithHmacSha1(getAWSSecretKey(),
                    Constants.SOAP_SERVICE_NAME + "ListAllMyBuckets" + convertDateToString(timestamp));
            ListAllMyBucketsResult result = s3SoapBinding.listAllMyBuckets(getAWSAccessKey(), timestamp, signature);

            ListAllMyBucketsEntry[] entries = result.getBuckets();
            buckets = new S3Bucket[entries.length];
            int index = 0;
            for (int i = 0; i < entries.length; i++) {
                ListAllMyBucketsEntry entry = (ListAllMyBucketsEntry) entries[i];
                S3Bucket bucket = new S3Bucket();
                bucket.setName(entry.getName());
                bucket.setCreationDate(entry.getCreationDate().getTime());
                buckets[index++] = bucket;
            }
        } catch (RuntimeException e) {
            throw e;
        } catch (Exception e) {
            throw new S3ServiceException("Unable to List Buckets", e);
        }
        return buckets;
    }

    public boolean isBucketAccessible(String bucketName) throws S3ServiceException {
        if (log.isDebugEnabled()) {
            log.debug("Checking existence of bucket: " + bucketName);
        }
        try {
            AmazonS3SoapBindingStub s3SoapBinding = getSoapBinding();
            Calendar timestamp = getTimeStamp(System.currentTimeMillis());
            String signature = ServiceUtils.signWithHmacSha1(getAWSSecretKey(),
                    Constants.SOAP_SERVICE_NAME + "ListBucket" + convertDateToString(timestamp));
            s3SoapBinding.listBucket(bucketName, null, null, new Integer(0), null, getAWSAccessKey(), timestamp,
                    signature, null);

            // If we get this far, the bucket exists.
            return true;
        } catch (RuntimeException e) {
            throw e;
        } catch (Exception e) {
            return false;
        }
    }

    public int checkBucketStatus(String bucketName) throws S3ServiceException {
        throw new S3ServiceException("The method checkBucketStatus(String bucketName) " + "is not implemented in "
                + this.getClass().getName());
    }

    protected S3Object[] listObjectsImpl(String bucketName, String prefix, String delimiter, long maxListingLength)
            throws S3ServiceException {
        return listObjectsInternalImpl(bucketName, prefix, delimiter, maxListingLength, true, null).getObjects();
    }

    protected S3ObjectsChunk listObjectsChunkedImpl(String bucketName, String prefix, String delimiter,
            long maxListingLength, String priorLastKey, boolean completeListing) throws S3ServiceException {
        return listObjectsInternalImpl(bucketName, prefix, delimiter, maxListingLength, completeListing,
                priorLastKey);
    }

    protected S3ObjectsChunk listObjectsInternalImpl(String bucketName, String prefix, String delimiter,
            long maxListingLength, boolean automaticallyMergeChunks, String priorLastKey)
            throws S3ServiceException {
        ArrayList objects = new ArrayList();
        ArrayList commonPrefixes = new ArrayList();

        boolean incompleteListing = true;

        try {
            while (incompleteListing) {
                AmazonS3SoapBindingStub s3SoapBinding = getSoapBinding();
                Calendar timestamp = getTimeStamp(System.currentTimeMillis());
                String signature = ServiceUtils.signWithHmacSha1(getAWSSecretKey(),
                        Constants.SOAP_SERVICE_NAME + "ListBucket" + convertDateToString(timestamp));
                ListBucketResult result = s3SoapBinding.listBucket(bucketName, prefix, priorLastKey,
                        new Integer((int) maxListingLength), delimiter, getAWSAccessKey(), timestamp, signature,
                        null);

                ListEntry[] entries = result.getContents();
                S3Object[] partialObjects = new S3Object[(entries == null ? 0 : entries.length)];

                if (log.isDebugEnabled()) {
                    log.debug("Found " + partialObjects.length + " objects in one batch");
                }
                for (int i = 0; entries != null && i < entries.length; i++) {
                    ListEntry entry = entries[i];
                    S3Object object = new S3Object(entry.getKey());
                    object.setLastModifiedDate(entry.getLastModified().getTime());
                    object.setETag(entry.getETag());
                    object.setContentLength(entry.getSize());
                    object.setStorageClass(entry.getStorageClass().toString());
                    object.setOwner(convertOwner(entry.getOwner()));
                    partialObjects[i] = object;

                    // This shouldn't be necessary, but result.getNextMarker() doesn't work as expected.
                    priorLastKey = object.getKey();
                }

                objects.addAll(Arrays.asList(partialObjects));

                PrefixEntry[] prefixEntries = result.getCommonPrefixes();
                if (prefixEntries != null) {
                    if (log.isDebugEnabled()) {
                        log.debug("Found " + prefixEntries.length + " common prefixes in one batch");
                    }
                }
                for (int i = 0; prefixEntries != null && i < prefixEntries.length; i++) {
                    PrefixEntry entry = prefixEntries[i];
                    commonPrefixes.add(entry.getPrefix());
                }

                incompleteListing = result.isIsTruncated();
                if (incompleteListing) {
                    if (result.getNextMarker() != null) {
                        // Use NextMarker as the marker for where subsequent listing should start
                        priorLastKey = result.getNextMarker();
                    } else {
                        // Use the prior last key instead of NextMarker if it isn't available.
                    }
                    if (log.isDebugEnabled()) {
                        log.debug("Yet to receive complete listing of bucket contents, "
                                + "last key for prior chunk: " + priorLastKey);
                    }
                } else {
                    priorLastKey = null;
                }
                if (!automaticallyMergeChunks)
                    break;
            }
        } catch (RuntimeException e) {
            throw e;
        } catch (Exception e) {
            throw new S3ServiceException("Unable to List Objects in bucket: " + bucketName, e);
        }
        if (automaticallyMergeChunks) {
            if (log.isDebugEnabled()) {
                log.debug("Found " + objects.size() + " objects in total");
            }
            return new S3ObjectsChunk(prefix, delimiter, (S3Object[]) objects.toArray(new S3Object[objects.size()]),
                    (String[]) commonPrefixes.toArray(new String[commonPrefixes.size()]), null);
        } else {
            return new S3ObjectsChunk(prefix, delimiter, (S3Object[]) objects.toArray(new S3Object[objects.size()]),
                    (String[]) commonPrefixes.toArray(new String[commonPrefixes.size()]), priorLastKey);
        }
    }

    protected S3Bucket createBucketImpl(String bucketName, String location, AccessControlList acl)
            throws S3ServiceException {
        if (location != S3Bucket.LOCATION_US) {
            throw new S3ServiceException("The SOAP API interface for S3 does "
                    + "not allow you to create buckets located anywhere other than " + "the US");
        }

        Grant[] grants = null;
        if (acl != null) {
            grants = convertACLtoGrants(acl);
        }
        try {
            AmazonS3SoapBindingStub s3SoapBinding = getSoapBinding();
            Calendar timestamp = getTimeStamp(System.currentTimeMillis());
            String signature = ServiceUtils.signWithHmacSha1(getAWSSecretKey(),
                    Constants.SOAP_SERVICE_NAME + "CreateBucket" + convertDateToString(timestamp));
            s3SoapBinding.createBucket(bucketName, grants, getAWSAccessKey(), timestamp, signature);

            S3Bucket bucket = new S3Bucket(bucketName);
            bucket.setAcl(acl);
            return bucket;
        } catch (RuntimeException e) {
            throw e;
        } catch (Exception e) {
            throw new S3ServiceException("Unable to Create Bucket: " + bucketName, e);
        }
    }

    protected String getBucketLocationImpl(String bucketName) throws S3ServiceException {
        throw new S3ServiceException("The SOAP API interface for S3 does "
                + "not allow you to retrieve location information for a bucket");
    }

    protected void deleteBucketImpl(String bucketName) throws S3ServiceException {
        try {
            AmazonS3SoapBindingStub s3SoapBinding = getSoapBinding();
            Calendar timestamp = getTimeStamp(System.currentTimeMillis());
            String signature = ServiceUtils.signWithHmacSha1(getAWSSecretKey(),
                    Constants.SOAP_SERVICE_NAME + "DeleteBucket" + convertDateToString(timestamp));
            s3SoapBinding.deleteBucket(bucketName, getAWSAccessKey(), timestamp, signature, null);
        } catch (RuntimeException e) {
            throw e;
        } catch (Exception e) {
            throw new S3ServiceException("Unable to Delete Bucket: " + bucketName, e);
        }
    }

    protected S3Object putObjectImpl(String bucketName, S3Object object) throws S3ServiceException {
        if (log.isDebugEnabled()) {
            log.debug("Creating Object with key " + object.getKey() + " in bucket " + bucketName);
        }

        Grant[] grants = null;
        if (object.getAcl() != null) {
            grants = convertACLtoGrants(object.getAcl());
        }
        MetadataEntry[] metadata = convertMetadata(object.getMetadataMap());

        try {
            AmazonS3SoapBindingStub s3SoapBinding = getSoapBinding();
            long contentLength = object.getContentLength();
            String contentType = object.getContentType();
            if (contentType == null) {
                // Set default content type.
                contentType = Mimetypes.MIMETYPE_OCTET_STREAM;
            }

            if (object.getDataInputStream() != null) {
                if (log.isDebugEnabled()) {
                    log.debug("Uploading data input stream for S3Object: " + object.getKey());
                }

                if (contentLength == 0 && object.getDataInputStream().available() > 0) {

                    if (log.isWarnEnabled()) {
                        log.warn("S3Object " + object.getKey()
                                + " - Content-Length was set to 0 despite having a non-empty data"
                                + " input stream. The Content-length will be determined in memory.");
                    }

                    // Read all data into memory to determine it's length.
                    BufferedInputStream bis = new BufferedInputStream(object.getDataInputStream());
                    ByteArrayOutputStream baos = new ByteArrayOutputStream();
                    BufferedOutputStream bos = new BufferedOutputStream(baos);
                    try {
                        byte[] buffer = new byte[8192];
                        int read = -1;
                        while ((read = bis.read(buffer)) != -1) {
                            bos.write(buffer, 0, read);
                        }
                    } finally {
                        if (bis != null) {
                            bis.close();
                        }
                        if (bos != null) {
                            bos.close();
                        }
                    }

                    contentLength = baos.size();
                    object.setDataInputStream(new ByteArrayInputStream(baos.toByteArray()));

                    if (log.isDebugEnabled()) {
                        log.debug("Content-Length value has been reset to " + contentLength);
                    }
                }

                DataHandler dataHandler = new DataHandler(
                        new SourceDataSource(null, contentType, new StreamSource(object.getDataInputStream())));
                s3SoapBinding.addAttachment(dataHandler);
            } else {
                DataHandler dataHandler = new DataHandler(
                        new SourceDataSource(null, contentType, new StreamSource()));
                s3SoapBinding.addAttachment(dataHandler);
            }

            Calendar timestamp = getTimeStamp(System.currentTimeMillis());
            String signature = ServiceUtils.signWithHmacSha1(getAWSSecretKey(),
                    Constants.SOAP_SERVICE_NAME + "PutObject" + convertDateToString(timestamp));
            PutObjectResult result = s3SoapBinding.putObject(bucketName, object.getKey(), metadata, contentLength,
                    grants, null, getAWSAccessKey(), timestamp, signature, null);

            // Ensure no data was corrupted, if we have the MD5 hash available to check.
            String eTag = result.getETag().substring(1, result.getETag().length() - 1);
            String eTagAsBase64 = ServiceUtils.toBase64(ServiceUtils.fromHex(eTag));
            String md5HashAsBase64 = object.getMd5HashAsBase64();
            if (md5HashAsBase64 != null && !eTagAsBase64.equals(md5HashAsBase64)) {
                throw new S3ServiceException(
                        "Object created but ETag returned by S3 does not match MD5 hash value of object");
            }

            object.setETag(result.getETag());
            object.setContentLength(contentLength);
            object.setContentType(contentType);
        } catch (RuntimeException e) {
            throw e;
        } catch (Exception e) {
            throw new S3ServiceException("Unable to Create Object: " + object.getKey(), e);
        }
        return object;
    }

    protected void deleteObjectImpl(String bucketName, String objectKey) throws S3ServiceException {
        try {
            AmazonS3SoapBindingStub s3SoapBinding = getSoapBinding();
            Calendar timestamp = getTimeStamp(System.currentTimeMillis());
            String signature = ServiceUtils.signWithHmacSha1(getAWSSecretKey(),
                    Constants.SOAP_SERVICE_NAME + "DeleteObject" + convertDateToString(timestamp));
            s3SoapBinding.deleteObject(bucketName, objectKey, getAWSAccessKey(), timestamp, signature, null);
        } catch (RuntimeException e) {
            throw e;
        } catch (Exception e) {
            throw new S3ServiceException("Unable to Delete Object: " + objectKey, e);
        }
    }

    protected Map copyObjectImpl(String sourceBucketName, String sourceObjectKey, String destinationBucketName,
            String destinationObjectKey, AccessControlList acl, Map destinationMetadata, Calendar ifModifiedSince,
            Calendar ifUnmodifiedSince, String[] ifMatchTags, String[] ifNoneMatchTags) throws S3ServiceException {
        try {
            AmazonS3SoapBindingStub s3SoapBinding = getSoapBinding();
            Calendar timestamp = getTimeStamp(System.currentTimeMillis());
            String signature = ServiceUtils.signWithHmacSha1(getAWSSecretKey(),
                    Constants.SOAP_SERVICE_NAME + "CopyObject" + convertDateToString(timestamp));

            MetadataDirective metadataDirective = null;
            MetadataEntry[] metadata = null;

            if (destinationMetadata != null) {
                metadataDirective = MetadataDirective.REPLACE;
                metadata = convertMetadata(destinationMetadata);
            } else {
                metadataDirective = MetadataDirective.COPY;
            }

            Grant[] grants = null;
            if (acl != null) {
                grants = convertACLtoGrants(acl);
            }

            CopyObjectResult result = s3SoapBinding.copyObject(sourceBucketName, sourceObjectKey,
                    destinationBucketName, destinationObjectKey, metadataDirective, metadata, grants,
                    ifModifiedSince, ifUnmodifiedSince, ifMatchTags, ifNoneMatchTags, null, getAWSAccessKey(),
                    timestamp, signature, null);

            Map resultMap = new HashMap();
            resultMap.put("ETag", result.getETag());
            resultMap.put("Last-Modified", result.getLastModified().getTime());
            return resultMap;
        } catch (RuntimeException e) {
            throw e;
        } catch (Exception e) {
            throw new S3ServiceException("Unable to Copy Object from '" + sourceBucketName + ":" + sourceObjectKey
                    + "' to '" + destinationBucketName + ":" + destinationObjectKey + "'", e);
        }
    }

    protected S3Object getObjectDetailsImpl(String bucketName, String objectKey, Calendar ifModifiedSince,
            Calendar ifUnmodifiedSince, String[] ifMatchTags, String[] ifNoneMatchTags) throws S3ServiceException {
        return getObjectImpl(false, bucketName, objectKey, ifModifiedSince, ifUnmodifiedSince, ifMatchTags,
                ifNoneMatchTags, null, null);
    }

    protected S3Object getObjectImpl(String bucketName, String objectKey, Calendar ifModifiedSince,
            Calendar ifUnmodifiedSince, String[] ifMatchTags, String[] ifNoneMatchTags, Long byteRangeStart,
            Long byteRangeEnd) throws S3ServiceException {
        return getObjectImpl(true, bucketName, objectKey, ifModifiedSince, ifUnmodifiedSince, ifMatchTags,
                ifNoneMatchTags, byteRangeStart, byteRangeEnd);
    }

    private S3Object getObjectImpl(boolean withData, String bucketName, String objectKey, Calendar ifModifiedSince,
            Calendar ifUnmodifiedSince, String[] ifMatchTags, String[] ifNoneMatchTags, Long byteRangeStart,
            Long byteRangeEnd) throws S3ServiceException {
        boolean useExtendedGet = ifModifiedSince != null || ifUnmodifiedSince != null || ifMatchTags != null
                || ifNoneMatchTags != null || byteRangeStart != null || byteRangeEnd != null;

        try {
            AmazonS3SoapBindingStub s3SoapBinding = getSoapBinding();
            Calendar timestamp = getTimeStamp(System.currentTimeMillis());
            GetObjectResult result = null;

            if (useExtendedGet) {
                if (log.isDebugEnabled()) {
                    log.debug("Using Extended GET to apply constraints: " + "ifModifiedSince="
                            + (ifModifiedSince != null ? ifModifiedSince.getTime().toString() : "null")
                            + ", ifUnmodifiedSince="
                            + (ifUnmodifiedSince != null ? ifUnmodifiedSince.getTime().toString() : "null")
                            + ", ifMatchTags="
                            + (ifMatchTags != null ? Arrays.asList(ifMatchTags).toString() : "null")
                            + ", ifNoneMatchTags="
                            + (ifNoneMatchTags != null ? Arrays.asList(ifNoneMatchTags).toString() : "null")
                            + ", byteRangeStart=" + byteRangeStart + ", byteRangeEnd=" + byteRangeEnd);
                }

                String signature = ServiceUtils.signWithHmacSha1(getAWSSecretKey(),
                        Constants.SOAP_SERVICE_NAME + "GetObjectExtended" + convertDateToString(timestamp));
                result = s3SoapBinding.getObjectExtended(bucketName, objectKey, true, withData, false,
                        byteRangeStart, byteRangeEnd, ifModifiedSince, ifUnmodifiedSince, ifMatchTags,
                        ifNoneMatchTags, Boolean.FALSE, getAWSAccessKey(), timestamp, signature, null);

                // Throw an exception if the preconditions failed.
                int expectedStatusCode = 200;
                if (byteRangeStart != null || byteRangeEnd != null) {
                    // Partial data responses have a status code of 206. 
                    expectedStatusCode = 206;
                }
                if (result.getStatus().getCode() != expectedStatusCode) {
                    throw new S3ServiceException("Precondition failed when getting object " + objectKey + ": "
                            + result.getStatus().getDescription());
                }
            } else {
                if (log.isDebugEnabled()) {
                    log.debug("Using standard GET (no constraints to apply)");
                }
                String signature = ServiceUtils.signWithHmacSha1(getAWSSecretKey(),
                        Constants.SOAP_SERVICE_NAME + "GetObject" + convertDateToString(timestamp));
                result = s3SoapBinding.getObject(bucketName, objectKey, true, withData, false, getAWSAccessKey(),
                        timestamp, signature, null);
            }

            S3Object object = new S3Object(objectKey);
            object.setETag(result.getETag());
            object.setLastModifiedDate(result.getLastModified().getTime());
            object.setBucketName(bucketName);

            // Get data details from the SOAP attachment.
            if (withData) {
                Object[] attachments = s3SoapBinding.getAttachments();
                if (log.isDebugEnabled()) {
                    log.debug("SOAP attachment count for " + object.getKey() + ": " + attachments.length);
                }
                for (int i = 0; i < attachments.length; i++) {
                    if (i > 0) {
                        throw new S3ServiceException(
                                "Received multiple SOAP attachment parts, this shouldn't happen");
                    }
                    AttachmentPart part = (AttachmentPart) attachments[i];
                    object.setContentType(part.getContentType());
                    object.setContentLength(part.getSize());
                    object.setDataInputStream(part.getDataHandler().getInputStream());
                }
            }

            // Populate object's metadata details.
            MetadataEntry[] metadata = result.getMetadata();
            for (int i = 0; i < metadata.length; i++) {
                MetadataEntry entry = metadata[i];
                object.addMetadata(entry.getName(), entry.getValue());
            }
            object.setMetadataComplete(true);

            return object;
        } catch (RuntimeException e) {
            throw e;
        } catch (Exception e) {
            throw new S3ServiceException("Unable to Get Object: " + objectKey, e);
        }
    }

    protected void putObjectAclImpl(String bucketName, String objectKey, AccessControlList acl)
            throws S3ServiceException {
        try {
            AmazonS3SoapBindingStub s3SoapBinding = getSoapBinding();
            Calendar timestamp = getTimeStamp(System.currentTimeMillis());
            Grant[] grants = convertACLtoGrants(acl);

            String signature = ServiceUtils.signWithHmacSha1(getAWSSecretKey(),
                    Constants.SOAP_SERVICE_NAME + "SetObjectAccessControlPolicy" + convertDateToString(timestamp));
            s3SoapBinding.setObjectAccessControlPolicy(bucketName, objectKey, grants, getAWSAccessKey(), timestamp,
                    signature, null);
        } catch (RuntimeException e) {
            throw e;
        } catch (Exception e) {
            throw new S3ServiceException("Unable to Put Object ACL", e);
        }
    }

    protected void putBucketAclImpl(String bucketName, AccessControlList acl) throws S3ServiceException {
        try {
            AmazonS3SoapBindingStub s3SoapBinding = getSoapBinding();
            Calendar timestamp = getTimeStamp(System.currentTimeMillis());
            Grant[] grants = convertACLtoGrants(acl);

            String signature = ServiceUtils.signWithHmacSha1(getAWSSecretKey(),
                    Constants.SOAP_SERVICE_NAME + "SetBucketAccessControlPolicy" + convertDateToString(timestamp));
            s3SoapBinding.setBucketAccessControlPolicy(bucketName, grants, getAWSAccessKey(), timestamp, signature,
                    null);
        } catch (RuntimeException e) {
            throw e;
        } catch (Exception e) {
            throw new S3ServiceException("Unable to Put Bucket ACL", e);
        }
    }

    protected AccessControlList getObjectAclImpl(String bucketName, String objectKey) throws S3ServiceException {
        try {
            AmazonS3SoapBindingStub s3SoapBinding = getSoapBinding();
            Calendar timestamp = getTimeStamp(System.currentTimeMillis());
            String signature = ServiceUtils.signWithHmacSha1(getAWSSecretKey(),
                    Constants.SOAP_SERVICE_NAME + "GetObjectAccessControlPolicy" + convertDateToString(timestamp));
            AccessControlPolicy result = s3SoapBinding.getObjectAccessControlPolicy(bucketName, objectKey,
                    getAWSAccessKey(), timestamp, signature, null);
            return convertAccessControlTypes(result);
        } catch (RuntimeException e) {
            throw e;
        } catch (Exception e) {
            throw new S3ServiceException("Unable to Get ACL", e);
        }
    }

    protected AccessControlList getBucketAclImpl(String bucketName) throws S3ServiceException {
        try {
            AmazonS3SoapBindingStub s3SoapBinding = getSoapBinding();
            Calendar timestamp = getTimeStamp(System.currentTimeMillis());
            String signature = ServiceUtils.signWithHmacSha1(getAWSSecretKey(),
                    Constants.SOAP_SERVICE_NAME + "GetBucketAccessControlPolicy" + convertDateToString(timestamp));
            AccessControlPolicy result = s3SoapBinding.getBucketAccessControlPolicy(bucketName, getAWSAccessKey(),
                    timestamp, signature, null);
            return convertAccessControlTypes(result);
        } catch (RuntimeException e) {
            throw e;
        } catch (Exception e) {
            throw new S3ServiceException("Unable to Get ACL", e);
        }
    }

    protected S3BucketLoggingStatus getBucketLoggingStatusImpl(String bucketName) throws S3ServiceException {
        try {
            AmazonS3SoapBindingStub s3SoapBinding = getSoapBinding();
            Calendar timestamp = getTimeStamp(System.currentTimeMillis());
            String signature = ServiceUtils.signWithHmacSha1(getAWSSecretKey(),
                    Constants.SOAP_SERVICE_NAME + "GetBucketLoggingStatus" + convertDateToString(timestamp));

            BucketLoggingStatus loggingStatus = s3SoapBinding.getBucketLoggingStatus(bucketName, getAWSAccessKey(),
                    timestamp, signature, null);
            LoggingSettings loggingSettings = loggingStatus.getLoggingEnabled();
            if (loggingSettings != null) {
                return new S3BucketLoggingStatus(loggingSettings.getTargetBucket(),
                        loggingSettings.getTargetPrefix());
            } else {
                return new S3BucketLoggingStatus();
            }
        } catch (RuntimeException e) {
            throw e;
        } catch (Exception e) {
            throw new S3ServiceException("Unable to Get Bucket logging status for " + bucketName, e);
        }
    }

    protected void setBucketLoggingStatusImpl(String bucketName, S3BucketLoggingStatus status)
            throws S3ServiceException {
        try {
            AmazonS3SoapBindingStub s3SoapBinding = getSoapBinding();
            Calendar timestamp = getTimeStamp(System.currentTimeMillis());
            String signature = ServiceUtils.signWithHmacSha1(getAWSSecretKey(),
                    Constants.SOAP_SERVICE_NAME + "SetBucketLoggingStatus" + convertDateToString(timestamp));

            LoggingSettings loggingSettings = null;
            if (status.isLoggingEnabled()) {
                loggingSettings = new LoggingSettings(status.getTargetBucketName(), status.getLogfilePrefix(),
                        new Grant[] {});
            }

            s3SoapBinding.setBucketLoggingStatus(bucketName, getAWSAccessKey(), timestamp, signature, null,
                    new BucketLoggingStatus(loggingSettings));
        } catch (RuntimeException e) {
            throw e;
        } catch (Exception e) {
            throw new S3ServiceException("Unable to Set Bucket logging status for " + bucketName + ": " + status,
                    e);
        }
    }

}