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}