This is an automated email from the ASF dual-hosted git repository.
rcordier pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/james-project.git
The following commit(s) were added to refs/heads/master by this push:
new b2b99615ca Improve IMAP partial body fetch (#2740)
b2b99615ca is described below
commit b2b99615ca2e705e199ffb91d1c99acc332fdf21
Author: Benoit TELLIER <[email protected]>
AuthorDate: Wed Jun 18 06:53:49 2025 +0200
Improve IMAP partial body fetch (#2740)
---
docs/modules/servers/partials/configure/imap.adoc | 17 +++
.../james/imap/processor/DefaultProcessor.java | 5 +-
.../james/imap/processor/fetch/FetchProcessor.java | 123 +++++++++++++++++++--
.../main/DefaultImapProcessorFactory.java | 26 ++++-
.../james/modules/protocols/IMAPServerModule.java | 6 +
.../imapserver/netty/IMAPHealthCheckTest.java | 4 +-
.../james/imapserver/netty/IMAPServerTest.java | 30 +++--
src/site/xdoc/server/config-imap4.xml | 17 +++
8 files changed, 208 insertions(+), 20 deletions(-)
diff --git a/docs/modules/servers/partials/configure/imap.adoc
b/docs/modules/servers/partials/configure/imap.adoc
index b6df670256..0d3084c121 100644
--- a/docs/modules/servers/partials/configure/imap.adoc
+++ b/docs/modules/servers/partials/configure/imap.adoc
@@ -211,3 +211,20 @@ James extensions.
== Mail user agents auto-configuration
Check this example on
link:https://github.com/apache/james-project/tree/master/examples/imap-autoconf[Mail
user agents auto-configuration].
+
+== Local cache for partial Fetch
+
+Because some clients uses partial fetch in order to emulate retriable download
of individual body parts
+we offer a way for James to cache in the IMAP session the latest partially
fetched message. This is done using
+a weak reference, a total size dedicated to the cache as well as a time to
leave cache cleanup, all of this
+being configurable.
+
+Example:
+
+....
+<imapserver>
+ <partialBodyFetchCacheEnabled>true</partialBodyFetchCacheEnabled>
+ <partialBodyFetchCacheDuration>2min</partialBodyFetchCacheDuration>
+ <partialBodyFetchCacheSize>500 MiB</partialBodyFetchCacheSize>
+</imapserver>
+....
\ No newline at end of file
diff --git
a/protocols/imap/src/main/java/org/apache/james/imap/processor/DefaultProcessor.java
b/protocols/imap/src/main/java/org/apache/james/imap/processor/DefaultProcessor.java
index 2ce7f03091..794954c85d 100644
---
a/protocols/imap/src/main/java/org/apache/james/imap/processor/DefaultProcessor.java
+++
b/protocols/imap/src/main/java/org/apache/james/imap/processor/DefaultProcessor.java
@@ -57,7 +57,8 @@ public class DefaultProcessor implements ImapProcessor {
QuotaManager
quotaManager,
QuotaRootResolver
quotaRootResolver,
MailboxCounterCorrector
mailboxCounterCorrector,
- MetricFactory
metricFactory) {
+ MetricFactory
metricFactory,
+
FetchProcessor.LocalCacheConfiguration localCacheConfiguration) {
PathConverter.Factory pathConverterFactory =
PathConverter.Factory.DEFAULT;
ImmutableList.Builder<AbstractProcessor> builder =
ImmutableList.builder();
@@ -91,7 +92,7 @@ public class DefaultProcessor implements ImapProcessor {
builder.add(new SearchProcessor(mailboxManager, statusResponseFactory,
metricFactory));
builder.add(new SelectProcessor(mailboxManager, eventBus,
statusResponseFactory, metricFactory, pathConverterFactory,
mailboxCounterCorrector));
builder.add(new NamespaceProcessor(mailboxManager,
statusResponseFactory, metricFactory, new NamespaceSupplier.Default()));
- builder.add(new FetchProcessor(mailboxManager, statusResponseFactory,
metricFactory));
+ builder.add(new FetchProcessor(mailboxManager, statusResponseFactory,
metricFactory, localCacheConfiguration));
builder.add(new StartTLSProcessor(statusResponseFactory));
builder.add(new UnselectProcessor(mailboxManager,
statusResponseFactory, metricFactory));
builder.add(new CompressProcessor(statusResponseFactory));
diff --git
a/protocols/imap/src/main/java/org/apache/james/imap/processor/fetch/FetchProcessor.java
b/protocols/imap/src/main/java/org/apache/james/imap/processor/fetch/FetchProcessor.java
index 5a19054e0b..0ab9930ebb 100644
---
a/protocols/imap/src/main/java/org/apache/james/imap/processor/fetch/FetchProcessor.java
+++
b/protocols/imap/src/main/java/org/apache/james/imap/processor/fetch/FetchProcessor.java
@@ -23,16 +23,23 @@ import static
org.apache.james.mailbox.MessageManager.MailboxMetaData.RecentMode
import static org.apache.james.mailbox.model.FetchGroup.FULL_CONTENT;
import static org.apache.james.util.ReactorUtils.logOnError;
+import java.lang.ref.Reference;
+import java.lang.ref.ReferenceQueue;
+import java.lang.ref.SoftReference;
+import java.time.Duration;
+import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.EnumSet;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.AtomicReference;
import jakarta.inject.Inject;
+import org.apache.commons.configuration2.Configuration;
import org.apache.james.core.Username;
import org.apache.james.imap.api.ImapConstants;
import org.apache.james.imap.api.display.HumanReadableText;
@@ -55,12 +62,15 @@ import org.apache.james.mailbox.MessageUid;
import org.apache.james.mailbox.exception.MailboxException;
import org.apache.james.mailbox.exception.MessageRangeException;
import org.apache.james.mailbox.model.FetchGroup;
+import org.apache.james.mailbox.model.MailboxId;
import org.apache.james.mailbox.model.MessageRange;
import org.apache.james.mailbox.model.MessageResult;
import org.apache.james.metrics.api.MetricFactory;
import org.apache.james.util.AuditTrail;
+import org.apache.james.util.DurationParser;
import org.apache.james.util.MDCBuilder;
import org.apache.james.util.ReactorUtils;
+import org.apache.james.util.SizeFormat;
import org.reactivestreams.Subscriber;
import org.reactivestreams.Subscription;
import org.slf4j.Logger;
@@ -77,6 +87,88 @@ import reactor.core.publisher.Mono;
import reactor.core.publisher.Sinks;
public class FetchProcessor extends AbstractMailboxProcessor<FetchRequest> {
+ public static final String CACHE_KEY = "message-saved-for-later";
+
+ public record LocalCacheConfiguration(Duration ttl, long sizeInBytes,
boolean enabled) {
+ public static final LocalCacheConfiguration DEFAULT = new
LocalCacheConfiguration(Duration.ofMinutes(2), 500 * 1024 * 1024, false);
+
+ public static LocalCacheConfiguration from(Configuration
configuration) {
+ return new LocalCacheConfiguration(
+
DurationParser.parse(configuration.getString("partialBodyFetchCacheDuration",
"2m"), ChronoUnit.MINUTES),
+
SizeFormat.parseAsByteCount(configuration.getString("partialBodyFetchCacheSize",
"500 MiB")),
+ configuration.getBoolean("partialBodyFetchCacheEnabled",
false));
+ }
+ }
+
+ record LocalCacheEntry(FetchGroup fetchGroup, MessageResult message) {
+
+ }
+
+ interface LocalMessageCache {
+ Optional<MessageResult> lookupInCache(MailboxId mailboxId, MessageUid
uid, FetchGroup fetchGroup);
+
+ void saveForLater(MessageResult messageResult, FetchGroup fetchGroup);
+ }
+
+ public static class DefaultLocalMessageCache implements LocalMessageCache {
+ public static final ReferenceQueue<LocalCacheEntry> REFERENCE_QUEUE =
new ReferenceQueue<>();
+ public static final AtomicLong TOTAL_SIZE = new AtomicLong(0);
+ private final ImapSession imapSession;
+ private final LocalCacheConfiguration configuration;
+
+ DefaultLocalMessageCache(ImapSession imapSession,
LocalCacheConfiguration configuration) {
+ this.imapSession = imapSession;
+ this.configuration = configuration;
+ }
+
+ @Override
+ public Optional<MessageResult> lookupInCache(MailboxId mailboxId,
MessageUid uid, FetchGroup fetchGroup) {
+ if (!configuration.enabled()) {
+ return Optional.empty();
+ }
+ return retrieveCachedEntry()
+ .filter(entry -> entry.fetchGroup().equals(fetchGroup))
+ .filter(entry ->
entry.message().getMailboxId().equals(mailboxId))
+ .filter(entry -> entry.message().getUid().equals(uid))
+ .map(LocalCacheEntry::message);
+ }
+
+ private Optional<LocalCacheEntry> retrieveCachedEntry() {
+ Optional maybeMessage =
Optional.ofNullable(imapSession.getAttribute(CACHE_KEY));
+
+ Optional<LocalCacheEntry> cacheContent =
maybeMessage.filter(SoftReference.class::isInstance)
+ .flatMap(ref -> {
+ SoftReference softReference = (SoftReference) ref;
+ return Optional.ofNullable(softReference.get());
+ })
+ .filter(LocalCacheEntry.class::isInstance)
+ .map(LocalCacheEntry.class::cast);
+ return cacheContent;
+ }
+
+ @Override
+ public void saveForLater(MessageResult messageResult, FetchGroup
fetchGroup) {
+ if (!configuration.enabled()) {
+ return;
+ }
+ if (TOTAL_SIZE.get() <= configuration.sizeInBytes()) {
+ SoftReference<LocalCacheEntry> ref = new SoftReference<>(new
LocalCacheEntry(fetchGroup, messageResult), REFERENCE_QUEUE);
+ TOTAL_SIZE.addAndGet(messageResult.getSize());
+ imapSession.setAttribute(CACHE_KEY, ref);
+ imapSession.schedule(() -> {
+ retrieveCachedEntry().ifPresent(entry ->
TOTAL_SIZE.addAndGet(-1 * entry.message().getSize()));
+ imapSession.setAttribute(CACHE_KEY, null);
+ }, configuration.ttl());
+
+ Reference<? extends LocalCacheEntry> referenceFromQueue;
+ while ((referenceFromQueue = REFERENCE_QUEUE.poll()) != null) {
+ Optional.ofNullable(referenceFromQueue.get())
+ .ifPresent(entry -> TOTAL_SIZE.addAndGet(-1 *
entry.message().getSize()));
+ }
+ }
+ }
+ }
+
static class FetchSubscriber implements Subscriber<FetchResponse> {
private final AtomicReference<Subscription> subscription = new
AtomicReference<>();
private final Sinks.One<Void> sink = Sinks.one();
@@ -142,10 +234,13 @@ public class FetchProcessor extends
AbstractMailboxProcessor<FetchRequest> {
private static final Logger LOGGER =
LoggerFactory.getLogger(FetchProcessor.class);
+ private final LocalCacheConfiguration localCacheConfiguration;
+
@Inject
public FetchProcessor(MailboxManager mailboxManager, StatusResponseFactory
factory,
- MetricFactory metricFactory) {
+ MetricFactory metricFactory, LocalCacheConfiguration
localCacheConfiguration) {
super(FetchRequest.class, mailboxManager, factory, metricFactory);
+ this.localCacheConfiguration = localCacheConfiguration;
}
@Override
@@ -253,12 +348,26 @@ public class FetchProcessor extends
AbstractMailboxProcessor<FetchRequest> {
.then();
} else {
FetchSubscriber fetchSubscriber = new FetchSubscriber(imapSession,
responder);
- Flux.fromIterable(consolidate(selected, ranges, fetch))
- .doOnNext(range -> auditTrail(mailbox, mailboxSession,
resultToFetch, range))
- .concatMap(range ->
Flux.from(mailbox.getMessagesReactive(range, resultToFetch, mailboxSession)))
- .filter(ids -> !fetch.contains(Item.MODSEQ) ||
ids.getModSeq().asLong() > fetch.getChangedSince())
- .concatMap(result -> toResponse(mailbox, fetch,
mailboxSession, selected, result))
- .subscribe(fetchSubscriber);
+
+ boolean singleMessage = ranges.size() == 1 &&
ranges.getFirst().getUidFrom().equals(ranges.getFirst().getUidTo());
+ boolean shouldCache =
fetch.getBodyElements().stream().anyMatch(bodyFetchElement ->
bodyFetchElement.getNumberOfOctets() != null);
+ if (singleMessage && shouldCache) {
+ DefaultLocalMessageCache localMessageCache = new
DefaultLocalMessageCache(imapSession, localCacheConfiguration);
+ localMessageCache.lookupInCache(mailbox.getId(),
ranges.getFirst().getUidFrom(), resultToFetch)
+ .map(Flux::just)
+ .orElseGet(() ->
Flux.from(mailbox.getMessagesReactive(ranges.getFirst(), resultToFetch,
mailboxSession))
+ .doOnNext(message ->
localMessageCache.saveForLater(message, resultToFetch)))
+ .filter(ids -> !fetch.contains(Item.MODSEQ) ||
ids.getModSeq().asLong() > fetch.getChangedSince())
+ .concatMap(result -> toResponse(mailbox, fetch,
mailboxSession, selected, result))
+ .subscribe(fetchSubscriber);
+ } else {
+ Flux.fromIterable(consolidate(selected, ranges, fetch))
+ .doOnNext(range -> auditTrail(mailbox, mailboxSession,
resultToFetch, range))
+ .concatMap(range ->
Flux.from(mailbox.getMessagesReactive(range, resultToFetch, mailboxSession)))
+ .filter(ids -> !fetch.contains(Item.MODSEQ) ||
ids.getModSeq().asLong() > fetch.getChangedSince())
+ .concatMap(result -> toResponse(mailbox, fetch,
mailboxSession, selected, result))
+ .subscribe(fetchSubscriber);
+ }
return fetchSubscriber.completionMono();
}
diff --git
a/protocols/imap/src/main/java/org/apache/james/imap/processor/main/DefaultImapProcessorFactory.java
b/protocols/imap/src/main/java/org/apache/james/imap/processor/main/DefaultImapProcessorFactory.java
index 95513bfc4e..31415b99cf 100644
---
a/protocols/imap/src/main/java/org/apache/james/imap/processor/main/DefaultImapProcessorFactory.java
+++
b/protocols/imap/src/main/java/org/apache/james/imap/processor/main/DefaultImapProcessorFactory.java
@@ -27,6 +27,7 @@ import org.apache.james.imap.api.process.MailboxTyper;
import org.apache.james.imap.message.response.UnpooledStatusResponseFactory;
import org.apache.james.imap.processor.DefaultProcessor;
import org.apache.james.imap.processor.base.UnknownRequestProcessor;
+import org.apache.james.imap.processor.fetch.FetchProcessor;
import org.apache.james.mailbox.MailboxCounterCorrector;
import org.apache.james.mailbox.MailboxManager;
import org.apache.james.mailbox.SubscriptionManager;
@@ -38,18 +39,37 @@ public class DefaultImapProcessorFactory {
public static ImapProcessor createDefaultProcessor(MailboxManager
mailboxManager, EventBus eventBus, SubscriptionManager subscriptionManager,
QuotaManager quotaManager, QuotaRootResolver quotaRootResolver,
MetricFactory metricFactory) {
- return createXListSupportingProcessor(mailboxManager, eventBus,
subscriptionManager, new DefaultMailboxTyper(), quotaManager,
quotaRootResolver, metricFactory);
+ return createXListSupportingProcessor(mailboxManager, eventBus,
subscriptionManager, new DefaultMailboxTyper(), quotaManager,
quotaRootResolver, metricFactory,
+ FetchProcessor.LocalCacheConfiguration.DEFAULT);
}
public static ImapProcessor createXListSupportingProcessor(MailboxManager
mailboxManager,
EventBus
eventBus, SubscriptionManager subscriptionManager,
- MailboxTyper
mailboxTyper, QuotaManager quotaManager, QuotaRootResolver quotaRootResolver,
MetricFactory metricFactory) {
+ MailboxTyper
mailboxTyper, QuotaManager quotaManager,
+
QuotaRootResolver quotaRootResolver, MetricFactory metricFactory,
+
FetchProcessor.LocalCacheConfiguration localCacheConfiguration) {
StatusResponseFactory statusResponseFactory = new
UnpooledStatusResponseFactory();
UnknownRequestProcessor unknownRequestImapProcessor = new
UnknownRequestProcessor(statusResponseFactory);
return
DefaultProcessor.createDefaultProcessor(unknownRequestImapProcessor,
mailboxManager,
- eventBus, subscriptionManager, statusResponseFactory,
mailboxTyper, quotaManager, quotaRootResolver, MailboxCounterCorrector.DEFAULT,
metricFactory);
+ eventBus, subscriptionManager, statusResponseFactory,
mailboxTyper, quotaManager, quotaRootResolver,
+ MailboxCounterCorrector.DEFAULT, metricFactory,
+ localCacheConfiguration);
+ }
+
+ public static ImapProcessor createXListSupportingProcessor(MailboxManager
mailboxManager,
+ EventBus
eventBus, SubscriptionManager subscriptionManager,
+ MailboxTyper
mailboxTyper, QuotaManager quotaManager,
+
QuotaRootResolver quotaRootResolver, MetricFactory metricFactory) {
+
+ StatusResponseFactory statusResponseFactory = new
UnpooledStatusResponseFactory();
+ UnknownRequestProcessor unknownRequestImapProcessor = new
UnknownRequestProcessor(statusResponseFactory);
+
+ return
DefaultProcessor.createDefaultProcessor(unknownRequestImapProcessor,
mailboxManager,
+ eventBus, subscriptionManager, statusResponseFactory,
mailboxTyper, quotaManager, quotaRootResolver,
+ MailboxCounterCorrector.DEFAULT, metricFactory,
+ FetchProcessor.LocalCacheConfiguration.DEFAULT);
}
}
diff --git
a/server/container/guice/protocols/imap/src/main/java/org/apache/james/modules/protocols/IMAPServerModule.java
b/server/container/guice/protocols/imap/src/main/java/org/apache/james/modules/protocols/IMAPServerModule.java
index ece9cdc95c..80a39d5725 100644
---
a/server/container/guice/protocols/imap/src/main/java/org/apache/james/modules/protocols/IMAPServerModule.java
+++
b/server/container/guice/protocols/imap/src/main/java/org/apache/james/modules/protocols/IMAPServerModule.java
@@ -24,6 +24,7 @@ import java.util.function.Function;
import java.util.stream.Stream;
import org.apache.commons.configuration2.HierarchicalConfiguration;
+import org.apache.commons.configuration2.ex.ConfigurationException;
import org.apache.commons.configuration2.tree.ImmutableNode;
import org.apache.commons.lang3.tuple.Pair;
import org.apache.james.ProtocolConfigurationSanitizer;
@@ -65,6 +66,7 @@ import org.apache.james.imap.processor.SelectProcessor;
import org.apache.james.imap.processor.StatusProcessor;
import org.apache.james.imap.processor.base.AbstractProcessor;
import org.apache.james.imap.processor.base.UnknownRequestProcessor;
+import org.apache.james.imap.processor.fetch.FetchProcessor;
import org.apache.james.imapserver.netty.IMAPHealthCheck;
import org.apache.james.imapserver.netty.IMAPServerFactory;
import org.apache.james.lifecycle.api.ConfigurationSanitizer;
@@ -219,6 +221,10 @@ public class IMAPServerModule extends AbstractModule {
});
}
+ @Provides
+ FetchProcessor.LocalCacheConfiguration
provideFetchLocalCacheConfiguration(ConfigurationProvider
configurationProvider) throws ConfigurationException {
+ return
FetchProcessor.LocalCacheConfiguration.from(configurationProvider.getConfiguration("imapserver"));
+ }
private void configureEnable(EnableProcessor enableProcessor,
ImmutableMap<Class, ImapProcessor> processorMap) {
processorMap.values().stream()
diff --git
a/server/protocols/protocols-imap4/src/test/java/org/apache/james/imapserver/netty/IMAPHealthCheckTest.java
b/server/protocols/protocols-imap4/src/test/java/org/apache/james/imapserver/netty/IMAPHealthCheckTest.java
index 25b5fa509d..5915dfe363 100644
---
a/server/protocols/protocols-imap4/src/test/java/org/apache/james/imapserver/netty/IMAPHealthCheckTest.java
+++
b/server/protocols/protocols-imap4/src/test/java/org/apache/james/imapserver/netty/IMAPHealthCheckTest.java
@@ -31,6 +31,7 @@ import
org.apache.james.imap.api.DefaultConnectionCheckFactory;
import org.apache.james.imap.api.ImapMessage;
import org.apache.james.imap.encode.main.DefaultImapEncoderFactory;
import org.apache.james.imap.main.DefaultImapDecoderFactory;
+import org.apache.james.imap.processor.fetch.FetchProcessor;
import org.apache.james.imap.processor.main.DefaultImapProcessorFactory;
import org.apache.james.mailbox.inmemory.InMemoryMailboxManager;
import org.apache.james.mailbox.inmemory.manager.InMemoryIntegrationResources;
@@ -87,7 +88,8 @@ public class IMAPHealthCheckTest {
null,
memoryIntegrationResources.getQuotaManager(),
memoryIntegrationResources.getQuotaRootResolver(),
- metricFactory),
+ metricFactory,
+ FetchProcessor.LocalCacheConfiguration.DEFAULT),
new RecordingMetricFactory(),
new NoopGaugeRegistry(),
new DefaultConnectionCheckFactory());
diff --git
a/server/protocols/protocols-imap4/src/test/java/org/apache/james/imapserver/netty/IMAPServerTest.java
b/server/protocols/protocols-imap4/src/test/java/org/apache/james/imapserver/netty/IMAPServerTest.java
index a648089857..0762409101 100644
---
a/server/protocols/protocols-imap4/src/test/java/org/apache/james/imapserver/netty/IMAPServerTest.java
+++
b/server/protocols/protocols-imap4/src/test/java/org/apache/james/imapserver/netty/IMAPServerTest.java
@@ -81,6 +81,7 @@ import org.apache.james.imap.api.ConnectionCheck;
import org.apache.james.imap.encode.main.DefaultImapEncoderFactory;
import org.apache.james.imap.main.DefaultImapDecoderFactory;
import org.apache.james.imap.processor.base.AbstractProcessor;
+import org.apache.james.imap.processor.fetch.FetchProcessor;
import org.apache.james.imap.processor.main.DefaultImapProcessorFactory;
import org.apache.james.jwt.OidcTokenFixture;
import org.apache.james.mailbox.MailboxSession;
@@ -160,7 +161,8 @@ class IMAPServerTest {
private InMemoryMailboxManager mailboxManager;
private IMAPServer
createImapServer(HierarchicalConfiguration<ImmutableNode> config,
- InMemoryIntegrationResources
inMemoryIntegrationResources) throws Exception {
+ InMemoryIntegrationResources
inMemoryIntegrationResources,
+ FetchProcessor.LocalCacheConfiguration
localCacheConfiguration) throws Exception {
memoryIntegrationResources = inMemoryIntegrationResources;
RecordingMetricFactory metricFactory = new RecordingMetricFactory();
@@ -178,7 +180,8 @@ class IMAPServerTest {
null,
memoryIntegrationResources.getQuotaManager(),
memoryIntegrationResources.getQuotaRootResolver(),
- metricFactory),
+ metricFactory,
+ localCacheConfiguration),
new ImapMetrics(metricFactory),
new NoopGaugeRegistry(), connectionChecks);
@@ -191,7 +194,7 @@ class IMAPServerTest {
return imapServer;
}
- private IMAPServer
createImapServer(HierarchicalConfiguration<ImmutableNode> config) throws
Exception {
+ private IMAPServer
createImapServer(HierarchicalConfiguration<ImmutableNode> config,
FetchProcessor.LocalCacheConfiguration localCacheConfiguration) throws
Exception {
authenticator = new FakeAuthenticator();
authenticator.addUser(USER, USER_PASS);
authenticator.addUser(USER2, USER_PASS);
@@ -208,11 +211,19 @@ class IMAPServerTest {
.storeQuotaManager()
.build();
- return createImapServer(config, memoryIntegrationResources);
+ return createImapServer(config, memoryIntegrationResources,
localCacheConfiguration);
+ }
+
+ private IMAPServer
createImapServer(HierarchicalConfiguration<ImmutableNode> config) throws
Exception {
+ return createImapServer(config,
FetchProcessor.LocalCacheConfiguration.DEFAULT);
+ }
+
+ private IMAPServer createImapServer(String configurationFile,
FetchProcessor.LocalCacheConfiguration localCacheConfiguration) throws
Exception {
+ return
createImapServer(ConfigLoader.getConfig(ClassLoaderUtils.getSystemResourceAsSharedStream(configurationFile)),
localCacheConfiguration);
}
private IMAPServer createImapServer(String configurationFile) throws
Exception {
- return
createImapServer(ConfigLoader.getConfig(ClassLoaderUtils.getSystemResourceAsSharedStream(configurationFile)));
+ return createImapServer(configurationFile,
FetchProcessor.LocalCacheConfiguration.DEFAULT);
}
private Set<ConnectionCheck> defaultConnectionChecks() {
@@ -285,7 +296,7 @@ class IMAPServerTest {
@BeforeEach
void beforeEach() throws Exception {
- imapServer = createImapServer("imapServer.xml");
+ imapServer = createImapServer("imapServer.xml",
FetchProcessor.LocalCacheConfiguration.DEFAULT);
port = imapServer.getListenAddresses().get(0).getPort();
}
@@ -340,6 +351,11 @@ class IMAPServerTest {
.select("INBOX")
.readFirstMessageInMailbox("BODY[]<8.12>"))
.contains("* 1 FETCH (FLAGS (\\Recent \\Seen) BODY[]<8>
{12}\r\nvalue\r\n\r\nBOD)\r\n");
+
+ assertThat(testIMAPClient
+ .select("INBOX")
+ .readFirstMessageInMailbox("BODY[]<8.12>"))
+ .contains("* 1 FETCH (BODY[]<8>
{12}\r\nvalue\r\n\r\nBOD)\r\n");
}
@Test
@@ -1313,7 +1329,7 @@ class IMAPServerTest {
config.addProperty("auth.oidc.oidcConfigurationURL",
"https://example.com/jwks");
config.addProperty("auth.oidc.scope", "email");
- imapServer = createImapServer(config, integrationResources);
+ imapServer = createImapServer(config, integrationResources,
FetchProcessor.LocalCacheConfiguration.DEFAULT);
port = imapServer.getListenAddresses().get(0).getPort();
}
diff --git a/src/site/xdoc/server/config-imap4.xml
b/src/site/xdoc/server/config-imap4.xml
index f1ee2717b2..0990cfeec6 100644
--- a/src/site/xdoc/server/config-imap4.xml
+++ b/src/site/xdoc/server/config-imap4.xml
@@ -201,6 +201,23 @@
<subsection name="Mail user agents auto-configuration">
<p>Check this example on <a
href="https://github.com/apache/james-project/tree/master/examples/imap-autoconf">Mail
user agents auto-configuration</a>.</p>
</subsection>
+
+ <subsection name="Local cache for partial Fetch">
+ <p>Because some clients uses partial fetch in order to emulate
retriable download of individual body parts
+ we offer a way for James to cache in the IMAP session the latest
partially fetched message. This is done using
+ a weak reference, a total size dedicated to the cache as well as a
time to leave cache cleanup, all of this
+ being configurable.</p>
+
+ <p>Example:</p>
+ </subsection>
+
+ <pre><code>
+<imapserver>
+
<partialBodyFetchCacheEnabled>true</partialBodyFetchCacheEnabled>
+
<partialBodyFetchCacheDuration>2min</partialBodyFetchCacheDuration>
+ <partialBodyFetchCacheSize>500
MiB</partialBodyFetchCacheSize>
+</imapserver>
+ </code></pre>
</section>
</body>
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]