Index: oak-api/src/main/java/org/apache/jackrabbit/oak/api/blob/BlobAccessProvider.java =================================================================== --- oak-api/src/main/java/org/apache/jackrabbit/oak/api/blob/BlobAccessProvider.java (nonexistent) +++ oak-api/src/main/java/org/apache/jackrabbit/oak/api/blob/BlobAccessProvider.java (working copy) @@ -0,0 +1,128 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF 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. + */ +package org.apache.jackrabbit.oak.api.blob; + +import java.net.URI; + +import org.apache.jackrabbit.oak.api.Blob; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.osgi.annotation.versioning.ProviderType; + +/** + * Extension interface applied to a class that indicates that the class + * implements the direct upload and direct download feature for {@link Blob}s. + */ +@ProviderType +public interface BlobAccessProvider { + + /** + * Begin a transaction to perform a direct blob upload to a storage + * location. This method will throw {@link IllegalArgumentException} if no + * valid upload can be arranged with the arguments specified. E.g. the max + * upload size specified divided by the number of URIs requested indicates + * the minimum size of each upload. If that size exceeds the maximum upload + * size supported by the service provider, {@link IllegalArgumentException} + * is thrown. + *
+ * Each service provider has specific limitations with regard to maximum + * upload sizes, maximum overall blob sizes, numbers of URIs in multi-part + * uploads, etc. which can lead to {@link IllegalArgumentException} being + * thrown. You should consult the documentation for your specific service + * provider for details. + *
+ * Beyond service provider limitations, the implementation may also choose + * to enforce its own limitations and may throw this exception based on + * those limitations. Configuration may also be used to set limitations so + * this exception may be thrown when configuration parameters are exceeded. + * + * @param maxUploadSizeInBytes the largest size of the blob to be + * uploaded, in bytes, based on the caller's best guess. If the + * actual size of the file to be uploaded is known, that value + * should be used. + * @param maxNumberOfURIs the maximum number of URIs the client is able to + * accept. If the client does not support multi-part uploading, this + * value should be 1. Note that the implementing class is not + * required to support multi-part uploading so it may return only a + * single upload URI regardless of the value passed in for this + * parameter. If the client is able to accept any number of URIs, a + * value of -1 may be passed in to indicate that the implementation + * is free to return as many URIs as it desires. + * @return A {@link BlobUpload} referencing this direct upload, or + * {@code null} if the underlying implementation doesn't support + * direct uploading. + * @throws IllegalArgumentException if {@code maxUploadSizeInBytes} is not + * a positive value, or if {@code maxNumberOfURIs} is not either a + * positive value or -1, or if the upload cannot be completed as + * requested, due to a mismatch between the request parameters and + * the capabilities of the service provider or the implementation. + */ + @Nullable + BlobUpload initiateBlobUpload(long maxUploadSizeInBytes, + int maxNumberOfURIs) + throws IllegalArgumentException; + + /** + * Complete a transaction for uploading a blob to a storage location via + * direct blob upload. + *
+ * This requires an {@code uploadToken} that can be obtained from the + * returned {@link BlobUpload} from a previous call to {@link + * #initiateBlobUpload(long, int)}. This token is required to complete + * the transaction for an upload to be valid and complete. The token + * includes encoded data about the transaction and may include a signature + * that will be verified by the implementation. + * + * @param uploadToken the upload token from a {@link BlobUpload} object + * returned from a previous call to {@link + * #initiateBlobUpload(long, int)}. + * @return The {@link Blob} that was created, or {@code null} if the object + * could not be created. + * @throws IllegalArgumentException if the {@code uploadToken} is null, + * empty, or cannot be parsed or is otherwise invalid, e.g. if the + * included signature does not match. + */ + @Nullable + Blob completeBlobUpload(@NotNull String uploadToken) + throws IllegalArgumentException; + + /** + * Obtain a download URI for a {@link Blob). This is usually a signed URI + * that can be used to directly download the blob corresponding to the + * provided {@link Blob}. + *
+ * A caller must specify a {@link BlobDownloadOptions} instance. The + * implementation will attempt to apply the specified {@code + * downloadOptions} to the subsequent download. For example, if the caller + * knows that the URI refers to a specific type of content, the caller can + * specify that content type by setting it in the {@code downloadOptions}. + * The caller may also use a default instance obtained via {@link + * BlobDownloadOptions#DEFAULT} in which case the caller is indicating that + * the default behavior of the service provider is acceptable. + * + * @param blob The {@link Blob} to be downloaded. + * @param downloadOptions A {@link BlobDownloadOptions} instance that + * specifies any download options to be used for the download URI. + * @return A URI to download the blob directly or {@code null} if the blob + * cannot be downloaded directly. + */ + @Nullable + URI getDownloadURI(@NotNull Blob blob, + @NotNull BlobDownloadOptions downloadOptions); +} Property changes on: oak-api/src/main/java/org/apache/jackrabbit/oak/api/blob/BlobAccessProvider.java ___________________________________________________________________ Added: svn:eol-style ## -0,0 +1 ## +native \ No newline at end of property Index: oak-api/src/main/java/org/apache/jackrabbit/oak/api/blob/BlobDownloadOptions.java =================================================================== --- oak-api/src/main/java/org/apache/jackrabbit/oak/api/blob/BlobDownloadOptions.java (nonexistent) +++ oak-api/src/main/java/org/apache/jackrabbit/oak/api/blob/BlobDownloadOptions.java (working copy) @@ -0,0 +1,118 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF 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. + */ +package org.apache.jackrabbit.oak.api.blob; + +import org.apache.jackrabbit.oak.api.Blob; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.osgi.annotation.versioning.ProviderType; + +/** + * Download options to be provided to a call to {@link + * BlobAccessProvider#getDownloadURI(Blob, BlobDownloadOptions)}. + *
+ * This object is an internal corollary to {@code + * org.apache.jackrabbit.api.binary.BinaryDownloadOptions}. + */ +@ProviderType +public class BlobDownloadOptions { + private static final String DISPOSITION_TYPE_INLINE = "inline"; + + private final String mediaType; + private final String characterEncoding; + private final String fileName; + private final String dispositionType; + + public static final BlobDownloadOptions DEFAULT = new BlobDownloadOptions(); + + private BlobDownloadOptions() { + this(null, null, null, DISPOSITION_TYPE_INLINE); + } + + /** + * Creates new download options. + * + * @param mediaType the internet media type for the blob. + * @param characterEncoding the character encoding for the blob. + * @param fileName the file name for the blob. + * @param dispositionType the disposition type. + */ + public BlobDownloadOptions(@Nullable String mediaType, + @Nullable String characterEncoding, + @Nullable String fileName, + @NotNull String dispositionType) { + if (dispositionType == null) { + throw new NullPointerException("dispositionType must not be null"); + } + this.mediaType = mediaType; + this.characterEncoding = characterEncoding; + this.fileName = fileName; + this.dispositionType = dispositionType; + } + + /** + * Returns the internet media type that should be assumed for the blob + * that is to be downloaded. This value should be a valid {@code + * jcr:mimeType}. + * + * @return The internet media type, or {@code null} if no type has been + * specified. + */ + @Nullable + public String getMediaType() { + return mediaType; + } + + /** + * Returns the character encoding that should be assumed for the blob that + * is to be downloaded. This value should be a valid {@code jcr:encoding}. + * + * @return The character encoding, or {@code null} if no encoding has been + * specified. + */ + @Nullable + public String getCharacterEncoding() { + return characterEncoding; + } + + /** + * Returns the filename that should be assumed for the blob that is to be + * downloaded. + * + * @return The file name, or {@code null} if no file name has been + * specified. + */ + @Nullable + public String getFileName() { + return fileName; + } + + /** + * Returns the disposition type that should be assumed for the binary that + * is to be downloaded. The default value of this setting is "inline". + * + * @return The disposition type. + * @see RFC + * 6266, Section 4.2 + */ + @NotNull + public String getDispositionType() { + return dispositionType; + } +} Property changes on: oak-api/src/main/java/org/apache/jackrabbit/oak/api/blob/BlobDownloadOptions.java ___________________________________________________________________ Added: svn:eol-style ## -0,0 +1 ## +native \ No newline at end of property Index: oak-api/src/main/java/org/apache/jackrabbit/oak/api/blob/BlobUpload.java =================================================================== --- oak-api/src/main/java/org/apache/jackrabbit/oak/api/blob/BlobUpload.java (nonexistent) +++ oak-api/src/main/java/org/apache/jackrabbit/oak/api/blob/BlobUpload.java (working copy) @@ -0,0 +1,123 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF 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. + */ +package org.apache.jackrabbit.oak.api.blob; + +import java.net.URI; +import java.util.Collection; + +import org.jetbrains.annotations.NotNull; +import org.osgi.annotation.versioning.ProviderType; + +/** + * An object containing information needed to complete a direct binary upload. + * A client wishing to perform a direct binary upload first calls {@link + * BlobAccessProvider#initiateBlobUpload(long, int)} which returns an + * instance of this type. The client then uses the provided URIs via {@link + * #getUploadURIs()} to upload the binary. After this is done, the client + * calls {@link BlobAccessProvider#completeBlobUpload(String)} passing + * in the upload token string obtained from this object via {@link + * #getUploadToken()}. + */ +@ProviderType +public interface BlobUpload { + /** + * Returns a token that uniquely identifies this upload. This token must be + * provided in a subsequent call to {@link + * BlobAccessProvider#completeBlobUpload(String)}. + * + * @return The unique upload token for this upload. + */ + @NotNull + String getUploadToken(); + + /** + * The smallest part size the client can send in a multi-part upload (not + * counting the final part). There is no guarantee made that splitting the + * binary into parts of this size can complete the full upload without + * exhausting the full supply of uploadURIs. In other words, clients + * wishing to perform a multi-part upload MUST split the binary into parts + * of at least this size, in bytes, but clients may need to use larger part + * sizes in order to upload the entire binary with the number of URIs + * provided. + *
+ * Note that some backends have lower-bound limits for the size of a part of + * a multi-part upload. You should consult the documentation for your + * specific service provider for details. + * + * @return The smallest part size acceptable, for multi-part uploads. + */ + long getMinPartSize(); + + /** + * The largest part size the client can send in a multi-part upload. The + * API guarantees that splitting the file into parts of this size will allow + * the client to complete the multi-part upload without requiring more URIs + * than those provided, SO LONG AS the file being uploaded is not larger + * than the {@code maxUploadSizeInBytes} specified in the original call. + *
+ * A smaller upload part size may also be used so long as it exceeds the + * value returned by {@link #getMinPartSize()}. Such smaller values may be + * more desirable for clients who wish to tune uploads to match network + * conditions; however, the only guarantee offered by the API is that using + * parts of the size returned by this method will work without using more + * URIs than those available in the Collection returned by + * {@link #getUploadURIs()}. + *
+ * If a client calls {@link + * BlobAccessProvider#initiateBlobUpload(long, int)} with a value of + * {@code maxUploadSizeInBytes} that ends up being smaller than the actual + * size of the binary to be uploaded, it may not be possible to complete the + * upload with the URIs provided. The client should initiate the + * transaction again with the correct size. + *
+ * Note that some backends have upper-bound limits for the size of a part of + * a multi-part upload. You should consult the documentation for your + * specific service provider for details. + * + * @return The largest part size acceptable, for multi-part uploads. + */ + long getMaxPartSize(); + + /** + * Returns a collection of direct-writable upload URIs for uploading a file, + * or file part in the case of multi-part uploading. This collection may + * contain only a single URI in the following cases: + * - If the client requested 1 as the value of {@code maxNumberOfURIs} in a + * call to {@link + * BlobAccessProvider#initiateBlobUpload(long, int)}, OR + * - If the implementing data store does not support multi-part uploading, + * OR + * - If the client-specified value for {@code maxUploadSizeInBytes} in a + * call to {@link + * BlobAccessProvider#initiateBlobUpload(long, int)} is less than + * or equal to the minimum supported size of a multi-part upload part. + *
+ * If the collection contains only a single URI the client should treat that + * URI as a direct single-put upload and write the entire binary to the + * single URI. Otherwise the client may choose to consume any number of + * URIs in the collection, up to the entire collection of URIs provided. + *
+ * Note that ordering matters; URIs should be consumed in sequence and
+ * should not be skipped.
+ *
+ * @return ordered collection of URIs to be consumed in sequence.
+ */
+ @NotNull
+ Collection