/* * Copyright 2017 LinkedIn Corp. 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. * 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. */ package com.github.ambry.account; import java.util.Collections; import java.util.HashSet; import java.util.Objects; import java.util.Set; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; /** * <p> * A representation of a container. A container virtually groups a number of blobs under the same {@link Account}, * so that an operation on a container can be applied to all the blobs of the container. When posting a blob, a * container needs to be specified. There can be multiple containers under the the same {@link Account}, but containers * cannot be nested. * </p> * <p> * Container name is provided by a user as an external reference to that container. Container id is an internal * identifier of the container, and is one-to-one mapped to a container name. Container name/id has to be distinct * within the same {@link Account}, but can be the same across different {@link Account}s. * </p> * <p> * Container is serialized into {@link JSONObject} in the {@code currentJsonVersion}, which is version 1 * for now. Below lists all the metadata versions and their formats: * </p> * <pre><code> * { * "containerName": "MyPrivateContainer", * "description": "This is my private container", * "isPrivate": "true", * "containerId": 0, * "version": 1, * "status": "active" * "parentAccountId": "101" * } * </code></pre> * <p> * A container object is immutable. To update a container, refer to {@link ContainerBuilder} and * {@link AccountBuilder}. * </p> */ public class Container { // constants static final String JSON_VERSION_KEY = "version"; static final String CONTAINER_NAME_KEY = "containerName"; static final String CONTAINER_ID_KEY = "containerId"; static final String STATUS_KEY = "status"; static final String CONTAINER_DELETE_TRIGGER_TIME_KEY = "deleteTriggerTime"; static final String DESCRIPTION_KEY = "description"; static final String IS_PRIVATE_KEY = "isPrivate"; static final String BACKUP_ENABLED_KEY = "backupEnabled"; static final String ENCRYPTED_KEY = "encrypted"; static final String PREVIOUSLY_ENCRYPTED_KEY = "previouslyEncrypted"; static final String CACHEABLE_KEY = "cacheable"; static final String MEDIA_SCAN_DISABLED_KEY = "mediaScanDisabled"; static final String REPLICATION_POLICY_KEY = "replicationPolicy"; static final String TTL_REQUIRED_KEY = "ttlRequired"; static final String SECURE_PATH_REQUIRED_KEY = "securePathRequired"; static final String CONTENT_TYPE_WHITELIST_FOR_FILENAMES_ON_DOWNLOAD = "contentTypeWhitelistForFilenamesOnDownload"; static final String PARENT_ACCOUNT_ID_KEY = "parentAccountId"; static final boolean BACKUP_ENABLED_DEFAULT_VALUE = false; static final boolean ENCRYPTED_DEFAULT_VALUE = false; static final boolean PREVIOUSLY_ENCRYPTED_DEFAULT_VALUE = ENCRYPTED_DEFAULT_VALUE; static final boolean MEDIA_SCAN_DISABLED_DEFAULT_VALUE = false; static final boolean TTL_REQUIRED_DEFAULT_VALUE = true; static final long CONTAINER_DELETE_TRIGGER_TIME_DEFAULT_VALUE = 0; static final boolean SECURE_PATH_REQUIRED_DEFAULT_VALUE = false; static final boolean CACHEABLE_DEFAULT_VALUE = true; static final Set<String> CONTENT_TYPE_WHITELIST_FOR_FILENAMES_ON_DOWNLOAD_DEFAULT_VALUE = Collections.emptySet(); public static final short JSON_VERSION_1 = 1; public static final short JSON_VERSION_2 = 2; /** * The current version to serialize in. This can be set through the {@link #setCurrentJsonVersion(short)} method. * This variable should be declared/initialized before the default containers are constructed. Otherwise, this * field will not be initialized when those constructors are called and it will fail. */ private static short currentJsonVersion = JSON_VERSION_2; /** * The id of {@link #UNKNOWN_CONTAINER}. */ public static final short UNKNOWN_CONTAINER_ID = -1; /** * The id for HelixAccountService to store {@link Account} metadata. */ public static final short HELIX_ACCOUNT_SERVICE_CONTAINER_ID = -2; /** * The id for the containers to be associated with the blobs that are put without specifying a target container, * but are specified public. {@link #DEFAULT_PUBLIC_CONTAINER} is one of the containers that use it. */ public static final short DEFAULT_PUBLIC_CONTAINER_ID = 0; /** * The id for the containers to be associated with the blobs that are put without specifying a target container, * but are specified private. {@link #DEFAULT_PRIVATE_CONTAINER} is one of the containers that use it. */ public static final short DEFAULT_PRIVATE_CONTAINER_ID = 1; /** * The name of {@link #UNKNOWN_CONTAINER}. */ public static final String UNKNOWN_CONTAINER_NAME = "ambry-unknown-container"; /** * The name for the containers to be associated with the blobs that are put without specifying a target container, * but are specified public. {@link #DEFAULT_PUBLIC_CONTAINER} is one of the containers that use it. */ public static final String DEFAULT_PUBLIC_CONTAINER_NAME = "default-public-container"; /** * The name for the containers to be associated with the blobs that are put without specifying a target container, * but are specified private. {@link #DEFAULT_PRIVATE_CONTAINER} is one of the containers that use it. */ public static final String DEFAULT_PRIVATE_CONTAINER_NAME = "default-private-container"; /** * The status of {@link #UNKNOWN_CONTAINER}. */ public static final ContainerStatus UNKNOWN_CONTAINER_STATUS = ContainerStatus.ACTIVE; /** * The delete trigger time of {@link #UNKNOWN_CONTAINER} */ public static final long UNKNOWN_CONTAINER_DELETE_TRIGGER_TIME = CONTAINER_DELETE_TRIGGER_TIME_DEFAULT_VALUE; /** * The delete trigger time of {@link #DEFAULT_PUBLIC_CONTAINER} */ public static final long DEFAULT_PUBLIC_CONTAINER_DELETE_TRIGGER_TIME = CONTAINER_DELETE_TRIGGER_TIME_DEFAULT_VALUE; /** * The delete trigger time of {@link #DEFAULT_PRIVATE_CONTAINER} */ public static final long DEFAULT_PRIVATE_CONTAINER_DELETE_TRIGGER_TIME = CONTAINER_DELETE_TRIGGER_TIME_DEFAULT_VALUE; /** * The status for the containers to be associated with the blobs that are put without specifying a target container, * but are specified public. {@link #DEFAULT_PUBLIC_CONTAINER} is one of the containers that use it. */ public static final ContainerStatus DEFAULT_PUBLIC_CONTAINER_STATUS = ContainerStatus.ACTIVE; /** * The status for the containers to be associated with the blobs that are put without specifying a target container, * but are specified private. {@link #DEFAULT_PRIVATE_CONTAINER} is one of the containers that use it. */ public static final ContainerStatus DEFAULT_PRIVATE_CONTAINER_STATUS = ContainerStatus.ACTIVE; /** * The description of {@link #UNKNOWN_CONTAINER}. */ public static final String UNKNOWN_CONTAINER_DESCRIPTION = "This is a container for the blobs without specifying a target account and container when they are put"; /** * The description for the containers to be associated with the blobs that are put without specifying a target * container, but are specified public. {@link #DEFAULT_PUBLIC_CONTAINER} is one of the containers that use it. */ public static final String DEFAULT_PUBLIC_CONTAINER_DESCRIPTION = "This is a container for the blobs without specifying a target account and container when they are put and isPrivate flag is false"; /** * The description for the containers to be associated with the blobs that are put without specifying a target * container, but are specified private. {@link #DEFAULT_PRIVATE_CONTAINER} is one of the containers that use it. */ public static final String DEFAULT_PRIVATE_CONTAINER_DESCRIPTION = "This is a container for the blobs without specifying a target account and container when they are put and isPrivate flag is true"; /** * The encryption setting of {@link #UNKNOWN_CONTAINER}. */ public static final boolean UNKNOWN_CONTAINER_ENCRYPTED_SETTING = ENCRYPTED_DEFAULT_VALUE; /** * The encryption setting of {@link #DEFAULT_PUBLIC_CONTAINER}. */ public static final boolean DEFAULT_PUBLIC_CONTAINER_ENCRYPTED_SETTING = ENCRYPTED_DEFAULT_VALUE; /** * The encryption setting of {@link #DEFAULT_PRIVATE_CONTAINER}. */ public static final boolean DEFAULT_PRIVATE_CONTAINER_ENCRYPTED_SETTING = ENCRYPTED_DEFAULT_VALUE; /** * The previously encrypted flag for {@link #UNKNOWN_CONTAINER}. */ public static final boolean UNKNOWN_CONTAINER_PREVIOUSLY_ENCRYPTED_SETTING = PREVIOUSLY_ENCRYPTED_DEFAULT_VALUE; /** * The previously encrypted flag for {@link #DEFAULT_PUBLIC_CONTAINER}. */ public static final boolean DEFAULT_PUBLIC_CONTAINER_PREVIOUSLY_ENCRYPTED_SETTING = PREVIOUSLY_ENCRYPTED_DEFAULT_VALUE; /** * The previously encrypted flag for {@link #DEFAULT_PRIVATE_CONTAINER}. */ public static final boolean DEFAULT_PRIVATE_CONTAINER_PREVIOUSLY_ENCRYPTED_SETTING = PREVIOUSLY_ENCRYPTED_DEFAULT_VALUE; /** * The cache setting of {@link #UNKNOWN_CONTAINER}. */ public static final boolean UNKNOWN_CONTAINER_CACHEABLE_SETTING = true; /** * The cache setting of {@link #DEFAULT_PUBLIC_CONTAINER}. */ public static final boolean DEFAULT_PUBLIC_CONTAINER_CACHEABLE_SETTING = true; /** * The cache setting of {@link #DEFAULT_PRIVATE_CONTAINER}. */ public static final boolean DEFAULT_PRIVATE_CONTAINER_CACHEABLE_SETTING = false; /** * The media scan disabled setting for {@link #UNKNOWN_CONTAINER}. */ public static final boolean UNKNOWN_CONTAINER_MEDIA_SCAN_DISABLED_SETTING = MEDIA_SCAN_DISABLED_DEFAULT_VALUE; /** * The media scan disabled setting for {@link #DEFAULT_PUBLIC_CONTAINER}. */ public static final boolean DEFAULT_PUBLIC_CONTAINER_MEDIA_SCAN_DISABLED_SETTING = MEDIA_SCAN_DISABLED_DEFAULT_VALUE; /** * The media scan disabled setting for {@link #DEFAULT_PRIVATE_CONTAINER}. */ public static final boolean DEFAULT_PRIVATE_CONTAINER_MEDIA_SCAN_DISABLED_SETTING = MEDIA_SCAN_DISABLED_DEFAULT_VALUE; /** * The ttl required setting for {@link #UNKNOWN_CONTAINER}. */ public static final boolean UNKNOWN_CONTAINER_TTL_REQUIRED_SETTING = TTL_REQUIRED_DEFAULT_VALUE; /** * The ttl required setting for {@link #DEFAULT_PUBLIC_CONTAINER}. */ public static final boolean DEFAULT_PUBLIC_CONTAINER_TTL_REQUIRED_SETTING = TTL_REQUIRED_DEFAULT_VALUE; /** * The ttl required setting for {@link #DEFAULT_PRIVATE_CONTAINER}. */ public static final boolean DEFAULT_PRIVATE_CONTAINER_TTL_REQUIRED_SETTING = TTL_REQUIRED_DEFAULT_VALUE; /** * The parent account id of {@link #UNKNOWN_CONTAINER}. */ public static final short UNKNOWN_CONTAINER_PARENT_ACCOUNT_ID = Account.UNKNOWN_ACCOUNT_ID; /** * The parent account id of {@link #DEFAULT_PUBLIC_CONTAINER}. */ public static final short DEFAULT_PUBLIC_CONTAINER_PARENT_ACCOUNT_ID = Account.UNKNOWN_ACCOUNT_ID; /** * The parent account id of {@link #DEFAULT_PRIVATE_CONTAINER}. */ public static final short DEFAULT_PRIVATE_CONTAINER_PARENT_ACCOUNT_ID = Account.UNKNOWN_ACCOUNT_ID; /** * A container defined specifically for the blobs put without specifying target account and container. In the * pre-containerization world, a put-blob request does not carry any information which account/container to store * the blob. These blobs are literally put into this container, because the target container information is unknown. * * DO NOT USE IN PRODUCTION CODE. */ @Deprecated public static final Container UNKNOWN_CONTAINER = new Container(UNKNOWN_CONTAINER_ID, UNKNOWN_CONTAINER_NAME, UNKNOWN_CONTAINER_STATUS, UNKNOWN_CONTAINER_DESCRIPTION, UNKNOWN_CONTAINER_ENCRYPTED_SETTING, UNKNOWN_CONTAINER_PREVIOUSLY_ENCRYPTED_SETTING, UNKNOWN_CONTAINER_CACHEABLE_SETTING, UNKNOWN_CONTAINER_MEDIA_SCAN_DISABLED_SETTING, null, UNKNOWN_CONTAINER_TTL_REQUIRED_SETTING, SECURE_PATH_REQUIRED_DEFAULT_VALUE, CONTENT_TYPE_WHITELIST_FOR_FILENAMES_ON_DOWNLOAD_DEFAULT_VALUE, BACKUP_ENABLED_DEFAULT_VALUE, UNKNOWN_CONTAINER_PARENT_ACCOUNT_ID, UNKNOWN_CONTAINER_DELETE_TRIGGER_TIME); /** * A container defined specifically for the blobs put without specifying target container but isPrivate flag is * set to {@code false}. * * DO NOT USE IN PRODUCTION CODE. */ @Deprecated public static final Container DEFAULT_PUBLIC_CONTAINER = new Container(DEFAULT_PUBLIC_CONTAINER_ID, DEFAULT_PUBLIC_CONTAINER_NAME, DEFAULT_PUBLIC_CONTAINER_STATUS, DEFAULT_PUBLIC_CONTAINER_DESCRIPTION, DEFAULT_PUBLIC_CONTAINER_ENCRYPTED_SETTING, DEFAULT_PUBLIC_CONTAINER_PREVIOUSLY_ENCRYPTED_SETTING, DEFAULT_PUBLIC_CONTAINER_CACHEABLE_SETTING, DEFAULT_PUBLIC_CONTAINER_MEDIA_SCAN_DISABLED_SETTING, null, DEFAULT_PUBLIC_CONTAINER_TTL_REQUIRED_SETTING, SECURE_PATH_REQUIRED_DEFAULT_VALUE, CONTENT_TYPE_WHITELIST_FOR_FILENAMES_ON_DOWNLOAD_DEFAULT_VALUE, BACKUP_ENABLED_DEFAULT_VALUE, DEFAULT_PUBLIC_CONTAINER_PARENT_ACCOUNT_ID, DEFAULT_PRIVATE_CONTAINER_DELETE_TRIGGER_TIME); /** * A container defined specifically for the blobs put without specifying target container but isPrivate flag is * set to {@code true}. * * DO NOT USE IN PRODUCTION CODE. */ @Deprecated public static final Container DEFAULT_PRIVATE_CONTAINER = new Container(DEFAULT_PRIVATE_CONTAINER_ID, DEFAULT_PRIVATE_CONTAINER_NAME, DEFAULT_PRIVATE_CONTAINER_STATUS, DEFAULT_PRIVATE_CONTAINER_DESCRIPTION, DEFAULT_PRIVATE_CONTAINER_ENCRYPTED_SETTING, DEFAULT_PRIVATE_CONTAINER_PREVIOUSLY_ENCRYPTED_SETTING, DEFAULT_PRIVATE_CONTAINER_CACHEABLE_SETTING, DEFAULT_PRIVATE_CONTAINER_MEDIA_SCAN_DISABLED_SETTING, null, DEFAULT_PRIVATE_CONTAINER_TTL_REQUIRED_SETTING, SECURE_PATH_REQUIRED_DEFAULT_VALUE, CONTENT_TYPE_WHITELIST_FOR_FILENAMES_ON_DOWNLOAD_DEFAULT_VALUE, BACKUP_ENABLED_DEFAULT_VALUE, DEFAULT_PRIVATE_CONTAINER_PARENT_ACCOUNT_ID, DEFAULT_PUBLIC_CONTAINER_DELETE_TRIGGER_TIME); // container field variables private final short id; private final String name; private final ContainerStatus status; private final long deleteTriggerTime; private final String description; private final boolean encrypted; private final boolean previouslyEncrypted; private final boolean cacheable; private final boolean backupEnabled; private final boolean mediaScanDisabled; private final String replicationPolicy; private final boolean ttlRequired; private final boolean securePathRequired; private final Set<String> contentTypeWhitelistForFilenamesOnDownload; private final short parentAccountId; /** * Constructing an {@link Container} object from container metadata. * @param metadata The metadata of the container in JSON. * @throws JSONException If fails to parse metadata. */ private Container(JSONObject metadata, short parentAccountId) throws JSONException { if (metadata == null) { throw new IllegalArgumentException("metadata cannot be null."); } this.parentAccountId = parentAccountId; short metadataVersion = (short) metadata.getInt(JSON_VERSION_KEY); switch (metadataVersion) { case JSON_VERSION_1: id = (short) metadata.getInt(CONTAINER_ID_KEY); name = metadata.getString(CONTAINER_NAME_KEY); status = ContainerStatus.valueOf(metadata.getString(STATUS_KEY)); deleteTriggerTime = CONTAINER_DELETE_TRIGGER_TIME_DEFAULT_VALUE; description = metadata.optString(DESCRIPTION_KEY); encrypted = ENCRYPTED_DEFAULT_VALUE; previouslyEncrypted = PREVIOUSLY_ENCRYPTED_DEFAULT_VALUE; cacheable = !metadata.getBoolean(IS_PRIVATE_KEY); backupEnabled = BACKUP_ENABLED_DEFAULT_VALUE; mediaScanDisabled = MEDIA_SCAN_DISABLED_DEFAULT_VALUE; replicationPolicy = null; ttlRequired = TTL_REQUIRED_DEFAULT_VALUE; securePathRequired = SECURE_PATH_REQUIRED_DEFAULT_VALUE; contentTypeWhitelistForFilenamesOnDownload = CONTENT_TYPE_WHITELIST_FOR_FILENAMES_ON_DOWNLOAD_DEFAULT_VALUE; break; case JSON_VERSION_2: id = (short) metadata.getInt(CONTAINER_ID_KEY); name = metadata.getString(CONTAINER_NAME_KEY); status = ContainerStatus.valueOf(metadata.getString(STATUS_KEY)); deleteTriggerTime = metadata.optLong(CONTAINER_DELETE_TRIGGER_TIME_KEY, CONTAINER_DELETE_TRIGGER_TIME_DEFAULT_VALUE); description = metadata.optString(DESCRIPTION_KEY); encrypted = metadata.optBoolean(ENCRYPTED_KEY, ENCRYPTED_DEFAULT_VALUE); previouslyEncrypted = metadata.optBoolean(PREVIOUSLY_ENCRYPTED_KEY, PREVIOUSLY_ENCRYPTED_DEFAULT_VALUE); cacheable = metadata.optBoolean(CACHEABLE_KEY, CACHEABLE_DEFAULT_VALUE); backupEnabled = metadata.optBoolean(BACKUP_ENABLED_KEY, BACKUP_ENABLED_DEFAULT_VALUE); mediaScanDisabled = metadata.optBoolean(MEDIA_SCAN_DISABLED_KEY, MEDIA_SCAN_DISABLED_DEFAULT_VALUE); replicationPolicy = metadata.optString(REPLICATION_POLICY_KEY, null); ttlRequired = metadata.optBoolean(TTL_REQUIRED_KEY, TTL_REQUIRED_DEFAULT_VALUE); securePathRequired = metadata.optBoolean(SECURE_PATH_REQUIRED_KEY, SECURE_PATH_REQUIRED_DEFAULT_VALUE); JSONArray contentTypeWhitelistForFilenamesOnDownloadJson = metadata.optJSONArray(CONTENT_TYPE_WHITELIST_FOR_FILENAMES_ON_DOWNLOAD); if (contentTypeWhitelistForFilenamesOnDownloadJson != null) { contentTypeWhitelistForFilenamesOnDownload = new HashSet<>(); contentTypeWhitelistForFilenamesOnDownloadJson.forEach( contentType -> contentTypeWhitelistForFilenamesOnDownload.add(contentType.toString())); } else { contentTypeWhitelistForFilenamesOnDownload = CONTENT_TYPE_WHITELIST_FOR_FILENAMES_ON_DOWNLOAD_DEFAULT_VALUE; } break; default: throw new IllegalStateException("Unsupported container json version=" + metadataVersion); } checkPreconditions(name, status, encrypted, previouslyEncrypted); } /** * Constructor that takes individual arguments. Cannot be null. * @param id The id of the container. * @param name The name of the container. Cannot be null. * @param status The status of the container. Cannot be null. * @param description The description of the container. Can be null. * @param encrypted {@code true} if blobs in the {@link Container} should be encrypted, {@code false} otherwise. * @param previouslyEncrypted {@code true} if this {@link Container} was encrypted in the past, or currently, and a * subset of blobs in it could still be encrypted. * @param cacheable {@code true} if cache control headers should be set to allow CDNs and browsers to cache blobs in * this container. * @param mediaScanDisabled {@code true} if media scanning for content in this container should be disabled. * @param replicationPolicy the replication policy to use. If {@code null}, the cluster's default will be used. * @param ttlRequired {@code true} if ttl is required on content created in this container. * @param securePathRequired {@code true} if secure path validation is required in this container. * @param contentTypeWhitelistForFilenamesOnDownload the set of content types for which the filename can be sent on * download * @param backupEnabled Whether backup is enabled for this container or not * @param parentAccountId The id of the parent {@link Account} of this container. */ Container(short id, String name, ContainerStatus status, String description, boolean encrypted, boolean previouslyEncrypted, boolean cacheable, boolean mediaScanDisabled, String replicationPolicy, boolean ttlRequired, boolean securePathRequired, Set<String> contentTypeWhitelistForFilenamesOnDownload, boolean backupEnabled, short parentAccountId, long deleteTriggerTime) { checkPreconditions(name, status, encrypted, previouslyEncrypted); this.id = id; this.name = name; this.status = status; this.description = description; this.cacheable = cacheable; this.parentAccountId = parentAccountId; switch (currentJsonVersion) { case JSON_VERSION_1: this.backupEnabled = BACKUP_ENABLED_DEFAULT_VALUE; this.encrypted = ENCRYPTED_DEFAULT_VALUE; this.previouslyEncrypted = PREVIOUSLY_ENCRYPTED_DEFAULT_VALUE; this.mediaScanDisabled = MEDIA_SCAN_DISABLED_DEFAULT_VALUE; this.replicationPolicy = null; this.deleteTriggerTime = CONTAINER_DELETE_TRIGGER_TIME_DEFAULT_VALUE; this.ttlRequired = TTL_REQUIRED_DEFAULT_VALUE; this.securePathRequired = SECURE_PATH_REQUIRED_DEFAULT_VALUE; this.contentTypeWhitelistForFilenamesOnDownload = CONTENT_TYPE_WHITELIST_FOR_FILENAMES_ON_DOWNLOAD_DEFAULT_VALUE; break; case JSON_VERSION_2: this.backupEnabled = backupEnabled; this.encrypted = encrypted; this.previouslyEncrypted = previouslyEncrypted; this.mediaScanDisabled = mediaScanDisabled; this.replicationPolicy = replicationPolicy; this.deleteTriggerTime = deleteTriggerTime; this.ttlRequired = ttlRequired; this.securePathRequired = securePathRequired; this.contentTypeWhitelistForFilenamesOnDownload = contentTypeWhitelistForFilenamesOnDownload == null ? Collections.emptySet() : contentTypeWhitelistForFilenamesOnDownload; break; default: throw new IllegalStateException("Unsupported container json version=" + currentJsonVersion); } } /** * @return the JSON version to serialize in. */ public static short getCurrentJsonVersion() { return currentJsonVersion; } /** * Set the JSON version to serialize in. Note that this is a static setting that will affect all {@link Container} * serialization. * @param currentJsonVersion the JSON version to serialize in. */ public static void setCurrentJsonVersion(short currentJsonVersion) { Container.currentJsonVersion = currentJsonVersion; } /** * Deserializes a {@link JSONObject} to a container object. * @param json The {@link JSONObject} to deserialize. * @param parentAccountId The ID of the parent {@link Account} of this container. This is passed in because it is * not always included in the JSON record. * @return A container object deserialized from the {@link JSONObject}. * @throws JSONException If parsing the {@link JSONObject} fails. */ static Container fromJson(JSONObject json, short parentAccountId) throws JSONException { return new Container(json, parentAccountId); } /** * Gets the metadata of the container. * @return The metadata of the container. * @throws JSONException If fails to compose metadata. */ JSONObject toJson() throws JSONException { JSONObject metadata = new JSONObject(); switch (currentJsonVersion) { case JSON_VERSION_1: metadata.put(JSON_VERSION_KEY, JSON_VERSION_1); metadata.put(CONTAINER_ID_KEY, id); metadata.put(CONTAINER_NAME_KEY, name); metadata.put(STATUS_KEY, status.name()); metadata.put(DESCRIPTION_KEY, description); metadata.put(IS_PRIVATE_KEY, !cacheable); metadata.put(PARENT_ACCOUNT_ID_KEY, parentAccountId); break; case JSON_VERSION_2: metadata.put(Container.JSON_VERSION_KEY, JSON_VERSION_2); metadata.put(CONTAINER_ID_KEY, id); metadata.put(CONTAINER_NAME_KEY, name); metadata.put(CONTAINER_DELETE_TRIGGER_TIME_KEY, deleteTriggerTime); metadata.put(Container.STATUS_KEY, status.name()); metadata.put(DESCRIPTION_KEY, description); metadata.put(ENCRYPTED_KEY, encrypted); metadata.put(PREVIOUSLY_ENCRYPTED_KEY, previouslyEncrypted); metadata.put(CACHEABLE_KEY, cacheable); metadata.put(BACKUP_ENABLED_KEY, backupEnabled); metadata.put(MEDIA_SCAN_DISABLED_KEY, mediaScanDisabled); metadata.putOpt(REPLICATION_POLICY_KEY, replicationPolicy); metadata.put(TTL_REQUIRED_KEY, ttlRequired); metadata.put(SECURE_PATH_REQUIRED_KEY, securePathRequired); if (contentTypeWhitelistForFilenamesOnDownload != null && !contentTypeWhitelistForFilenamesOnDownload.isEmpty()) { JSONArray contentTypeWhitelistForFilenamesOnDownloadJson = new JSONArray(); contentTypeWhitelistForFilenamesOnDownload.forEach(contentTypeWhitelistForFilenamesOnDownloadJson::put); metadata.put(CONTENT_TYPE_WHITELIST_FOR_FILENAMES_ON_DOWNLOAD, contentTypeWhitelistForFilenamesOnDownloadJson); } break; default: throw new IllegalStateException("Unsupported container json version=" + currentJsonVersion); } return metadata; } /** * Gets the id of the container. * @return The id of the container. */ public short getId() { return id; } /** * Gets the name of the container. * @return The name of the container. */ public String getName() { return name; } /** * Gets the status of the container. * @return The status of the container. */ public ContainerStatus getStatus() { return status; } /** * Gets the delete trigger time of the container. * @return The delete trigger time of the container. */ public Long getDeleteTriggerTime() { return deleteTriggerTime; } /** * Gets the description of the container. * @return The description of the container. */ public String getDescription() { return description; } /** * @return {@code true} if blobs in the {@link Container} should be encrypted, {@code false} otherwise. */ public boolean isEncrypted() { return encrypted; } /** * @return {@code true} if blobs in the {@link Container} should be backed up, {@code false} otherwise. */ public boolean isBackupEnabled() { return backupEnabled; } /** * @return {@code true} if this {@link Container} was encrypted in the past, and a subset of blobs in it could still * be encrypted. */ public boolean wasPreviouslyEncrypted() { return previouslyEncrypted; } /** * @return {@code true} if cache control headers should be set to allow CDNs and browsers to cache blobs in this * container. */ public boolean isCacheable() { return cacheable; } /** * @return {@code true} if media scans should be disabled on content created in this container. */ public boolean isMediaScanDisabled() { return mediaScanDisabled; } /** * @return the replication policy desired by the container. Can be {@code null} if the container has no preference. */ public String getReplicationPolicy() { return replicationPolicy; } /** * @return {@code true} if ttl is required on content created in this container. */ public boolean isTtlRequired() { return ttlRequired; } /** * @return the set of content types for which the filename can be sent on download */ public Set<String> getContentTypeWhitelistForFilenamesOnDownload() { return contentTypeWhitelistForFilenamesOnDownload; } /** * @return {@code true} if secure path validation is required for url to access blobs in this container. */ public boolean isSecurePathRequired() { return securePathRequired; } /** * Gets the if of the {@link Account} that owns this container. * @return The id of the parent {@link Account} of this container. */ public short getParentAccountId() { return parentAccountId; } /** * Generates a String representation that uniquely identifies this container. The string * is in the format of {@code Container[accountId:containerId]}. * @return The String representation of this container. */ @Override public String toString() { return "Container[" + getParentAccountId() + ":" + getId() + "]"; } @Override public boolean equals(Object o) { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } Container container = (Container) o; return id == container.id && encrypted == container.encrypted && previouslyEncrypted == container.previouslyEncrypted && cacheable == container.cacheable && mediaScanDisabled == container.mediaScanDisabled && parentAccountId == container.parentAccountId && Objects.equals(name, container.name) && status == container.status && deleteTriggerTime == container.deleteTriggerTime && Objects.equals(description, container.description) && Objects.equals(replicationPolicy, container.replicationPolicy) && ttlRequired == container.ttlRequired && securePathRequired == container.securePathRequired && Objects.equals( contentTypeWhitelistForFilenamesOnDownload, container.contentTypeWhitelistForFilenamesOnDownload); } @Override public int hashCode() { return Objects.hash(id, parentAccountId); } /** * Checks if any required fields is missing for a {@link Container} or for any incompatible settings. * @param name The name of the container. Cannot be null. * @param status The status of the container. Cannot be null. * @param encrypted {@code true} if blobs in the {@link Container} should be encrypted, {@code false} otherwise. * @param previouslyEncrypted {@code true} if this {@link Container} was encrypted in the past, or currently, and a * subset of blobs in it could still be encrypted. */ private void checkPreconditions(String name, ContainerStatus status, boolean encrypted, boolean previouslyEncrypted) { if (name == null || status == null) { throw new IllegalStateException("Either of required fields name=" + name + " or status=" + status + " is null"); } if (encrypted && !previouslyEncrypted) { throw new IllegalStateException("previouslyEncrypted should be true if the container is currently encrypted"); } } /** * Status of the container. {@code ACTIVE} means this container is in operational state, {@code INACTIVE} means * the container has been deactivated, and {@code DELETE_IN_PROGRESS} means blobs in this container are being * deleted and no active ACL. */ public enum ContainerStatus { ACTIVE, INACTIVE, DELETE_IN_PROGRESS } }