You are viewing a plain text version of this content. The canonical link for it is here.
Posted to server-dev@james.apache.org by bt...@apache.org on 2020/01/13 09:55:48 UTC
[james-project] 02/10: JAMES-2921 Propose an Hybrid blobStore
This is an automated email from the ASF dual-hosted git repository.
btellier pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/james-project.git
commit 7cfa6fe9d5909b414ee61ac10e460f1024087884
Author: Benoit Tellier <bt...@linagora.com>
AuthorDate: Fri Jan 3 18:06:59 2020 +0700
JAMES-2921 Propose an Hybrid blobStore
---
.../apache/james/blob/union/HybridBlobStore.java | 182 +++++++
.../apache/james/blob/union/UnionBlobStore.java | 223 --------
.../james/blob/union/HybridBlobStoreTest.java | 516 ++++++++++++++++++
.../james/blob/union/UnionBlobStoreTest.java | 604 ---------------------
.../blobstore/BlobStoreChoosingConfiguration.java | 6 +-
.../modules/blobstore/BlobStoreChoosingModule.java | 10 +-
.../BlobStoreChoosingConfigurationTest.java | 14 +-
.../blobstore/BlobStoreChoosingModuleTest.java | 14 +-
8 files changed, 720 insertions(+), 849 deletions(-)
diff --git a/server/blob/blob-union/src/main/java/org/apache/james/blob/union/HybridBlobStore.java b/server/blob/blob-union/src/main/java/org/apache/james/blob/union/HybridBlobStore.java
new file mode 100644
index 0000000..45d1813
--- /dev/null
+++ b/server/blob/blob-union/src/main/java/org/apache/james/blob/union/HybridBlobStore.java
@@ -0,0 +1,182 @@
+/****************************************************************
+ * 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.james.blob.union;
+
+import java.io.BufferedInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+import org.apache.james.blob.api.BlobId;
+import org.apache.james.blob.api.BlobStore;
+import org.apache.james.blob.api.BucketName;
+import org.apache.james.blob.api.ObjectNotFoundException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.common.base.MoreObjects;
+import com.google.common.base.Preconditions;
+
+import reactor.core.publisher.Mono;
+
+public class HybridBlobStore implements BlobStore {
+ @FunctionalInterface
+ public interface RequireLowCost {
+ RequirePerforming lowCost(BlobStore blobStore);
+ }
+
+ @FunctionalInterface
+ public interface RequirePerforming {
+ Builder highPerformance(BlobStore blobStore);
+ }
+
+ public static class Builder {
+ private final BlobStore lowCostBlobStore;
+ private final BlobStore highPerformanceBlobStore;
+
+ Builder(BlobStore lowCostBlobStore, BlobStore highPerformanceBlobStore) {
+ this.lowCostBlobStore = lowCostBlobStore;
+ this.highPerformanceBlobStore = highPerformanceBlobStore;
+ }
+
+ public HybridBlobStore build() {
+ return new HybridBlobStore(
+ lowCostBlobStore,
+ highPerformanceBlobStore);
+ }
+ }
+
+ private static final Logger LOGGER = LoggerFactory.getLogger(HybridBlobStore.class);
+ private static final int SIZE_THRESHOLD = 32 * 1024;
+
+ public static RequireLowCost builder() {
+ return lowCost -> highPerformance -> new Builder(lowCost, highPerformance);
+ }
+
+ private final BlobStore lowCostBlobStore;
+ private final BlobStore highPerformanceBlobStore;
+
+ private HybridBlobStore(BlobStore lowCostBlobStore, BlobStore highPerformanceBlobStore) {
+ this.lowCostBlobStore = lowCostBlobStore;
+ this.highPerformanceBlobStore = highPerformanceBlobStore;
+ }
+
+ @Override
+ public Mono<BlobId> save(BucketName bucketName, byte[] data, StoragePolicy storagePolicy) {
+ return selectBlobStore(storagePolicy, Mono.just(data.length > SIZE_THRESHOLD))
+ .flatMap(blobStore -> blobStore.save(bucketName, data, storagePolicy));
+ }
+
+ @Override
+ public Mono<BlobId> save(BucketName bucketName, InputStream data, StoragePolicy storagePolicy) {
+ Preconditions.checkNotNull(data);
+
+ BufferedInputStream bufferedInputStream = new BufferedInputStream(data, SIZE_THRESHOLD + 1);
+ return selectBlobStore(storagePolicy, Mono.fromCallable(() -> isItABigStream(bufferedInputStream)))
+ .flatMap(blobStore -> blobStore.save(bucketName, bufferedInputStream, storagePolicy));
+ }
+
+ private Mono<BlobStore> selectBlobStore(StoragePolicy storagePolicy, Mono<Boolean> largeData) {
+ switch (storagePolicy) {
+ case LOW_COST:
+ return Mono.just(lowCostBlobStore);
+ case SIZE_BASED:
+ return largeData.map(isLarge -> {
+ if (isLarge) {
+ return lowCostBlobStore;
+ }
+ return highPerformanceBlobStore;
+ });
+ case HIGH_PERFORMANCE:
+ return Mono.just(highPerformanceBlobStore);
+ default:
+ throw new RuntimeException("Unknown storage policy: " + storagePolicy);
+ }
+ }
+
+ private boolean isItABigStream(InputStream bufferedData) throws IOException {
+ bufferedData.mark(0);
+ bufferedData.skip(SIZE_THRESHOLD);
+ boolean isItABigStream = bufferedData.read() != -1;
+ bufferedData.reset();
+ return isItABigStream;
+ }
+
+ @Override
+ public BucketName getDefaultBucketName() {
+ Preconditions.checkState(
+ lowCostBlobStore.getDefaultBucketName()
+ .equals(highPerformanceBlobStore.getDefaultBucketName()),
+ "lowCostBlobStore and highPerformanceBlobStore doen't have same defaultBucketName which could lead to " +
+ "unexpected result when interact with other APIs");
+
+ return lowCostBlobStore.getDefaultBucketName();
+ }
+
+ @Override
+ public Mono<byte[]> readBytes(BucketName bucketName, BlobId blobId) {
+ return Mono.defer(() -> highPerformanceBlobStore.readBytes(bucketName, blobId))
+ .onErrorResume(this::logAndReturnEmpty)
+ .switchIfEmpty(Mono.defer(() -> lowCostBlobStore.readBytes(bucketName, blobId)));
+ }
+
+ @Override
+ public InputStream read(BucketName bucketName, BlobId blobId) {
+ try {
+ return highPerformanceBlobStore.read(bucketName, blobId);
+ } catch (ObjectNotFoundException e) {
+ return lowCostBlobStore.read(bucketName, blobId);
+ } catch (Exception e) {
+ LOGGER.error("Error reading {} {} in {}, falling back to {}", bucketName, blobId, highPerformanceBlobStore, lowCostBlobStore);
+ return lowCostBlobStore.read(bucketName, blobId);
+ }
+ }
+
+ @Override
+ public Mono<Void> deleteBucket(BucketName bucketName) {
+ return Mono.defer(() -> lowCostBlobStore.deleteBucket(bucketName))
+ .and(highPerformanceBlobStore.deleteBucket(bucketName))
+ .onErrorResume(this::logDeleteFailureAndReturnEmpty);
+ }
+
+ @Override
+ public Mono<Void> delete(BucketName bucketName, BlobId blobId) {
+ return Mono.defer(() -> lowCostBlobStore.delete(bucketName, blobId))
+ .and(highPerformanceBlobStore.delete(bucketName, blobId))
+ .onErrorResume(this::logDeleteFailureAndReturnEmpty);
+ }
+
+ private <T> Mono<T> logAndReturnEmpty(Throwable throwable) {
+ LOGGER.error("error happens from current blob store, fall back to lowCost blob store", throwable);
+ return Mono.empty();
+ }
+
+ private <T> Mono<T> logDeleteFailureAndReturnEmpty(Throwable throwable) {
+ LOGGER.error("Cannot delete from either lowCost or highPerformance blob store", throwable);
+ return Mono.empty();
+ }
+
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(this)
+ .add("lowCostBlobStore", lowCostBlobStore)
+ .add("highPerformanceBlobStore", highPerformanceBlobStore)
+ .toString();
+ }
+}
diff --git a/server/blob/blob-union/src/main/java/org/apache/james/blob/union/UnionBlobStore.java b/server/blob/blob-union/src/main/java/org/apache/james/blob/union/UnionBlobStore.java
deleted file mode 100644
index 37f7c8a..0000000
--- a/server/blob/blob-union/src/main/java/org/apache/james/blob/union/UnionBlobStore.java
+++ /dev/null
@@ -1,223 +0,0 @@
-/****************************************************************
- * 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.james.blob.union;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.PushbackInputStream;
-import java.util.Optional;
-import java.util.function.Function;
-
-import org.apache.james.blob.api.BlobId;
-import org.apache.james.blob.api.BlobStore;
-import org.apache.james.blob.api.BucketName;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import com.github.fge.lambdas.Throwing;
-import com.google.common.annotations.VisibleForTesting;
-import com.google.common.base.MoreObjects;
-import com.google.common.base.Preconditions;
-
-import reactor.core.publisher.Mono;
-
-public class UnionBlobStore implements BlobStore {
-
- @FunctionalInterface
- public interface StorageOperation<T> {
- Mono<BlobId> save(BucketName bucketName, T data, StoragePolicy storagePolicy);
- }
-
- @FunctionalInterface
- public interface RequireCurrent {
- RequireLegacy current(BlobStore blobStore);
- }
-
- @FunctionalInterface
- public interface RequireLegacy {
- Builder legacy(BlobStore blobStore);
- }
-
- public static class Builder {
- private final BlobStore currentBlobStore;
- private final BlobStore legacyBlobStore;
-
- Builder(BlobStore currentBlobStore, BlobStore legacyBlobStore) {
- this.currentBlobStore = currentBlobStore;
- this.legacyBlobStore = legacyBlobStore;
- }
-
- public UnionBlobStore build() {
- return new UnionBlobStore(
- currentBlobStore,
- legacyBlobStore);
- }
- }
-
- private static final Logger LOGGER = LoggerFactory.getLogger(UnionBlobStore.class);
- private static final int UNAVAILABLE = -1;
-
- public static RequireCurrent builder() {
- return current -> legacy -> new Builder(current, legacy);
- }
-
- private final BlobStore currentBlobStore;
- private final BlobStore legacyBlobStore;
-
- private UnionBlobStore(BlobStore currentBlobStore, BlobStore legacyBlobStore) {
- this.currentBlobStore = currentBlobStore;
- this.legacyBlobStore = legacyBlobStore;
- }
-
- @Override
- public Mono<BlobId> save(BucketName bucketName, byte[] data, StoragePolicy storagePolicy) {
- try {
- return saveToCurrentFallbackIfFails(bucketName, data, storagePolicy,
- currentBlobStore::save,
- legacyBlobStore::save);
- } catch (Exception e) {
- LOGGER.error("exception directly happens while saving bytes data, fall back to legacy blob store", e);
- return legacyBlobStore.save(bucketName, data, storagePolicy);
- }
- }
-
- @Override
- public Mono<BlobId> save(BucketName bucketName, String data, StoragePolicy storagePolicy) {
- try {
- return saveToCurrentFallbackIfFails(bucketName, data, storagePolicy,
- currentBlobStore::save,
- legacyBlobStore::save);
- } catch (Exception e) {
- LOGGER.error("exception directly happens while saving String data, fall back to legacy blob store", e);
- return legacyBlobStore.save(bucketName, data, storagePolicy);
- }
- }
-
- @Override
- public BucketName getDefaultBucketName() {
- Preconditions.checkState(
- currentBlobStore.getDefaultBucketName()
- .equals(legacyBlobStore.getDefaultBucketName()),
- "currentBlobStore and legacyBlobStore doen't have same defaultBucketName which could lead to " +
- "unexpected result when interact with other APIs");
-
- return currentBlobStore.getDefaultBucketName();
- }
-
- @Override
- public Mono<BlobId> save(BucketName bucketName, InputStream data, StoragePolicy storagePolicy) {
- try {
- return saveToCurrentFallbackIfFails(bucketName, data, storagePolicy,
- currentBlobStore::save,
- legacyBlobStore::save);
- } catch (Exception e) {
- LOGGER.error("exception directly happens while saving InputStream data, fall back to legacy blob store", e);
- return legacyBlobStore.save(bucketName, data, storagePolicy);
- }
- }
-
- @Override
- public Mono<byte[]> readBytes(BucketName bucketName, BlobId blobId) {
- try {
- return readBytesFallBackIfFailsOrEmptyResult(bucketName, blobId);
- } catch (Exception e) {
- LOGGER.error("exception directly happens while readBytes, fall back to legacy blob store", e);
- return Mono.defer(() -> legacyBlobStore.readBytes(bucketName, blobId));
- }
- }
-
- @Override
- public InputStream read(BucketName bucketName, BlobId blobId) {
- try {
- return readFallBackIfEmptyResult(bucketName, blobId);
- } catch (Exception e) {
- LOGGER.error("exception directly happens while read, fall back to legacy blob store", e);
- return legacyBlobStore.read(bucketName, blobId);
- }
- }
-
- @Override
- public Mono<Void> deleteBucket(BucketName bucketName) {
- return Mono.defer(() -> currentBlobStore.deleteBucket(bucketName))
- .and(legacyBlobStore.deleteBucket(bucketName))
- .onErrorResume(this::logDeleteFailureAndReturnEmpty);
- }
-
- @Override
- public Mono<Void> delete(BucketName bucketName, BlobId blobId) {
- return Mono.defer(() -> currentBlobStore.delete(bucketName, blobId))
- .and(legacyBlobStore.delete(bucketName, blobId))
- .onErrorResume(this::logDeleteFailureAndReturnEmpty);
- }
-
- private InputStream readFallBackIfEmptyResult(BucketName bucketName, BlobId blobId) {
- return Optional.ofNullable(currentBlobStore.read(bucketName, blobId))
- .map(PushbackInputStream::new)
- .filter(Throwing.predicate(this::streamHasContent).sneakyThrow())
- .<InputStream>map(Function.identity())
- .orElseGet(() -> legacyBlobStore.read(bucketName, blobId));
- }
-
- @VisibleForTesting
- boolean streamHasContent(PushbackInputStream pushBackIS) throws IOException {
- int byteRead = pushBackIS.read();
- if (byteRead != UNAVAILABLE) {
- pushBackIS.unread(byteRead);
- return true;
- }
- return false;
- }
-
- private Mono<byte[]> readBytesFallBackIfFailsOrEmptyResult(BucketName bucketName, BlobId blobId) {
- return Mono.defer(() -> currentBlobStore.readBytes(bucketName, blobId))
- .onErrorResume(this::logAndReturnEmpty)
- .switchIfEmpty(legacyBlobStore.readBytes(bucketName, blobId));
- }
-
- private <T> Mono<BlobId> saveToCurrentFallbackIfFails(
- BucketName bucketName,
- T data,
- StoragePolicy storagePolicy,
- StorageOperation<T> currentSavingOperation,
- StorageOperation<T> fallbackSavingOperationSupplier) {
-
- return Mono.defer(() -> currentSavingOperation.save(bucketName, data, storagePolicy))
- .onErrorResume(this::logAndReturnEmpty)
- .switchIfEmpty(Mono.defer(() -> fallbackSavingOperationSupplier.save(bucketName, data, storagePolicy)));
- }
-
- private <T> Mono<T> logAndReturnEmpty(Throwable throwable) {
- LOGGER.error("error happens from current blob store, fall back to legacy blob store", throwable);
- return Mono.empty();
- }
-
- private <T> Mono<T> logDeleteFailureAndReturnEmpty(Throwable throwable) {
- LOGGER.error("Cannot delete from either legacy or current blob store", throwable);
- return Mono.empty();
- }
-
- @Override
- public String toString() {
- return MoreObjects.toStringHelper(this)
- .add("currentBlobStore", currentBlobStore)
- .add("legacyBlobStore", legacyBlobStore)
- .toString();
- }
-}
diff --git a/server/blob/blob-union/src/test/java/org/apache/james/blob/union/HybridBlobStoreTest.java b/server/blob/blob-union/src/test/java/org/apache/james/blob/union/HybridBlobStoreTest.java
new file mode 100644
index 0000000..97bb738
--- /dev/null
+++ b/server/blob/blob-union/src/test/java/org/apache/james/blob/union/HybridBlobStoreTest.java
@@ -0,0 +1,516 @@
+/****************************************************************
+ * 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.james.blob.union;
+
+import static org.apache.james.blob.api.BlobStore.StoragePolicy.LOW_COST;
+import static org.apache.james.blob.api.BlobStore.StoragePolicy.HIGH_PERFORMANCE;
+import static org.apache.james.blob.api.BlobStore.StoragePolicy.SIZE_BASED;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatCode;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+
+import java.io.ByteArrayInputStream;
+import java.io.InputStream;
+
+import org.apache.james.blob.api.BlobId;
+import org.apache.james.blob.api.BlobStore;
+import org.apache.james.blob.api.BlobStoreContract;
+import org.apache.james.blob.api.BucketName;
+import org.apache.james.blob.api.HashBlobId;
+import org.apache.james.blob.api.ObjectNotFoundException;
+import org.apache.james.blob.api.ObjectStoreException;
+import org.apache.james.blob.memory.MemoryBlobStore;
+import org.assertj.core.api.SoftAssertions;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+
+import com.github.fge.lambdas.Throwing;
+import com.google.common.base.MoreObjects;
+
+import reactor.core.publisher.Mono;
+
+class HybridBlobStoreTest implements BlobStoreContract {
+
+ private static class FailingBlobStore implements BlobStore {
+ @Override
+ public Mono<BlobId> save(BucketName bucketName, InputStream data, StoragePolicy storagePolicy) {
+ return Mono.error(new RuntimeException("broken everywhere"));
+ }
+
+ @Override
+ public Mono<BlobId> save(BucketName bucketName, byte[] data, StoragePolicy storagePolicy) {
+ return Mono.error(new RuntimeException("broken everywhere"));
+ }
+
+ @Override
+ public Mono<BlobId> save(BucketName bucketName, String data, StoragePolicy storagePolicy) {
+ return Mono.error(new RuntimeException("broken everywhere"));
+ }
+
+ @Override
+ public BucketName getDefaultBucketName() {
+ return BucketName.DEFAULT;
+ }
+
+ @Override
+ public Mono<byte[]> readBytes(BucketName bucketName, BlobId blobId) {
+ return Mono.error(new RuntimeException("broken everywhere"));
+ }
+
+ @Override
+ public InputStream read(BucketName bucketName, BlobId blobId) {
+ throw new RuntimeException("broken everywhere");
+ }
+
+ @Override
+ public Mono<Void> deleteBucket(BucketName bucketName) {
+ return Mono.error(new RuntimeException("broken everywhere"));
+ }
+
+ @Override
+ public Mono<Void> delete(BucketName bucketName, BlobId blobId) {
+ return Mono.error(new RuntimeException("broken everywhere"));
+ }
+
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(this)
+ .toString();
+ }
+ }
+
+ private static class ThrowingBlobStore implements BlobStore {
+
+ @Override
+ public Mono<BlobId> save(BucketName bucketName, byte[] data, StoragePolicy storagePolicy) {
+ throw new RuntimeException("broken everywhere");
+ }
+
+ @Override
+ public Mono<BlobId> save(BucketName bucketName, String data, StoragePolicy storagePolicy) {
+ throw new RuntimeException("broken everywhere");
+ }
+
+ @Override
+ public BucketName getDefaultBucketName() {
+ return BucketName.DEFAULT;
+ }
+
+ @Override
+ public Mono<BlobId> save(BucketName bucketName, InputStream data, StoragePolicy storagePolicy) {
+ throw new RuntimeException("broken everywhere");
+ }
+
+ @Override
+ public Mono<byte[]> readBytes(BucketName bucketName, BlobId blobId) {
+ throw new RuntimeException("broken everywhere");
+ }
+
+ @Override
+ public InputStream read(BucketName bucketName, BlobId blobId) {
+ throw new RuntimeException("broken everywhere");
+ }
+
+ @Override
+ public Mono<Void> deleteBucket(BucketName bucketName) {
+ return Mono.error(new RuntimeException("broken everywhere"));
+ }
+
+ @Override
+ public Mono<Void> delete(BucketName bucketName, BlobId blobId) {
+ return Mono.error(new RuntimeException("broken everywhere"));
+ }
+
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(this)
+ .toString();
+ }
+ }
+
+ private static final HashBlobId.Factory BLOB_ID_FACTORY = new HashBlobId.Factory();
+ private static final String STRING_CONTENT = "blob content";
+ private static final byte [] BLOB_CONTENT = STRING_CONTENT.getBytes();
+
+ private MemoryBlobStore lowCostBlobStore;
+ private MemoryBlobStore highPerformanceBlobStore;
+ private HybridBlobStore hybridBlobStore;
+
+ @BeforeEach
+ void setup() {
+ lowCostBlobStore = new MemoryBlobStore(BLOB_ID_FACTORY);
+ highPerformanceBlobStore = new MemoryBlobStore(BLOB_ID_FACTORY);
+ hybridBlobStore = HybridBlobStore.builder()
+ .lowCost(lowCostBlobStore)
+ .highPerformance(highPerformanceBlobStore)
+ .build();
+ }
+
+ @Override
+ public BlobStore testee() {
+ return hybridBlobStore;
+ }
+
+ @Override
+ public BlobId.Factory blobIdFactory() {
+ return BLOB_ID_FACTORY;
+ }
+
+ @Nested
+ class StoragePolicyTests {
+ @Test
+ void saveShouldRelyOnLowCostWhenLowCost() {
+ BlobId blobId = hybridBlobStore.save(BucketName.DEFAULT, BLOB_CONTENT, LOW_COST).block();
+
+ SoftAssertions.assertSoftly(softly -> {
+ softly.assertThat(lowCostBlobStore.read(BucketName.DEFAULT, blobId))
+ .hasSameContentAs(new ByteArrayInputStream(BLOB_CONTENT));
+ softly.assertThatThrownBy(() -> highPerformanceBlobStore.read(BucketName.DEFAULT, blobId))
+ .isInstanceOf(ObjectNotFoundException.class);
+ });
+ }
+
+ @Test
+ void saveShouldRelyOnPerformingWhenPerforming() {
+ BlobId blobId = hybridBlobStore.save(BucketName.DEFAULT, BLOB_CONTENT, HIGH_PERFORMANCE).block();
+
+ SoftAssertions.assertSoftly(softly -> {
+ softly.assertThat(highPerformanceBlobStore.read(BucketName.DEFAULT, blobId))
+ .hasSameContentAs(new ByteArrayInputStream(BLOB_CONTENT));
+ softly.assertThatThrownBy(() -> lowCostBlobStore.read(BucketName.DEFAULT, blobId))
+ .isInstanceOf(ObjectNotFoundException.class);
+ });
+ }
+
+ @Test
+ void saveShouldRelyOnPerformingWhenSizeBasedAndSmall() {
+ BlobId blobId = hybridBlobStore.save(BucketName.DEFAULT, BLOB_CONTENT, SIZE_BASED).block();
+
+ SoftAssertions.assertSoftly(softly -> {
+ softly.assertThat(highPerformanceBlobStore.read(BucketName.DEFAULT, blobId))
+ .hasSameContentAs(new ByteArrayInputStream(BLOB_CONTENT));
+ softly.assertThatThrownBy(() -> lowCostBlobStore.read(BucketName.DEFAULT, blobId))
+ .isInstanceOf(ObjectNotFoundException.class);
+ });
+ }
+
+ @Test
+ void saveShouldRelyOnLowCostWhenSizeBasedAndBig() {
+ BlobId blobId = hybridBlobStore.save(BucketName.DEFAULT, TWELVE_MEGABYTES, SIZE_BASED).block();
+
+ SoftAssertions.assertSoftly(softly -> {
+ softly.assertThat(lowCostBlobStore.read(BucketName.DEFAULT, blobId))
+ .satisfies(Throwing.consumer(inputStream -> assertThat(inputStream.read()).isGreaterThan(0)));
+ softly.assertThatThrownBy(() -> highPerformanceBlobStore.read(BucketName.DEFAULT, blobId))
+ .isInstanceOf(ObjectNotFoundException.class);
+ });
+ }
+
+ @Test
+ void saveInputStreamShouldRelyOnLowCostWhenLowCost() {
+ BlobId blobId = hybridBlobStore.save(BucketName.DEFAULT, new ByteArrayInputStream(BLOB_CONTENT), LOW_COST).block();
+
+ SoftAssertions.assertSoftly(softly -> {
+ softly.assertThat(lowCostBlobStore.read(BucketName.DEFAULT, blobId))
+ .hasSameContentAs(new ByteArrayInputStream(BLOB_CONTENT));
+ softly.assertThatThrownBy(() -> highPerformanceBlobStore.read(BucketName.DEFAULT, blobId))
+ .isInstanceOf(ObjectNotFoundException.class);
+ });
+ }
+
+ @Test
+ void saveInputStreamShouldRelyOnPerformingWhenPerforming() {
+ BlobId blobId = hybridBlobStore.save(BucketName.DEFAULT, new ByteArrayInputStream(BLOB_CONTENT), HIGH_PERFORMANCE).block();
+
+ SoftAssertions.assertSoftly(softly -> {
+ softly.assertThat(highPerformanceBlobStore.read(BucketName.DEFAULT, blobId))
+ .hasSameContentAs(new ByteArrayInputStream(BLOB_CONTENT));
+ softly.assertThatThrownBy(() -> lowCostBlobStore.read(BucketName.DEFAULT, blobId))
+ .isInstanceOf(ObjectNotFoundException.class);
+ });
+ }
+
+ @Test
+ void saveInputStreamShouldRelyOnPerformingWhenSizeBasedAndSmall() {
+ BlobId blobId = hybridBlobStore.save(BucketName.DEFAULT, new ByteArrayInputStream(BLOB_CONTENT), SIZE_BASED).block();
+
+ SoftAssertions.assertSoftly(softly -> {
+ softly.assertThat(highPerformanceBlobStore.read(BucketName.DEFAULT, blobId))
+ .hasSameContentAs(new ByteArrayInputStream(BLOB_CONTENT));
+ softly.assertThatThrownBy(() -> lowCostBlobStore.read(BucketName.DEFAULT, blobId))
+ .isInstanceOf(ObjectNotFoundException.class);
+ });
+ }
+
+ @Test
+ void saveInputStreamShouldRelyOnLowCostWhenSizeBasedAndBig() {
+ BlobId blobId = hybridBlobStore.save(BucketName.DEFAULT, new ByteArrayInputStream(TWELVE_MEGABYTES), SIZE_BASED).block();
+
+ SoftAssertions.assertSoftly(softly -> {
+ softly.assertThat(lowCostBlobStore.read(BucketName.DEFAULT, blobId))
+ .satisfies(Throwing.consumer(inputStream -> assertThat(inputStream.read()).isGreaterThan(0)));
+ softly.assertThatThrownBy(() -> highPerformanceBlobStore.read(BucketName.DEFAULT, blobId))
+ .isInstanceOf(ObjectNotFoundException.class);
+ });
+ }
+ }
+
+ @Nested
+ class LowCostSaveThrowsExceptionDirectly {
+ @Test
+ void saveShouldFailWhenException() {
+ MemoryBlobStore highPerformanceBlobStore = new MemoryBlobStore(BLOB_ID_FACTORY);
+ HybridBlobStore hybridBlobStore = HybridBlobStore.builder()
+ .lowCost(new ThrowingBlobStore())
+ .highPerformance(highPerformanceBlobStore)
+ .build();
+
+ assertThatThrownBy(() -> hybridBlobStore.save(hybridBlobStore.getDefaultBucketName(), BLOB_CONTENT, LOW_COST).block())
+ .isInstanceOf(RuntimeException.class);
+ }
+
+ @Test
+ void saveInputStreamShouldFailWhenException() {
+ MemoryBlobStore highPerformanceBlobStore = new MemoryBlobStore(BLOB_ID_FACTORY);
+ HybridBlobStore hybridBlobStore = HybridBlobStore.builder()
+ .lowCost(new ThrowingBlobStore())
+ .highPerformance(highPerformanceBlobStore)
+ .build();
+
+ assertThatThrownBy(() -> hybridBlobStore.save(hybridBlobStore.getDefaultBucketName(), new ByteArrayInputStream(BLOB_CONTENT), LOW_COST).block())
+ .isInstanceOf(RuntimeException.class);
+ }
+ }
+
+ @Nested
+ class LowCostSaveCompletesExceptionally {
+
+ @Test
+ void saveShouldFailWhenLowCostCompletedExceptionally() {
+ MemoryBlobStore highPerformanceBlobStore = new MemoryBlobStore(BLOB_ID_FACTORY);
+ HybridBlobStore hybridBlobStore = HybridBlobStore.builder()
+ .lowCost(new FailingBlobStore())
+ .highPerformance(highPerformanceBlobStore)
+ .build();
+
+ assertThatThrownBy(() -> hybridBlobStore.save(hybridBlobStore.getDefaultBucketName(), BLOB_CONTENT, LOW_COST).block())
+ .isInstanceOf(RuntimeException.class);
+ }
+
+ @Test
+ void saveInputStreamShouldFallBackToPerformingWhenLowCostCompletedExceptionally() {
+ MemoryBlobStore highPerformanceBlobStore = new MemoryBlobStore(BLOB_ID_FACTORY);
+ HybridBlobStore hybridBlobStore = HybridBlobStore.builder()
+ .lowCost(new FailingBlobStore())
+ .highPerformance(highPerformanceBlobStore)
+ .build();
+
+ assertThatThrownBy(() -> hybridBlobStore.save(hybridBlobStore.getDefaultBucketName(), new ByteArrayInputStream(BLOB_CONTENT), LOW_COST).block())
+ .isInstanceOf(RuntimeException.class);
+ }
+
+ }
+
+ @Nested
+ class LowCostReadThrowsExceptionDirectly {
+
+ @Test
+ void readShouldReturnFallbackToPerformingWhenLowCostGotException() {
+ MemoryBlobStore highPerformanceBlobStore = new MemoryBlobStore(BLOB_ID_FACTORY);
+ HybridBlobStore hybridBlobStore = HybridBlobStore.builder()
+ .lowCost(new ThrowingBlobStore())
+ .highPerformance(highPerformanceBlobStore)
+ .build();
+ BlobId blobId = highPerformanceBlobStore.save(hybridBlobStore.getDefaultBucketName(), BLOB_CONTENT, LOW_COST).block();
+
+ assertThat(hybridBlobStore.read(hybridBlobStore.getDefaultBucketName(), blobId))
+ .hasSameContentAs(new ByteArrayInputStream(BLOB_CONTENT));
+ }
+
+ @Test
+ void readBytesShouldReturnFallbackToPerformingWhenLowCostGotException() {
+ MemoryBlobStore highPerformanceBlobStore = new MemoryBlobStore(BLOB_ID_FACTORY);
+
+ HybridBlobStore hybridBlobStore = HybridBlobStore.builder()
+ .lowCost(new ThrowingBlobStore())
+ .highPerformance(highPerformanceBlobStore)
+ .build();
+ BlobId blobId = highPerformanceBlobStore.save(hybridBlobStore.getDefaultBucketName(), BLOB_CONTENT, LOW_COST).block();
+
+ assertThat(hybridBlobStore.readBytes(hybridBlobStore.getDefaultBucketName(), blobId).block())
+ .isEqualTo(BLOB_CONTENT);
+ }
+
+ }
+
+ @Nested
+ class LowCostReadCompletesExceptionally {
+
+ @Test
+ void readShouldReturnFallbackToPerformingWhenLowCostCompletedExceptionally() {
+ MemoryBlobStore highPerformanceBlobStore = new MemoryBlobStore(BLOB_ID_FACTORY);
+ HybridBlobStore hybridBlobStore = HybridBlobStore.builder()
+ .lowCost(new FailingBlobStore())
+ .highPerformance(highPerformanceBlobStore)
+ .build();
+ BlobId blobId = highPerformanceBlobStore.save(hybridBlobStore.getDefaultBucketName(), BLOB_CONTENT, LOW_COST).block();
+
+ assertThat(hybridBlobStore.read(hybridBlobStore.getDefaultBucketName(), blobId))
+ .hasSameContentAs(new ByteArrayInputStream(BLOB_CONTENT));
+ }
+
+ @Test
+ void readBytesShouldReturnFallbackToPerformingWhenLowCostCompletedExceptionally() {
+ MemoryBlobStore highPerformanceBlobStore = new MemoryBlobStore(BLOB_ID_FACTORY);
+ HybridBlobStore hybridBlobStore = HybridBlobStore.builder()
+ .lowCost(new FailingBlobStore())
+ .highPerformance(highPerformanceBlobStore)
+ .build();
+ BlobId blobId = highPerformanceBlobStore.save(hybridBlobStore.getDefaultBucketName(), BLOB_CONTENT, LOW_COST).block();
+
+ assertThat(hybridBlobStore.readBytes(hybridBlobStore.getDefaultBucketName(), blobId).block())
+ .isEqualTo(BLOB_CONTENT);
+ }
+ }
+
+ @Test
+ void readShouldReturnFromLowCostWhenAvailable() {
+ BlobId blobId = lowCostBlobStore.save(lowCostBlobStore.getDefaultBucketName(), BLOB_CONTENT, LOW_COST).block();
+
+ assertThat(hybridBlobStore.read(hybridBlobStore.getDefaultBucketName(), blobId))
+ .hasSameContentAs(new ByteArrayInputStream(BLOB_CONTENT));
+ }
+
+ @Test
+ void readShouldReturnFromPerformingWhenLowCostNotAvailable() {
+ BlobId blobId = highPerformanceBlobStore.save(hybridBlobStore.getDefaultBucketName(), BLOB_CONTENT, LOW_COST).block();
+
+ assertThat(hybridBlobStore.read(hybridBlobStore.getDefaultBucketName(), blobId))
+ .hasSameContentAs(new ByteArrayInputStream(BLOB_CONTENT));
+ }
+
+ @Test
+ void readBytesShouldReturnFromLowCostWhenAvailable() {
+ BlobId blobId = lowCostBlobStore.save(lowCostBlobStore.getDefaultBucketName(), BLOB_CONTENT, LOW_COST).block();
+
+ assertThat(hybridBlobStore.readBytes(lowCostBlobStore.getDefaultBucketName(), blobId).block())
+ .isEqualTo(BLOB_CONTENT);
+ }
+
+ @Test
+ void readBytesShouldReturnFromPerformingWhenLowCostNotAvailable() {
+ BlobId blobId = highPerformanceBlobStore.save(hybridBlobStore.getDefaultBucketName(), BLOB_CONTENT, LOW_COST).block();
+
+ assertThat(hybridBlobStore.readBytes(hybridBlobStore.getDefaultBucketName(), blobId).block())
+ .isEqualTo(BLOB_CONTENT);
+ }
+
+ @Test
+ void deleteBucketShouldDeleteBothLowCostAndPerformingBuckets() {
+ BlobId blobId1 = highPerformanceBlobStore.save(BucketName.DEFAULT, BLOB_CONTENT, LOW_COST).block();
+ BlobId blobId2 = lowCostBlobStore.save(BucketName.DEFAULT, BLOB_CONTENT, LOW_COST).block();
+
+ hybridBlobStore.deleteBucket(BucketName.DEFAULT).block();
+
+ assertThatThrownBy(() -> highPerformanceBlobStore.readBytes(BucketName.DEFAULT, blobId1).block())
+ .isInstanceOf(ObjectStoreException.class);
+ assertThatThrownBy(() -> lowCostBlobStore.readBytes(BucketName.DEFAULT, blobId2).block())
+ .isInstanceOf(ObjectStoreException.class);
+ }
+
+ @Test
+ void deleteBucketShouldDeleteLowCostBucketEvenWhenPerformingDoesNotExist() {
+ BlobId blobId = lowCostBlobStore.save(BucketName.DEFAULT, BLOB_CONTENT, LOW_COST).block();
+
+ hybridBlobStore.deleteBucket(BucketName.DEFAULT).block();
+
+ assertThatThrownBy(() -> lowCostBlobStore.readBytes(BucketName.DEFAULT, blobId).block())
+ .isInstanceOf(ObjectStoreException.class);
+ }
+
+ @Test
+ void deleteBucketShouldDeletePerformingBucketEvenWhenLowCostDoesNotExist() {
+ BlobId blobId = highPerformanceBlobStore.save(BucketName.DEFAULT, BLOB_CONTENT, LOW_COST).block();
+
+ hybridBlobStore.deleteBucket(BucketName.DEFAULT).block();
+
+ assertThatThrownBy(() -> highPerformanceBlobStore.readBytes(BucketName.DEFAULT, blobId).block())
+ .isInstanceOf(ObjectStoreException.class);
+ }
+
+ @Test
+ void deleteBucketShouldNotThrowWhenLowCostAndPerformingBucketsDoNotExist() {
+ assertThatCode(() -> hybridBlobStore.deleteBucket(BucketName.DEFAULT).block())
+ .doesNotThrowAnyException();
+ }
+
+ @Test
+ void getDefaultBucketNameShouldThrowWhenBlobStoreDontShareTheSameDefaultBucketName() {
+ lowCostBlobStore = new MemoryBlobStore(BLOB_ID_FACTORY, BucketName.of("lowCost"));
+ highPerformanceBlobStore = new MemoryBlobStore(BLOB_ID_FACTORY, BucketName.of("highPerformance"));
+ hybridBlobStore = HybridBlobStore.builder()
+ .lowCost(lowCostBlobStore)
+ .highPerformance(highPerformanceBlobStore)
+ .build();
+
+ assertThatThrownBy(() -> hybridBlobStore.getDefaultBucketName())
+ .isInstanceOf(IllegalStateException.class);
+ }
+
+ @Test
+ void deleteShouldDeleteBothLowCostAndPerformingBlob() {
+ BlobId blobId1 = hybridBlobStore.save(BucketName.DEFAULT, BLOB_CONTENT, LOW_COST).block();
+ BlobId blobId2 = hybridBlobStore.save(BucketName.DEFAULT, BLOB_CONTENT, HIGH_PERFORMANCE).block();
+
+ hybridBlobStore.delete(BucketName.DEFAULT, blobId1).block();
+
+ assertThatThrownBy(() -> highPerformanceBlobStore.readBytes(BucketName.DEFAULT, blobId1).block())
+ .isInstanceOf(ObjectStoreException.class);
+ assertThatThrownBy(() -> lowCostBlobStore.readBytes(BucketName.DEFAULT, blobId2).block())
+ .isInstanceOf(ObjectStoreException.class);
+ }
+
+ @Test
+ void deleteShouldDeleteLowCostBlobEvenWhenPerformingDoesNotExist() {
+ BlobId blobId = lowCostBlobStore.save(BucketName.DEFAULT, BLOB_CONTENT, LOW_COST).block();
+
+ hybridBlobStore.delete(BucketName.DEFAULT, blobId).block();
+
+ assertThatThrownBy(() -> lowCostBlobStore.readBytes(BucketName.DEFAULT, blobId).block())
+ .isInstanceOf(ObjectStoreException.class);
+ }
+
+ @Test
+ void deleteShouldDeletePerformingBlobEvenWhenLowCostDoesNotExist() {
+ BlobId blobId = highPerformanceBlobStore.save(BucketName.DEFAULT, BLOB_CONTENT, LOW_COST).block();
+
+ hybridBlobStore.delete(BucketName.DEFAULT, blobId).block();
+
+ assertThatThrownBy(() -> highPerformanceBlobStore.readBytes(BucketName.DEFAULT, blobId).block())
+ .isInstanceOf(ObjectStoreException.class);
+ }
+
+ @Test
+ void deleteShouldNotThrowWhenLowCostAndPerformingBlobsDoNotExist() {
+ assertThatCode(() -> hybridBlobStore.delete(BucketName.DEFAULT, blobIdFactory().randomId()).block())
+ .doesNotThrowAnyException();
+ }
+}
diff --git a/server/blob/blob-union/src/test/java/org/apache/james/blob/union/UnionBlobStoreTest.java b/server/blob/blob-union/src/test/java/org/apache/james/blob/union/UnionBlobStoreTest.java
deleted file mode 100644
index e8ccd81..0000000
--- a/server/blob/blob-union/src/test/java/org/apache/james/blob/union/UnionBlobStoreTest.java
+++ /dev/null
@@ -1,604 +0,0 @@
-/****************************************************************
- * 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.james.blob.union;
-
-import static org.apache.james.blob.api.BlobStore.StoragePolicy.LowCost;
-import static org.apache.james.blob.api.BlobStore.StoragePolicy.LowCost;
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.assertj.core.api.Assertions.assertThatCode;
-import static org.assertj.core.api.Assertions.assertThatThrownBy;
-
-import java.io.ByteArrayInputStream;
-import java.io.InputStream;
-import java.io.PushbackInputStream;
-import java.util.List;
-import java.util.function.Function;
-import java.util.stream.Stream;
-
-import org.apache.james.blob.api.BlobId;
-import org.apache.james.blob.api.BlobStore;
-import org.apache.james.blob.api.BlobStoreContract;
-import org.apache.james.blob.api.BucketName;
-import org.apache.james.blob.api.HashBlobId;
-import org.apache.james.blob.api.ObjectStoreException;
-import org.apache.james.blob.memory.MemoryBlobStore;
-import org.apache.james.util.StreamUtils;
-import org.assertj.core.api.SoftAssertions;
-import org.junit.jupiter.api.BeforeEach;
-import org.junit.jupiter.api.Nested;
-import org.junit.jupiter.api.Test;
-import org.junit.jupiter.api.TestInstance;
-import org.junit.jupiter.params.ParameterizedTest;
-import org.junit.jupiter.params.provider.Arguments;
-import org.junit.jupiter.params.provider.MethodSource;
-
-import com.google.common.base.MoreObjects;
-import com.google.common.collect.ImmutableList;
-
-import reactor.core.publisher.Mono;
-
-class UnionBlobStoreTest implements BlobStoreContract {
-
- private static class FailingBlobStore implements BlobStore {
- @Override
- public Mono<BlobId> save(BucketName bucketName, InputStream data, StoragePolicy storagePolicy) {
- return Mono.error(new RuntimeException("broken everywhere"));
- }
-
- @Override
- public Mono<BlobId> save(BucketName bucketName, byte[] data, StoragePolicy storagePolicy) {
- return Mono.error(new RuntimeException("broken everywhere"));
- }
-
- @Override
- public Mono<BlobId> save(BucketName bucketName, String data, StoragePolicy storagePolicy) {
- return Mono.error(new RuntimeException("broken everywhere"));
- }
-
- @Override
- public BucketName getDefaultBucketName() {
- return BucketName.DEFAULT;
- }
-
- @Override
- public Mono<byte[]> readBytes(BucketName bucketName, BlobId blobId) {
- return Mono.error(new RuntimeException("broken everywhere"));
- }
-
- @Override
- public InputStream read(BucketName bucketName, BlobId blobId) {
- throw new RuntimeException("broken everywhere");
- }
-
- @Override
- public Mono<Void> deleteBucket(BucketName bucketName) {
- return Mono.error(new RuntimeException("broken everywhere"));
- }
-
- @Override
- public Mono<Void> delete(BucketName bucketName, BlobId blobId) {
- return Mono.error(new RuntimeException("broken everywhere"));
- }
-
- @Override
- public String toString() {
- return MoreObjects.toStringHelper(this)
- .toString();
- }
- }
-
- private static class ThrowingBlobStore implements BlobStore {
-
- @Override
- public Mono<BlobId> save(BucketName bucketName, byte[] data, StoragePolicy storagePolicy) {
- throw new RuntimeException("broken everywhere");
- }
-
- @Override
- public Mono<BlobId> save(BucketName bucketName, String data, StoragePolicy storagePolicy) {
- throw new RuntimeException("broken everywhere");
- }
-
- @Override
- public BucketName getDefaultBucketName() {
- return BucketName.DEFAULT;
- }
-
- @Override
- public Mono<BlobId> save(BucketName bucketName, InputStream data, StoragePolicy storagePolicy) {
- throw new RuntimeException("broken everywhere");
- }
-
- @Override
- public Mono<byte[]> readBytes(BucketName bucketName, BlobId blobId) {
- throw new RuntimeException("broken everywhere");
- }
-
- @Override
- public InputStream read(BucketName bucketName, BlobId blobId) {
- throw new RuntimeException("broken everywhere");
- }
-
- @Override
- public Mono<Void> deleteBucket(BucketName bucketName) {
- return Mono.error(new RuntimeException("broken everywhere"));
- }
-
- @Override
- public Mono<Void> delete(BucketName bucketName, BlobId blobId) {
- return Mono.error(new RuntimeException("broken everywhere"));
- }
-
- @Override
- public String toString() {
- return MoreObjects.toStringHelper(this)
- .toString();
- }
- }
-
- private static final HashBlobId.Factory BLOB_ID_FACTORY = new HashBlobId.Factory();
- private static final String STRING_CONTENT = "blob content";
- private static final byte [] BLOB_CONTENT = STRING_CONTENT.getBytes();
-
- private MemoryBlobStore currentBlobStore;
- private MemoryBlobStore legacyBlobStore;
- private UnionBlobStore unionBlobStore;
-
- @BeforeEach
- void setup() {
- currentBlobStore = new MemoryBlobStore(BLOB_ID_FACTORY);
- legacyBlobStore = new MemoryBlobStore(BLOB_ID_FACTORY);
- unionBlobStore = UnionBlobStore.builder()
- .current(currentBlobStore)
- .legacy(legacyBlobStore)
- .build();
- }
-
- @Override
- public BlobStore testee() {
- return unionBlobStore;
- }
-
- @Override
- public BlobId.Factory blobIdFactory() {
- return BLOB_ID_FACTORY;
- }
-
- @Nested
- class CurrentSaveThrowsExceptionDirectly {
-
- @Test
- void saveShouldFallBackToLegacyWhenCurrentGotException() {
- MemoryBlobStore legacyBlobStore = new MemoryBlobStore(BLOB_ID_FACTORY);
- UnionBlobStore unionBlobStore = UnionBlobStore.builder()
- .current(new ThrowingBlobStore())
- .legacy(legacyBlobStore)
- .build();
- BlobId blobId = unionBlobStore.save(unionBlobStore.getDefaultBucketName(), BLOB_CONTENT, LowCost).block();
-
- SoftAssertions.assertSoftly(softly -> {
- softly.assertThat(unionBlobStore.read(unionBlobStore.getDefaultBucketName(), blobId))
- .hasSameContentAs(new ByteArrayInputStream(BLOB_CONTENT));
- softly.assertThat(legacyBlobStore.read(unionBlobStore.getDefaultBucketName(), blobId))
- .hasSameContentAs(new ByteArrayInputStream(BLOB_CONTENT));
- });
- }
-
- @Test
- void saveInputStreamShouldFallBackToLegacyWhenCurrentGotException() {
- MemoryBlobStore legacyBlobStore = new MemoryBlobStore(BLOB_ID_FACTORY);
- UnionBlobStore unionBlobStore = UnionBlobStore.builder()
- .current(new ThrowingBlobStore())
- .legacy(legacyBlobStore)
- .build();
- BlobId blobId = unionBlobStore.save(unionBlobStore.getDefaultBucketName(), new ByteArrayInputStream(BLOB_CONTENT), LowCost).block();
-
- SoftAssertions.assertSoftly(softly -> {
- softly.assertThat(unionBlobStore.read(unionBlobStore.getDefaultBucketName(), blobId))
- .hasSameContentAs(new ByteArrayInputStream(BLOB_CONTENT));
- softly.assertThat(legacyBlobStore.read(unionBlobStore.getDefaultBucketName(), blobId))
- .hasSameContentAs(new ByteArrayInputStream(BLOB_CONTENT));
- });
- }
- }
-
- @Nested
- class CurrentSaveCompletesExceptionally {
-
- @Test
- void saveShouldFallBackToLegacyWhenCurrentCompletedExceptionally() {
- MemoryBlobStore legacyBlobStore = new MemoryBlobStore(BLOB_ID_FACTORY);
- UnionBlobStore unionBlobStore = UnionBlobStore.builder()
- .current(new FailingBlobStore())
- .legacy(legacyBlobStore)
- .build();
- BlobId blobId = unionBlobStore.save(unionBlobStore.getDefaultBucketName(), BLOB_CONTENT, LowCost).block();
-
- SoftAssertions.assertSoftly(softly -> {
- softly.assertThat(unionBlobStore.read(unionBlobStore.getDefaultBucketName(), blobId))
- .hasSameContentAs(new ByteArrayInputStream(BLOB_CONTENT));
- softly.assertThat(legacyBlobStore.read(unionBlobStore.getDefaultBucketName(), blobId))
- .hasSameContentAs(new ByteArrayInputStream(BLOB_CONTENT));
- });
- }
-
- @Test
- void saveInputStreamShouldFallBackToLegacyWhenCurrentCompletedExceptionally() {
- MemoryBlobStore legacyBlobStore = new MemoryBlobStore(BLOB_ID_FACTORY);
- UnionBlobStore unionBlobStore = UnionBlobStore.builder()
- .current(new FailingBlobStore())
- .legacy(legacyBlobStore)
- .build();
- BlobId blobId = unionBlobStore.save(unionBlobStore.getDefaultBucketName(), new ByteArrayInputStream(BLOB_CONTENT), LowCost).block();
-
- SoftAssertions.assertSoftly(softly -> {
- softly.assertThat(unionBlobStore.read(unionBlobStore.getDefaultBucketName(), blobId))
- .hasSameContentAs(new ByteArrayInputStream(BLOB_CONTENT));
- softly.assertThat(legacyBlobStore.read(unionBlobStore.getDefaultBucketName(), blobId))
- .hasSameContentAs(new ByteArrayInputStream(BLOB_CONTENT));
- });
- }
-
- }
-
- @Nested
- class CurrentReadThrowsExceptionDirectly {
-
- @Test
- void readShouldReturnFallbackToLegacyWhenCurrentGotException() {
- MemoryBlobStore legacyBlobStore = new MemoryBlobStore(BLOB_ID_FACTORY);
- UnionBlobStore unionBlobStore = UnionBlobStore.builder()
- .current(new ThrowingBlobStore())
- .legacy(legacyBlobStore)
- .build();
- BlobId blobId = legacyBlobStore.save(unionBlobStore.getDefaultBucketName(), BLOB_CONTENT, LowCost).block();
-
- assertThat(unionBlobStore.read(unionBlobStore.getDefaultBucketName(), blobId))
- .hasSameContentAs(new ByteArrayInputStream(BLOB_CONTENT));
- }
-
- @Test
- void readBytesShouldReturnFallbackToLegacyWhenCurrentGotException() {
- MemoryBlobStore legacyBlobStore = new MemoryBlobStore(BLOB_ID_FACTORY);
-
- UnionBlobStore unionBlobStore = UnionBlobStore.builder()
- .current(new ThrowingBlobStore())
- .legacy(legacyBlobStore)
- .build();
- BlobId blobId = legacyBlobStore.save(unionBlobStore.getDefaultBucketName(), BLOB_CONTENT, LowCost).block();
-
- assertThat(unionBlobStore.readBytes(unionBlobStore.getDefaultBucketName(), blobId).block())
- .isEqualTo(BLOB_CONTENT);
- }
-
- }
-
- @Nested
- class CurrentReadCompletesExceptionally {
-
- @Test
- void readShouldReturnFallbackToLegacyWhenCurrentCompletedExceptionally() {
- MemoryBlobStore legacyBlobStore = new MemoryBlobStore(BLOB_ID_FACTORY);
- UnionBlobStore unionBlobStore = UnionBlobStore.builder()
- .current(new FailingBlobStore())
- .legacy(legacyBlobStore)
- .build();
- BlobId blobId = legacyBlobStore.save(unionBlobStore.getDefaultBucketName(), BLOB_CONTENT, LowCost).block();
-
- assertThat(unionBlobStore.read(unionBlobStore.getDefaultBucketName(), blobId))
- .hasSameContentAs(new ByteArrayInputStream(BLOB_CONTENT));
- }
-
- @Test
- void readBytesShouldReturnFallbackToLegacyWhenCurrentCompletedExceptionally() {
- MemoryBlobStore legacyBlobStore = new MemoryBlobStore(BLOB_ID_FACTORY);
- UnionBlobStore unionBlobStore = UnionBlobStore.builder()
- .current(new FailingBlobStore())
- .legacy(legacyBlobStore)
- .build();
- BlobId blobId = legacyBlobStore.save(unionBlobStore.getDefaultBucketName(), BLOB_CONTENT, LowCost).block();
-
- assertThat(unionBlobStore.readBytes(unionBlobStore.getDefaultBucketName(), blobId).block())
- .isEqualTo(BLOB_CONTENT);
- }
- }
-
- @TestInstance(TestInstance.Lifecycle.PER_CLASS)
- @Nested
- class CurrentAndLegacyCouldNotComplete {
-
-
- Stream<Function<UnionBlobStore, Mono<?>>> blobStoreOperationsReturnFutures() {
- return Stream.of(
- blobStore -> blobStore.save(blobStore.getDefaultBucketName(), BLOB_CONTENT, LowCost),
- blobStore -> blobStore.save(blobStore.getDefaultBucketName(), STRING_CONTENT, LowCost),
- blobStore -> blobStore.save(blobStore.getDefaultBucketName(), new ByteArrayInputStream(BLOB_CONTENT), LowCost),
- blobStore -> blobStore.readBytes(blobStore.getDefaultBucketName(), BLOB_ID_FACTORY.randomId()));
- }
-
- Stream<Function<UnionBlobStore, InputStream>> blobStoreOperationsNotReturnFutures() {
- return Stream.of(
- blobStore -> blobStore.read(blobStore.getDefaultBucketName(), BLOB_ID_FACTORY.randomId()));
- }
-
- Stream<Arguments> blobStoresCauseReturnExceptionallyFutures() {
- List<UnionBlobStore> futureThrowingUnionBlobStores = ImmutableList.of(
- UnionBlobStore.builder()
- .current(new ThrowingBlobStore())
- .legacy(new FailingBlobStore())
- .build(),
- UnionBlobStore.builder()
- .current(new FailingBlobStore())
- .legacy(new ThrowingBlobStore())
- .build(),
- UnionBlobStore.builder()
- .current(new FailingBlobStore())
- .legacy(new FailingBlobStore())
- .build());
-
- return blobStoreOperationsReturnFutures()
- .flatMap(blobStoreFunction -> futureThrowingUnionBlobStores
- .stream()
- .map(blobStore -> Arguments.of(blobStore, blobStoreFunction)));
- }
-
- Stream<Arguments> blobStoresCauseThrowExceptions() {
- UnionBlobStore throwingUnionBlobStore = UnionBlobStore.builder()
- .current(new ThrowingBlobStore())
- .legacy(new ThrowingBlobStore())
- .build();
-
- return StreamUtils.flatten(
- blobStoreOperationsReturnFutures()
- .map(blobStoreFunction -> Arguments.of(throwingUnionBlobStore, blobStoreFunction)),
- blobStoreOperationsNotReturnFutures()
- .map(blobStoreFunction -> Arguments.of(throwingUnionBlobStore, blobStoreFunction)));
- }
-
- @ParameterizedTest
- @MethodSource("blobStoresCauseThrowExceptions")
- void operationShouldThrow(UnionBlobStore blobStoreThrowsException,
- Function<UnionBlobStore, Mono<?>> blobStoreOperation) {
- assertThatThrownBy(() -> blobStoreOperation.apply(blobStoreThrowsException).block())
- .isInstanceOf(RuntimeException.class);
- }
-
- @ParameterizedTest
- @MethodSource("blobStoresCauseReturnExceptionallyFutures")
- void operationShouldReturnExceptionallyFuture(UnionBlobStore blobStoreReturnsExceptionallyFuture,
- Function<UnionBlobStore, Mono<?>> blobStoreOperation) {
- Mono<?> mono = blobStoreOperation.apply(blobStoreReturnsExceptionallyFuture);
- assertThatThrownBy(mono::block).isInstanceOf(RuntimeException.class);
- }
- }
-
- @Test
- void readShouldReturnFromCurrentWhenAvailable() {
- BlobId blobId = currentBlobStore.save(currentBlobStore.getDefaultBucketName(), BLOB_CONTENT, LowCost).block();
-
- assertThat(unionBlobStore.read(unionBlobStore.getDefaultBucketName(), blobId))
- .hasSameContentAs(new ByteArrayInputStream(BLOB_CONTENT));
- }
-
- @Test
- void readShouldReturnFromLegacyWhenCurrentNotAvailable() {
- BlobId blobId = legacyBlobStore.save(unionBlobStore.getDefaultBucketName(), BLOB_CONTENT, LowCost).block();
-
- assertThat(unionBlobStore.read(unionBlobStore.getDefaultBucketName(), blobId))
- .hasSameContentAs(new ByteArrayInputStream(BLOB_CONTENT));
- }
-
- @Test
- void readBytesShouldReturnFromCurrentWhenAvailable() {
- BlobId blobId = currentBlobStore.save(currentBlobStore.getDefaultBucketName(), BLOB_CONTENT, LowCost).block();
-
- assertThat(unionBlobStore.readBytes(currentBlobStore.getDefaultBucketName(), blobId).block())
- .isEqualTo(BLOB_CONTENT);
- }
-
- @Test
- void readBytesShouldReturnFromLegacyWhenCurrentNotAvailable() {
- BlobId blobId = legacyBlobStore.save(unionBlobStore.getDefaultBucketName(), BLOB_CONTENT, LowCost).block();
-
- assertThat(unionBlobStore.readBytes(unionBlobStore.getDefaultBucketName(), blobId).block())
- .isEqualTo(BLOB_CONTENT);
- }
-
- @Test
- void saveShouldWriteToCurrent() {
- BlobId blobId = unionBlobStore.save(unionBlobStore.getDefaultBucketName(), BLOB_CONTENT, LowCost).block();
-
- assertThat(currentBlobStore.readBytes(currentBlobStore.getDefaultBucketName(), blobId).block())
- .isEqualTo(BLOB_CONTENT);
- }
-
- @Test
- void saveShouldNotWriteToLegacy() {
- BlobId blobId = unionBlobStore.save(unionBlobStore.getDefaultBucketName(), BLOB_CONTENT, LowCost).block();
-
- assertThatThrownBy(() -> legacyBlobStore.readBytes(legacyBlobStore.getDefaultBucketName(), blobId).block())
- .isInstanceOf(ObjectStoreException.class);
- }
-
- @Test
- void saveStringShouldWriteToCurrent() {
- BlobId blobId = unionBlobStore.save(unionBlobStore.getDefaultBucketName(), STRING_CONTENT, LowCost).block();
-
- assertThat(currentBlobStore.readBytes(currentBlobStore.getDefaultBucketName(), blobId).block())
- .isEqualTo(BLOB_CONTENT);
- }
-
- @Test
- void saveStringShouldNotWriteToLegacy() {
- BlobId blobId = unionBlobStore.save(unionBlobStore.getDefaultBucketName(), STRING_CONTENT, LowCost).block();
-
- assertThatThrownBy(() -> legacyBlobStore.readBytes(legacyBlobStore.getDefaultBucketName(), blobId).block())
- .isInstanceOf(ObjectStoreException.class);
- }
-
- @Test
- void saveInputStreamShouldWriteToCurrent() {
- BlobId blobId = unionBlobStore.save(unionBlobStore.getDefaultBucketName(), new ByteArrayInputStream(BLOB_CONTENT), LowCost).block();
-
- assertThat(currentBlobStore.readBytes(currentBlobStore.getDefaultBucketName(), blobId).block())
- .isEqualTo(BLOB_CONTENT);
- }
-
- @Test
- void saveInputStreamShouldNotWriteToLegacy() {
- BlobId blobId = unionBlobStore.save(unionBlobStore.getDefaultBucketName(), new ByteArrayInputStream(BLOB_CONTENT), LowCost).block();
-
- assertThatThrownBy(() -> legacyBlobStore.readBytes(legacyBlobStore.getDefaultBucketName(), blobId).block())
- .isInstanceOf(ObjectStoreException.class);
- }
-
- @Test
- void streamHasContentShouldReturnTrueWhenStreamHasContent() throws Exception {
- PushbackInputStream pushBackIS = new PushbackInputStream(new ByteArrayInputStream(BLOB_CONTENT));
-
- assertThat(unionBlobStore.streamHasContent(pushBackIS))
- .isTrue();
- }
-
- @Test
- void streamHasContentShouldReturnFalseWhenStreamHasNoContent() throws Exception {
- PushbackInputStream pushBackIS = new PushbackInputStream(new ByteArrayInputStream(new byte[0]));
-
- assertThat(unionBlobStore.streamHasContent(pushBackIS))
- .isFalse();
- }
-
- @Test
- void streamHasContentShouldNotThrowWhenStreamHasNoContent() {
- PushbackInputStream pushBackIS = new PushbackInputStream(new ByteArrayInputStream(new byte[0]));
-
- assertThatCode(() -> unionBlobStore.streamHasContent(pushBackIS))
- .doesNotThrowAnyException();
- }
-
- @Test
- void streamHasContentShouldNotDrainPushBackStreamContent() throws Exception {
- PushbackInputStream pushBackIS = new PushbackInputStream(new ByteArrayInputStream(BLOB_CONTENT));
- unionBlobStore.streamHasContent(pushBackIS);
-
- assertThat(pushBackIS)
- .hasSameContentAs(new ByteArrayInputStream(BLOB_CONTENT));
- }
-
- @Test
- void streamHasContentShouldKeepStreamEmptyWhenStreamIsEmpty() throws Exception {
- PushbackInputStream pushBackIS = new PushbackInputStream(new ByteArrayInputStream(new byte[0]));
- unionBlobStore.streamHasContent(pushBackIS);
-
- assertThat(pushBackIS)
- .hasSameContentAs(new ByteArrayInputStream(new byte[0]));
- }
-
- @Test
- void deleteBucketShouldDeleteBothCurrentAndLegacyBuckets() {
- BlobId legacyBlobId = legacyBlobStore.save(BucketName.DEFAULT, BLOB_CONTENT, LowCost).block();
- BlobId currentBlobId = currentBlobStore.save(BucketName.DEFAULT, BLOB_CONTENT, LowCost).block();
-
- unionBlobStore.deleteBucket(BucketName.DEFAULT).block();
-
- assertThatThrownBy(() -> legacyBlobStore.readBytes(BucketName.DEFAULT, legacyBlobId).block())
- .isInstanceOf(ObjectStoreException.class);
- assertThatThrownBy(() -> currentBlobStore.readBytes(BucketName.DEFAULT, currentBlobId).block())
- .isInstanceOf(ObjectStoreException.class);
- }
-
- @Test
- void deleteBucketShouldDeleteCurrentBucketEvenWhenLegacyDoesNotExist() {
- BlobId currentBlobId = currentBlobStore.save(BucketName.DEFAULT, BLOB_CONTENT, LowCost).block();
-
- unionBlobStore.deleteBucket(BucketName.DEFAULT).block();
-
- assertThatThrownBy(() -> currentBlobStore.readBytes(BucketName.DEFAULT, currentBlobId).block())
- .isInstanceOf(ObjectStoreException.class);
- }
-
- @Test
- void deleteBucketShouldDeleteLegacyBucketEvenWhenCurrentDoesNotExist() {
- BlobId legacyBlobId = legacyBlobStore.save(BucketName.DEFAULT, BLOB_CONTENT, LowCost).block();
-
- unionBlobStore.deleteBucket(BucketName.DEFAULT).block();
-
- assertThatThrownBy(() -> legacyBlobStore.readBytes(BucketName.DEFAULT, legacyBlobId).block())
- .isInstanceOf(ObjectStoreException.class);
- }
-
- @Test
- void deleteBucketShouldNotThrowWhenCurrentAndLegacyBucketsDoNotExist() {
- assertThatCode(() -> unionBlobStore.deleteBucket(BucketName.DEFAULT).block())
- .doesNotThrowAnyException();
- }
-
- @Test
- void getDefaultBucketNameShouldThrowWhenBlobStoreDontShareTheSameDefaultBucketName() {
- currentBlobStore = new MemoryBlobStore(BLOB_ID_FACTORY, BucketName.of("current"));
- legacyBlobStore = new MemoryBlobStore(BLOB_ID_FACTORY, BucketName.of("legacy"));
- unionBlobStore = UnionBlobStore.builder()
- .current(currentBlobStore)
- .legacy(legacyBlobStore)
- .build();
-
- assertThatThrownBy(() -> unionBlobStore.getDefaultBucketName())
- .isInstanceOf(IllegalStateException.class);
- }
-
- @Test
- void deleteShouldDeleteBothCurrentAndLegacyBlob() {
- BlobId legacyBlobId = legacyBlobStore.save(BucketName.DEFAULT, BLOB_CONTENT, LowCost).block();
- BlobId currentBlobId = currentBlobStore.save(BucketName.DEFAULT, BLOB_CONTENT, LowCost).block();
-
- unionBlobStore.delete(BucketName.DEFAULT, currentBlobId).block();
-
- assertThatThrownBy(() -> legacyBlobStore.readBytes(BucketName.DEFAULT, legacyBlobId).block())
- .isInstanceOf(ObjectStoreException.class);
- assertThatThrownBy(() -> currentBlobStore.readBytes(BucketName.DEFAULT, currentBlobId).block())
- .isInstanceOf(ObjectStoreException.class);
- }
-
- @Test
- void deleteShouldDeleteCurrentBlobEvenWhenLegacyDoesNotExist() {
- BlobId currentBlobId = currentBlobStore.save(BucketName.DEFAULT, BLOB_CONTENT, LowCost).block();
-
- unionBlobStore.delete(BucketName.DEFAULT, currentBlobId).block();
-
- assertThatThrownBy(() -> currentBlobStore.readBytes(BucketName.DEFAULT, currentBlobId).block())
- .isInstanceOf(ObjectStoreException.class);
- }
-
- @Test
- void deleteShouldDeleteLegacyBlobEvenWhenCurrentDoesNotExist() {
- BlobId legacyBlobId = legacyBlobStore.save(BucketName.DEFAULT, BLOB_CONTENT, LowCost).block();
-
- unionBlobStore.delete(BucketName.DEFAULT, legacyBlobId).block();
-
- assertThatThrownBy(() -> legacyBlobStore.readBytes(BucketName.DEFAULT, legacyBlobId).block())
- .isInstanceOf(ObjectStoreException.class);
- }
-
- @Test
- void deleteShouldNotThrowWhenCurrentAndLegacyBlobsDoNotExist() {
- assertThatCode(() -> unionBlobStore.delete(BucketName.DEFAULT, blobIdFactory().randomId()).block())
- .doesNotThrowAnyException();
- }
-}
diff --git a/server/container/guice/cassandra-rabbitmq-guice/src/main/java/org/apache/james/modules/blobstore/BlobStoreChoosingConfiguration.java b/server/container/guice/cassandra-rabbitmq-guice/src/main/java/org/apache/james/modules/blobstore/BlobStoreChoosingConfiguration.java
index bde669c..84bc7fb 100644
--- a/server/container/guice/cassandra-rabbitmq-guice/src/main/java/org/apache/james/modules/blobstore/BlobStoreChoosingConfiguration.java
+++ b/server/container/guice/cassandra-rabbitmq-guice/src/main/java/org/apache/james/modules/blobstore/BlobStoreChoosingConfiguration.java
@@ -34,7 +34,7 @@ public class BlobStoreChoosingConfiguration {
public enum BlobStoreImplName {
CASSANDRA("cassandra"),
OBJECTSTORAGE("objectstorage"),
- UNION("union");
+ HYBRID("hybrid");
static String supportedImplNames() {
return Stream.of(BlobStoreImplName.values())
@@ -82,8 +82,8 @@ public class BlobStoreChoosingConfiguration {
return new BlobStoreChoosingConfiguration(BlobStoreImplName.OBJECTSTORAGE);
}
- public static BlobStoreChoosingConfiguration union() {
- return new BlobStoreChoosingConfiguration(BlobStoreImplName.UNION);
+ public static BlobStoreChoosingConfiguration hybrid() {
+ return new BlobStoreChoosingConfiguration(BlobStoreImplName.HYBRID);
}
private final BlobStoreImplName implementation;
diff --git a/server/container/guice/cassandra-rabbitmq-guice/src/main/java/org/apache/james/modules/blobstore/BlobStoreChoosingModule.java b/server/container/guice/cassandra-rabbitmq-guice/src/main/java/org/apache/james/modules/blobstore/BlobStoreChoosingModule.java
index 7011d1e..2ab354d 100644
--- a/server/container/guice/cassandra-rabbitmq-guice/src/main/java/org/apache/james/modules/blobstore/BlobStoreChoosingModule.java
+++ b/server/container/guice/cassandra-rabbitmq-guice/src/main/java/org/apache/james/modules/blobstore/BlobStoreChoosingModule.java
@@ -33,7 +33,7 @@ import org.apache.james.blob.api.MetricableBlobStore;
import org.apache.james.blob.cassandra.CassandraBlobModule;
import org.apache.james.blob.cassandra.CassandraBlobStore;
import org.apache.james.blob.objectstorage.ObjectStorageBlobStore;
-import org.apache.james.blob.union.UnionBlobStore;
+import org.apache.james.blob.union.HybridBlobStore;
import org.apache.james.modules.mailbox.ConfigurationComponent;
import org.apache.james.modules.objectstorage.ObjectStorageDependenciesModule;
import org.apache.james.utils.PropertiesProvider;
@@ -82,10 +82,10 @@ public class BlobStoreChoosingModule extends AbstractModule {
return swiftBlobStoreProvider.get();
case CASSANDRA:
return cassandraBlobStoreProvider.get();
- case UNION:
- return UnionBlobStore.builder()
- .current(swiftBlobStoreProvider.get())
- .legacy(cassandraBlobStoreProvider.get())
+ case HYBRID:
+ return HybridBlobStore.builder()
+ .lowCost(swiftBlobStoreProvider.get())
+ .highPerformance(cassandraBlobStoreProvider.get())
.build();
default:
throw new RuntimeException(String.format("can not get the right blobstore provider with configuration %s",
diff --git a/server/container/guice/cassandra-rabbitmq-guice/src/test/java/org/apache/james/modules/blobstore/BlobStoreChoosingConfigurationTest.java b/server/container/guice/cassandra-rabbitmq-guice/src/test/java/org/apache/james/modules/blobstore/BlobStoreChoosingConfigurationTest.java
index a5e1eb1..2ef718d 100644
--- a/server/container/guice/cassandra-rabbitmq-guice/src/test/java/org/apache/james/modules/blobstore/BlobStoreChoosingConfigurationTest.java
+++ b/server/container/guice/cassandra-rabbitmq-guice/src/test/java/org/apache/james/modules/blobstore/BlobStoreChoosingConfigurationTest.java
@@ -31,7 +31,7 @@ class BlobStoreChoosingConfigurationTest {
private static final String OBJECT_STORAGE = "objectstorage";
private static final String CASSANDRA = "cassandra";
- private static final String UNION = "union";
+ private static final String HYBRID = "hybrid";
@Test
void shouldMatchBeanContract() {
@@ -45,7 +45,7 @@ class BlobStoreChoosingConfigurationTest {
assertThatThrownBy(() -> BlobStoreChoosingConfiguration.from(configuration))
.isInstanceOf(IllegalStateException.class)
- .hasMessage("implementation property is missing please use one of supported values in: cassandra, objectstorage, union");
+ .hasMessage("implementation property is missing please use one of supported values in: cassandra, objectstorage, hybrid");
}
@Test
@@ -55,7 +55,7 @@ class BlobStoreChoosingConfigurationTest {
assertThatThrownBy(() -> BlobStoreChoosingConfiguration.from(configuration))
.isInstanceOf(IllegalStateException.class)
- .hasMessage("implementation property is missing please use one of supported values in: cassandra, objectstorage, union");
+ .hasMessage("implementation property is missing please use one of supported values in: cassandra, objectstorage, hybrid");
}
@Test
@@ -65,7 +65,7 @@ class BlobStoreChoosingConfigurationTest {
assertThatThrownBy(() -> BlobStoreChoosingConfiguration.from(configuration))
.isInstanceOf(IllegalStateException.class)
- .hasMessage("implementation property is missing please use one of supported values in: cassandra, objectstorage, union");
+ .hasMessage("implementation property is missing please use one of supported values in: cassandra, objectstorage, hybrid");
}
@Test
@@ -75,7 +75,7 @@ class BlobStoreChoosingConfigurationTest {
assertThatThrownBy(() -> BlobStoreChoosingConfiguration.from(configuration))
.isInstanceOf(IllegalArgumentException.class)
- .hasMessage("un_supported is not a valid name of BlobStores, please use one of supported values in: cassandra, objectstorage, union");
+ .hasMessage("un_supported is not a valid name of BlobStores, please use one of supported values in: cassandra, objectstorage, hybrid");
}
@Test
@@ -93,13 +93,13 @@ class BlobStoreChoosingConfigurationTest {
@Test
void fromShouldReturnConfigurationWhenBlobStoreImplIsUnion() {
PropertiesConfiguration configuration = new PropertiesConfiguration();
- configuration.addProperty("implementation", UNION);
+ configuration.addProperty("implementation", HYBRID);
assertThat(
BlobStoreChoosingConfiguration.from(configuration)
.getImplementation()
.getName())
- .isEqualTo(UNION);
+ .isEqualTo(HYBRID);
}
@Test
diff --git a/server/container/guice/cassandra-rabbitmq-guice/src/test/java/org/apache/james/modules/blobstore/BlobStoreChoosingModuleTest.java b/server/container/guice/cassandra-rabbitmq-guice/src/test/java/org/apache/james/modules/blobstore/BlobStoreChoosingModuleTest.java
index ce4354b..4e04a5b 100644
--- a/server/container/guice/cassandra-rabbitmq-guice/src/test/java/org/apache/james/modules/blobstore/BlobStoreChoosingModuleTest.java
+++ b/server/container/guice/cassandra-rabbitmq-guice/src/test/java/org/apache/james/modules/blobstore/BlobStoreChoosingModuleTest.java
@@ -27,7 +27,7 @@ import org.apache.commons.configuration2.PropertiesConfiguration;
import org.apache.james.FakePropertiesProvider;
import org.apache.james.blob.cassandra.CassandraBlobStore;
import org.apache.james.blob.objectstorage.ObjectStorageBlobStore;
-import org.apache.james.blob.union.UnionBlobStore;
+import org.apache.james.blob.union.HybridBlobStore;
import org.apache.james.modules.blobstore.BlobStoreChoosingConfiguration.BlobStoreImplName;
import org.apache.james.modules.mailbox.ConfigurationComponent;
import org.junit.jupiter.api.Test;
@@ -105,16 +105,16 @@ class BlobStoreChoosingModuleTest {
}
@Test
- void provideChoosingConfigurationShouldReturnUnionConfigurationWhenConfigurationImplIsUnion() throws Exception {
+ void provideChoosingConfigurationShouldReturnHybridConfigurationWhenConfigurationImplIsHybrid() throws Exception {
BlobStoreChoosingModule module = new BlobStoreChoosingModule();
PropertiesConfiguration configuration = new PropertiesConfiguration();
- configuration.addProperty("implementation", BlobStoreImplName.UNION.getName());
+ configuration.addProperty("implementation", BlobStoreImplName.HYBRID.getName());
FakePropertiesProvider propertyProvider = FakePropertiesProvider.builder()
.register(ConfigurationComponent.NAME, configuration)
.build();
assertThat(module.provideChoosingConfiguration(propertyProvider))
- .isEqualTo(BlobStoreChoosingConfiguration.union());
+ .isEqualTo(BlobStoreChoosingConfiguration.hybrid());
}
@Test
@@ -149,11 +149,11 @@ class BlobStoreChoosingModuleTest {
}
@Test
- void provideBlobStoreShouldReturnUnionBlobStoreWhenUnionConfigured() {
+ void provideBlobStoreShouldReturnHybridBlobStoreWhenHybridConfigured() {
BlobStoreChoosingModule module = new BlobStoreChoosingModule();
- assertThat(module.provideBlobStore(BlobStoreChoosingConfiguration.union(),
+ assertThat(module.provideBlobStore(BlobStoreChoosingConfiguration.hybrid(),
CASSANDRA_BLOBSTORE_PROVIDER, OBJECT_STORAGE_BLOBSTORE_PROVIDER))
- .isInstanceOf(UnionBlobStore.class);
+ .isInstanceOf(HybridBlobStore.class);
}
}
\ No newline at end of file
---------------------------------------------------------------------
To unsubscribe, e-mail: server-dev-unsubscribe@james.apache.org
For additional commands, e-mail: server-dev-help@james.apache.org