/* * SPDX-License-Identifier: Apache-2.0 * * The OpenSearch Contributors require contributions made to * this file be licensed under the Apache-2.0 license or a * compatible open source license. */ /* * Licensed to Elasticsearch under one or more contributor * license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright * ownership. Elasticsearch licenses this file to you 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. */ /* * Modifications Copyright OpenSearch Contributors. See * GitHub history for details. */ package org.opensearch.common.blobstore; import org.opensearch.action.ActionListener; import java.io.IOException; import java.io.InputStream; import java.nio.file.FileAlreadyExistsException; import java.nio.file.NoSuchFileException; import java.util.ArrayList; import java.util.Comparator; import java.util.List; import java.util.Map; /** * An interface for managing a repository of blob entries, where each blob entry is just a named group of bytes. * * @opensearch.internal */ public interface BlobContainer { /** * Gets the {@link BlobPath} that defines the implementation specific paths to where the blobs are contained. * * @return the BlobPath where the blobs are contained */ BlobPath path(); /** * Tests whether a blob with the given blob name exists in the container. * * @param blobName * The name of the blob whose existence is to be determined. * @return {@code true} if a blob exists in the {@link BlobContainer} with the given name, and {@code false} otherwise. */ boolean blobExists(String blobName) throws IOException; /** * Creates a new {@link InputStream} for the given blob name. * * @param blobName * The name of the blob to get an {@link InputStream} for. * @return The {@code InputStream} to read the blob. * @throws NoSuchFileException if the blob does not exist * @throws IOException if the blob can not be read. */ InputStream readBlob(String blobName) throws IOException; /** * Creates a new {@link InputStream} that can be used to read the given blob starting from * a specific {@code position} in the blob. The {@code length} is an indication of the * number of bytes that are expected to be read from the {@link InputStream}. * * @param blobName The name of the blob to get an {@link InputStream} for. * @param position The position in the blob where the next byte will be read. * @param length An indication of the number of bytes to be read. * @return The {@code InputStream} to read the blob. * @throws NoSuchFileException if the blob does not exist * @throws IOException if the blob can not be read. */ InputStream readBlob(String blobName, long position, long length) throws IOException; /** * Provides a hint to clients for a suitable length to use with {@link BlobContainer#readBlob(String, long, long)}. * * Some blob containers have nontrivial costs attached to each readBlob call, so it is a good idea for consumers to speculatively * request more data than they need right now and to re-use this stream for future needs if possible. * * Also, some blob containers return streams that are expensive to close before the stream has been fully consumed, and the cost may * depend on the length of the data that was left unconsumed. For these containers it's best to bound the cost of a partial read by * bounding the length of the data requested. * * @return a hint to consumers regarding the length of data to request if there is a good chance that future reads can be satisfied from * the same stream. * */ default long readBlobPreferredLength() { throw new UnsupportedOperationException(); // NORELEASE } /** * Reads blob content from the input stream and writes it to the container in a new blob with the given name. * This method assumes the container does not already contain a blob of the same blobName. If a blob by the * same name already exists, the operation will fail and an {@link IOException} will be thrown. * * @param blobName * The name of the blob to write the contents of the input stream to. * @param inputStream * The input stream from which to retrieve the bytes to write to the blob. * @param blobSize * The size of the blob to be written, in bytes. It is implementation dependent whether * this value is used in writing the blob to the repository. * @param failIfAlreadyExists * whether to throw a FileAlreadyExistsException if the given blob already exists * @throws FileAlreadyExistsException if failIfAlreadyExists is true and a blob by the same name already exists * @throws IOException if the input stream could not be read, or the target blob could not be written to. */ void writeBlob(String blobName, InputStream inputStream, long blobSize, boolean failIfAlreadyExists) throws IOException; /** * Reads blob content from the input stream and writes it to the container in a new blob with the given name, * using an atomic write operation if the implementation supports it. * * This method assumes the container does not already contain a blob of the same blobName. If a blob by the * same name already exists, the operation will fail and an {@link IOException} will be thrown. * * @param blobName * The name of the blob to write the contents of the input stream to. * @param inputStream * The input stream from which to retrieve the bytes to write to the blob. * @param blobSize * The size of the blob to be written, in bytes. It is implementation dependent whether * this value is used in writing the blob to the repository. * @param failIfAlreadyExists * whether to throw a FileAlreadyExistsException if the given blob already exists * @throws FileAlreadyExistsException if failIfAlreadyExists is true and a blob by the same name already exists * @throws IOException if the input stream could not be read, or the target blob could not be written to. */ void writeBlobAtomic(String blobName, InputStream inputStream, long blobSize, boolean failIfAlreadyExists) throws IOException; /** * Deletes this container and all its contents from the repository. * * @return delete result * @throws IOException on failure */ DeleteResult delete() throws IOException; /** * Deletes the blobs with given names. This method will not throw an exception * when one or multiple of the given blobs don't exist and simply ignore this case. * * @param blobNames The names of the blob to delete. * @throws IOException if a subset of blob exists but could not be deleted. */ void deleteBlobsIgnoringIfNotExists(List blobNames) throws IOException; /** * Lists all blobs in the container. * * @return A map of all the blobs in the container. The keys in the map are the names of the blobs and * the values are {@link BlobMetadata}, containing basic information about each blob. * @throws IOException if there were any failures in reading from the blob container. */ Map listBlobs() throws IOException; /** * Lists all child containers under this container. A child container is defined as a container whose {@link #path()} method returns * a path that has this containers {@link #path()} return as its prefix and has one more path element than the current * container's path. * * @return Map of name of the child container to child container * @throws IOException on failure to list child containers */ Map children() throws IOException; /** * Lists all blobs in the container that match the specified prefix. * * @param blobNamePrefix * The prefix to match against blob names in the container. * @return A map of the matching blobs in the container. The keys in the map are the names of the blobs * and the values are {@link BlobMetadata}, containing basic information about each blob. * @throws IOException if there were any failures in reading from the blob container. */ Map listBlobsByPrefix(String blobNamePrefix) throws IOException; /** * The type representing sort order of blob names */ enum BlobNameSortOrder { LEXICOGRAPHIC(Comparator.comparing(BlobMetadata::name)); final Comparator comparator; public Comparator comparator() { return comparator; } BlobNameSortOrder(final Comparator comparator) { this.comparator = comparator; } } /** * Lists all blobs in the container that match the specified prefix in lexicographic order * @param blobNamePrefix The prefix to match against blob names in the container. * @param limit Limits the result size to min(limit, number of keys) * @param blobNameSortOrder Comparator to sort keys with * @param listener the listener to be notified upon request completion */ default void listBlobsByPrefixInSortedOrder( String blobNamePrefix, int limit, BlobNameSortOrder blobNameSortOrder, ActionListener> listener ) { if (limit < 0) { throw new IllegalArgumentException("limit should not be a negative value"); } try { List blobNames = new ArrayList<>(listBlobsByPrefix(blobNamePrefix).values()); blobNames.sort(blobNameSortOrder.comparator()); listener.onResponse(blobNames.subList(0, Math.min(blobNames.size(), limit))); } catch (Exception e) { listener.onFailure(e); } } }