This is an automated email from the ASF dual-hosted git repository.

quantranhong1999 pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/james-project.git

commit 1412ee9f6c0eedbfac512dc1a2f1672084cad549
Author: Benoit TELLIER <[email protected]>
AuthorDate: Fri Sep 4 17:53:31 2026 +0200

    JAMES-4209 Pluggable mechanism to populate blob store cache
---
 .../james/blob/api/BlobStoreCacheCallback.java     | 43 ++++++++++++++++++++++
 .../blob/cassandra/cache/CachedBlobStore.java      | 11 +++++-
 .../modules/mailbox/CassandraMailboxModule.java    |  5 +++
 .../blobstore/BlobStoreCacheModulesChooser.java    |  8 ++++
 .../MetaDataFixInconsistenciesServiceTest.java     |  4 +-
 .../Pop3MetaDataFixInconsistenciesRoutesTest.java  |  4 +-
 6 files changed, 72 insertions(+), 3 deletions(-)

diff --git 
a/server/blob/blob-api/src/main/java/org/apache/james/blob/api/BlobStoreCacheCallback.java
 
b/server/blob/blob-api/src/main/java/org/apache/james/blob/api/BlobStoreCacheCallback.java
new file mode 100644
index 0000000000..01bd51ea5a
--- /dev/null
+++ 
b/server/blob/blob-api/src/main/java/org/apache/james/blob/api/BlobStoreCacheCallback.java
@@ -0,0 +1,43 @@
+/****************************************************************
+ * 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.api;
+
+import org.reactivestreams.Publisher;
+
+import reactor.core.publisher.Mono;
+
+/**
+ * Populates the blob store cache for a blob that was written through {@link 
BlobStoreDAO} rather than
+ * through {@link BlobStore}.
+ *
+ * <p>Callers needing the metadata of a blob have to go through {@link 
BlobStoreDAO}, which sits below the
+ * caching decorator and thus knows nothing of {@link 
BlobStore.StoragePolicy}. This callback gives them
+ * back the caching that a {@code SIZE_BASED} save would have performed.</p>
+ *
+ * <p>The caller vouches for the blob being worth caching: stored in the 
default bucket, and semantically
+ * what a non-{@code LOW_COST} storage policy expresses. Implementations 
remain free to decline, typically
+ * on payload size.</p>
+ */
+@FunctionalInterface
+public interface BlobStoreCacheCallback {
+    BlobStoreCacheCallback NOOP = (blobId, bytes) -> Mono.empty();
+
+    Publisher<Void> cacheIfNeeded(BlobId blobId, byte[] bytes);
+}
diff --git 
a/server/blob/blob-cassandra/src/main/java/org/apache/james/blob/cassandra/cache/CachedBlobStore.java
 
b/server/blob/blob-cassandra/src/main/java/org/apache/james/blob/cassandra/cache/CachedBlobStore.java
index b6c7bdb724..aaeaae51ee 100644
--- 
a/server/blob/blob-cassandra/src/main/java/org/apache/james/blob/cassandra/cache/CachedBlobStore.java
+++ 
b/server/blob/blob-cassandra/src/main/java/org/apache/james/blob/cassandra/cache/CachedBlobStore.java
@@ -33,6 +33,7 @@ import jakarta.inject.Named;
 import org.apache.commons.io.IOUtils;
 import org.apache.james.blob.api.BlobId;
 import org.apache.james.blob.api.BlobStore;
+import org.apache.james.blob.api.BlobStoreCacheCallback;
 import org.apache.james.blob.api.BucketName;
 import org.apache.james.blob.api.ObjectNotFoundException;
 import org.apache.james.blob.api.ObjectStoreIOException;
@@ -47,7 +48,7 @@ import com.google.common.io.ByteSource;
 
 import reactor.core.publisher.Mono;
 
