package com.xiaomi.infra.galaxy.fds.android.model; import java.text.ParseException; import java.util.Date; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; import org.apache.http.Header; import com.xiaomi.infra.galaxy.fds.android.util.Consts; import com.xiaomi.infra.galaxy.fds.android.util.Util; /** * Represents the object metadata that is stored with Galaxy FDS. This includes * custom user-supplied metadata, as well as the standard HTTP headers that * Galaxy FDS sends and receives (Content-Length, Content-Type, etc.). */ public class ObjectMetadata { private static final Set<String> PREDEFINED_HEADERS = new HashSet<String>(); static { PREDEFINED_HEADERS.add(HttpHeaders.LAST_MODIFIED); PREDEFINED_HEADERS.add(HttpHeaders.CONTENT_MD5); PREDEFINED_HEADERS.add(HttpHeaders.CONTENT_TYPE); PREDEFINED_HEADERS.add(HttpHeaders.CONTENT_LENGTH); PREDEFINED_HEADERS.add(HttpHeaders.CONTENT_ENCODING); PREDEFINED_HEADERS.add(HttpHeaders.CACHE_CONTROL); } public static ObjectMetadata parseObjectMetadata(Header[] headers) { ObjectMetadata metadata = new ObjectMetadata(); for (Header header : headers) { String key = header.getName(); String value = header.getValue(); // If user defined metadata, keep it if (key.startsWith(Consts.XIAOMI_META_HEADER_PREFIX)) { metadata.addUserMetadata(key, value); } else if (PREDEFINED_HEADERS.contains(key)) { metadata.addPredefinedMetadata(key, value); } } return metadata; } /** * Custom user metadata, represented in responses with the x-xiaomi-meta- * header prefix */ private final Map<String, String> userMetadata = new HashMap<String, String>(); /** * Predefined metadata */ private final Map<String, String> predefinedMetadata = new HashMap<String, String>(); /** * <p> * Gets the custom user-metadata for the associated object. * </p> * <p> * Galaxy FDS can store additional metadata on objects by internally * representing it as HTTP headers prefixed with "x-xiaomi-meta-". Use * user-metadata to store arbitrary metadata alongside their data in Galaxy * FDS. When setting user metadata, callers <i>should not</i> include the * internal "x-xiaomi-meta-" prefix; this library will handle that for them. * Likewise, when callers retrieve custom user-metadata, they will not see * the "x-xiaomi-meta-" header prefix. * </p> * <p> * User-metadata keys are <b>case insensitive</b> and will be returned as * lowercase strings, even if they were originally specified with uppercase * strings. * </p> * <p> * Note that user-metadata for an object is limited by the HTTP request * header limit. All HTTP headers included in a request (including user * metadata headers and other standard HTTP headers) must be less than 8KB. * </p> * * @return The custom user metadata for the associated object. */ public Map<String, String> getUserMetadata() { return userMetadata; } /** * <p> * Adds the key value pair of custom user-metadata for the associated * object. If the entry in the custom user-metadata map already contains the * specified key, it will be replaced with these new contents. * </p> * <p> * Galaxy FDS can store additional metadata on objects by internally * representing it as HTTP headers prefixed with "x-xiaomi-meta-". * Use user-metadata to store arbitrary metadata alongside their data in * Galaxy FDS. When setting user metadata, callers <i>should not</i> include * the internal "x-xiaomi-meta-" prefix; this library will handle that for * them. Likewise, when callers retrieve custom user-metadata, they will not * see the "x-xiaomi-meta-" header prefix. * </p> * <p> * Note that user-metadata for an object is limited by the HTTP request * header limit. All HTTP headers included in a request (including user * metadata headers and other standard HTTP headers) must be less than 8KB. * </p> * * @param key The key for the custom user metadata entry. Note that the key * should not include * the internal FDS HTTP header prefix. * @param value The value for the custom user-metadata entry. */ public void addUserMetadata(String key, String value) { this.checkMetadata(key); this.userMetadata.put(key, value); } /** * <p> * Gets the Content-Length HTTP header indicating the size of the * associated object in bytes. * </p> * <p> * This field is required when uploading objects to FDS, but the Galaxy FDS * Java client will automatically set it when working directly with files. * When uploading directly from a stream, set this field if possible. * Otherwise the client must buffer the entire stream in order to calculate * the content length before sending the data to Galaxy FDS. * </p> * <p> * For more information on the Content-Length HTTP header, see <a * href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.13"> * http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.13</a> * </p> * * @return The Content-Length HTTP header indicating the size of the * associated object in bytes. Returns <code>-1</code> if it hasn't been set yet. */ public long getContentLength() { String contentLength = predefinedMetadata.get(HttpHeaders.CONTENT_LENGTH); if (contentLength != null) { return Long.parseLong(contentLength); } else { return -1; } } /** * <p> * Sets the Content-Length HTTP header indicating the size of the * associated object in bytes. * </p> * <p> * This field is required when uploading objects to FDS, but the Galaxy FDS * Java client will automatically set it when working directly with files. When * uploading directly from a stream, set this field if * possible. Otherwise the client must buffer the entire stream in * order to calculate the content length before sending the data to * Galaxy FDS. * </p> * <p> * For more information on the Content-Length HTTP header, see <a * href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.13"> * http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.13</a> * </p> * * @param contentLength The Content-Length HTTP header indicating the size of * the associated object in bytes. * @see ObjectMetadata#getContentLength() */ public void setContentLength(long contentLength) { predefinedMetadata.put(HttpHeaders.CONTENT_LENGTH, Long.toString(contentLength)); } /** * <p> * Gets the base64 encoded 128-bit MD5 digest of the associated object * (content - not including headers) according to RFC 1864. This data is * used as a message integrity check to verify that the data received by * Galaxy FDS is the same data that the caller sent. * </p> * <p> * This field represents the base64 encoded 128-bit MD5 digest digest of an * object's content as calculated on the caller's side. * FDS. * </p> * <p> * The Galaxy FDS Java client will attempt to calculate this field automatically * when uploading files to Galaxy FDS. * </p> * * @return The base64 encoded MD5 hash of the content for the associated * object. Returns <code>null</code> if the MD5 hash of the content * hasn't been set. * @see ObjectMetadata#setContentMD5(String) */ public String getContentMD5() { return predefinedMetadata.get(HttpHeaders.CONTENT_MD5); } /** * <p> * Sets the base64 encoded 128-bit MD5 digest of the associated object * (content - not including headers) according to RFC 1864. This data is used * as a message integrity check to verify that the data received by Galaxy FDS * is the same data that the caller sent. * </p> * <p> * The Galaxy FDS Java client will attempt to calculate this field * automatically when uploading files to Galaxy FDS. * </p> * * @param contentMD5 The base64 encoded MD5 hash of the content for the object * associated with this metadata. * @see ObjectMetadata#getContentMD5() */ public void setContentMD5(String contentMD5) { predefinedMetadata.put(HttpHeaders.CONTENT_MD5, contentMD5); } /** * <p> * Gets the Content-Type HTTP header, which indicates the type of content * stored in the associated object. The value of this header is a standard * MIME type. * </p> * <p> * When uploading files, the Galaxy FDS Java client will attempt to determine * the correct content type if one hasn't been set yet. Users are * responsible for ensuring a suitable content type is set when uploading * streams. If no content type is provided and cannot be determined by * the filename, the default content type, "application/octet-stream", will * be used. * </p> * <p> * For more information on the Content-Type header, see <a * href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.17"> * http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.17</a> * </p> * * @return The HTTP Content-Type header, indicating the type of content * stored in the associated FDS object. Returns <code>null</code> * if it hasn't been * set. * @see ObjectMetadata#setContentType(String) */ public String getContentType() { return predefinedMetadata.get(HttpHeaders.CONTENT_TYPE); } /** * <p> * Sets the Content-Type HTTP header indicating the type of content stored in * the associated object. The value of this header is a standard MIME type. * </p> * <p> * When uploading files, the Galaxy FDS Java client will attempt to determine * the correct content type if one hasn't been set yet. Users are responsible * for ensuring a suitable content type is set when uploading streams. If no * content type is provided and cannot be determined by the filename, the * default content type "application/octet-stream" will be used. * </p> * <p> * For more information on the Content-Type header, see <a * href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.17"> * http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.17</a> * </p> * * @param contentType The HTTP Content-Type header indicating the type of * content stored in the associated FDS object. * @see ObjectMetadata#getContentType() */ public void setContentType(String contentType) { predefinedMetadata.put(HttpHeaders.CONTENT_TYPE, contentType); } /** * <p> * Gets the optional Content-Encoding HTTP header specifying what content * encodings have been applied to the object and what decoding mechanisms must * be applied in order to obtain the media-type referenced by the Content-Type * field. * </p> * <p> * For more information on how the Content-Encoding HTTP header works, see * <a * href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.11"> * http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.11</a> * </p> * * @return The HTTP Content-Encoding header. * Returns <code>null</code> if it hasn't been set. * @see ObjectMetadata#setContentType(String) */ public String getContentEncoding() { return predefinedMetadata.get(HttpHeaders.CONTENT_ENCODING); } /** * <p> * Sets the optional Content-Encoding HTTP header specifying what * content encodings have been applied to the object and what decoding * mechanisms must be applied in order to obtain the media-type referenced * by the Content-Type field. * </p> * <p> * For more information on how the Content-Encoding HTTP header works, see * <a * href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.11"> * http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.11</a> * </p> * * @param contentEncoding The HTTP Content-Encoding header, as defined in RFC * 2616. * @see <a * href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.11" * >http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.11</a> * @see ObjectMetadata#getContentType() */ public void setContentEncoding(String contentEncoding) { predefinedMetadata.put(HttpHeaders.CONTENT_ENCODING, contentEncoding); } /** * <p> * Gets the optional Cache-Control HTTP header which allows the user to * specify caching behavior along the HTTP request/reply chain. * </p> * <p> * For more information on how the Cache-Control HTTP header affects HTTP * requests and responses, see <a * href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.9"> * http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.9</a> * </p> * * @return The HTTP Cache-Control header as defined in RFC 2616. * Returns <code>null</code> if * it hasn't been set. * @see ObjectMetadata#setCacheControl(String) */ public String getCacheControl() { return predefinedMetadata.get(HttpHeaders.CACHE_CONTROL); } /** * <p> * Sets the optional Cache-Control HTTP header which allows the user to * specify caching behavior along the HTTP request/reply chain. * </p> * <p> * For more information on how the Cache-Control HTTP header affects HTTP * requests and responses see <a * href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.9"> * http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.9</a> * </p> * * @param cacheControl The HTTP Cache-Control header as defined in RFC 2616. * @see ObjectMetadata#getCacheControl() */ public void setCacheControl(String cacheControl) { predefinedMetadata.put(HttpHeaders.CACHE_CONTROL, cacheControl); } /** * Gets the value of the Last-Modified header, indicating the date * and time at which Galaxy FDS last recorded a modification to the * associated object. * * @return The date and time at which Galaxy FDS last recorded a modification * to the associated object. Returns <code>null</code> if * the Last-Modified header hasn't been set. */ public Date getLastModified() { String lastModified = predefinedMetadata.get(HttpHeaders.LAST_MODIFIED); if (lastModified != null) { try { return Util.parseDate(lastModified); } catch (ParseException e) { return null; } } else { return null; } } /** * For internal use only. Sets the Last-Modified header value * indicating the date and time at which Galaxy FDS last recorded a * modification to the associated object. * * @param lastModified The date and time at which Galaxy FDS last recorded a * modification to the associated object. */ public void setLastModified(Date lastModified) { predefinedMetadata.put(HttpHeaders.LAST_MODIFIED, Util.formatDateString(lastModified)); } /** * For internal use only. Add predefined metadata * @param key The key for the predefined metadata * @param value The value for the predefined metadata entry. */ public void addPredefinedMetadata(String key, String value) { this.checkMetadata(key); predefinedMetadata.put(key, value); } /** * Get all the metadata entries * @return All the metadata entries include predefined and user-defined metadata */ public Map<String, String> getAllMetadata() { Map<String, String> copy = new HashMap<String, String>(predefinedMetadata); copy.putAll(userMetadata); return copy; } private void checkMetadata(String key) { boolean isValid = key.startsWith(Consts.XIAOMI_META_HEADER_PREFIX); if (!isValid) { for (String m : PREDEFINED_HEADERS) { if (key.equals(m)) { isValid = true; break; } } } if (!isValid) { throw new RuntimeException("Invalid metadata: " + key, null); } } }