Skip to content
Open
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,8 @@ public class PostgresConfiguration {
public static final String SSL_MODE_DEFAULT_VALUE = "allow";
public static final String JOOQ_REACTIVE_TIMEOUT = "jooq.reactive.timeout";
public static final Duration JOOQ_REACTIVE_TIMEOUT_DEFAULT_VALUE = Duration.ofSeconds(10);
public static final String ATTACHMENT_STORAGE_ENABLED = "attachment.storage.enabled";
public static final boolean ATTACHMENT_STORAGE_ENABLED_DEFAULT_VALUE = true;

public static class Credential {
private final String username;
Expand Down Expand Up @@ -95,6 +97,7 @@ public static class Builder {
private Optional<Integer> byPassRLSPoolMaxSize = Optional.empty();
private Optional<String> sslMode = Optional.empty();
private Optional<Duration> jooqReactiveTimeout = Optional.empty();
private Optional<Boolean> attachmentStorageEnabled = Optional.empty();

public Builder databaseName(String databaseName) {
this.databaseName = Optional.of(databaseName);
Expand Down Expand Up @@ -241,6 +244,16 @@ public Builder jooqReactiveTimeout(Optional<Duration> jooqReactiveTimeout) {
return this;
}

public Builder attachmentStorageEnabled(Optional<Boolean> attachmentStorageEnabled) {
this.attachmentStorageEnabled = attachmentStorageEnabled;
return this;
}

public Builder attachmentStorageEnabled(Boolean attachmentStorageEnabled) {
this.attachmentStorageEnabled = Optional.of(attachmentStorageEnabled);
return this;
}

public PostgresConfiguration build() {
Preconditions.checkArgument(username.isPresent() && !username.get().isBlank(), "You need to specify username");
Preconditions.checkArgument(password.isPresent() && !password.get().isBlank(), "You need to specify password");
Expand All @@ -262,7 +275,8 @@ public PostgresConfiguration build() {
byPassRLSPoolInitialSize.orElse(BY_PASS_RLS_POOL_INITIAL_SIZE_DEFAULT_VALUE),
byPassRLSPoolMaxSize.orElse(BY_PASS_RLS_POOL_MAX_SIZE_DEFAULT_VALUE),
SSLMode.fromValue(sslMode.orElse(SSL_MODE_DEFAULT_VALUE)),
jooqReactiveTimeout.orElse(JOOQ_REACTIVE_TIMEOUT_DEFAULT_VALUE));
jooqReactiveTimeout.orElse(JOOQ_REACTIVE_TIMEOUT_DEFAULT_VALUE),
attachmentStorageEnabled.orElse(ATTACHMENT_STORAGE_ENABLED_DEFAULT_VALUE));
}
}

Expand All @@ -288,6 +302,7 @@ public static PostgresConfiguration from(Configuration propertiesConfiguration)
.sslMode(Optional.ofNullable(propertiesConfiguration.getString(SSL_MODE)))
.jooqReactiveTimeout(Optional.ofNullable(propertiesConfiguration.getString(JOOQ_REACTIVE_TIMEOUT))
.map(value -> DurationParser.parse(value, ChronoUnit.SECONDS)))
.attachmentStorageEnabled(propertiesConfiguration.getBoolean(ATTACHMENT_STORAGE_ENABLED, ATTACHMENT_STORAGE_ENABLED_DEFAULT_VALUE))
.build();
}

Expand All @@ -304,12 +319,13 @@ public static PostgresConfiguration from(Configuration propertiesConfiguration)
private final Integer byPassRLSPoolMaxSize;
private final SSLMode sslMode;
private final Duration jooqReactiveTimeout;
private final boolean attachmentStorageEnabled;

private PostgresConfiguration(String host, int port, String databaseName, String databaseSchema,
Credential defaultCredential, Credential byPassRLSCredential, RowLevelSecurity rowLevelSecurity,
Integer poolInitialSize, Integer poolMaxSize,
Integer byPassRLSPoolInitialSize, Integer byPassRLSPoolMaxSize,
SSLMode sslMode, Duration jooqReactiveTimeout) {
SSLMode sslMode, Duration jooqReactiveTimeout, boolean attachmentStorageEnabled) {
this.host = host;
this.port = port;
this.databaseName = databaseName;
Expand All @@ -323,6 +339,7 @@ private PostgresConfiguration(String host, int port, String databaseName, String
this.byPassRLSPoolMaxSize = byPassRLSPoolMaxSize;
this.sslMode = sslMode;
this.jooqReactiveTimeout = jooqReactiveTimeout;
this.attachmentStorageEnabled = attachmentStorageEnabled;
}

public String getHost() {
Expand Down Expand Up @@ -377,9 +394,13 @@ public Duration getJooqReactiveTimeout() {
return jooqReactiveTimeout;
}

public boolean isAttachmentStorageEnabled() {
return attachmentStorageEnabled;
}

@Override
public final int hashCode() {
return Objects.hash(host, port, databaseName, databaseSchema, defaultCredential, byPassRLSCredential, rowLevelSecurity, poolInitialSize, poolMaxSize, sslMode, jooqReactiveTimeout);
return Objects.hash(host, port, databaseName, databaseSchema, defaultCredential, byPassRLSCredential, rowLevelSecurity, poolInitialSize, poolMaxSize, sslMode, jooqReactiveTimeout, attachmentStorageEnabled);
}

@Override
Expand All @@ -397,7 +418,8 @@ public final boolean equals(Object o) {
&& Objects.equals(this.poolInitialSize, that.poolInitialSize)
&& Objects.equals(this.poolMaxSize, that.poolMaxSize)
&& Objects.equals(this.sslMode, that.sslMode)
&& Objects.equals(this.jooqReactiveTimeout, that.jooqReactiveTimeout);
&& Objects.equals(this.jooqReactiveTimeout, that.jooqReactiveTimeout)
&& Objects.equals(this.attachmentStorageEnabled, that.attachmentStorageEnabled);
}
return false;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@

import jakarta.inject.Inject;

import org.apache.james.backends.postgres.PostgresConfiguration;
import org.apache.james.blob.api.BlobStore;
import org.apache.james.core.Username;
import org.apache.james.events.Event;
Expand Down Expand Up @@ -63,20 +64,23 @@ public static class DeleteMessageListenerGroup extends Group {
private final PostgresMailboxMessageDAO.Factory mailboxMessageDAOFactory;
private final PostgresAttachmentDAO.Factory attachmentDAOFactory;
private final PostgresThreadDAO.Factory threadDAOFactory;
private final PostgresConfiguration postgresConfiguration;

@Inject
public DeleteMessageListener(BlobStore blobStore,
PostgresMailboxMessageDAO.Factory mailboxMessageDAOFactory,
PostgresMessageDAO.Factory messageDAOFactory,
PostgresAttachmentDAO.Factory attachmentDAOFactory,
PostgresThreadDAO.Factory threadDAOFactory,
PostgresConfiguration postgresConfiguration,
Set<DeletionCallback> deletionCallbackList) {
this.messageDAOFactory = messageDAOFactory;
this.mailboxMessageDAOFactory = mailboxMessageDAOFactory;
this.blobStore = blobStore;
this.deletionCallbackList = deletionCallbackList;
this.attachmentDAOFactory = attachmentDAOFactory;
this.threadDAOFactory = threadDAOFactory;
this.postgresConfiguration = postgresConfiguration;
}

@Override
Expand Down Expand Up @@ -140,7 +144,9 @@ private Mono<Void> handleMessageDeletion(PostgresMessageDAO postgresMessageDAO,
.flatMap(msgId -> postgresMessageDAO.retrieveMessage(messageId)
.flatMap(executeDeletionCallbacks(mailboxId, owner))
.then(deleteBodyBlob(msgId, postgresMessageDAO))
.then(deleteAttachment(msgId, attachmentDAO))
.then(postgresConfiguration.isAttachmentStorageEnabled()
? deleteAttachment(msgId, attachmentDAO)
: Mono.empty())
Copy link
Contributor

@Arsnael Arsnael Oct 23, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The conditional operator ain't really much used in James codebase. We usually prefer extracting this into a private method and using an explicit if condition.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok. I’ll fix it right away.

.then(threadDAO.deleteSome(owner, msgId))
.then(postgresMessageDAO.deleteByMessageId(msgId)));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,8 @@ public class PostgresMailboxSessionMapperFactory extends MailboxSessionMapperFac
private final BlobId.Factory blobIdFactory;
private final Clock clock;
private final RowLevelSecurity rowLevelSecurity;
private final boolean attachmentStorageEnabled;
private final PostgresConfiguration postgresConfiguration;

@Inject
public PostgresMailboxSessionMapperFactory(PostgresExecutor.Factory executorFactory,
Expand All @@ -74,6 +76,8 @@ public PostgresMailboxSessionMapperFactory(PostgresExecutor.Factory executorFact
this.blobIdFactory = blobIdFactory;
this.clock = clock;
this.rowLevelSecurity = postgresConfiguration.getRowLevelSecurity();
this.attachmentStorageEnabled = postgresConfiguration.isAttachmentStorageEnabled();
this.postgresConfiguration = postgresConfiguration;
}

@Override
Expand Down Expand Up @@ -140,13 +144,17 @@ public PostgresAttachmentMapper getAttachmentMapper(MailboxSession session) {
return createAttachmentMapper(session);
}

public boolean isAttachmentStorageEnabled() {
return attachmentStorageEnabled;
}

protected DeleteMessageListener deleteMessageListener() {
PostgresMessageDAO.Factory postgresMessageDAOFactory = new PostgresMessageDAO.Factory(blobIdFactory, executorFactory);
PostgresMailboxMessageDAO.Factory postgresMailboxMessageDAOFactory = new PostgresMailboxMessageDAO.Factory(executorFactory);
PostgresAttachmentDAO.Factory attachmentDAOFactory = new PostgresAttachmentDAO.Factory(executorFactory, blobIdFactory);
PostgresThreadDAO.Factory threadDAOFactory = new PostgresThreadDAO.Factory(executorFactory);

return new DeleteMessageListener(blobStore, postgresMailboxMessageDAOFactory, postgresMessageDAOFactory,
attachmentDAOFactory, threadDAOFactory, ImmutableSet.of());
attachmentDAOFactory, threadDAOFactory, postgresConfiguration, ImmutableSet.of());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -70,12 +70,24 @@ public PostgresMessageManager(PostgresMailboxSessionMapperFactory mapperFactory,
Clock clock, PreDeletionHooks preDeletionHooks) {
super(StoreMailboxManager.DEFAULT_NO_MESSAGE_CAPABILITIES, mapperFactory, index, eventBus, locker, mailbox,
quotaManager, quotaRootResolver, batchSizes, storeRightManager, preDeletionHooks,
new MessageStorer.WithAttachment(mapperFactory, messageIdFactory, new MessageFactory.StoreMessageFactory(), mapperFactory, messageParser, threadIdGuessingAlgorithm, clock));
createMessageStorer(mapperFactory, messageIdFactory, messageParser, threadIdGuessingAlgorithm, clock));
this.storeRightManager = storeRightManager;
this.mapperFactory = mapperFactory;
this.mailbox = mailbox;
}

private static MessageStorer createMessageStorer(PostgresMailboxSessionMapperFactory mapperFactory,
MessageId.Factory messageIdFactory,
MessageParser messageParser,
ThreadIdGuessingAlgorithm threadIdGuessingAlgorithm,
Clock clock) {
if (mapperFactory.isAttachmentStorageEnabled()) {
return new MessageStorer.WithAttachment(mapperFactory, messageIdFactory, new MessageFactory.StoreMessageFactory(), mapperFactory, messageParser, threadIdGuessingAlgorithm, clock);
} else {
return new MessageStorer.WithoutAttachment(mapperFactory, messageIdFactory, new MessageFactory.StoreMessageFactory(), threadIdGuessingAlgorithm, clock);
}
}


@Override
public Flags getPermanentFlags(MailboxSession session) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,8 +82,9 @@ public class PostgresMailboxManagerAttachmentTest extends AbstractMailboxManager
void beforeAll() throws Exception {
BlobId.Factory blobIdFactory = new PlainBlobId.Factory();
DeDuplicationBlobStore blobStore = new DeDuplicationBlobStore(new MemoryBlobStoreDAO(), BucketName.DEFAULT, blobIdFactory);
PostgresConfiguration postgresConfiguration = PostgresConfiguration.builder().username("a").password("a").build();
mapperFactory = new PostgresMailboxSessionMapperFactory(postgresExtension.getExecutorFactory(), Clock.systemUTC(), blobStore, blobIdFactory,
PostgresConfiguration.builder().username("a").password("a").build());
postgresConfiguration);

MailboxACLResolver aclResolver = new UnionMailboxACLResolver();
MessageParser messageParser = new MessageParser();
Expand All @@ -107,7 +108,7 @@ void beforeAll() throws Exception {
PostgresThreadDAO.Factory threadDAOFactory = new PostgresThreadDAO.Factory(postgresExtension.getExecutorFactory());

eventBus.register(new DeleteMessageListener(blobStore, postgresMailboxMessageDAOFactory, postgresMessageDAOFactory,
attachmentDAOFactory, threadDAOFactory, ImmutableSet.of()));
attachmentDAOFactory, threadDAOFactory, postgresConfiguration, ImmutableSet.of()));

mailboxManager = new PostgresMailboxManager(mapperFactory, sessionProvider,
messageParser, new PostgresMessageId.Factory(),
Expand Down
Loading