001/*
002 * (C) Copyright 2019 Nuxeo (http://nuxeo.com/) and others.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *     http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 *
016 * Contributors:
017 *     Florent Guillaume
018 */
019package org.nuxeo.ecm.core.blob;
020
021import java.io.IOException;
022import java.io.InputStream;
023import java.nio.file.Path;
024import java.util.NoSuchElementException;
025import java.util.Objects;
026import java.util.Optional;
027
028import javax.validation.constraints.NotNull;
029
030import org.nuxeo.ecm.core.blob.binary.BinaryGarbageCollector;
031
032/**
033 * Interface for basic access to storage of a Blob (read/write/copy/delete).
034 * <p>
035 * A blob is identified by a key, and holds a stream of bytes. It may have some associated metadata (filename, content
036 * type).
037 * <p>
038 * A blob store may have versioning. When this is the case, the write method will return a key that includes a version
039 * number. This same complete key must subsequently be provided to the read or delete methods. With versioning, two
040 * writes may be requested with the same key in the blob context, and both will succeed because they return keys that
041 * include a version number to distinguish them.
042 *
043 * @since 11.1
044 */
045public interface BlobStore {
046
047    /** Name used for debugging / tracing. */
048    String getName();
049
050    /**
051     * Whether this blob store has versioning.
052     * <p>
053     * With versioning, two writes may use the same key. The returned keys will include a different version number to
054     * distinguish the writes.
055     */
056    boolean hasVersioning();
057
058    /**
059     * Gets the key strategy used by the store.
060     */
061    KeyStrategy getKeyStrategy();
062
063    /**
064     * Writes a blob.
065     *
066     * @param blobContext the blob context
067     * @return the blob key
068     */
069    String writeBlob(BlobContext blobContext) throws IOException;
070
071    /**
072     * Writes a blob.
073     * <p>
074     * Note that the returned key may be different than the one requested by the {@link BlobWriteContext}, if the blob
075     * store needs additional version info to retrieve it.
076     *
077     * @param blobWriteContext the context of the blob write, including the blob
078     * @return the key to use to read this blob in the future
079     */
080    String writeBlob(BlobWriteContext blobWriteContext) throws IOException;
081
082    /**
083     * Checks if blob copy/move from another blob store to this one can be done efficiently.
084     *
085     * @param sourceStore the source store
086     * @return {@code true} if the copy/move can be done efficiently
087     */
088    boolean copyBlobIsOptimized(BlobStore sourceStore);
089
090    /**
091     * Writes a file based on a key, as a copy/move from a source in another blob store.
092     * <p>
093     * If the copy/move is requested to be atomic, then the destination file is created atomically. In case of atomic
094     * move, in some stores the destination will be created atomically but the source will only be deleted afterwards.
095     *
096     * @param key the key
097     * @param sourceStore the source store
098     * @param sourceKey the source key
099     * @param atomicMove {@code true} for an atomic move, {@code false} for a regular copy
100     * @return {@code true} if the file was found in the source store, {@code false} if it was not found
101     */
102    boolean copyBlob(String key, BlobStore sourceStore, String sourceKey, boolean atomicMove) throws IOException;
103
104    /**
105     * A class representing an unknown value, a missing value, or a present (non-null) value.
106     */
107    class OptionalOrUnknown<T> {
108
109        private static final OptionalOrUnknown<?> UNKNOWN = new OptionalOrUnknown<>();
110
111        private static final OptionalOrUnknown<?> MISSING = new OptionalOrUnknown<>(null);
112
113        private Optional<T> value;
114
115        // constructor for unknown value
116        private OptionalOrUnknown() {
117            this.value = null; // NOSONAR
118        }
119
120        // constructor for missing or present value
121        private OptionalOrUnknown(T value) {
122            this.value = value == null ? Optional.empty() : Optional.of(value);
123        }
124
125        /**
126         * Returns an unknown instance.
127         */
128        public static <T> OptionalOrUnknown<T> unknown() {
129            @SuppressWarnings("unchecked")
130            OptionalOrUnknown<T> t = (OptionalOrUnknown<T>) UNKNOWN;
131            return t;
132        }
133
134        /**
135         * Returns a missing instance.
136         */
137        public static <T> OptionalOrUnknown<T> missing() {
138            @SuppressWarnings("unchecked")
139            OptionalOrUnknown<T> t = (OptionalOrUnknown<T>) MISSING;
140            return t;
141        }
142
143        /**
144         * Returns an instance of a present, non-null value.
145         */
146        public static <T> OptionalOrUnknown<T> of(T value) {
147            return new OptionalOrUnknown<>(Objects.requireNonNull(value));
148        }
149
150        /**
151         * Returns {@code true} if the value is unknown, otherwise {@code false}.
152         */
153        public boolean isUnknown() {
154            return value == null; // NOSONAR
155        }
156
157        /**
158         * Returns {@code true} if the value is known (missing or present), otherwise {@code false}.
159         */
160        public boolean isKnown() {
161            return value != null; // NOSONAR
162        }
163
164        /**
165         * Returns {@code true} if the value is present, otherwise {@code false}.
166         */
167        public boolean isPresent() {
168            return value != null && value.isPresent(); // NOSONAR
169        }
170
171        /**
172         * Returns {@code true} if the value is missing, otherwise {@code false}.
173         */
174        public boolean isMissing() {
175            return value != null && !value.isPresent(); // NOSONAR
176        }
177
178        /**
179         * Returns the value if it is present, otherwise throws {@code NoSuchElementException}.
180         *
181         * @throws NoSuchElementException if there is no value present
182         */
183        @NotNull
184        public T get() {
185            if (value == null || !value.isPresent()) { // NOSONAR
186                throw new NoSuchElementException("No value known and present");
187            }
188            return value.get();
189        }
190
191        @Override
192        public boolean equals(Object obj) {
193            if (this == obj) {
194                return true;
195            }
196            if (!(obj instanceof OptionalOrUnknown)) {
197                return false;
198            }
199            OptionalOrUnknown<?> other = (OptionalOrUnknown<?>) obj;
200            return Objects.equals(value, other.value);
201        }
202
203        @Override
204        public int hashCode() {
205            return Objects.hashCode(value);
206        }
207    }
208
209    /**
210     * Gets an already-existing file containing the blob for the given key, if present.
211     * <p>
212     * Note that this method is best-effort, it may return unknown even though the blob exists in the store, it's just
213     * that it's not handily available locally in a file.
214     *
215     * @param key the blob key
216     * @return the file containing the blob, or empty if the blob cannot be found, or unknown if no file is available
217     *         locally
218     */
219    @NotNull
220    OptionalOrUnknown<Path> getFile(String key);
221
222    /**
223     * Gets the stream of the blob for the given key, if present.
224     * <p>
225     * Note that this method is best-effort, it may return unknown even though the blob exists in the store, it's just
226     * that it's not efficient to return it as a stream.
227     *
228     * @param key the blob key
229     * @return the blob stream, or empty if the blob cannot be found, or unknown if no stream is efficiently available
230     */
231    @NotNull
232    OptionalOrUnknown<InputStream> getStream(String key) throws IOException;
233
234    /**
235     * Reads a blob based on its key into the given file.
236     *
237     * @param key the blob key
238     * @param dest the file to use to store the fetched data
239     * @return {@code true} if the file was fetched, {@code false} if the file was not found
240     */
241    boolean readBlob(String key, Path dest) throws IOException;
242
243    /**
244     * Sets properties on a blob.
245     *
246     * @param blobUpdateContext the blob update context
247     */
248    void writeBlobProperties(BlobUpdateContext blobUpdateContext) throws IOException;
249
250    /**
251     * Deletes a blob.
252     *
253     * @param blobContext the blob context
254     */
255    void deleteBlob(BlobContext blobContext);
256
257    /**
258     * Deletes a blob based on a key. No error occurs if the blob does not exist.
259     * <p>
260     * This method does not throw {@link IOException}, but may log an error message.
261     *
262     * @param key the blob key
263     */
264    void deleteBlob(String key);
265
266    /**
267     * Returns the binary garbage collector (GC).
268     * <p>
269     * Several calls to this method will return the same GC, so that its status can be monitored using
270     * {@link BinaryGarbageCollector#isInProgress}.
271     *
272     * @return the binary GC
273     */
274    BinaryGarbageCollector getBinaryGarbageCollector();
275
276    /**
277     * If this blob store wraps another one, returns it, otherwise returns this.
278     *
279     * @return the lowest-level blob store
280     */
281    BlobStore unwrap();
282
283}