-public class CachedBlobStore implements BlobStore {
+public class CachedBlobStore implements BlobStore, BlobStoreCacheCallback {
 
     private static class ReadAheadInputStream {
 
@@ -361,6 +362,14 @@ public class CachedBlobStore implements BlobStore {
         return Mono.from(cache.cache(blobId, bytes));
     }
 
+    @Override
+    public Publisher<Void> cacheIfNeeded(BlobId blobId, byte[] bytes) {
+        if (isAbleToCache(bytes)) {
+            return saveInCache(blobId, bytes);
+        }
+        return Mono.empty();
+    }
+
     private boolean isAbleToCache(BucketName bucketName, byte[] bytes, 
StoragePolicy storagePolicy) {
         return isAbleToCache(bucketName, storagePolicy) && 
isAbleToCache(bytes);
     }
diff --git 
a/server/container/guice/cassandra/src/main/java/org/apache/james/modules/mailbox/CassandraMailboxModule.java
 
b/server/container/guice/cassandra/src/main/java/org/apache/james/modules/mailbox/CassandraMailboxModule.java
index d71e7e44ad..5dd427a2a6 100644
--- 
a/server/container/guice/cassandra/src/main/java/org/apache/james/modules/mailbox/CassandraMailboxModule.java
+++ 
b/server/container/guice/cassandra/src/main/java/org/apache/james/modules/mailbox/CassandraMailboxModule.java
@@ -34,6 +34,7 @@ import 
org.apache.james.adapter.mailbox.UserRepositoryAuthenticator;
 import org.apache.james.backends.cassandra.components.CassandraDataDefinition;
 import 
org.apache.james.backends.cassandra.init.configuration.CassandraConfiguration;
 import org.apache.james.blob.api.BlobReferenceSource;
+import org.apache.james.blob.api.BlobStoreCacheCallback;
 import org.apache.james.events.EventListener;
 import org.apache.james.eventsourcing.Event;
 import org.apache.james.eventsourcing.eventstore.JsonEventSerializer;
@@ -142,6 +143,7 @@ import com.google.inject.Provides;
 import com.google.inject.Scopes;
 import com.google.inject.TypeLiteral;
 import com.google.inject.multibindings.Multibinder;
+import com.google.inject.multibindings.OptionalBinder;
 import com.google.inject.name.Names;
 
 public class CassandraMailboxModule extends AbstractModule {
@@ -163,6 +165,9 @@ public class CassandraMailboxModule extends AbstractModule {
         bind(CassandraMailboxPathV3DAO.class).in(Scopes.SINGLETON);
         bind(CassandraMailboxRecentsDAO.class).in(Scopes.SINGLETON);
         bind(CassandraMessageDAOV3.class).in(Scopes.SINGLETON);
+        // Overridden by CachedBlobStore when the blob store cache is enabled.
+        OptionalBinder.newOptionalBinder(binder(), 
BlobStoreCacheCallback.class)
+            .setDefault().toInstance(BlobStoreCacheCallback.NOOP);
         bind(CassandraMessageIdDAO.class).in(Scopes.SINGLETON);
         bind(CassandraMessageIdToImapUidDAO.class).in(Scopes.SINGLETON);
         bind(CassandraUserMailboxRightsDAO.class).in(Scopes.SINGLETON);
diff --git 
a/server/container/guice/distributed/src/main/java/org/apache/james/modules/blobstore/BlobStoreCacheModulesChooser.java
 
b/server/container/guice/distributed/src/main/java/org/apache/james/modules/blobstore/BlobStoreCacheModulesChooser.java
index 3b1c275596..0bc09c2ca5 100644
--- 
a/server/container/guice/distributed/src/main/java/org/apache/james/modules/blobstore/BlobStoreCacheModulesChooser.java
+++ 
b/server/container/guice/distributed/src/main/java/org/apache/james/modules/blobstore/BlobStoreCacheModulesChooser.java
@@ -29,6 +29,7 @@ import 
org.apache.commons.configuration2.ex.ConfigurationException;
 import org.apache.james.backends.cassandra.components.CassandraDataDefinition;
 import org.apache.james.backends.cassandra.init.configuration.InjectionNames;
 import org.apache.james.blob.api.BlobStore;
+import org.apache.james.blob.api.BlobStoreCacheCallback;
 import org.apache.james.blob.api.MetricableBlobStore;
 import org.apache.james.blob.cassandra.cache.BlobStoreCache;
 import org.apache.james.blob.cassandra.cache.CachedBlobStore;
@@ -48,6 +49,7 @@ import com.google.inject.Provides;
 import com.google.inject.Scopes;
 import com.google.inject.Singleton;
 import com.google.inject.multibindings.Multibinder;
+import com.google.inject.multibindings.OptionalBinder;
 import com.google.inject.name.Names;
 
 public class BlobStoreCacheModulesChooser {
@@ -67,6 +69,12 @@ public class BlobStoreCacheModulesChooser {
         protected void configure() {
             bind(CassandraBlobStoreCache.class).in(Scopes.SINGLETON);
             bind(BlobStoreCache.class).to(CassandraBlobStoreCache.class);
+            bind(CachedBlobStore.class).in(Scopes.SINGLETON);
+
+            // Lets writes performed through the BlobStoreDAO, which knows 
nothing of StoragePolicy,
+            // still populate the cache.
+            OptionalBinder.newOptionalBinder(binder(), 
BlobStoreCacheCallback.class)
+                .setBinding().to(CachedBlobStore.class);
 
             Multibinder.newSetBinder(binder(), CassandraDataDefinition.class, 
Names.named(InjectionNames.CACHE))
                 .addBinding()
diff --git 
a/server/protocols/protocols-pop3-distributed/src/test/java/org/apache/james/pop3server/mailbox/task/MetaDataFixInconsistenciesServiceTest.java
 
b/server/protocols/protocols-pop3-distributed/src/test/java/org/apache/james/pop3server/mailbox/task/MetaDataFixInconsistenciesServiceTest.java
index 88edbd757d..d16a5aa895 100644
--- 
a/server/protocols/protocols-pop3-distributed/src/test/java/org/apache/james/pop3server/mailbox/task/MetaDataFixInconsistenciesServiceTest.java
+++ 
b/server/protocols/protocols-pop3-distributed/src/test/java/org/apache/james/pop3server/mailbox/task/MetaDataFixInconsistenciesServiceTest.java
@@ -31,6 +31,7 @@ import 
org.apache.james.backends.cassandra.CassandraClusterExtension;
 import org.apache.james.backends.cassandra.components.CassandraDataDefinition;
 import 
org.apache.james.backends.cassandra.init.configuration.CassandraConfiguration;
 import 
org.apache.james.backends.cassandra.versions.CassandraSchemaVersionDataDefinition;
+import org.apache.james.blob.api.BlobStoreCacheCallback;
 import org.apache.james.blob.api.BlobStoreDAO;
 import org.apache.james.blob.api.PlainBlobId;
 import org.apache.james.blob.cassandra.CassandraBlobDataDefinition;
@@ -170,7 +171,8 @@ public class MetaDataFixInconsistenciesServiceTest {
                 .passthrough(),
             Mockito.mock(BlobStoreDAO.class),
             new PlainBlobId.Factory(),
-            CassandraConfiguration.DEFAULT_CONFIGURATION);
+            CassandraConfiguration.DEFAULT_CONFIGURATION,
+            BlobStoreCacheCallback.NOOP);
         testee = new MetaDataFixInconsistenciesService(imapUidDAO, 
pop3MetadataStore, cassandraMessageDAOV3);
     }
 
diff --git 
a/server/protocols/webadmin/webadmin-pop3/src/test/java/org/apache/james/pop3/webadmin/Pop3MetaDataFixInconsistenciesRoutesTest.java
 
b/server/protocols/webadmin/webadmin-pop3/src/test/java/org/apache/james/pop3/webadmin/Pop3MetaDataFixInconsistenciesRoutesTest.java
index e0e31212b6..67faea05ff 100644
--- 
a/server/protocols/webadmin/webadmin-pop3/src/test/java/org/apache/james/pop3/webadmin/Pop3MetaDataFixInconsistenciesRoutesTest.java
+++ 
b/server/protocols/webadmin/webadmin-pop3/src/test/java/org/apache/james/pop3/webadmin/Pop3MetaDataFixInconsistenciesRoutesTest.java
@@ -35,6 +35,7 @@ import 
org.apache.james.backends.cassandra.CassandraClusterExtension;
 import org.apache.james.backends.cassandra.components.CassandraDataDefinition;
 import 
org.apache.james.backends.cassandra.init.configuration.CassandraConfiguration;
 import 
org.apache.james.backends.cassandra.versions.CassandraSchemaVersionDataDefinition;
+import org.apache.james.blob.api.BlobStoreCacheCallback;
 import org.apache.james.blob.api.BlobStoreDAO;
 import org.apache.james.blob.api.PlainBlobId;
 import org.apache.james.blob.cassandra.CassandraBlobDataDefinition;
@@ -188,7 +189,8 @@ class Pop3MetaDataFixInconsistenciesRoutesTest {
                 .passthrough(),
             Mockito.mock(BlobStoreDAO.class),
             new PlainBlobId.Factory(),
-            CassandraConfiguration.DEFAULT_CONFIGURATION);
+            CassandraConfiguration.DEFAULT_CONFIGURATION,
+            BlobStoreCacheCallback.NOOP);
         MetaDataFixInconsistenciesService fixInconsistenciesService = new 
MetaDataFixInconsistenciesService(imapUidDAO, pop3MetadataStore, 
cassandraMessageDAOV3);
 
         taskManager = new MemoryTaskManager(new Hostname("foo"));


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to