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 <btell...@linagora.com> 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: notifications-unsubscr...@james.apache.org For additional commands, e-mail: notifications-h...@james.apache.org