![JAR search and dependency download from the Maven repository](/logo.png)
src.org.jets3t.service.model.S3Object Maven / Gradle / Ivy
/*
* 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.model;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.security.NoSuchAlgorithmException;
import java.text.ParseException;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.jets3t.service.Constants;
import org.jets3t.service.S3ServiceException;
import org.jets3t.service.acl.AccessControlList;
import org.jets3t.service.io.RepeatableFileInputStream;
import org.jets3t.service.utils.Mimetypes;
import org.jets3t.service.utils.ServiceUtils;
/**
* An S3 object.
*
* @author James Murty
*/
public class S3Object extends BaseS3Object implements Cloneable {
private static final Log log = LogFactory.getLog(S3Object.class);
private static final long serialVersionUID = -2883501141593631181L;
/*
* Listing of the metadata names that are required in S3 objects, or are used frequently
* in jets3t applications.
*/
public static final String METADATA_HEADER_LAST_MODIFIED_DATE = "Last-Modified";
public static final String METADATA_HEADER_DATE = "Date";
public static final String METADATA_HEADER_OWNER = "Owner";
public static final String METADATA_HEADER_ETAG = "ETag";
public static final String METADATA_HEADER_HASH_MD5 = "md5-hash";
public static final String METADATA_HEADER_ORIGINAL_HASH_MD5 = "original-md5-hash";
public static final String METADATA_HEADER_CONTENT_MD5 = "Content-MD5";
public static final String METADATA_HEADER_CONTENT_LENGTH = "Content-Length";
public static final String METADATA_HEADER_CONTENT_TYPE = "Content-Type";
public static final String METADATA_HEADER_CONTENT_ENCODING = "Content-Encoding";
public static final String METADATA_HEADER_STORAGE_CLASS = "Storage-Class";
public static final String METADATA_HEADER_CONTENT_DISPOSITION = "Content-Disposition";
public static final String METADATA_HEADER_CONTENT_LANGUAGE = "Content-Language";
private String key = null;
private String bucketName = null;
private transient InputStream dataInputStream = null;
private AccessControlList acl = null;
private boolean isMetadataComplete = false;
/**
* Store references to files when the object's data comes from a file, to allow for lazy
* opening of the file's input streams.
*/
private File dataInputFile = null;
/**
* Create an object representing a file. The object is initialised with the file's name
* as its key, the file's content as its data, a content type based on the file's extension
* (see {@link Mimetypes}), and a content length matching the file's size.
* The file's MD5 hash value is also calculated and provided to S3, so the service
* can verify that no data are corrupted in transit.
*
* @param bucket
* the bucket the object belongs to, or will be placed in.
* @param file
* the file the object will represent. This file must exist and be readable.
*
* @throws IOException when an i/o error occurred reading the file
* @throws NoSuchAlgorithmException when this JRE doesn't support the MD5 hash algorithm
*/
public S3Object(S3Bucket bucket, File file) throws NoSuchAlgorithmException, IOException {
this(bucket, file.getName());
setContentLength(file.length());
setContentType(Mimetypes.getInstance().getMimetype(file));
if (!file.exists()) {
throw new FileNotFoundException("Cannot read from file: " + file.getAbsolutePath());
}
setDataInputFile(file);
setMd5Hash(ServiceUtils.computeMD5Hash(new FileInputStream(file)));
}
/**
* Create an object representing text data. The object is initialized with the given
* key, the given string as its data content (encoded as UTF-8), a content type of
* text/plain; charset=utf-8
, and a content length matching the
* string's length.
* The given string's MD5 hash value is also calculated and provided to S3, so the service
* can verify that no data are corrupted in transit.
*
* @param bucket
* the bucket the object belongs to, or will be placed in.
* @param key
* the key name for the object.
* @param dataString
* the text data the object will contain. Text data will be encoded as UTF-8.
* This string cannot be null.
*
* @throws IOException
* @throws NoSuchAlgorithmException when this JRE doesn't support the MD5 hash algorithm
*/
public S3Object(S3Bucket bucket, String key, String dataString) throws NoSuchAlgorithmException, IOException
{
this(bucket, key);
ByteArrayInputStream bais = new ByteArrayInputStream(
dataString.getBytes(Constants.DEFAULT_ENCODING));
setDataInputStream(bais);
setContentLength(bais.available());
setContentType("text/plain; charset=utf-8");
setMd5Hash(ServiceUtils.computeMD5Hash(dataString.getBytes(Constants.DEFAULT_ENCODING)));
}
/**
* Create an object without any associated data, and no associated bucket.
*
* @param key
* the key name for the object.
*/
public S3Object(String key) {
this.key = key;
}
/**
* Create an object without any associated data.
*
* @param bucket
* the bucket the object belongs to, or will be placed in.
* @param key
* the key name for the object.
*/
public S3Object(S3Bucket bucket, String key) {
if (bucket != null) {
this.bucketName = bucket.getName();
}
this.key = key;
}
public String toString() {
return "S3Object [key=" + getKey() + ",bucket=" + (bucketName == null ? "" : bucketName)
+ ",lastModified=" + getLastModifiedDate() + ", dataInputStream=" + dataInputStream
+ "] Metadata=" + getMetadataMap();
}
/**
* Returns an input stream containing this object's data, or null if there is
* no data associated with the object.
*
* If you are downloading data from S3, you should consider verifying the
* integrity of the data you read from this stream using one of the
* {@link #verifyData(InputStream)} methods.
*
* @throws S3ServiceException
*/
public InputStream getDataInputStream() throws S3ServiceException {
if (dataInputStream == null && dataInputFile != null) {
try {
// Use a repeatable file data input stream, so transmissions can be retried if necessary.
dataInputStream = new RepeatableFileInputStream(dataInputFile);
} catch (FileNotFoundException e) {
throw new S3ServiceException("Cannot open file input stream", e);
}
}
return dataInputStream;
}
/**
* Sets an input stream containing the data content to associate with this object.
*
* Note: If the data content comes from a file, use the alternate method
* {@link #setDataInputFile(File)} which allows objects to lazily open files and avoid any
* Operating System limits on the number of files that may be opened simultaneously.
*
* Note 2: This method does not calculate an MD5 hash of the input data,
* which means S3 will not be able to recognize if data are corrupted in transit.
* To allow S3 to verify data you upload, you should set the MD5 hash value of
* your data using {@link #setMd5Hash(byte[])}.
*
* This method will set the object's file data reference to null.
*
* @param dataInputStream
* an input stream containing the object's data.
*/
public void setDataInputStream(InputStream dataInputStream) {
this.dataInputFile = null;
this.dataInputStream = dataInputStream;
}
/**
* Sets the file containing the data content to associate with this object. This file will
* be automatically opened as an input stream only when absolutely necessary, that is when
* {@link #getDataInputStream()} is called.
*
* Note 2: This method does not calculate an MD5 hash of the input data,
* which means S3 will not be able to recognize if data are corrupted in transit.
* To allow S3 to verify data you upload, you should set the MD5 hash value of
* your data using {@link #setMd5Hash(byte[])}.
*
* This method will set the object's input stream data reference to null.
*
* @param dataInputFile
* a file containing the object's data.
*/
public void setDataInputFile(File dataInputFile) {
this.dataInputStream = null;
this.dataInputFile = dataInputFile;
}
/**
* Closes the object's data input stream if it exists.
*
* @throws IOException
*/
public void closeDataInputStream() throws IOException {
if (this.dataInputStream != null) {
this.dataInputStream.close();
this.dataInputStream = null;
}
}
/**
* @return
* the ETag value of the object as returned by S3 when an object is created. The ETag value
* does not include quote (") characters. This value will be null if the object's ETag value
* is not known, such as when an object has not yet been uploaded to S3.
*/
public String getETag() {
String etag = (String) getMetadata(METADATA_HEADER_ETAG);
if (etag != null) {
if (etag.startsWith("\"") && etag.endsWith("\"")) {
return etag.substring(1, etag.length() -1);
}
}
return etag;
}
/**
* Set the ETag value of the object based on information returned from S3.
* This method should only by used by code that reads S3 responses.
*
* @param etag
* the ETag value as provided by S3.
*/
public void setETag(String etag) {
addMetadata(METADATA_HEADER_ETAG, etag);
}
/**
* @return
* the hex-encoded MD5 hash of an object's data contents as stored in the jets3t-specific metadata
* item md5-hash
, or null if the hash value is not available.
*/
public String getMd5HashAsHex() {
return (String) getMetadata(METADATA_HEADER_HASH_MD5);
}
/**
* @return
* the Base64-encoded MD5 hash of an object's data contents as stored in the metadata
* item Content-MD5
, or as derived from an ETag
or
* md5-hash
hex-encoded version of the hash. Returns null if the hash value is not
* available.
*/
public String getMd5HashAsBase64() {
String md5HashBase64 = (String) getMetadata(METADATA_HEADER_CONTENT_MD5);
if (md5HashBase64 == null) {
// Try converting the object's ETag (a hex-encoded md5 hash).
if (getETag() != null) {
return ServiceUtils.toBase64(ServiceUtils.fromHex(getETag()));
}
// Try converting the object's md5-hash (another hex-encoded md5 hash).
if (getMd5HashAsHex() != null) {
return ServiceUtils.toBase64(ServiceUtils.fromHex(getMd5HashAsHex()));
}
}
return md5HashBase64;
}
/**
* Set the MD5 hash value of this object's data.
* The hash value is stored as metadata under Content-MD5
(Base64-encoded)
* and the jets3t-specific md5-hash
(Hex-encoded).
*
* @param md5Hash
* the MD5 hash value of the object's data.
*/
public void setMd5Hash(byte[] md5Hash) {
addMetadata(METADATA_HEADER_HASH_MD5, ServiceUtils.toHex(md5Hash));
addMetadata(METADATA_HEADER_CONTENT_MD5, ServiceUtils.toBase64(md5Hash));
}
/**
* @return
* the last modified date of this object, as provided by S3. If the last modified date is not
* available (e.g. if the object has only just been created) the object's creation date is
* returned instead. If both last modified and creation dates are unavailable, null is returned.
*/
public Date getLastModifiedDate() {
Date lastModifiedDate = (Date) getMetadata(METADATA_HEADER_LAST_MODIFIED_DATE);
if (lastModifiedDate == null) {
// Perhaps this object has just been created, in which case we can use the Date metadata.
lastModifiedDate = (Date) getMetadata(METADATA_HEADER_DATE);
}
return lastModifiedDate;
}
/**
* Set this object's last modified date based on information returned from S3.
* This method should only by used internally by code that reads the last modified date
* from an S3 response; it must not be set prior to uploading data to S3.
*
* @param lastModifiedDate
*/
public void setLastModifiedDate(Date lastModifiedDate) {
addMetadata(METADATA_HEADER_LAST_MODIFIED_DATE, lastModifiedDate);
}
/**
* @return
* this object's owner, or null if the owner is not available.
*/
public S3Owner getOwner() {
return (S3Owner) getMetadata(METADATA_HEADER_OWNER);
}
/**
* Set this object's owner object based on information returned from S3.
* This method should only by used by code that reads S3 responses.
*
* @param owner
*/
public void setOwner(S3Owner owner) {
addMetadata(METADATA_HEADER_OWNER, owner);
}
/**
* @return
* the content length, or size, of this object's data, or 0 if it is unknown.
*/
public long getContentLength() {
Object contentLength = getMetadata(METADATA_HEADER_CONTENT_LENGTH);
if (contentLength == null) {
return 0;
} else {
return Long.parseLong(contentLength.toString());
}
}
/**
* Set this object's content length. The content length is set internally by JetS3t for
* objects that are retrieved from S3. For objects that are uploaded into S3, JetS3t
* automatically calculates the content length if the data is provided to the String- or
* File-based S3Object constructor. If you manually provide data to this object via the
* {@link #setDataInputStream(InputStream)} or {@link #setDataInputFile(File)} methods,
* you must also set the content length value.
* @param size
*/
public void setContentLength(long size) {
addMetadata(METADATA_HEADER_CONTENT_LENGTH, String.valueOf(size));
}
/**
* @return
* the storage class of the object.
*/
public String getStorageClass() {
return (String) getMetadata(METADATA_HEADER_STORAGE_CLASS);
}
/**
* Set the storage class based on information returned from S3.
* This method should only by used by code that reads S3 responses.
*
* @param storageClass
*/
public void setStorageClass(String storageClass) {
addMetadata(METADATA_HEADER_STORAGE_CLASS, storageClass);
}
/**
* @return
* the content type of the object
*/
public String getContentType() {
return (String) getMetadata(METADATA_HEADER_CONTENT_TYPE);
}
/**
* Set the content type of the object. JetS3t can help you determine the
* content type when the associated data is a File (see {@link Mimetypes}).
* You should set the content type for associated String or InputStream data.
*
* @param contentType
*/
public void setContentType(String contentType) {
addMetadata(METADATA_HEADER_CONTENT_TYPE, contentType);
}
/**
* @return
* the content language of this object, or null if it is unknown.
*/
public String getContentLanguage() {
return (String) getMetadata(METADATA_HEADER_CONTENT_LANGUAGE);
}
/**
* Set the content language of the object.
* @param contentLanguage
*/
public void setContentLanguage(String contentLanguage) {
addMetadata(METADATA_HEADER_CONTENT_LANGUAGE, contentLanguage);
}
/**
* @return
* the content disposition of this object, or null if it is unknown.
*/
public String getContentDisposition() {
return (String) getMetadata(METADATA_HEADER_CONTENT_DISPOSITION);
}
/**
* Set the content disposition of the object.
* @param contentDisposition
*/
public void setContentDisposition(String contentDisposition) {
addMetadata(METADATA_HEADER_CONTENT_DISPOSITION, contentDisposition);
}
/**
* @return
* the content encoding of this object, or null if it is unknown.
*/
public String getContentEncoding() {
return (String) getMetadata(METADATA_HEADER_CONTENT_ENCODING);
}
/**
* Set the content encoding of this object.
* @param contentEncoding
*/
public void setContentEncoding(String contentEncoding) {
addMetadata(METADATA_HEADER_CONTENT_ENCODING, contentEncoding);
}
/**
* @return
* the name of the bucket this object belongs to or will be placed into, or null if none is set.
*/
public String getBucketName() {
return bucketName;
}
/**
* Set the name of the bucket this object belongs to or will be placed into.
* @param bucketName the name for the bucket.
*/
public void setBucketName(String bucketName) {
this.bucketName = bucketName;
}
/**
* @return
* the object's ACL, or null if it is unknown.
*/
public AccessControlList getAcl() {
return acl;
}
/**
* Set the object's ACL. If a pre-canned REST ACL is used, the plain-text representation
* of the canned ACL is also added as a metadata header x-amz-acl
.
*
* @param acl
*/
public void setAcl(AccessControlList acl) {
this.acl = acl;
if (acl != null) {
if (AccessControlList.REST_CANNED_PRIVATE.equals(acl)) {
addMetadata(Constants.REST_HEADER_PREFIX + "acl", "private");
} else if (AccessControlList.REST_CANNED_PUBLIC_READ.equals(acl)) {
addMetadata(Constants.REST_HEADER_PREFIX + "acl", "public-read");
} else if (AccessControlList.REST_CANNED_PUBLIC_READ_WRITE.equals(acl)) {
addMetadata(Constants.REST_HEADER_PREFIX + "acl", "public-read-write");
} else if (AccessControlList.REST_CANNED_AUTHENTICATED_READ.equals(acl)) {
addMetadata(Constants.REST_HEADER_PREFIX + "acl", "authenticated-read");
} else {
// Non-REST canned ACLs are not added as headers...
}
}
}
/**
* @return
* the key of this object.
*/
public String getKey() {
return key;
}
/**
* Set the key of this object.
* @param key the key for this object.
*/
public void setKey(String key) {
this.key = key;
}
/**
* @return
* true if the object's metadata are considered complete, such as when the object's metadata
* has been retrieved from S3 by a HEAD request. If this value is not true, the metadata
* information in this object should not be considered authoritative.
*/
public boolean isMetadataComplete() {
return isMetadataComplete;
}
/**
* S3 Object metadata are only complete when it is populated with all values following
* a HEAD or GET request.
* This method should only by used by code that reads S3 responses.
*
* @param isMetadataComplete
*/
public void setMetadataComplete(boolean isMetadataComplete) {
this.isMetadataComplete = isMetadataComplete;
}
/**
* Add metadata information to the object. If date metadata items (as recognized by name)
* are added and the value is not a date, the value is parsed as an ISO 8601 string.
* @param name
* @param value
*/
public void addMetadata(String name, Object value) {
try {
if (METADATA_HEADER_LAST_MODIFIED_DATE.equals(name) && !(value instanceof Date)) {
value = ServiceUtils.parseIso8601Date(value.toString());
} else if (METADATA_HEADER_DATE.equals(name) && !(value instanceof Date)) {
value = ServiceUtils.parseIso8601Date(value.toString());
}
} catch (ParseException e) {
if (log.isErrorEnabled()) {
log.error("Unable to parse value we expect to be a valid date: "
+ name + "=" + value, e);
}
}
super.addMetadata(name, value);
}
/**
* Add all the metadata information to the object from the provided map.
*
* @param metadata
*/
public void addAllMetadata(Map metadata) {
Iterator iter = metadata.entrySet().iterator();
while (iter.hasNext()) {
Map.Entry entry = (Map.Entry) iter.next();
addMetadata(entry.getKey().toString(), entry.getValue());
}
}
/**
* Returns only those object metadata items that can be modified in
* S3. This list excludes those that are set by the S3 service, and
* those that are specific to a particular HTTP request/response
* session (such as request identifiers).
*
* @return
* the limited set of metadata items that S3 allows users to control.
*/
public Map getModifiableMetadata() {
Map objectMetadata = new HashMap(getMetadataMap());
objectMetadata.remove(S3Object.METADATA_HEADER_CONTENT_LENGTH);
objectMetadata.remove(S3Object.METADATA_HEADER_DATE);
objectMetadata.remove(S3Object.METADATA_HEADER_ETAG);
objectMetadata.remove(S3Object.METADATA_HEADER_LAST_MODIFIED_DATE);
objectMetadata.remove(S3Object.METADATA_HEADER_OWNER);
objectMetadata.remove("id-2"); // HTTP request-specific information
objectMetadata.remove("request-id"); // HTTP request-specific information
return objectMetadata;
}
public Object clone() {
S3Object clone = new S3Object(key);
clone.bucketName = bucketName;
clone.dataInputStream = dataInputStream;
clone.acl = acl;
clone.isMetadataComplete = isMetadataComplete;
clone.dataInputFile = dataInputFile;
clone.addAllMetadata(getMetadataMap());
return clone;
}
/**
* Calculates the MD5 hash value of the given data object, and compares it
* against this object's hash (as stored in the Content-MD5 header for
* uploads, or the ETag header for downloads).
*
* @param downloadedFile
* @return
* true if the calculated MD5 hash value of the file matches this object's
* hash value, false otherwise.
*
* @throws NoSuchAlgorithmException
* @throws FileNotFoundException
* @throws IOException
*/
public boolean verifyData(File downloadedFile)
throws NoSuchAlgorithmException, FileNotFoundException, IOException
{
return getMd5HashAsBase64().equals(
ServiceUtils.toBase64(
ServiceUtils.computeMD5Hash(
new FileInputStream(downloadedFile))));
}
/**
* Calculates the MD5 hash value of the given data object, and compares it
* against this object's hash (as stored in the Content-MD5 header for
* uploads, or the ETag header for downloads).
*
* @param downloadedData
* @return
* true if the calculated MD5 hash value of the bytes matches this object's
* hash value, false otherwise.
*
* @throws NoSuchAlgorithmException
* @throws FileNotFoundException
* @throws IOException
*/
public boolean verifyData(byte[] downloadedData)
throws NoSuchAlgorithmException, FileNotFoundException, IOException
{
return getMd5HashAsBase64().equals(
ServiceUtils.toBase64(
ServiceUtils.computeMD5Hash(downloadedData)));
}
/**
* Calculates the MD5 hash value of the given data object, and compares it
* against this object's hash (as stored in the Content-MD5 header for
* uploads, or the ETag header for downloads).
*
* @param downloadedDataStream
* the input stream of a downloaded S3Object.
*
* @return
* true if the calculated MD5 hash value of the input stream matches this
* object's hash value, false otherwise.
*
* @throws NoSuchAlgorithmException
* @throws FileNotFoundException
* @throws IOException
*/
public boolean verifyData(InputStream downloadedDataStream)
throws NoSuchAlgorithmException, FileNotFoundException, IOException
{
return getMd5HashAsBase64().equals(
ServiceUtils.toBase64(
ServiceUtils.computeMD5Hash(downloadedDataStream)));
}
}