com.emc.vipr.services.s3.ViPRS3Client.java Source code

Java tutorial

Introduction

Here is the source code for com.emc.vipr.services.s3.ViPRS3Client.java

Source

/*
 * Copyright 2013 EMC Corporation. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License").
 * You may not use this file except in compliance with the License.
 * A copy of the License is located at
 *
 * http://www.apache.org/licenses/LICENSE-2.0.txt
 *
 * 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.emc.vipr.services.s3;

import com.amazonaws.*;
import com.amazonaws.auth.AWSCredentials;
import com.amazonaws.auth.AWSCredentialsProvider;
import com.amazonaws.auth.Signer;
import com.amazonaws.event.ProgressListenerCallbackExecutor;
import com.amazonaws.http.HttpMethodName;
import com.amazonaws.http.HttpResponse;
import com.amazonaws.internal.StaticCredentialsProvider;
import com.amazonaws.regions.RegionUtils;
import com.amazonaws.services.s3.AmazonS3;
import com.amazonaws.services.s3.AmazonS3Client;
import com.amazonaws.services.s3.Headers;
import com.amazonaws.services.s3.S3ClientOptions;
import com.amazonaws.services.s3.internal.*;
import com.amazonaws.services.s3.model.*;
import com.amazonaws.transform.Unmarshaller;
import com.amazonaws.util.BinaryUtils;
import com.amazonaws.util.Md5Utils;
import com.emc.vipr.services.s3.model.*;
import com.netflix.loadbalancer.LoadBalancerStats;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import java.io.*;
import java.net.URI;
import java.net.URISyntaxException;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
import java.util.List;
import java.util.Map;

public class ViPRS3Client extends AmazonS3Client implements ViPRS3, AmazonS3 {
    private static Log log = LogFactory.getLog(ViPRS3Client.class);

    protected static final String IP_ADDRESS_PATTERN = "^([0-9]{1,3}\\.){3}[0-9]{1,3}$";

    protected String namespace;

    /**
     * Constructs a new ViPR S3 client using the specified endpoint, AWS credentials to
     * access the EMC ViPR S3 protocol.
     *
     * @param endpoint
     *            The ViPR S3 endpoint (i.e. "https://vipr-data.emc.com:9021")
     * @param awsCredentials
     *            The AWS credentials to use when making requests
     *            with this client.
     *
     * @see AmazonS3Client#AmazonS3Client()
     * @see AmazonS3Client#AmazonS3Client(AWSCredentials)
     */
    public ViPRS3Client(String endpoint, AWSCredentials awsCredentials) {
        this(endpoint, new StaticCredentialsProvider(awsCredentials), new ClientConfiguration());
    }

    /**
     * Constructs a new ViPR S3 client using the specified endpoint, AWS credentials and
     * client configuration to access the EMC ViPR S3 protocol.
     *
     * @param endpoint
     *            The ViPR S3 endpoint (i.e. "https://vipr-data.emc.com:9021")
     * @param awsCredentials
     *            The AWS credentials to use when making requests
     *            with this client.
     * @param clientConfiguration
     *            The client configuration options controlling how this client
     *            connects (e.g. proxy settings, retry counts, etc).
     *
     * @see AmazonS3Client#AmazonS3Client()
     * @see AmazonS3Client#AmazonS3Client(AWSCredentials, ClientConfiguration)
     */
    public ViPRS3Client(String endpoint, AWSCredentials awsCredentials, ClientConfiguration clientConfiguration) {
        this(endpoint, new StaticCredentialsProvider(awsCredentials), clientConfiguration);
    }

    /**
     * Constructs a new ViPR S3 client using the specified endpoint, AWS credentials
     * provider to access the EMC ViPR S3 protocol.
     *
     * @param endpoint
     *            The ViPR S3 endpoint (i.e. "https://vipr-data.emc.com:9021")
     * @param credentialsProvider
     *            The AWS credentials provider which will provide credentials
     *            to authenticate requests.
     * @see AmazonS3Client#AmazonS3Client(AWSCredentialsProvider)
     */
    public ViPRS3Client(String endpoint, AWSCredentialsProvider credentialsProvider) {
        this(endpoint, credentialsProvider, new ClientConfiguration());
    }

    /**
     * Constructs a new ViPR S3 client using the specified endpoint, AWS credentials and
     * client configuration to access the EMC ViPR S3 protocol.
     *
     * @param endpoint
     *            The ViPR S3 endpoint (i.e. "https://vipr-data.emc.com:9021")
     * @param credentialsProvider
     *            The AWS credentials provider which will provide credentials
     *            to authenticate requests.
     * @param clientConfiguration
     *            The client configuration options controlling how this client
     *            connects (e.g. proxy settings, retry counts, etc).
     */
    public ViPRS3Client(String endpoint, AWSCredentialsProvider credentialsProvider,
            ClientConfiguration clientConfiguration) {
        super(credentialsProvider, clientConfiguration == null ? new ClientConfiguration() : clientConfiguration);
        setEndpoint(endpoint);
    }

    /**
     * Constructs a client with all options specified in a ViPRS3Config instance.  Use this constructor to enable the
     * smart client.  Older constructors cannot differentiate between load balancing and virtual hosting (which is not
     * supported by the smart client), so this constructor is required for the smart client.
     *
     * Note that when the smart client is enabled, you *cannot* use DNS (virtual host) style buckets or namespaces.
     * Because of the nature of client-side load balancing, you must use path/header style requests.
     */
    public ViPRS3Client(ViPRS3Config viprConfig) {
        super(viprConfig.getCredentialsProvider(), viprConfig.getClientConfiguration());
        this.client = new ViPRS3HttpClient(viprConfig);
        setEndpoint(viprConfig.getProtocol() + "://" + viprConfig.getVipHost());

        // enable path-style requests (cannot use DNS with client-side load balancing)
        S3ClientOptions options = new S3ClientOptions();
        options.setPathStyleAccess(true);
        setS3ClientOptions(options);
    }

    public LoadBalancerStats getLoadBalancerStats() {
        if (client instanceof ViPRS3HttpClient)
            return ((ViPRS3HttpClient) client).getLoadBalancerStats();
        throw new UnsupportedOperationException(
                "this is not a load-balanced client (try constructing it with ViPRS3Config)");
    }

    public UpdateObjectResult updateObject(String bucketName, String key, File file, long startOffset)
            throws AmazonClientException {
        UpdateObjectRequest request = new UpdateObjectRequest(bucketName, key, file).withUpdateOffset(startOffset);

        return updateObject(request);
    }

    public UpdateObjectResult updateObject(String bucketName, String key, InputStream input,
            ObjectMetadata metadata, long startOffset) throws AmazonClientException {
        UpdateObjectRequest request = new UpdateObjectRequest(bucketName, key, input, metadata)
                .withUpdateOffset(startOffset);

        return updateObject(request);
    }

    public UpdateObjectResult updateObject(UpdateObjectRequest request) throws AmazonClientException {
        ObjectMetadata returnedMetadata = doPut(request);
        UpdateObjectResult result = new UpdateObjectResult();
        result.setETag(returnedMetadata.getETag());
        result.setVersionId(returnedMetadata.getVersionId());
        result.setServerSideEncryption(returnedMetadata.getServerSideEncryption());
        result.setExpirationTime(returnedMetadata.getExpirationTime());
        result.setExpirationTimeRuleId(returnedMetadata.getExpirationTimeRuleId());
        return result;
    }

    public AppendObjectResult appendObject(String bucketName, String key, File file) throws AmazonClientException {
        AppendObjectRequest request = new AppendObjectRequest(bucketName, key, file);

        return appendObject(request);
    }

    public AppendObjectResult appendObject(String bucketName, String key, InputStream input,
            ObjectMetadata metadata) throws AmazonClientException {
        AppendObjectRequest request = new AppendObjectRequest(bucketName, key, input, metadata);

        return appendObject(request);
    }

    public AppendObjectResult appendObject(AppendObjectRequest request) throws AmazonClientException {
        ObjectMetadata returnedMetadata = doPut(request);
        AppendObjectResult result = new AppendObjectResult();
        result.setETag(returnedMetadata.getETag());
        result.setVersionId(returnedMetadata.getVersionId());
        result.setServerSideEncryption(returnedMetadata.getServerSideEncryption());
        result.setExpirationTime(returnedMetadata.getExpirationTime());
        result.setExpirationTimeRuleId(returnedMetadata.getExpirationTimeRuleId());
        result.setAppendOffset(
                Long.parseLong("" + returnedMetadata.getRawMetadata().get(ViPRConstants.APPEND_OFFSET_HEADER)));
        return result;
    }

    public BucketFileAccessModeResult setBucketFileAccessMode(SetBucketFileAccessModeRequest putAccessModeRequest)
            throws AmazonClientException {
        assertParameterNotNull(putAccessModeRequest,
                "The SetBucketFileAccessModeRequest parameter must be specified");

        String bucketName = putAccessModeRequest.getBucketName();
        assertParameterNotNull(bucketName, "The bucket name parameter must be specified when changing access mode");

        Request<SetBucketFileAccessModeRequest> request = createRequest(bucketName, null, putAccessModeRequest,
                HttpMethodName.PUT);
        request.addParameter(ViPRConstants.ACCESS_MODE_PARAMETER, null);
        request.addHeader(Headers.CONTENT_TYPE, Mimetypes.MIMETYPE_XML);

        if (putAccessModeRequest.getAccessMode() != null) {
            request.addHeader(ViPRConstants.FILE_ACCESS_MODE_HEADER,
                    putAccessModeRequest.getAccessMode().toString());
        }
        if (putAccessModeRequest.getDuration() != 0) {
            request.addHeader(ViPRConstants.FILE_ACCESS_DURATION_HEADER,
                    Long.toString(putAccessModeRequest.getDuration()));
        }
        if (putAccessModeRequest.getHostList() != null) {
            request.addHeader(ViPRConstants.FILE_ACCESS_HOST_LIST_HEADER,
                    join(",", putAccessModeRequest.getHostList()));
        }
        if (putAccessModeRequest.getUid() != null) {
            request.addHeader(ViPRConstants.FILE_ACCESS_UID_HEADER, putAccessModeRequest.getUid());
        }
        if (putAccessModeRequest.getToken() != null) {
            request.addHeader(ViPRConstants.FILE_ACCESS_TOKEN_HEADER, putAccessModeRequest.getToken());
        }
        if (putAccessModeRequest.isPreserveIngestPaths()) {
            request.addHeader(ViPRConstants.FILE_ACCESS_PRESERVE_INGEST_PATHS, "true");
        }

        return invoke(request, new AbstractS3ResponseHandler<BucketFileAccessModeResult>() {
            public AmazonWebServiceResponse<BucketFileAccessModeResult> handle(HttpResponse response)
                    throws Exception {
                BucketFileAccessModeResult result = new BucketFileAccessModeResult();
                Map<String, String> headers = response.getHeaders();

                if (headers.containsKey(ViPRConstants.FILE_ACCESS_MODE_HEADER))
                    result.setAccessMode(ViPRConstants.FileAccessMode
                            .valueOf(headers.get(ViPRConstants.FILE_ACCESS_MODE_HEADER)));
                if (headers.containsKey(ViPRConstants.FILE_ACCESS_DURATION_HEADER))
                    result.setDuration(Long.parseLong(headers.get(ViPRConstants.FILE_ACCESS_DURATION_HEADER)));
                if (headers.containsKey(ViPRConstants.FILE_ACCESS_HOST_LIST_HEADER))
                    result.setHostList(
                            Arrays.asList(headers.get(ViPRConstants.FILE_ACCESS_HOST_LIST_HEADER).split(",")));
                if (headers.containsKey(ViPRConstants.FILE_ACCESS_UID_HEADER))
                    result.setUid(headers.get(ViPRConstants.FILE_ACCESS_UID_HEADER));
                if (headers.containsKey(ViPRConstants.FILE_ACCESS_START_TOKEN_HEADER))
                    result.setStartToken(headers.get(ViPRConstants.FILE_ACCESS_START_TOKEN_HEADER));
                if (headers.containsKey(ViPRConstants.FILE_ACCESS_END_TOKEN_HEADER))
                    result.setEndToken(headers.get(ViPRConstants.FILE_ACCESS_END_TOKEN_HEADER));
                if (headers.containsKey(ViPRConstants.FILE_ACCESS_PRESERVE_INGEST_PATHS))
                    result.setPreserveIngestPaths(
                            Boolean.parseBoolean(headers.get(ViPRConstants.FILE_ACCESS_PRESERVE_INGEST_PATHS)));

                AmazonWebServiceResponse<BucketFileAccessModeResult> awsResponse = parseResponseMetadata(response);
                awsResponse.setResult(result);
                return awsResponse;
            }
        }, bucketName, null);
    }

    public BucketFileAccessModeResult getBucketFileAccessMode(String bucketName) throws AmazonClientException {
        assertParameterNotNull(bucketName, "The bucket name parameter must be specified when querying access mode");

        Request<GenericBucketRequest> request = createRequest(bucketName, null,
                new GenericBucketRequest(bucketName), HttpMethodName.GET);
        request.addParameter(ViPRConstants.ACCESS_MODE_PARAMETER, null);

        return invoke(request, new AbstractS3ResponseHandler<BucketFileAccessModeResult>() {
            public AmazonWebServiceResponse<BucketFileAccessModeResult> handle(HttpResponse response)
                    throws Exception {
                BucketFileAccessModeResult result = new BucketFileAccessModeResult();
                Map<String, String> headers = response.getHeaders();

                if (headers.containsKey(ViPRConstants.FILE_ACCESS_MODE_HEADER))
                    result.setAccessMode(ViPRConstants.FileAccessMode
                            .valueOf(headers.get(ViPRConstants.FILE_ACCESS_MODE_HEADER)));
                if (headers.containsKey(ViPRConstants.FILE_ACCESS_DURATION_HEADER))
                    result.setDuration(Long.parseLong(headers.get(ViPRConstants.FILE_ACCESS_DURATION_HEADER)));
                if (headers.containsKey(ViPRConstants.FILE_ACCESS_HOST_LIST_HEADER))
                    result.setHostList(
                            Arrays.asList(headers.get(ViPRConstants.FILE_ACCESS_HOST_LIST_HEADER).split(",")));
                if (headers.containsKey(ViPRConstants.FILE_ACCESS_UID_HEADER))
                    result.setUid(headers.get(ViPRConstants.FILE_ACCESS_UID_HEADER));
                if (headers.containsKey(ViPRConstants.FILE_ACCESS_START_TOKEN_HEADER))
                    result.setStartToken(headers.get(ViPRConstants.FILE_ACCESS_START_TOKEN_HEADER));
                if (headers.containsKey(ViPRConstants.FILE_ACCESS_END_TOKEN_HEADER))
                    result.setEndToken(headers.get(ViPRConstants.FILE_ACCESS_END_TOKEN_HEADER));
                if (headers.containsKey(ViPRConstants.FILE_ACCESS_PRESERVE_INGEST_PATHS))
                    result.setPreserveIngestPaths(
                            Boolean.parseBoolean(headers.get(ViPRConstants.FILE_ACCESS_PRESERVE_INGEST_PATHS)));

                AmazonWebServiceResponse<BucketFileAccessModeResult> awsResponse = parseResponseMetadata(response);
                awsResponse.setResult(result);
                return awsResponse;
            }
        }, bucketName, null);
    }

    public GetFileAccessResult getFileAccess(GetFileAccessRequest getFileAccessRequest)
            throws AmazonClientException {
        assertParameterNotNull(getFileAccessRequest, "The GetFileAccessRequest parameter must be specified");

        String bucketName = getFileAccessRequest.getBucketName();
        assertParameterNotNull(bucketName, "The bucket name parameter must be specified when querying file access");

        Request<GetFileAccessRequest> request = createRequest(bucketName, null, getFileAccessRequest,
                HttpMethodName.GET);
        request.addParameter(ViPRConstants.FILE_ACCESS_PARAMETER, null);

        if (getFileAccessRequest.getMarker() != null) {
            request.addParameter(ViPRConstants.MARKER_PARAMETER, getFileAccessRequest.getMarker());
        }
        if (getFileAccessRequest.getMaxKeys() > 0) { // TODO: is this an appropriate indicator?
            request.addParameter(ViPRConstants.MAX_KEYS_PARAMETER,
                    Long.toString(getFileAccessRequest.getMaxKeys()));
        }

        return invoke(request, new AbstractS3ResponseHandler<GetFileAccessResult>() {
            public AmazonWebServiceResponse<GetFileAccessResult> handle(HttpResponse response) throws Exception {
                log.trace("Beginning to parse fileaccess response XML");
                GetFileAccessResult result = new GetFileAccessResultUnmarshaller()
                        .unmarshall(response.getContent());
                log.trace("Done parsing fileaccess response XML");

                AmazonWebServiceResponse<GetFileAccessResult> awsResponse = parseResponseMetadata(response);
                awsResponse.setResult(result);
                return awsResponse;
            }
        }, bucketName, null);
    }

    public ListDataNodesResult listDataNodes(ListDataNodesRequest listDataNodesRequest)
            throws AmazonClientException {
        Request<ListDataNodesRequest> request = createRequest(null, null, listDataNodesRequest, HttpMethodName.GET);
        request.addParameter(ViPRConstants.ENDPOINT_PARAMETER, null);

        return invoke(request, new AbstractS3ResponseHandler<ListDataNodesResult>() {
            public AmazonWebServiceResponse<ListDataNodesResult> handle(HttpResponse response) throws Exception {
                log.trace("Beginning to parse endpoint response XML");
                ListDataNodesResult result = new ListDataNodesResultUnmarshaller()
                        .unmarshall(response.getContent());
                log.trace("Done parsing endpoint response XML");

                AmazonWebServiceResponse<ListDataNodesResult> awsResponse = parseResponseMetadata(response);
                awsResponse.setResult(result);
                return awsResponse;
            }
        }, null, null);
    }

    /**
     * Overridden to specify the namespace via vHost or header. This choice is consistent with the
     * bucket convention used in the standard AWS client.  vHost buckets implies vHost namespace, otherwise the
     * namespace is specified in a header.
     */
    @Override
    protected <X extends AmazonWebServiceRequest> Request<X> createRequest(String bucketName, String key,
            X originalRequest, HttpMethodName httpMethod) {
        Request<X> request = super.createRequest(bucketName, key, originalRequest, httpMethod);

        if (namespace != null) {
            // is this a vHost request?
            if (isVHostRequest(request)) {
                // then prepend the namespace and bucket into the request host
                request.setEndpoint(convertToVirtualHostEndpoint(namespace, bucketName));
            } else {
                // otherwise add a header for namespace
                if (!request.getHeaders().containsKey(ViPRConstants.NAMESPACE_HEADER))
                    request.addHeader(ViPRConstants.NAMESPACE_HEADER, namespace);
            }
        }

        return request;
    }

    /**
     * Overridden to provide our own signer, which will include x-emc headers and namespace in the signature.
     */
    @Override
    protected Signer createSigner(Request<?> request, String bucketName, String key) {
        String resourcePath = "/" + ((bucketName != null) ? bucketName + "/" : "") + ((key != null) ? key : "");

        // if we're using a vHost request, the namespace must be prepended to the resource path when signing
        if (namespace != null && isVHostRequest(request)) {
            resourcePath = "/" + namespace + resourcePath;
        }

        return new ViPRS3Signer(request.getHttpMethod().toString(), resourcePath);
    }

    protected boolean isVHostRequest(Request<?> request) {
        return !(clientOptions.isPathStyleAccess() || request.getEndpoint().getHost().matches(IP_ADDRESS_PATTERN));
    }

    /**
     * Converts the current endpoint set for this client into virtual addressing
     * style, by placing the name of the specified bucket and namespace before the S3
     * endpoint.
     *
     * Convention is bucket.namespace.root-host (i.e. my-bucket.my-namespace.vipr-s3.my-company.com)
     *
     * @param namespace
     *            The namespace to use in the virtual addressing style
     *            of the returned URI.
     * @param bucketName
     *            The name of the bucket to use in the virtual addressing style
     *            of the returned URI.
     *
     * @return A new URI, creating from the current service endpoint URI and the
     *         specified namespace and bucket.
     */
    protected URI convertToVirtualHostEndpoint(String namespace, String bucketName) {
        try {
            return new URI(
                    endpoint.getScheme() + "://" + bucketName + "." + namespace + "." + endpoint.getAuthority());
        } catch (URISyntaxException e) {
            throw new IllegalArgumentException("Invalid namespace or bucket name: " + bucketName + "." + namespace,
                    e);
        }
    }

    /**
     * Executes a (Subclass of) PutObjectRequest.  In particular, we check for subclasses
     * of the UpdateObjectRequest and inject the value of the Range header.  This version
     * also returns the raw ObjectMetadata for the response so callers can construct
     * their own result objects.
     * @param putObjectRequest the request to execute
     * @return an ObjectMetadata containing the response headers.
     */
    protected ObjectMetadata doPut(PutObjectRequest putObjectRequest) {
        assertParameterNotNull(putObjectRequest,
                "The PutObjectRequest parameter must be specified when uploading an object");

        String bucketName = putObjectRequest.getBucketName();
        String key = putObjectRequest.getKey();
        ObjectMetadata metadata = putObjectRequest.getMetadata();
        InputStream input = putObjectRequest.getInputStream();
        if (metadata == null)
            metadata = new ObjectMetadata();

        assertParameterNotNull(bucketName, "The bucket name parameter must be specified when uploading an object");
        assertParameterNotNull(key, "The key parameter must be specified when uploading an object");

        /*
         * This is compatible with progress listener set by either the legacy
         * method GetObjectRequest#setProgressListener or the new method
         * GetObjectRequest#setGeneralProgressListener.
         */
        com.amazonaws.event.ProgressListener progressListener = putObjectRequest.getGeneralProgressListener();
        ProgressListenerCallbackExecutor progressListenerCallbackExecutor = ProgressListenerCallbackExecutor
                .wrapListener(progressListener);

        // If a file is specified for upload, we need to pull some additional
        // information from it to auto-configure a few options
        if (putObjectRequest.getFile() != null) {
            File file = putObjectRequest.getFile();

            // Always set the content length, even if it's already set
            metadata.setContentLength(file.length());

            // Only set the content type if it hasn't already been set
            if (metadata.getContentType() == null) {
                metadata.setContentType(Mimetypes.getInstance().getMimetype(file));
            }

            FileInputStream fileInputStream = null;
            try {
                fileInputStream = new FileInputStream(file);
                byte[] md5Hash = Md5Utils.computeMD5Hash(fileInputStream);
                metadata.setContentMD5(BinaryUtils.toBase64(md5Hash));
            } catch (Exception e) {
                throw new AmazonClientException("Unable to calculate MD5 hash: " + e.getMessage(), e);
            } finally {
                try {
                    fileInputStream.close();
                } catch (Exception e) {
                }
            }

            try {
                input = new RepeatableFileInputStream(file);
            } catch (FileNotFoundException fnfe) {
                throw new AmazonClientException("Unable to find file to upload", fnfe);
            }
        }

        Request<PutObjectRequest> request = createRequest(bucketName, key, putObjectRequest, HttpMethodName.PUT);

        if (putObjectRequest.getAccessControlList() != null) {
            addAclHeaders(request, putObjectRequest.getAccessControlList());
        } else if (putObjectRequest.getCannedAcl() != null) {
            request.addHeader(Headers.S3_CANNED_ACL, putObjectRequest.getCannedAcl().toString());
        }

        if (putObjectRequest.getStorageClass() != null) {
            request.addHeader(Headers.STORAGE_CLASS, putObjectRequest.getStorageClass());
        }

        if (putObjectRequest.getRedirectLocation() != null) {
            request.addHeader(Headers.REDIRECT_LOCATION, putObjectRequest.getRedirectLocation());
            if (input == null) {
                input = new ByteArrayInputStream(new byte[0]);
            }
        }

        // Use internal interface to differentiate 0 from unset.
        if (metadata.getRawMetadata().get(Headers.CONTENT_LENGTH) == null) {
            /*
             * There's nothing we can do except for let the HTTP client buffer
             * the input stream contents if the caller doesn't tell us how much
             * data to expect in a stream since we have to explicitly tell
             * Amazon S3 how much we're sending before we start sending any of
             * it.
             */
            log.warn("No content length specified for stream data.  "
                    + "Stream contents will be buffered in memory and could result in " + "out of memory errors.");
        }

        if (progressListenerCallbackExecutor != null) {
            com.amazonaws.event.ProgressReportingInputStream progressReportingInputStream = new com.amazonaws.event.ProgressReportingInputStream(
                    input, progressListenerCallbackExecutor);
            fireProgressEvent(progressListenerCallbackExecutor,
                    com.amazonaws.event.ProgressEvent.STARTED_EVENT_CODE);
        }

        if (!input.markSupported()) {
            int streamBufferSize = Constants.DEFAULT_STREAM_BUFFER_SIZE;
            String bufferSizeOverride = System.getProperty("com.amazonaws.sdk.s3.defaultStreamBufferSize");
            if (bufferSizeOverride != null) {
                try {
                    streamBufferSize = Integer.parseInt(bufferSizeOverride);
                } catch (Exception e) {
                    log.warn("Unable to parse buffer size override from value: " + bufferSizeOverride);
                }
            }

            input = new RepeatableInputStream(input, streamBufferSize);
        }

        MD5DigestCalculatingInputStream md5DigestStream = null;
        if (metadata.getContentMD5() == null) {
            /*
             * If the user hasn't set the content MD5, then we don't want to
             * buffer the whole stream in memory just to calculate it. Instead,
             * we can calculate it on the fly and validate it with the returned
             * ETag from the object upload.
             */
            try {
                md5DigestStream = new MD5DigestCalculatingInputStream(input);
                input = md5DigestStream;
            } catch (NoSuchAlgorithmException e) {
                log.warn("No MD5 digest algorithm available.  Unable to calculate "
                        + "checksum and verify data integrity.", e);
            }
        }

        if (metadata.getContentType() == null) {
            /*
             * Default to the "application/octet-stream" if the user hasn't
             * specified a content type.
             */
            metadata.setContentType(Mimetypes.MIMETYPE_OCTET_STREAM);
        }

        populateRequestMetadata(request, metadata);
        request.setContent(input);

        if (putObjectRequest instanceof UpdateObjectRequest) {
            request.addHeader(Headers.RANGE, "bytes=" + ((UpdateObjectRequest) putObjectRequest).getUpdateRange());
        }

        ObjectMetadata returnedMetadata = null;
        try {
            returnedMetadata = invoke(request, new S3MetadataResponseHandler(), bucketName, key);
        } catch (AmazonClientException ace) {
            fireProgressEvent(progressListenerCallbackExecutor,
                    com.amazonaws.event.ProgressEvent.FAILED_EVENT_CODE);
            throw ace;
        } finally {
            try {
                input.close();
            } catch (Exception e) {
                log.warn("Unable to cleanly close input stream: " + e.getMessage(), e);
            }
        }

        String contentMd5 = metadata.getContentMD5();
        if (md5DigestStream != null) {
            contentMd5 = BinaryUtils.toBase64(md5DigestStream.getMd5Digest());
        }

        // Can't verify MD5 on appends/update (yet).
        if (!(putObjectRequest instanceof UpdateObjectRequest)) {
            if (returnedMetadata != null && contentMd5 != null) {
                byte[] clientSideHash = BinaryUtils.fromBase64(contentMd5);
                byte[] serverSideHash = BinaryUtils.fromHex(returnedMetadata.getETag());

                if (!Arrays.equals(clientSideHash, serverSideHash)) {
                    fireProgressEvent(progressListenerCallbackExecutor,
                            com.amazonaws.event.ProgressEvent.FAILED_EVENT_CODE);
                    throw new AmazonClientException("Unable to verify integrity of data upload.  "
                            + "Client calculated content hash didn't match hash calculated by Amazon S3.  "
                            + "You may need to delete the data stored in Amazon S3.");
                }
            }
        }

        fireProgressEvent(progressListenerCallbackExecutor, com.amazonaws.event.ProgressEvent.COMPLETED_EVENT_CODE);

        return returnedMetadata;
    }

    /**
     * Gets the current ViPR namespace for this client.
     * @return the ViPR namespace associated with this client
     */
    public String getNamespace() {
        return namespace;
    }

    /**
     * Sets the ViPR namespace to use for this client.
     *
     * @param namespace the namespace to set
     */
    public synchronized void setNamespace(String namespace) {
        this.namespace = namespace;
    }

    /**
     * ViPR-specific create bucket command.  This version of the command adds some
     * options specific to EMC ViPR, specifically the ability to set the ViPR project ID
     * and Object Virtual Pool ID on the new bucket.
     * @param createBucketRequest the configuration parameters for the new bucket.
     */
    public Bucket createBucket(ViPRCreateBucketRequest createBucketRequest)
            throws AmazonClientException, AmazonServiceException {
        assertParameterNotNull(createBucketRequest,
                "The CreateBucketRequest parameter must be specified when creating a bucket");

        String bucketName = createBucketRequest.getBucketName();
        String region = createBucketRequest.getRegion();
        assertParameterNotNull(bucketName, "The bucket name parameter must be specified when creating a bucket");

        if (bucketName != null)
            bucketName = bucketName.trim();
        BucketNameUtils.validateBucketName(bucketName);

        Request<ViPRCreateBucketRequest> request = createRequest(bucketName, null, createBucketRequest,
                HttpMethodName.PUT);

        if (createBucketRequest.getAccessControlList() != null) {
            addAclHeaders(request, createBucketRequest.getAccessControlList());
        } else if (createBucketRequest.getCannedAcl() != null) {
            request.addHeader(Headers.S3_CANNED_ACL, createBucketRequest.getCannedAcl().toString());
        }

        // ViPR specific: projectId,  vpoolId and fsAccessEnabled.
        if (createBucketRequest.getProjectId() != null) {
            request.addHeader(ViPRConstants.PROJECT_HEADER, createBucketRequest.getProjectId());
        }
        if (createBucketRequest.getVpoolId() != null) {
            request.addHeader(ViPRConstants.VPOOL_HEADER, createBucketRequest.getVpoolId());
        }
        if (createBucketRequest.isFsAccessEnabled()) {
            request.addHeader(ViPRConstants.FS_ACCESS_ENABLED, "true");
        }

        /*
         * If we're talking to a region-specific endpoint other than the US, we
         * *must* specify a location constraint. Try to derive the region from
         * the endpoint.
         */
        if (!(this.endpoint.getHost().equals(Constants.S3_HOSTNAME)) && (region == null || region.isEmpty())) {

            try {
                region = RegionUtils.getRegionByEndpoint(this.endpoint.getHost()).getName();
            } catch (IllegalArgumentException exception) {
                // Endpoint does not correspond to a known region; send the
                // request with no location constraint and hope for the best.
            }

        }

        /*
         * We can only send the CreateBucketConfiguration if we're *not*
         * creating a bucket in the US region.
         */
        if (region != null && !region.toUpperCase().equals(Region.US_Standard.toString())) {
            XmlWriter xml = new XmlWriter();
            xml.start("CreateBucketConfiguration", "xmlns", Constants.XML_NAMESPACE);
            xml.start("LocationConstraint").value(region).end();
            xml.end();

            request.setContent(new ByteArrayInputStream(xml.getBytes()));
        }

        invoke(request, voidResponseHandler, bucketName, null);

        return new Bucket(bucketName);
    }

    protected String join(String delimiter, List<?> values) {
        return join(delimiter, values.toArray());
    }

    protected String join(String delimiter, Object... values) {
        StringBuilder joined = new StringBuilder();
        for (Object value : values) {
            joined.append(value).append(delimiter);
        }
        return joined.substring(0, joined.length() - 1);
    }

    public static final class GetFileAccessResultUnmarshaller
            implements Unmarshaller<GetFileAccessResult, InputStream> {

        public GetFileAccessResult unmarshall(InputStream in) throws Exception {
            return new ViPRResponsesSaxParser().parseFileAccessResult(in).getResult();
        }
    }

    public static final class ListDataNodesResultUnmarshaller
            implements Unmarshaller<ListDataNodesResult, InputStream> {

        @Override
        public ListDataNodesResult unmarshall(InputStream in) throws Exception {
            return new ViPRResponsesSaxParser().parseListDataNodeResult(in).getResult();
        }
    }
}