diff --git a/.gitmodules b/.gitmodules
index cb3cf38a26d..c5a70a2c4bb 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -1,3 +1,4 @@
[submodule "james-project"]
path = james-project
url = https://github.com/apache/james-project
+ branch = postgresql
diff --git a/Jenkinsfile b/Jenkinsfile
index 6b788969d70..7855be310d9 100644
--- a/Jenkinsfile
+++ b/Jenkinsfile
@@ -44,22 +44,15 @@ pipeline {
stage('Deliver Docker images') {
when {
anyOf {
- branch 'master'
+ branch 'postgresql'
buildingTag()
}
}
steps {
script {
- env.DOCKER_TAG = 'branch-master'
- if (env.TAG_NAME) {
- env.DOCKER_TAG = env.TAG_NAME
- }
-
- echo "Docker tag: ${env.DOCKER_TAG}"
// build and push docker images
dir("tmail-backend") {
- sh 'mvn -Pci jib:build -Djib.to.auth.username=$DOCKER_HUB_CREDENTIAL_USR -Djib.to.auth.password=$DOCKER_HUB_CREDENTIAL_PSW -Djib.to.tags=distributed-$DOCKER_TAG -pl apps/distributed -X'
- sh 'mvn -Pci jib:build -Djib.to.auth.username=$DOCKER_HUB_CREDENTIAL_USR -Djib.to.auth.password=$DOCKER_HUB_CREDENTIAL_PSW -Djib.to.tags=memory-$DOCKER_TAG -pl apps/memory -X'
+ sh 'mvn -Pci jib:build -Djib.to.auth.username=$DOCKER_HUB_CREDENTIAL_USR -Djib.to.auth.password=$DOCKER_HUB_CREDENTIAL_PSW -Djib.to.image=linagora/tmail-backend:postgresql-experimental -Djib.to.tags=postgresql-experimental -pl apps/postgres -X'
}
}
}
diff --git a/james-project b/james-project
index 00a4ef8e6f6..38c880ac9e6 160000
--- a/james-project
+++ b/james-project
@@ -1 +1 @@
-Subproject commit 00a4ef8e6f6bf10fd8c5afea636d0a0b88c28bbe
+Subproject commit 38c880ac9e67fa38e0dc80a1870d5fafa2ff1d4e
diff --git a/tmail-backend/apps/distributed/pom.xml b/tmail-backend/apps/distributed/pom.xml
index 0c7f30c0f23..485db375bd8 100644
--- a/tmail-backend/apps/distributed/pom.xml
+++ b/tmail-backend/apps/distributed/pom.xml
@@ -73,6 +73,10 @@
${project.groupId}
team-mailboxes-guice
+
+ ${project.groupId}
+ tmail-guice-common
+
${project.groupId}
tmail-guice-distributed
diff --git a/tmail-backend/apps/distributed/src/main/java/com/linagora/tmail/james/app/DistributedEmailAddressContactEventDeadLettersModule.java b/tmail-backend/apps/distributed/src/main/java/com/linagora/tmail/james/app/DistributedEmailAddressContactEventDeadLettersModule.java
index 3be9c7f506b..bca75290c84 100644
--- a/tmail-backend/apps/distributed/src/main/java/com/linagora/tmail/james/app/DistributedEmailAddressContactEventDeadLettersModule.java
+++ b/tmail-backend/apps/distributed/src/main/java/com/linagora/tmail/james/app/DistributedEmailAddressContactEventDeadLettersModule.java
@@ -1,28 +1,21 @@
package com.linagora.tmail.james.app;
-
-import org.apache.james.events.CassandraEventDeadLetters;
-import org.apache.james.events.CassandraEventDeadLettersDAO;
-import org.apache.james.events.CassandraEventDeadLettersGroupDAO;
import org.apache.james.events.EventDeadLetters;
-import com.datastax.oss.driver.api.core.CqlSession;
import com.google.inject.AbstractModule;
import com.google.inject.Provides;
import com.google.inject.Singleton;
import com.google.inject.name.Named;
import com.linagora.tmail.james.jmap.EmailAddressContactInjectKeys;
-import com.linagora.tmail.james.jmap.contact.TmailJmapEventSerializer;
public class DistributedEmailAddressContactEventDeadLettersModule extends AbstractModule {
+ // TODO: for pass ci on postgresql branch,
+ // It should be replaced by https://github.com/linagora/tmail-backend/pull/1016 (when rebase master branch)
@Provides
@Singleton
@Named(EmailAddressContactInjectKeys.AUTOCOMPLETE)
- EventDeadLetters provideEventDeadLetters(CassandraEventDeadLettersGroupDAO cassandraEventDeadLettersGroupDAO,
- CqlSession session,
- TmailJmapEventSerializer tmailJmapEventSerializer) {
- return new CassandraEventDeadLetters(new CassandraEventDeadLettersDAO(session, tmailJmapEventSerializer),
- cassandraEventDeadLettersGroupDAO);
+ EventDeadLetters provideEventDeadLetters(EventDeadLetters eventDeadLetters) {
+ return eventDeadLetters;
}
}
diff --git a/tmail-backend/apps/distributed/src/main/java/com/linagora/tmail/james/app/DistributedJamesConfiguration.java b/tmail-backend/apps/distributed/src/main/java/com/linagora/tmail/james/app/DistributedJamesConfiguration.java
index 75ffc0d0f16..71332af2037 100644
--- a/tmail-backend/apps/distributed/src/main/java/com/linagora/tmail/james/app/DistributedJamesConfiguration.java
+++ b/tmail-backend/apps/distributed/src/main/java/com/linagora/tmail/james/app/DistributedJamesConfiguration.java
@@ -20,8 +20,8 @@
import com.github.fge.lambdas.Throwing;
import com.linagora.tmail.OpenPaasModuleChooserConfiguration;
+import com.linagora.tmail.UsersRepositoryModuleChooser;
import com.linagora.tmail.blob.guice.BlobStoreConfiguration;
-import com.linagora.tmail.combined.identity.UsersRepositoryModuleChooser;
import com.linagora.tmail.encrypted.MailboxConfiguration;
import com.linagora.tmail.james.jmap.firebase.FirebaseModuleChooserConfiguration;
import com.linagora.tmail.james.jmap.service.discovery.LinagoraServicesDiscoveryModuleChooserConfiguration;
diff --git a/tmail-backend/apps/distributed/src/main/java/com/linagora/tmail/james/app/DistributedServer.java b/tmail-backend/apps/distributed/src/main/java/com/linagora/tmail/james/app/DistributedServer.java
index 35931d35bae..58b41507675 100644
--- a/tmail-backend/apps/distributed/src/main/java/com/linagora/tmail/james/app/DistributedServer.java
+++ b/tmail-backend/apps/distributed/src/main/java/com/linagora/tmail/james/app/DistributedServer.java
@@ -49,6 +49,7 @@
import org.apache.james.modules.data.CassandraSieveQuotaLegacyModule;
import org.apache.james.modules.data.CassandraSieveQuotaModule;
import org.apache.james.modules.data.CassandraSieveRepositoryModule;
+import org.apache.james.modules.data.CassandraUsersRepositoryModule;
import org.apache.james.modules.data.CassandraVacationModule;
import org.apache.james.modules.event.JMAPEventBusModule;
import org.apache.james.modules.event.RabbitMQEventBusModule;
@@ -103,6 +104,7 @@
import org.apache.james.quota.search.QuotaSearcher;
import org.apache.james.quota.search.scanning.ScanningQuotaSearcher;
import org.apache.james.rate.limiter.redis.RedisRateLimiterModule;
+import org.apache.james.user.cassandra.CassandraUsersDAO;
import org.apache.james.utils.InitializationOperation;
import org.apache.james.utils.InitilizationOperationBuilder;
import org.apache.james.vault.VaultConfiguration;
@@ -120,13 +122,14 @@
import com.google.inject.multibindings.ProvidesIntoSet;
import com.google.inject.name.Names;
import com.google.inject.util.Modules;
+import com.linagora.tmail.DatabaseCombinedUserRequireModule;
import com.linagora.tmail.OpenPaasModule;
import com.linagora.tmail.OpenPaasModuleChooserConfiguration;
import com.linagora.tmail.ScheduledReconnectionHandler;
+import com.linagora.tmail.UsersRepositoryModuleChooser;
import com.linagora.tmail.blob.guice.BlobStoreCacheModulesChooser;
import com.linagora.tmail.blob.guice.BlobStoreConfiguration;
import com.linagora.tmail.blob.guice.BlobStoreModulesChooser;
-import com.linagora.tmail.combined.identity.UsersRepositoryModuleChooser;
import com.linagora.tmail.contact.RabbitMQEmailAddressContactModule;
import com.linagora.tmail.encrypted.ClearEmailContentFactory;
import com.linagora.tmail.encrypted.EncryptedMailboxManager;
@@ -350,7 +353,9 @@ public static GuiceJamesServer createServer(DistributedJamesConfiguration config
.combineWith(MailQueueViewChoice.ModuleChooser.choose(configuration.mailQueueViewChoice()))
.combineWith(BlobStoreModulesChooser.chooseModules(blobStoreConfiguration))
.combineWith(BlobStoreCacheModulesChooser.chooseModules(blobStoreConfiguration))
- .combineWith(UsersRepositoryModuleChooser.chooseModules(configuration.usersRepositoryImplementation()))
+ .combineWith(new UsersRepositoryModuleChooser(
+ DatabaseCombinedUserRequireModule.of(CassandraUsersDAO.class),
+ new CassandraUsersRepositoryModule()).chooseModule(configuration.usersRepositoryImplementation()))
.combineWith(chooseFirebase(configuration.firebaseModuleChooserConfiguration()))
.combineWith(chooseLinagoraServicesDiscovery(configuration.linagoraServicesDiscoveryModuleChooserConfiguration()))
.combineWith(chooseOpenPaasModule(configuration.openPaasModuleChooserConfiguration()))
diff --git a/tmail-backend/apps/distributed/src/test/java/com/linagora/tmail/james/app/CombinedIdentityJamesServerTest.java b/tmail-backend/apps/distributed/src/test/java/com/linagora/tmail/james/app/CombinedIdentityJamesServerTest.java
index f383986e579..f707e397ade 100644
--- a/tmail-backend/apps/distributed/src/test/java/com/linagora/tmail/james/app/CombinedIdentityJamesServerTest.java
+++ b/tmail-backend/apps/distributed/src/test/java/com/linagora/tmail/james/app/CombinedIdentityJamesServerTest.java
@@ -14,10 +14,10 @@
import org.junit.jupiter.api.extension.RegisterExtension;
import com.google.inject.multibindings.Multibinder;
+import com.linagora.tmail.UsersRepositoryModuleChooser;
import com.linagora.tmail.combined.identity.CombinedUsersRepository;
import com.linagora.tmail.combined.identity.LdapExtension;
import com.linagora.tmail.combined.identity.UsersRepositoryClassProbe;
-import com.linagora.tmail.combined.identity.UsersRepositoryModuleChooser;
import com.linagora.tmail.module.LinagoraTestJMAPServerModule;
public class CombinedIdentityJamesServerTest {
diff --git a/tmail-backend/apps/distributed/src/test/java/com/linagora/tmail/james/app/DistributedServerWithOpenPaasRabbitMqConfiguredTest.java b/tmail-backend/apps/distributed/src/test/java/com/linagora/tmail/james/app/DistributedServerWithOpenPaasRabbitMqConfiguredTest.java
index 52a76a9416e..ab2adcd6b9f 100644
--- a/tmail-backend/apps/distributed/src/test/java/com/linagora/tmail/james/app/DistributedServerWithOpenPaasRabbitMqConfiguredTest.java
+++ b/tmail-backend/apps/distributed/src/test/java/com/linagora/tmail/james/app/DistributedServerWithOpenPaasRabbitMqConfiguredTest.java
@@ -11,9 +11,9 @@
import com.google.inject.multibindings.Multibinder;
import com.linagora.tmail.OpenPaasModuleChooserConfiguration;
+import com.linagora.tmail.UsersRepositoryModuleChooser;
import com.linagora.tmail.combined.identity.LdapExtension;
import com.linagora.tmail.combined.identity.UsersRepositoryClassProbe;
-import com.linagora.tmail.combined.identity.UsersRepositoryModuleChooser;
public class DistributedServerWithOpenPaasRabbitMqConfiguredTest {
@RegisterExtension
diff --git a/tmail-backend/apps/pom.xml b/tmail-backend/apps/pom.xml
index 606ac7c2aec..33c61951162 100644
--- a/tmail-backend/apps/pom.xml
+++ b/tmail-backend/apps/pom.xml
@@ -16,5 +16,6 @@
memory
distributed
+ postgres
\ No newline at end of file
diff --git a/tmail-backend/apps/postgres/README.md b/tmail-backend/apps/postgres/README.md
new file mode 100644
index 00000000000..26e4c4abe24
--- /dev/null
+++ b/tmail-backend/apps/postgres/README.md
@@ -0,0 +1,10 @@
+## Administration Operations
+## Clean up data
+
+To clean up some data on the specific TMail data structures, that will be redundant again after a long time, you can execute the SQL queries `clean_up_data_tmail.sql`.
+
+The data that in:
+- `label_change` table
+- `ticket` table
+
+Note that the `clean_up_data_tmail.sql` should be merged with [the SQL clean up script on Apache James](https://github.com/apache/james-project/blob/postgresql/server/apps/postgres-app/clean_up.sql) to clean data on James tables as well.
diff --git a/tmail-backend/apps/postgres/clean_up_data_tmail.sql b/tmail-backend/apps/postgres/clean_up_data_tmail.sql
new file mode 100644
index 00000000000..744ccb2be66
--- /dev/null
+++ b/tmail-backend/apps/postgres/clean_up_data_tmail.sql
@@ -0,0 +1,25 @@
+-- This is a script to delete old rows from some tables. One of the attempts to clean up the never-used data after a long time.
+
+DO
+$$
+ DECLARE
+ days_to_keep INTEGER;
+ ticket_ttl_in_seconds INTEGER;
+ BEGIN
+ -- Set the number of days dynamically
+ days_to_keep := 60;
+
+ -- Delete rows older than the specified number of days in the `label_change` table
+ DELETE
+ FROM label_change
+ WHERE created_date < current_timestamp - interval '1 day' * days_to_keep;
+
+ -- Set TTL in seconds for the Ticket table cleanup
+ ticket_ttl_in_seconds := 120;
+
+ -- Delete rows older than the specified TTL in the `Ticket` table
+ DELETE
+ FROM ticket
+ WHERE created_date < current_timestamp - interval '1 second' * ticket_ttl_in_seconds;
+ END
+$$;
\ No newline at end of file
diff --git a/tmail-backend/apps/postgres/docker-compose-distributed.yml b/tmail-backend/apps/postgres/docker-compose-distributed.yml
new file mode 100644
index 00000000000..75440a98469
--- /dev/null
+++ b/tmail-backend/apps/postgres/docker-compose-distributed.yml
@@ -0,0 +1,98 @@
+version: '3.4'
+
+x-common-environment: &common-environment
+ POSTGRES_DB: tmail
+ POSTGRES_USER: tmail
+ POSTGRES_PASSWORD: secret1
+
+services:
+
+ tmail-backend:
+ depends_on:
+ postgres:
+ condition: service_started
+ opensearch:
+ condition: service_healthy
+ s3:
+ condition: service_started
+ rabbitmq:
+ condition: service_started
+ image: linagora/tmail-backend-postgresql-experimental
+ container_name: tmail-backend
+ hostname: tmail-backend.local
+ command:
+ - --generate-keystore
+ environment:
+ <<: *common-environment
+ OBJECT_STORAGE_ENDPOINT: http://s3.docker.test:8000/
+ OBJECT_STORAGE_REGION: us-east-1
+ OBJECT_STORAGE_ACCESS_KEY_ID: accessKey1
+ OBJECT_STORAGE_SECRET_KEY: secretKey1
+
+ ports:
+ - "80:80"
+ - "25:25"
+ - "110:110"
+ - "143:143"
+ - "465:465"
+ - "587:587"
+ - "993:993"
+ - "8000:8000"
+ volumes:
+ - ./sample-configuration/distributed/opensearch.properties:/root/conf/opensearch.properties
+ - ./sample-configuration/distributed/blob.properties:/root/conf/blob.properties
+ - ./sample-configuration/distributed/rabbitmq.properties:/root/conf/rabbitmq.properties
+ - ${RSA_PUBLICKEY_PATH}:/root/conf/jwt_publickey # Replace with absolute path to your RSA public key
+ - ${RSA_PRIVATEKEY_PATH}:/root/conf/jwt_privatekey # Replace with absolute path to your RSA private key
+ # Key generation:
+ # openssl genrsa -out jwt_privatekey 4096
+ # openssl rsa -in jwt_privatekey -pubout > jwt_publickey
+ networks:
+ - tmail
+
+ opensearch:
+ image: opensearchproject/opensearch:2.8.0
+ container_name: opensearch
+ healthcheck:
+ test: ["CMD", "curl", "-s", "http://opensearch:9200"]
+ interval: 3s
+ timeout: 10s
+ retries: 5
+ environment:
+ - discovery.type=single-node
+ - DISABLE_INSTALL_DEMO_CONFIG=true
+ - DISABLE_SECURITY_PLUGIN=true
+ networks:
+ - tmail
+
+ postgres:
+ image: postgres:16.1
+ container_name: postgres
+ ports:
+ - "5432:5432"
+ environment:
+ <<: *common-environment
+ networks:
+ - tmail
+
+ s3:
+ image: registry.scality.com/cloudserver/cloudserver:8.7.25
+ container_name: s3.docker.test
+ environment:
+ SCALITY_ACCESS_KEY_ID: accessKey1
+ SCALITY_SECRET_ACCESS_KEY: secretKey1
+ LOG_LEVEL: trace
+ REMOTE_MANAGEMENT_DISABLE: 1
+ networks:
+ - tmail
+
+ rabbitmq:
+ image: rabbitmq:3.12.1-management
+ ports:
+ - "5672:5672"
+ - "15672:15672"
+ networks:
+ - tmail
+
+networks:
+ tmail:
\ No newline at end of file
diff --git a/tmail-backend/apps/postgres/docker-compose.yml b/tmail-backend/apps/postgres/docker-compose.yml
new file mode 100644
index 00000000000..a43355c4d31
--- /dev/null
+++ b/tmail-backend/apps/postgres/docker-compose.yml
@@ -0,0 +1,41 @@
+version: '3.4'
+
+x-common-environment: &common-environment
+ POSTGRES_DB: tmail
+ POSTGRES_USER: tmail
+ POSTGRES_PASSWORD: secret1
+
+services:
+
+ tmail-backend:
+ depends_on:
+ - postgres
+ image: linagora/tmail-backend-postgresql-experimental
+ container_name: tmail-backend
+ hostname: tmail-backend.local
+ command:
+ - --generate-keystore
+ volumes:
+ - ${RSA_PUBLICKEY_PATH}:/root/conf/jwt_publickey # Replace with absolute path to your RSA public key
+ - ${RSA_PRIVATEKEY_PATH}:/root/conf/jwt_privatekey # Replace with absolute path to your RSA private key
+ # Key generation:
+ # openssl genrsa -out jwt_privatekey 4096
+ # openssl rsa -in jwt_privatekey -pubout > jwt_publickey
+ ports:
+ - "80:80"
+ - "25:25"
+ - "110:110"
+ - "143:143"
+ - "465:465"
+ - "587:587"
+ - "993:993"
+ - "8000:8000"
+ environment:
+ <<: *common-environment
+
+ postgres:
+ image: postgres:16.1
+ ports:
+ - "5432:5432"
+ environment:
+ <<: *common-environment
\ No newline at end of file
diff --git a/tmail-backend/apps/postgres/imap_smtp_test.sh b/tmail-backend/apps/postgres/imap_smtp_test.sh
new file mode 100644
index 00000000000..caa4a8e304a
--- /dev/null
+++ b/tmail-backend/apps/postgres/imap_smtp_test.sh
@@ -0,0 +1,93 @@
+#!/bin/bash
+
+# Domain Configuration
+WEBADMIN_BASE_URL="http://localhost:8000"
+DOMAIN_NAME="domain.local"
+
+# IMAP Configuration
+IMAP_SERVER="localhost"
+IMAP_PORT=993
+ALICE_USERNAME="alice@${DOMAIN_NAME}"
+ALICE_PASSWORD="secret"
+
+BOB_USERNAME="bob@${DOMAIN_NAME}"
+BOB_PASSWORD="secret"
+
+# SMTP Configuration
+SMTP_SERVER="localhost"
+SMTP_PORT=25
+SMTP_FROM="${BOB_USERNAME}"
+SMTP_TO="${ALICE_USERNAME}"
+SMTP_SUBJECT="Test Email"
+SMTP_BODY="Hello Alice, this is a test email."
+
+# Function to create a new domain using curl
+create_domain() {
+ echo "Creating domain: ${DOMAIN_NAME}"
+ curl -X PUT ${WEBADMIN_BASE_URL}/domains/${DOMAIN_NAME}
+}
+
+# Function to create a new user using curl
+create_user() {
+ local username="$1"
+ local password="$2"
+ echo "Creating user: ${username}"
+ curl -L -X PUT "${WEBADMIN_BASE_URL}/users/${username}" \
+ -H 'Content-Type: application/json' \
+ -d "{\"password\":\"${password}\"}"
+}
+
+# Function to send email using telnet
+send_email() {
+ {
+ sleep 2
+ echo "EHLO example.com"
+ sleep 2
+ echo "MAIL FROM:<${SMTP_FROM}>"
+ sleep 2
+ echo "RCPT TO:<${SMTP_TO}>"
+ sleep 2
+ echo "DATA"
+ sleep 2
+ echo "Subject: ${SMTP_SUBJECT}"
+ echo ""
+ echo "${SMTP_BODY}"
+ echo "."
+ sleep 2
+ echo "QUIT"
+ } | telnet ${SMTP_SERVER} ${SMTP_PORT}
+}
+
+# Function to fetch emails using openssl
+fetch_emails() {
+ {
+ sleep 2
+ echo "a1 LOGIN ${ALICE_USERNAME} ${ALICE_PASSWORD}"
+ sleep 2
+ echo "a2 SELECT INBOX"
+ sleep 2
+ echo "a3 SEARCH UNSEEN SUBJECT \"${SMTP_SUBJECT}\" FROM \"${SMTP_FROM}\""
+ sleep 2
+ echo "a4 LOGOUT"
+ } | openssl s_client -connect ${IMAP_SERVER}:${IMAP_PORT} -quiet
+}
+
+# Create domain and users
+create_domain
+create_user "${ALICE_USERNAME}" "${ALICE_PASSWORD}"
+create_user "${BOB_USERNAME}" "${BOB_PASSWORD}"
+
+# SMTP Test
+echo "Sending test email..."
+send_email
+
+# IMAP Test
+echo "Fetching emails from ${ALICE_USERNAME}..."
+# Fetch emails using openssl and check if the email from Bob is present
+if fetch_emails | grep -q "a3 OK"; then
+ echo "Email from Bob found in Alice's inbox."
+else
+ echo "Email from Bob not found in Alice's inbox."
+fi
+
+echo "Test script completed."
diff --git a/tmail-backend/apps/postgres/pom.xml b/tmail-backend/apps/postgres/pom.xml
new file mode 100644
index 00000000000..dad72c546b6
--- /dev/null
+++ b/tmail-backend/apps/postgres/pom.xml
@@ -0,0 +1,463 @@
+
+
+ 4.0.0
+
+ com.linagora.tmail
+ apps
+ 1.0.0-SNAPSHOT
+
+
+ postgres
+ Team-mail :: Apps :: Postgres
+
+
+
+ ${james.groupId}
+ blob-s3
+ test-jar
+ test
+
+
+ ${project.groupId}
+ blob-guice
+
+
+ ${project.groupId}
+ combined-identity
+ ${project.version}
+
+
+ ${project.groupId}
+ combined-identity
+ ${project.version}
+ test-jar
+
+
+ ${project.groupId}
+ jmap-extensions-opensearch
+
+
+ ${project.groupId}
+ tmail-guice-distributed
+
+
+ ${project.groupId}
+ tmail-guice-distributed
+ test-jar
+ test
+
+
+ ${project.groupId}
+ tmail-openpaas
+ test
+
+
+ ${james.groupId}
+ apache-james-backends-rabbitmq
+ test-jar
+ test
+
+
+ ${james.groupId}
+ james-server-data-ldap
+ test-jar
+ test
+
+
+ ${james.groupId}
+ blob-s3-guice
+ test-jar
+ test
+
+
+ ${james.groupId}
+ apache-james-mailbox-opensearch
+ test-jar
+ test
+
+
+ ${james.groupId}
+ james-server-guice-opensearch
+ test-jar
+ test
+
+
+ ${james.groupId}
+ james-server-guice-opensearch
+
+
+ ${james.groupId}
+ apache-james-backends-opensearch
+ test-jar
+ test
+
+
+ ${james.groupId}
+ james-server-postgres-app
+ test-jar
+ test
+
+
+ ${james.groupId}
+ apache-james-backends-postgres
+ test-jar
+ test
+
+
+ ${james.groupId}
+ apache-james-mailbox-postgres
+ test-jar
+ test
+
+
+ ${james.groupId}
+ james-server-data-postgres
+
+
+ ${james.groupId}
+ james-server-guice-mailbox-postgres
+
+
+ ${james.groupId}
+ james-server-guice-sieve-postgres
+
+
+ ${james.groupId}
+ james-server-postgres-common-guice
+
+
+ ${james.groupId}
+ james-server-postgres-common-guice
+ test-jar
+ test
+
+
+ ${james.groupId}
+ james-server-rate-limiter-redis
+
+
+ org.testcontainers
+ postgresql
+ test
+
+
+
+ ${james.groupId}
+ apache-mime4j-dom
+
+
+ ${project.groupId}
+ blobid-list
+
+
+ ${project.groupId}
+ encrypted-mailbox-guice
+
+
+ ${project.groupId}
+ jmap-extensions
+
+
+ ${project.groupId}
+ jmap-extensions
+ test-jar
+ test
+
+
+ ${project.groupId}
+ jmap-extensions-postgres
+
+
+ ${project.groupId}
+ mailbox-encrypted-postgres
+
+
+ ${project.groupId}
+ tmail-healthcheck
+
+
+ ${project.groupId}
+ tmail-guice-common
+
+
+ ${project.groupId}
+ smtp-extensions
+
+
+ ${project.groupId}
+ team-mailboxes
+
+
+ ${project.groupId}
+ team-mailboxes-guice
+
+
+ ${project.groupId}
+ tmail-guice-distributed
+ test-jar
+ test
+
+
+ ${project.groupId}
+ tmail-guice-jmap
+ test-jar
+ test
+
+
+ ${project.groupId}
+ tmail-mailets
+
+
+ ${project.groupId}
+ tmail-rate-limiter-postgres
+
+
+ ${project.groupId}
+ tmail-webadmin-mailbox
+
+
+ ${project.groupId}
+ webadmin-email-address-contact
+
+
+ ${project.groupId}
+ webadmin-rate-limit
+
+
+ ${james.groupId}
+ apache-james-linshare
+ test-jar
+ test
+
+
+ ${project.groupId}
+ tmail-webadmin-team-mailboxes
+
+
+ ${james.groupId}
+ apache-mailet-icalendar
+
+
+ ${james.groupId}
+ blob-export-guice
+ test-jar
+ test
+
+
+ ${james.groupId}
+ jmap-rfc-8621-integration-tests-common
+ test
+
+
+ ${james.groupId}
+ james-server-cli
+ runtime
+
+
+ ${james.groupId}
+ james-server-guice-common
+ test-jar
+ test
+
+
+ ${james.groupId}
+ james-server-guice-jmap
+ test-jar
+ test
+
+
+ ${james.groupId}
+ james-server-rate-limiter
+
+
+ ${james.groupId}
+ james-server-testing
+ test
+
+
+ ${james.groupId}
+ james-server-webadmin-core
+ test-jar
+ test
+
+
+ ch.qos.logback.contrib
+ logback-json-classic
+
+
+ org.apache.james
+ james-server-postgres-app
+
+
+ com.linagora.tmail
+ combined-identity
+ test
+
+
+ ${james.groupId}
+ james-server-guice-webadmin-mail-over-web
+
+
+
+
+
+
+ com.googlecode.maven-download-plugin
+ download-maven-plugin
+
+
+ install-glowroot
+
+ wget
+
+ package
+
+
+ https://github.com/glowroot/glowroot/releases/download/v0.14.0/glowroot-0.14.0-dist.zip
+
+ true
+ ${project.build.directory}
+ 16073f10204751cd71d3b4ea93be2649
+
+
+
+ package-async-profiler
+
+ wget
+
+ package
+
+
+ https://github.com/async-profiler/async-profiler/releases/download/v2.9/async-profiler-2.9-linux-x64.tar.gz
+
+ true
+ ${project.build.directory}
+ 29127cee36b7acf069d31603b4558361
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-resources-plugin
+
+
+ copy-glowroot-resources
+
+ copy-resources
+
+ package
+
+ ${basedir}/target/glowroot
+
+
+ src/main/glowroot
+ true
+
+
+
+
+
+
+
+ com.google.cloud.tools
+ jib-maven-plugin
+
+
+ ${jib.base.image}
+
+
+ linagora/tmail-backend-postgresql-experimental
+
+ latest
+
+
+
+ com.linagora.tmail.james.app.PostgresTmailServer
+
+ 80
+
+ 143
+
+ 993
+
+ 25
+
+ 465
+
+ 587
+
+ 4000
+
+ 4190
+
+ 8000
+
+
+ /root
+ USE_CURRENT_TIMESTAMP
+
+
+
+
+ sample-configuration
+ /root/conf
+
+
+ src/main/scripts
+ /usr/bin
+
+
+ target/glowroot
+ /root/glowroot
+
+
+ target/async-profiler-2.9-linux-x64
+ /root/async-profiler
+
+
+ src/main/extensions-jars
+ /root/extensions-jars
+
+
+ src/main/eml-template
+ /root/eml-template
+
+
+
+
+ /usr/bin/james-cli
+ 755
+
+
+
+ /root/async-profiler/profiler.sh
+ 755
+
+
+
+ /root/async-profiler/build/libasyncProfiler.so
+ 755
+
+
+
+ /root/async-profiler/build/jattach
+ 755
+
+
+
+
+
+
+
+
+ buildTar
+
+ package
+
+
+
+
+
+
diff --git a/tmail-backend/apps/postgres/sample-configuration/batchsizes.properties b/tmail-backend/apps/postgres/sample-configuration/batchsizes.properties
new file mode 100644
index 00000000000..1784f95d79e
--- /dev/null
+++ b/tmail-backend/apps/postgres/sample-configuration/batchsizes.properties
@@ -0,0 +1,9 @@
+# Those properties let you configure the number of messages queried at the same time.
+# IMAP FETCH command
+fetch.metadata=200
+fetch.headers=200
+fetch.full=50
+# IMAP COPY command
+copy=100
+# IMAP MOVE command
+move=100
diff --git a/tmail-backend/apps/postgres/sample-configuration/blob.properties b/tmail-backend/apps/postgres/sample-configuration/blob.properties
new file mode 100644
index 00000000000..78ea935448f
--- /dev/null
+++ b/tmail-backend/apps/postgres/sample-configuration/blob.properties
@@ -0,0 +1,66 @@
+# ============================================= BlobStore Implementation ==================================
+# Read https://james.apache.org/server/config-blobstore.html for further details
+
+# Choose your BlobStore implementation
+# Mandatory, allowed values are: file, s3, postgres.
+implementation=postgres
+
+# ========================================= Deduplication ========================================
+# If you choose to enable deduplication, the mails with the same content will be stored only once.
+# Warning: Once this feature is enabled, there is no turning back as turning it off will lead to the deletion of all
+# the mails sharing the same content once one is deleted.
+# Mandatory, Allowed values are: true, false
+deduplication.enable=true
+
+# deduplication.family needs to be incremented every time the deduplication.generation.duration is changed
+# Positive integer, defaults to 1
+# deduplication.gc.generation.family=1
+
+# Duration of generation.
+# Deduplication only takes place within a singe generation.
+# Only items two generation old can be garbage collected. (This prevent concurrent insertions issues and
+# accounts for a clock skew).
+# deduplication.family needs to be incremented everytime this parameter is changed.
+# Duration. Default unit: days. Defaults to 30 days.
+# deduplication.gc.generation.duration=30days
+
+# ========================================= Encryption ========================================
+# If you choose to enable encryption, the blob content will be encrypted before storing them in the BlobStore.
+# Warning: Once this feature is enabled, there is no turning back as turning it off will lead to all content being
+# encrypted. This comes at a performance impact but presents you from leaking data if, for instance the third party
+# offering you a S3 service is compromised.
+# Optional, Allowed values are: true, false, defaults to false
+encryption.aes.enable=false
+
+# Mandatory (if AES encryption is enabled) salt and password. Salt needs to be an hexadecimal encoded string
+#encryption.aes.password=xxx
+#encryption.aes.salt=73616c7479
+# Optional, defaults to PBKDF2WithHmacSHA512
+#encryption.aes.private.key.algorithm=PBKDF2WithHmacSHA512
+
+# ============================================ Blobs Exporting ==============================================
+# Read https://james.apache.org/server/config-blob-export.html for further details
+
+# Choosing blob exporting mechanism, allowed mechanism are: localFile, linshare
+# LinShare is a file sharing service, will be explained in the below section
+# Optional, default is localFile
+blob.export.implementation=localFile
+
+# ======================================= Local File Blobs Exporting ========================================
+# Optional, directory to store exported blob, directory path follows James file system format
+# default is file://var/blobExporting
+blob.export.localFile.directory=file://var/blobExporting
+
+# ======================================= LinShare File Blobs Exporting ========================================
+# LinShare is a sharing service where you can use james, connects to an existing LinShare server and shares files to
+# other mail addresses as long as those addresses available in LinShare. For example you can deploy James and LinShare
+# sharing the same LDAP repository
+# Mandatory if you choose LinShare, url to connect to LinShare service
+# blob.export.linshare.url=http://linshare:8080
+
+# ======================================= LinShare Configuration BasicAuthentication ===================================
+# Authentication is mandatory if you choose LinShare, TechnicalAccount is need to connect to LinShare specific service.
+# For Example: It will be formalized to 'Authorization: Basic {Credential of UUID/password}'
+
+# blob.export.linshare.technical.account.uuid=Technical_Account_UUID
+# blob.export.linshare.technical.account.password=password
\ No newline at end of file
diff --git a/tmail-backend/apps/postgres/sample-configuration/distributed/blob.properties b/tmail-backend/apps/postgres/sample-configuration/distributed/blob.properties
new file mode 100644
index 00000000000..a02854b2313
--- /dev/null
+++ b/tmail-backend/apps/postgres/sample-configuration/distributed/blob.properties
@@ -0,0 +1,104 @@
+# ============================================= BlobStore Implementation ==================================
+# Read https://james.apache.org/server/config-blobstore.html for further details
+
+# Choose your BlobStore implementation
+# Mandatory, allowed values are: file, s3, postgres.
+implementation=s3
+
+# ========================================= Deduplication ========================================
+# If you choose to enable deduplication, the mails with the same content will be stored only once.
+# Warning: Once this feature is enabled, there is no turning back as turning it off will lead to the deletion of all
+# the mails sharing the same content once one is deleted.
+# Mandatory, Allowed values are: true, false
+deduplication.enable=true
+
+# deduplication.family needs to be incremented every time the deduplication.generation.duration is changed
+# Positive integer, defaults to 1
+# deduplication.gc.generation.family=1
+
+# Duration of generation.
+# Deduplication only takes place within a singe generation.
+# Only items two generation old can be garbage collected. (This prevent concurrent insertions issues and
+# accounts for a clock skew).
+# deduplication.family needs to be incremented everytime this parameter is changed.
+# Duration. Default unit: days. Defaults to 30 days.
+# deduplication.gc.generation.duration=30days
+
+# ========================================= Encryption ========================================
+# If you choose to enable encryption, the blob content will be encrypted before storing them in the BlobStore.
+# Warning: Once this feature is enabled, there is no turning back as turning it off will lead to all content being
+# encrypted. This comes at a performance impact but presents you from leaking data if, for instance the third party
+# offering you a S3 service is compromised.
+# Optional, Allowed values are: true, false, defaults to false
+encryption.aes.enable=false
+
+# Mandatory (if AES encryption is enabled) salt and password. Salt needs to be an hexadecimal encoded string
+#encryption.aes.password=xxx
+#encryption.aes.salt=73616c7479
+# Optional, defaults to PBKDF2WithHmacSHA512
+#encryption.aes.private.key.algorithm=PBKDF2WithHmacSHA512
+
+# ============================================== ObjectStorage ============================================
+
+# ========================================= ObjectStorage Buckets ==========================================
+# bucket names prefix
+# Optional, default no prefix
+# objectstorage.bucketPrefix=prod-
+
+# Default bucket name
+# Optional, default is bucketPrefix + `default`
+# objectstorage.namespace=james
+
+# ========================================= ObjectStorage on S3 =============================================
+# Mandatory if you choose s3 storage service, S3 authentication endpoint
+objectstorage.s3.endPoint=${env:OBJECT_STORAGE_ENDPOINT:-http://s3.docker.test:8000/}
+
+# Mandatory if you choose s3 storage service, S3 region
+#objectstorage.s3.region=eu-west-1
+objectstorage.s3.region=${env:OBJECT_STORAGE_REGION:-eu-west-1}
+
+# Mandatory if you choose aws-s3 storage service, access key id configured in S3
+objectstorage.s3.accessKeyId=${env:OBJECT_STORAGE_ACCESS_KEY_ID:-accessKey1}
+
+# Mandatory if you choose s3 storage service, secret key configured in S3
+objectstorage.s3.secretKey=${env:OBJECT_STORAGE_SECRET_KEY:-secretKey1}
+
+# Optional if you choose s3 storage service: The trust store file, secret, and algorithm to use
+# when connecting to the storage service. If not specified falls back to Java defaults.
+#objectstorage.s3.truststore.path=
+#objectstorage.s3.truststore.type=JKS
+#objectstorage.s3.truststore.secret=
+#objectstorage.s3.truststore.algorithm=SunX509
+
+
+# optional: Object read in memory will be rejected if they exceed the size limit exposed here. Size, exemple `100M`.
+# Supported units: K, M, G, defaults to B if no unit is specified. If unspecified, big object won't be prevented
+# from being loaded in memory. This settings complements protocol limits.
+# objectstorage.s3.in.read.limit=50M
+
+# ============================================ Blobs Exporting ==============================================
+# Read https://james.apache.org/server/config-blob-export.html for further details
+
+# Choosing blob exporting mechanism, allowed mechanism are: localFile, linshare
+# LinShare is a file sharing service, will be explained in the below section
+# Optional, default is localFile
+blob.export.implementation=localFile
+
+# ======================================= Local File Blobs Exporting ========================================
+# Optional, directory to store exported blob, directory path follows James file system format
+# default is file://var/blobExporting
+blob.export.localFile.directory=file://var/blobExporting
+
+# ======================================= LinShare File Blobs Exporting ========================================
+# LinShare is a sharing service where you can use james, connects to an existing LinShare server and shares files to
+# other mail addresses as long as those addresses available in LinShare. For example you can deploy James and LinShare
+# sharing the same LDAP repository
+# Mandatory if you choose LinShare, url to connect to LinShare service
+# blob.export.linshare.url=http://linshare:8080
+
+# ======================================= LinShare Configuration BasicAuthentication ===================================
+# Authentication is mandatory if you choose LinShare, TechnicalAccount is need to connect to LinShare specific service.
+# For Example: It will be formalized to 'Authorization: Basic {Credential of UUID/password}'
+
+# blob.export.linshare.technical.account.uuid=Technical_Account_UUID
+# blob.export.linshare.technical.account.password=password
diff --git a/tmail-backend/apps/postgres/sample-configuration/distributed/opensearch.properties b/tmail-backend/apps/postgres/sample-configuration/distributed/opensearch.properties
new file mode 100644
index 00000000000..df261c5dee6
--- /dev/null
+++ b/tmail-backend/apps/postgres/sample-configuration/distributed/opensearch.properties
@@ -0,0 +1,101 @@
+# 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.
+
+# This template file can be used as example for James Server configuration
+# DO NOT USE IT AS SUCH AND ADAPT IT TO YOUR NEEDS
+
+# Configuration file for OpenSearch
+# Read https://james.apache.org/server/config-opensearch.html for further details
+
+opensearch.masterHost=opensearch
+opensearch.port=9200
+
+# Optional. Only http or https are accepted, default is http
+# opensearch.hostScheme=http
+
+# Optional, default is `default`
+# Choosing the SSL check strategy when using https scheme
+# default: Use the default SSL TrustStore of the system.
+# ignore: Ignore SSL Validation check (not recommended).
+# override: Override the SSL Context to use a custom TrustStore containing ES server's certificate.
+# opensearch.hostScheme.https.sslValidationStrategy=default
+
+# Optional. Required when using 'https' scheme and 'override' sslValidationStrategy
+# Configure OpenSearch rest client to use this trustStore file to recognize nginx's ssl certificate.
+# You need to specify both trustStorePath and trustStorePassword
+# opensearch.hostScheme.https.trustStorePath=/file/to/trust/keystore.jks
+
+# Optional. Required when using 'https' scheme and 'override' sslValidationStrategy
+# Configure OpenSearch rest client to use this trustStore file with the specified password.
+# You need to specify both trustStorePath and trustStorePassword
+# opensearch.hostScheme.https.trustStorePassword=myJKSPassword
+
+# Optional. default is `default`
+# Configure OpenSearch rest client to use host name verifier during SSL handshake
+# default: using the default hostname verifier provided by apache http client.
+# accept_any_hostname: accept any host (not recommended).
+# opensearch.hostScheme.https.hostNameVerifier=default
+
+# Optional.
+# Basic auth username to access opensearch.
+# Ignore opensearch.user and opensearch.password to not be using authentication (default behaviour).
+# Otherwise, you need to specify both properties.
+# opensearch.user=elasticsearch
+
+# Optional.
+# Basic auth password to access opensearch.
+# Ignore opensearch.user and opensearch.password to not be using authentication (default behaviour).
+# Otherwise, you need to specify both properties.
+# opensearch.password=secret
+
+# You can alternatively provide a list of hosts following this format :
+# opensearch.hosts=host1:9200,host2:9200
+# opensearch.clusterName=cluster
+
+opensearch.nb.shards=5
+opensearch.nb.replica=1
+opensearch.index.waitForActiveShards=1
+opensearch.retryConnection.maxRetries=7
+opensearch.retryConnection.minDelay=3000
+# Index or not attachments (default value: true)
+# Note: Attachments not implemented yet for postgresql, false for now
+opensearch.indexAttachments=false
+
+# Search overrides allow resolution of predefined search queries against alternative sources of data
+# and allow bypassing opensearch. This is useful to handle most resynchronisation queries that
+# are simple enough to be resolved against Cassandra.
+#
+# Possible values are:
+# - `org.apache.james.mailbox.postgres.search.AllSearchOverride` Some IMAP clients uses SEARCH ALL to fully list messages in
+# a mailbox and detect deletions. This is typically done by clients not supporting QRESYNC and from an IMAP perspective
+# is considered an optimisation as less data is transmitted compared to a FETCH command. Resolving such requests against
+# Postgresql is enabled by this search override and likely desirable.
+# - `org.apache.james.mailbox.postgres.search.UidSearchOverride`. Same as above but restricted by ranges.
+# - `org.apache.james.mailbox.postgres.search.DeletedSearchOverride`. Find deleted messages by looking up in the relevant Postgresql
+# table.
+# - `org.apache.james.mailbox.postgres.search.DeletedWithRangeSearchOverride`. Same as above but limited by ranges.
+# - `org.apache.james.mailbox.postgres.search.NotDeletedWithRangeSearchOverride`. List non deleted messages in a given range.
+# Lists all messages and filters out deleted message thus this is based on the following heuristic: most messages are not marked as deleted.
+# - `org.apache.james.mailbox.postgres.search.UnseenSearchOverride`. List unseen messages in the corresponding Postgresql index.
+#
+# Please note that custom overrides can be defined here.
+#
+# opensearch.search.overrides=org.apache.james.mailbox.postgres.search.AllSearchOverride,org.apache.james.mailbox.postgres.search.DeletedSearchOverride, org.apache.james.mailbox.postgres.search.DeletedWithRangeSearchOverride,org.apache.james.mailbox.postgres.search.NotDeletedWithRangeSearchOverride,org.apache.james.mailbox.postgres.search.UidSearchOverride,org.apache.james.mailbox.postgres.search.UnseenSearchOverride
+
+# Optional. Default is `false`
+# When set to true, James will attempt to reindex from the indexed message when moved. If the message is not found, it will fall back to the old behavior (The message will be indexed from the blobStore source)
+# opensearch.message.index.optimize.move=false
\ No newline at end of file
diff --git a/tmail-backend/apps/postgres/sample-configuration/distributed/rabbitmq.properties b/tmail-backend/apps/postgres/sample-configuration/distributed/rabbitmq.properties
new file mode 100644
index 00000000000..c8b3f526026
--- /dev/null
+++ b/tmail-backend/apps/postgres/sample-configuration/distributed/rabbitmq.properties
@@ -0,0 +1,103 @@
+# RabbitMQ configuration
+
+# Read https://james.apache.org/server/config-rabbitmq.html for further details
+
+# Mandatory
+uri=amqp://rabbitmq:5672
+# If you use a vhost, specify it as well at the end of the URI
+# uri=amqp://rabbitmq:5672/vhost
+
+# Vhost to use for creating queues and exchanges
+# Optional, only use this if you have invalid URIs containing characters like '_'
+# vhost=vhost1
+
+# Optional, default to the host specified as part of the URI.
+# Allow creating cluster aware connections.
+# hosts=ip1:5672,ip2:5672
+
+# RabbitMQ Administration Management
+# Mandatory
+management.uri=http://rabbitmq:15672
+# Mandatory
+management.user=guest
+# Mandatory
+management.password=guest
+
+# Configure retries count to retrieve a connection. Exponential backoff is performed between each retries.
+# Optional integer, defaults to 10
+#connection.pool.retries=10
+# Configure initial duration (in ms) between two connection retries. Exponential backoff is performed between each retries.
+# Optional integer, defaults to 100
+#connection.pool.min.delay.ms=100
+# Configure retries count to retrieve a channel. Exponential backoff is performed between each retries.
+# Optional integer, defaults to 3
+#channel.pool.retries=3
+# Configure timeout duration (in ms) to obtain a rabbitmq channel. Defaults to 30 seconds.
+# Optional integer, defaults to 30 seconds.
+#channel.pool.max.delay.ms=30000
+# Configure the size of the channel pool.
+# Optional integer, defaults to 3
+#channel.pool.size=3
+
+# Boolean. Whether to activate Quorum queue usage for use cases that benefits from it (work queue).
+# Quorum queues enables high availability.
+# False (default value) results in the usage of classic queues.
+#quorum.queues.enable=true
+
+# Strictly positive integer. The replication factor to use when creating quorum queues.
+#quorum.queues.replication.factor
+
+# Parameters for the Cassandra administrative view
+
+# Whether the Cassandra administrative view should be activated. Boolean value defaulting to true.
+# Not necessarily needed for MDA deployments, mail queue management adds significant complexity.
+# cassandra.view.enabled=true
+
+# Period of the window. Too large values will lead to wide rows while too little values might lead to many queries.
+# Use the number of mail per Cassandra row, along with your expected traffic, to determine this value
+# This value can only be decreased to a value dividing the current value
+# Optional, default 1h
+mailqueue.view.sliceWindow=1h
+
+# Use to distribute the emails of a given slice within your cassandra cluster
+# A good value is 2*cassandraNodeCount
+# This parameter can only be increased.
+# Optional, default 1
+mailqueue.view.bucketCount=1
+
+# Determine the probability to update the browse start pointer
+# Too little value will lead to unnecessary reads. Too big value will lead to more expensive browse.
+# Choose this parameter so that it get's update one time every one-two sliceWindow
+# Optional, default 1000
+mailqueue.view.updateBrowseStartPace=1000
+
+# Enables or disables the gauge metric on the mail queue size
+# Computing the size of the mail queue is currently implemented on top of browse operation and thus have a linear complexity
+# Metrics get exported periodically as configured in opensearch.properties, thus getSize is also called periodically
+# Choose to disable it when the mail queue size is getting too big
+# Note that this is as well a temporary workaround until we get 'getSize' method better optimized
+# Optional, default false
+mailqueue.size.metricsEnabled=false
+
+# Whether to enable task consumption on this node. Tasks are WebAdmin triggered long running jobs.
+# Disable with caution (this only makes sense in a distributed setup where other nodes consume tasks).
+# Defaults to true.
+task.consumption.enabled=true
+
+# Configure task queue consumer timeout. References: https://www.rabbitmq.com/consumers.html#acknowledgement-timeout. Required at least RabbitMQ version 3.12 to have effect.
+# This is used to avoid the task queue consumer (which could run very long tasks) being disconnected by RabbitMQ after the default acknowledgement timeout 30 minutes.
+# Optional. Duration (support multiple time units cf `DurationParser`), defaults to 1 day.
+#task.queue.consumer.timeout=1day
+
+# Configure queue ttl (in ms). References: https://www.rabbitmq.com/ttl.html#queue-ttl.
+# This is used only on queues used to share notification patterns, are exclusive to a node. If omitted, it will not add the TTL configure when declaring queues.
+# Optional integer, defaults is 3600000.
+#notification.queue.ttl=3600000
+
+# AMQP resources parameters for the subscriber email address contact messages. In order to synchronize contacts between Team-mail backend and 3rd.
+# AQMP uri
+address.contact.uri=amqp://rabbitmq:5672
+address.contact.user=guest
+address.contact.password=guest
+# Queue name
+address.contact.queue=AddressContactQueue1
\ No newline at end of file
diff --git a/tmail-backend/apps/postgres/sample-configuration/dnsservice.xml b/tmail-backend/apps/postgres/sample-configuration/dnsservice.xml
new file mode 100644
index 00000000000..1c4fb9edc7e
--- /dev/null
+++ b/tmail-backend/apps/postgres/sample-configuration/dnsservice.xml
@@ -0,0 +1,27 @@
+
+
+
+
+
+
+ true
+ false
+ 50000
+
diff --git a/tmail-backend/apps/postgres/sample-configuration/domainlist.xml b/tmail-backend/apps/postgres/sample-configuration/domainlist.xml
new file mode 100644
index 00000000000..605439fbd0e
--- /dev/null
+++ b/tmail-backend/apps/postgres/sample-configuration/domainlist.xml
@@ -0,0 +1,27 @@
+
+
+
+
+
+
+ false
+ false
+ localhost
+
diff --git a/tmail-backend/apps/postgres/sample-configuration/imapserver.xml b/tmail-backend/apps/postgres/sample-configuration/imapserver.xml
new file mode 100644
index 00000000000..e9208cff97e
--- /dev/null
+++ b/tmail-backend/apps/postgres/sample-configuration/imapserver.xml
@@ -0,0 +1,62 @@
+
+
+
+
+
+
+
+
+ imapserver
+ 0.0.0.0:143
+ 200
+
+
+ file://conf/keystore
+ james72laBalle
+ org.bouncycastle.jce.provider.BouncyCastleProvider
+
+ 0
+ 0
+ 120
+ SECONDS
+ true
+ true
+
+
+ imapserver-ssl
+ 0.0.0.0:993
+ 200
+
+
+ file://conf/keystore
+ james72laBalle
+ org.bouncycastle.jce.provider.BouncyCastleProvider
+
+ 0
+ 0
+ 120
+ SECONDS
+ true
+
+
diff --git a/tmail-backend/apps/postgres/sample-configuration/jmap.properties b/tmail-backend/apps/postgres/sample-configuration/jmap.properties
new file mode 100644
index 00000000000..9d1ecbed26c
--- /dev/null
+++ b/tmail-backend/apps/postgres/sample-configuration/jmap.properties
@@ -0,0 +1,37 @@
+# Configuration file for JMAP
+# Read https://james.apache.org/server/config-jmap.html for further details
+
+enabled=true
+
+tls.keystoreURL=file://conf/keystore
+tls.secret=james72laBalle
+
+#
+# If you wish to use OAuth authentication, you should provide a valid JWT public key.
+# The following entry specify the link to the URL of the public key file,
+# which should be a PEM format file.
+#
+jwt.publickeypem.url=file://conf/jwt_publickey
+
+# Should simple Email/query be resolved against a Cassandra projection, or should we resolve them against OpenSearch?
+# This enables a higher resilience, but the projection needs to be correctly populated. False by default.
+# view.email.query.enabled=true
+
+# For generate short lived token
+jwt.privatekeypem.url=file://conf/jwt_privatekey
+
+# If you want to specify authentication strategies for Jmap rfc-8621 version
+# For custom Authentication Strategy not inside package "org.apache.james.jmap.http", you have to specify its FQDN
+authentication.strategy.rfc8621=JWTAuthenticationStrategy,BasicAuthenticationStrategy,com.linagora.tmail.james.jmap.ticket.TicketAuthenticationStrategy
+
+# Gives an URL for OpenID discovery being exposed on .well-known/webfinger endpoint
+# CF https://openid.net/specs/openid-connect-discovery-1_0.html
+# oidc.provider.url=https://auth.linagora.com/auth/realms/jmap
+
+# The directory location for the email templates used to generate the reply mail for the CalendarEvent method.
+calendarEvent.reply.mailTemplateLocation=file://eml-template/
+
+# The supported languages for replying to CalendarEvent emails
+calendarEvent.reply.supportedLanguages=en,fr
+
+url.prefix=http://localhost
\ No newline at end of file
diff --git a/tmail-backend/apps/postgres/sample-configuration/jmx.properties b/tmail-backend/apps/postgres/sample-configuration/jmx.properties
new file mode 100644
index 00000000000..e56235f9b4a
--- /dev/null
+++ b/tmail-backend/apps/postgres/sample-configuration/jmx.properties
@@ -0,0 +1,26 @@
+# 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.
+#
+
+# This template file can be used as example for James Server configuration
+# DO NOT USE IT AS SUCH AND ADAPT IT TO YOUR NEEDS
+
+# Read https://james.apache.org/server/config-system.html#jmx.properties for further details
+
+jmx.enabled=true
+jmx.address=127.0.0.1
+jmx.port=9999
diff --git a/tmail-backend/apps/postgres/sample-configuration/jvm.properties b/tmail-backend/apps/postgres/sample-configuration/jvm.properties
new file mode 100644
index 00000000000..c8a0ae2cdb6
--- /dev/null
+++ b/tmail-backend/apps/postgres/sample-configuration/jvm.properties
@@ -0,0 +1,56 @@
+# ============================================= Extra JVM System Properties ===========================================
+# To avoid clutter on the command line, any properties in this file will be added as system properties on server start.
+
+# Example: If you need an option -Dmy.property=whatever, you can instead add it here as
+# my.property=whatever
+
+# (Optional). String (size, integer + size units, example: `12 KIB`, supported units are bytes KIB MIB GIB TIB). Defaults to 100KIB.
+# This governs the threshold MimeMessageInputStreamSource relies on for storing MimeMessage content on disk.
+# Below, data is stored in memory. Above data is stored on disk.
+# Lower values will lead to longer processing time but will minimize heap memory usage. Modern SSD hardware
+# should however support a high throughput. Higher values will lead to faster single mail processing at the cost
+# of higher heap usage.
+#james.message.memory.threshold=12K
+
+# Optional. Boolean. Defaults to false. Recommended value is false.
+# Should MimeMessageWrapper use a copy of the message in memory? Or should bigger message exceeding james.message.memory.threshold
+# be copied to temporary files?
+#james.message.usememorycopy=false
+
+# Mode level of resource leak detection. It is used to detect a resource not be disposed of before it's garbage-collected.
+# Example `MimeMessageInputStreamSource`
+# Optional. Allowed values are: none, simple, advanced, testing
+# - none: Disables resource leak detection.
+# - simple: Enables output a simplistic error log if a leak is encountered and would free the resources (default).
+# - advanced: Enables output an advanced error log implying the place of allocation of the underlying object and would free resources.
+# - testing: Enables output an advanced error log implying the place of allocation of the underlying object and rethrow an error, that action is being taken by the development team.
+#james.lifecycle.leak.detection.mode=simple
+
+# Should we add the host in the MDC logging context for incoming IMAP, SMTP, POP3? Doing so, a DNS resolution
+# is attempted for each incoming connection, which can be costly. Remote IP is always added to the logging context.
+# Optional. Boolean. Defaults to true.
+#james.protocols.mdc.hostname=true
+
+# Manage netty leak detection level see https://netty.io/wiki/reference-counted-objects.html#leak-detection-levels
+# io.netty.leakDetection.level=SIMPLE
+
+# Should James exit on Startup error? Boolean, defaults to true. This prevents partial startup.
+# james.exit.on.startup.error=true
+
+# Fails explicitly on missing configuration file rather that taking implicit values. Defautls to false.
+# james.fail.on.missing.configuration=true
+
+# JMX, when enable causes RMI to plan System.gc every hour. Set this instead to once every 1000h.
+sun.rmi.dgc.server.gcInterval=3600000000
+sun.rmi.dgc.client.gcInterval=3600000000
+
+# Automatically generate a JMX password upon start. CLI is able to retrieve this password.
+james.jmx.credential.generation=true
+
+# Disable Remote Code Execution feature from JMX
+# CF https://github.com/AdoptOpenJDK/openjdk-jdk11/blob/19fb8f93c59dfd791f62d41f332db9e306bc1422/src/java.management/share/classes/com/sun/jmx/remote/security/MBeanServerAccessController.java#L646
+jmx.remote.x.mlet.allow.getMBeansFromURL=false
+
+# Relax validating `*` and `%` characters in the mailbox name. Defaults to false.
+# Be careful turning on this as `%` and `*` are ambiguous for the LIST / LSUB commands that interpret those as wildcard thus returning all mailboxes matching the pattern.
+james.relaxed.mailbox.name.validation=true
diff --git a/tmail-backend/apps/postgres/sample-configuration/listeners.xml b/tmail-backend/apps/postgres/sample-configuration/listeners.xml
new file mode 100644
index 00000000000..d43af15c39a
--- /dev/null
+++ b/tmail-backend/apps/postgres/sample-configuration/listeners.xml
@@ -0,0 +1,29 @@
+
+
+
+
+
+
+ true
+
+ org.apache.james.jmap.event.PopulateEmailQueryViewListener
+ true
+
+
\ No newline at end of file
diff --git a/tmail-backend/apps/postgres/sample-configuration/logback.xml b/tmail-backend/apps/postgres/sample-configuration/logback.xml
new file mode 100644
index 00000000000..6f386173fe2
--- /dev/null
+++ b/tmail-backend/apps/postgres/sample-configuration/logback.xml
@@ -0,0 +1,42 @@
+
+
+
+
+ true
+
+
+
+
+ %d{HH:mm:ss.SSS} %highlight([%-5level]) %logger{15} - %msg%n%rEx
+ false
+
+
+
+
+ /logs/james.log
+
+ /logs/james.%i.log.tar.gz
+ 1
+ 3
+
+
+
+ 100MB
+
+
+
+ %d{HH:mm:ss.SSS} [%-5level] %logger{15} - %msg%n%rEx
+ false
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/tmail-backend/apps/postgres/sample-configuration/mailbox.properties b/tmail-backend/apps/postgres/sample-configuration/mailbox.properties
new file mode 100644
index 00000000000..4f3fd787131
--- /dev/null
+++ b/tmail-backend/apps/postgres/sample-configuration/mailbox.properties
@@ -0,0 +1 @@
+gpg.encryption.enable=false
\ No newline at end of file
diff --git a/tmail-backend/apps/postgres/sample-configuration/mailetcontainer.xml b/tmail-backend/apps/postgres/sample-configuration/mailetcontainer.xml
new file mode 100644
index 00000000000..87ca1447ce7
--- /dev/null
+++ b/tmail-backend/apps/postgres/sample-configuration/mailetcontainer.xml
@@ -0,0 +1,159 @@
+
+
+
+
+
+
+
+
+
+
+ postmaster
+
+
+
+ 20
+ postgres://var/mail/error/
+
+
+
+
+
+
+ postgres://var/mail/relay-limit-exceeded/
+
+
+ transport
+
+
+
+
+
+ mailetContainerErrors
+
+
+ ignore
+
+
+ postgres://var/mail/error/
+ propagate
+
+
+
+
+
+
+
+
+
+
+
+
+
+ rrt-error
+
+
+
+
+ local-delivery
+
+
+ local-address-error
+ 550 - Requested action not taken: no such user here
+
+
+
+ relay
+
+
+
+
+
+
+
+
+
+
+
+
+
+ outgoing
+ 5000, 100000, 500000
+ 3
+ 0
+ 10
+ true
+ bounces
+
+
+
+
+
+ mailetContainerLocalAddressError
+
+
+ none
+
+
+ postgres://var/mail/address-error/
+
+
+
+
+
+ mailetContainerRelayDenied
+
+
+ none
+
+
+ postgres://var/mail/relay-denied/
+ Warning: You are sending an e-mail to a remote server. You must be authenticated to perform such an operation
+
+
+
+
+
+ bounces
+
+
+ false
+
+
+
+
+
+ postgres://var/mail/rrt-error/
+ true
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/tmail-backend/apps/postgres/sample-configuration/mailrepositorystore.xml b/tmail-backend/apps/postgres/sample-configuration/mailrepositorystore.xml
new file mode 100644
index 00000000000..445f2727f29
--- /dev/null
+++ b/tmail-backend/apps/postgres/sample-configuration/mailrepositorystore.xml
@@ -0,0 +1,35 @@
+
+
+
+
+
+
+
+ postgres
+
+
+
+ postgres
+
+
+
+
+
+
diff --git a/tmail-backend/apps/postgres/sample-configuration/postgres.properties b/tmail-backend/apps/postgres/sample-configuration/postgres.properties
new file mode 100644
index 00000000000..a32efa67180
--- /dev/null
+++ b/tmail-backend/apps/postgres/sample-configuration/postgres.properties
@@ -0,0 +1,20 @@
+# String. Optional, default to 'postgres'. Database name.
+database.name=${env:POSTGRES_DB:-postgres}
+
+# String. Optional, default to 'public'. Database schema.
+database.schema=${env:POSTGRES_SCHEMA:-public}
+
+# String. Optional, default to 'localhost'. Database host.
+database.host=${env:POSTGRES_HOST:-postgres}
+
+# Integer. Optional, default to 5432. Database port.
+database.port=${env:POSTGRES_PORT:-5432}
+
+# String. Required. Database username.
+database.username=${env:POSTGRES_USER:-tmail}
+
+# String. Required. Database password of the user.
+database.password=${env:POSTGRES_PASSWORD:-secret1}
+
+# Boolean. Optional, default to false. Whether to enable row level security.
+row.level.security.enabled=false
diff --git a/tmail-backend/apps/postgres/sample-configuration/recipientrewritetable.xml b/tmail-backend/apps/postgres/sample-configuration/recipientrewritetable.xml
new file mode 100644
index 00000000000..1a512c60351
--- /dev/null
+++ b/tmail-backend/apps/postgres/sample-configuration/recipientrewritetable.xml
@@ -0,0 +1,28 @@
+
+
+
+
+
+
+
+ true
+ 10
+
+
diff --git a/tmail-backend/apps/postgres/sample-configuration/search.properties b/tmail-backend/apps/postgres/sample-configuration/search.properties
new file mode 100644
index 00000000000..51833746a92
--- /dev/null
+++ b/tmail-backend/apps/postgres/sample-configuration/search.properties
@@ -0,0 +1,2 @@
+# not for production purposes. To be replaced by PG based search.
+implementation=scanning
\ No newline at end of file
diff --git a/tmail-backend/apps/postgres/sample-configuration/smtpserver.xml b/tmail-backend/apps/postgres/sample-configuration/smtpserver.xml
new file mode 100644
index 00000000000..d6ac746f797
--- /dev/null
+++ b/tmail-backend/apps/postgres/sample-configuration/smtpserver.xml
@@ -0,0 +1,104 @@
+
+
+
+
+
+
+
+
+ smtpserver-global
+ 0.0.0.0:25
+ 200
+
+ file://conf/keystore
+ james72laBalle
+ org.bouncycastle.jce.provider.BouncyCastleProvider
+ SunX509
+
+ 360
+ 0
+ 0
+ false
+ 127.0.0.0/8
+ false
+ 0
+ true
+ Apache JAMES awesome SMTP Server
+
+
+
+
+
+ smtpserver-TLS
+ 0.0.0.0:465
+ 200
+
+ file://conf/keystore
+ james72laBalle
+ org.bouncycastle.jce.provider.BouncyCastleProvider
+ SunX509
+
+ 360
+ 0
+ 0
+
+ true
+ 127.0.0.0/8
+
+ false
+ 0
+ true
+ Apache JAMES awesome SMTP Server
+
+
+
+
+
+ smtpserver-authenticated
+ 0.0.0.0:587
+ 200
+
+ file://conf/keystore
+ james72laBalle
+ org.bouncycastle.jce.provider.BouncyCastleProvider
+ SunX509
+
+ 360
+ 0
+ 0
+
+ true
+ 127.0.0.0/8
+
+ false
+ 0
+ true
+ Apache JAMES awesome SMTP Server
+
+
+
+
+
+
+
diff --git a/tmail-backend/apps/postgres/sample-configuration/usersrepository.xml b/tmail-backend/apps/postgres/sample-configuration/usersrepository.xml
new file mode 100644
index 00000000000..e32286f482a
--- /dev/null
+++ b/tmail-backend/apps/postgres/sample-configuration/usersrepository.xml
@@ -0,0 +1,40 @@
+
+
+
+
+
+
+ SHA-512
+ true
+ true
+
+
+
+
diff --git a/tmail-backend/apps/postgres/sample-configuration/webadmin.properties b/tmail-backend/apps/postgres/sample-configuration/webadmin.properties
new file mode 100644
index 00000000000..5d72d99b744
--- /dev/null
+++ b/tmail-backend/apps/postgres/sample-configuration/webadmin.properties
@@ -0,0 +1,54 @@
+# 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.
+
+# This template file can be used as example for James Server configuration
+# DO NOT USE IT AS SUCH AND ADAPT IT TO YOUR NEEDS
+
+# Read https://james.apache.org/server/config-webadmin.html for further details
+
+enabled=true
+port=8000
+host=0.0.0.0
+
+# Defaults to false
+https.enabled=false
+
+# Compulsory when enabling HTTPS
+#https.keystore=/path/to/keystore
+#https.password=password
+
+# Optional when enabling HTTPS (self signed)
+#https.trust.keystore
+#https.trust.password
+
+# Defaults to false
+#jwt.enabled=true
+#
+## If you wish to use OAuth authentication, you should provide a valid JWT public key.
+## The following entry specify the link to the URL of the public key file,
+## which should be a PEM format file.
+##
+#jwt.publickeypem.url=file://conf/jwt_publickey
+
+# Defaults to false
+#cors.enable=true
+#cors.origin
+
+# List of fully qualified class names that should be exposed over webadmin
+# in addition to your product default routes. Routes needs to be located
+# within the classpath or in the ./extensions-jars folder.
+#extensions.routes=
\ No newline at end of file
diff --git a/tmail-backend/apps/postgres/src/main/eml-template/calendar_reply_accepted-en.eml b/tmail-backend/apps/postgres/src/main/eml-template/calendar_reply_accepted-en.eml
new file mode 100644
index 00000000000..d3e89e8556f
--- /dev/null
+++ b/tmail-backend/apps/postgres/src/main/eml-template/calendar_reply_accepted-en.eml
@@ -0,0 +1,5 @@
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Subject: ACCEPTED: {{EVENT_TITLE}} @ {{EVENT_START_DATE}} ({{ATTENDEE}})
+
+{{ATTENDEE}} has accepted this invitation.
diff --git a/tmail-backend/apps/postgres/src/main/eml-template/calendar_reply_accepted-fr.eml b/tmail-backend/apps/postgres/src/main/eml-template/calendar_reply_accepted-fr.eml
new file mode 100644
index 00000000000..476719d6cc9
--- /dev/null
+++ b/tmail-backend/apps/postgres/src/main/eml-template/calendar_reply_accepted-fr.eml
@@ -0,0 +1,5 @@
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Subject: =?ISO-8859-1?Q?ACCEPT=C9?=: {{EVENT_TITLE}} @ {{EVENT_START_DATE}} ({{ATTENDEE}})
+
+{{ATTENDEE}} a accepté cette invitation.
diff --git a/tmail-backend/apps/postgres/src/main/eml-template/calendar_reply_declined-en.eml b/tmail-backend/apps/postgres/src/main/eml-template/calendar_reply_declined-en.eml
new file mode 100644
index 00000000000..52bb30af4ec
--- /dev/null
+++ b/tmail-backend/apps/postgres/src/main/eml-template/calendar_reply_declined-en.eml
@@ -0,0 +1,5 @@
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Subject: Declined: {{EVENT_TITLE}} @ {{EVENT_START_DATE}} ({{ATTENDEE}})
+
+{{ATTENDEE}} has declined this invitation.
diff --git a/tmail-backend/apps/postgres/src/main/eml-template/calendar_reply_declined-fr.eml b/tmail-backend/apps/postgres/src/main/eml-template/calendar_reply_declined-fr.eml
new file mode 100644
index 00000000000..b733953f820
--- /dev/null
+++ b/tmail-backend/apps/postgres/src/main/eml-template/calendar_reply_declined-fr.eml
@@ -0,0 +1,5 @@
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Subject: =?ISO-8859-1?Q?D=E9clin=E9?=: {{EVENT_TITLE}} @ {{EVENT_START_DATE}} ({{ATTENDEE}})
+
+{{ATTENDEE}} a décliné cette invitation.
diff --git a/tmail-backend/apps/postgres/src/main/eml-template/calendar_reply_tentative-en.eml b/tmail-backend/apps/postgres/src/main/eml-template/calendar_reply_tentative-en.eml
new file mode 100644
index 00000000000..954a905d652
--- /dev/null
+++ b/tmail-backend/apps/postgres/src/main/eml-template/calendar_reply_tentative-en.eml
@@ -0,0 +1,5 @@
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Subject: Tentatively Accepted: {{EVENT_TITLE}} @ {{EVENT_START_DATE}} ({{ATTENDEE}})
+
+{{ATTENDEE}} has replied Maybe to this invitation.
diff --git a/tmail-backend/apps/postgres/src/main/eml-template/calendar_reply_tentative-fr.eml b/tmail-backend/apps/postgres/src/main/eml-template/calendar_reply_tentative-fr.eml
new file mode 100644
index 00000000000..df613e1a1b3
--- /dev/null
+++ b/tmail-backend/apps/postgres/src/main/eml-template/calendar_reply_tentative-fr.eml
@@ -0,0 +1,5 @@
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Subject: =?ISO-8859-1?Q?Accept=E9_provisoirement?=: {{EVENT_TITLE}} @ {{EVENT_START_DATE}} ({{ATTENDEE}})
+
+{{ATTENDEE}} a répondu Peut-être à cette invitation.
diff --git a/tmail-backend/apps/postgres/src/main/extensions-jars/README.md b/tmail-backend/apps/postgres/src/main/extensions-jars/README.md
new file mode 100644
index 00000000000..2cea7599812
--- /dev/null
+++ b/tmail-backend/apps/postgres/src/main/extensions-jars/README.md
@@ -0,0 +1,5 @@
+# Adding Jars to JAMES
+
+The jar in this folder will be added to JAMES classpath when mounted under /root/extensions-jars inside the running container.
+
+You can use it to add you customs Mailets/Matchers.
diff --git a/tmail-backend/apps/postgres/src/main/glowroot/admin.json b/tmail-backend/apps/postgres/src/main/glowroot/admin.json
new file mode 100644
index 00000000000..c75c59d555a
--- /dev/null
+++ b/tmail-backend/apps/postgres/src/main/glowroot/admin.json
@@ -0,0 +1,5 @@
+{
+ "web": {
+ "bindAddress": "0.0.0.0"
+ }
+}
\ No newline at end of file
diff --git a/tmail-backend/apps/postgres/src/main/glowroot/plugins/blobstore.json b/tmail-backend/apps/postgres/src/main/glowroot/plugins/blobstore.json
new file mode 100644
index 00000000000..84291c6e16e
--- /dev/null
+++ b/tmail-backend/apps/postgres/src/main/glowroot/plugins/blobstore.json
@@ -0,0 +1,26 @@
+{
+ "name": "BlobStore Plugin",
+ "id": "blob_store",
+ "instrumentation": [
+ {
+ "captureKind": "trace-entry",
+ "traceEntryMessageTemplate": "{{this.class.name}}.{{methodName}}",
+ "timerName": "blobstore",
+ "className": "org.apache.james.blob.objectstorage.BlobPutter",
+ "methodName": "putDirectly",
+ "methodParameterTypes": [
+ ".."
+ ]
+ },
+ {
+ "captureKind": "trace-entry",
+ "traceEntryMessageTemplate": "{{this.class.name}}.{{methodName}}",
+ "timerName": "blobstore",
+ "className": "org.apache.james.blob.objectstorage.BlobPutter",
+ "methodName": "putAndComputeId",
+ "methodParameterTypes": [
+ ".."
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/tmail-backend/apps/postgres/src/main/glowroot/plugins/imap.json b/tmail-backend/apps/postgres/src/main/glowroot/plugins/imap.json
new file mode 100644
index 00000000000..d27904feb5e
--- /dev/null
+++ b/tmail-backend/apps/postgres/src/main/glowroot/plugins/imap.json
@@ -0,0 +1,19 @@
+{
+ "name": "IMAP Plugin",
+ "id": "imap",
+ "instrumentation": [
+ {
+ "className": "org.apache.james.imap.processor.base.AbstractChainedProcessor",
+ "methodName": "doProcess",
+ "methodParameterTypes": [
+ ".."
+ ],
+ "captureKind": "transaction",
+ "transactionType": "IMAP",
+ "transactionNameTemplate": "IMAP processor : {{this.class.name}}",
+ "alreadyInTransactionBehavior": "capture-trace-entry",
+ "traceEntryMessageTemplate": "{{this.class.name}}.{{methodName}}",
+ "timerName": "imapProcessor"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/tmail-backend/apps/postgres/src/main/glowroot/plugins/mailboxListener.json b/tmail-backend/apps/postgres/src/main/glowroot/plugins/mailboxListener.json
new file mode 100644
index 00000000000..54a55ac1e4c
--- /dev/null
+++ b/tmail-backend/apps/postgres/src/main/glowroot/plugins/mailboxListener.json
@@ -0,0 +1,19 @@
+{
+ "name": "MailboxListener Plugin",
+ "id": "mailboxListener",
+ "instrumentation": [
+ {
+ "className": "org.apache.james.mailbox.events.MailboxListener",
+ "methodName": "event",
+ "methodParameterTypes": [
+ ".."
+ ],
+ "captureKind": "transaction",
+ "transactionType": "MailboxListener",
+ "transactionNameTemplate": "MailboxListener : {{this.class.name}}",
+ "alreadyInTransactionBehavior": "capture-trace-entry",
+ "traceEntryMessageTemplate": "{{this.class.name}}.{{methodName}}",
+ "timerName": "mailboxListener"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/tmail-backend/apps/postgres/src/main/glowroot/plugins/smtp.json b/tmail-backend/apps/postgres/src/main/glowroot/plugins/smtp.json
new file mode 100644
index 00000000000..393bac9d9c3
--- /dev/null
+++ b/tmail-backend/apps/postgres/src/main/glowroot/plugins/smtp.json
@@ -0,0 +1,19 @@
+{
+ "name": "SMTP Plugin",
+ "id": "smtp",
+ "instrumentation": [
+ {
+ "className": "org.apache.james.protocols.smtp.core.AbstractHookableCmdHandler",
+ "methodName": "onCommand",
+ "methodParameterTypes": [
+ ".."
+ ],
+ "captureKind": "transaction",
+ "transactionType": "SMTP",
+ "transactionNameTemplate": "SMTP command : {{this.class.name}}",
+ "alreadyInTransactionBehavior": "capture-trace-entry",
+ "traceEntryMessageTemplate": "{{this.class.name}}.{{methodName}}",
+ "timerName": "smtpProcessor"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/tmail-backend/apps/postgres/src/main/glowroot/plugins/spooler.json b/tmail-backend/apps/postgres/src/main/glowroot/plugins/spooler.json
new file mode 100644
index 00000000000..fd7732de8b2
--- /dev/null
+++ b/tmail-backend/apps/postgres/src/main/glowroot/plugins/spooler.json
@@ -0,0 +1,45 @@
+{
+ "name": "Spooler Plugin",
+ "id": "spooler",
+ "instrumentation": [
+ {
+ "className": "org.apache.james.mailetcontainer.api.MailProcessor",
+ "methodName": "service",
+ "methodParameterTypes": [
+ ".."
+ ],
+ "captureKind": "transaction",
+ "transactionType": "Spooler",
+ "transactionNameTemplate": "Mailet processor : {{this.class.name}}",
+ "alreadyInTransactionBehavior": "capture-trace-entry",
+ "traceEntryMessageTemplate": "{{this.class.name}}.{{methodName}}",
+ "timerName": "mailetProcessor"
+ },
+ {
+ "className": "org.apache.mailet.Mailet",
+ "methodName": "service",
+ "methodParameterTypes": [
+ ".."
+ ],
+ "captureKind": "transaction",
+ "transactionType": "Mailet",
+ "transactionNameTemplate": "Mailet : {{this.class.name}}",
+ "alreadyInTransactionBehavior": "capture-trace-entry",
+ "traceEntryMessageTemplate": "{{this.class.name}}.{{methodName}}",
+ "timerName": "mailet"
+ },
+ {
+ "className": "org.apache.mailet.Matcher",
+ "methodName": "match",
+ "methodParameterTypes": [
+ ".."
+ ],
+ "captureKind": "transaction",
+ "transactionType": "Matcher",
+ "transactionNameTemplate": "Mailet processor : {{this.class.name}}",
+ "alreadyInTransactionBehavior": "capture-trace-entry",
+ "traceEntryMessageTemplate": "{{this.class.name}}.{{methodName}}",
+ "timerName": "matcher"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/tmail-backend/apps/postgres/src/main/glowroot/plugins/task.json b/tmail-backend/apps/postgres/src/main/glowroot/plugins/task.json
new file mode 100644
index 00000000000..8f04c69e741
--- /dev/null
+++ b/tmail-backend/apps/postgres/src/main/glowroot/plugins/task.json
@@ -0,0 +1,19 @@
+{
+ "name": "Task Plugin",
+ "id": "task",
+ "instrumentation": [
+ {
+ "className": "org.apache.james.task.Task",
+ "methodName": "run",
+ "methodParameterTypes": [
+ ".."
+ ],
+ "captureKind": "transaction",
+ "transactionType": "TASK",
+ "transactionNameTemplate": "TASK : {{this.class.name}}",
+ "alreadyInTransactionBehavior": "capture-trace-entry",
+ "traceEntryMessageTemplate": "{{this.class.name}}.{{methodName}}",
+ "timerName": "task"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/tmail-backend/apps/postgres/src/main/java/com/linagora/tmail/james/app/PostgresEmailAddressContactEventDeadLettersModule.java b/tmail-backend/apps/postgres/src/main/java/com/linagora/tmail/james/app/PostgresEmailAddressContactEventDeadLettersModule.java
new file mode 100644
index 00000000000..56c3fc47ab5
--- /dev/null
+++ b/tmail-backend/apps/postgres/src/main/java/com/linagora/tmail/james/app/PostgresEmailAddressContactEventDeadLettersModule.java
@@ -0,0 +1,23 @@
+package com.linagora.tmail.james.app;
+
+import jakarta.inject.Named;
+
+import org.apache.james.backends.postgres.utils.PostgresExecutor;
+import org.apache.james.events.EventDeadLetters;
+import org.apache.james.events.PostgresEventDeadLetters;
+
+import com.google.inject.AbstractModule;
+import com.google.inject.Provides;
+import com.google.inject.Singleton;
+import com.linagora.tmail.james.jmap.EmailAddressContactInjectKeys;
+import com.linagora.tmail.james.jmap.contact.TmailJmapEventSerializer;
+
+public class PostgresEmailAddressContactEventDeadLettersModule extends AbstractModule {
+
+ @Provides
+ @Singleton
+ @Named(EmailAddressContactInjectKeys.AUTOCOMPLETE)
+ EventDeadLetters provideEventDeadLettersForAutoComplete(PostgresExecutor postgresExecutor, TmailJmapEventSerializer tmailJmapEventSerializer) {
+ return new PostgresEventDeadLetters(postgresExecutor, tmailJmapEventSerializer);
+ }
+}
diff --git a/tmail-backend/apps/postgres/src/main/java/com/linagora/tmail/james/app/PostgresTmailConfiguration.java b/tmail-backend/apps/postgres/src/main/java/com/linagora/tmail/james/app/PostgresTmailConfiguration.java
new file mode 100644
index 00000000000..25dc850e0cb
--- /dev/null
+++ b/tmail-backend/apps/postgres/src/main/java/com/linagora/tmail/james/app/PostgresTmailConfiguration.java
@@ -0,0 +1,234 @@
+package com.linagora.tmail.james.app;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.util.Optional;
+
+import org.apache.commons.configuration2.ex.ConfigurationException;
+import org.apache.james.PostgresJamesConfiguration;
+import org.apache.james.SearchConfiguration;
+import org.apache.james.backends.postgres.PostgresConfiguration;
+import org.apache.james.filesystem.api.FileSystem;
+import org.apache.james.filesystem.api.JamesDirectoriesProvider;
+import org.apache.james.jmap.JMAPModule;
+import org.apache.james.modules.queue.rabbitmq.MailQueueViewChoice;
+import org.apache.james.server.core.JamesServerResourceLoader;
+import org.apache.james.server.core.MissingArgumentException;
+import org.apache.james.server.core.configuration.Configuration;
+import org.apache.james.server.core.configuration.FileConfigurationProvider;
+import org.apache.james.server.core.filesystem.FileSystemImpl;
+import org.apache.james.utils.PropertiesProvider;
+
+import com.github.fge.lambdas.Throwing;
+import com.linagora.tmail.UsersRepositoryModuleChooser;
+import com.linagora.tmail.blob.guice.BlobStoreConfiguration;
+import com.linagora.tmail.encrypted.MailboxConfiguration;
+import com.linagora.tmail.james.jmap.firebase.FirebaseModuleChooserConfiguration;
+import com.linagora.tmail.james.jmap.service.discovery.LinagoraServicesDiscoveryModuleChooserConfiguration;
+
+public record PostgresTmailConfiguration(ConfigurationPath configurationPath, JamesDirectoriesProvider directories,
+ MailboxConfiguration mailboxConfiguration,
+ BlobStoreConfiguration blobStoreConfiguration,
+ SearchConfiguration searchConfiguration,
+ UsersRepositoryModuleChooser.Implementation usersRepositoryImplementation,
+ MailQueueViewChoice mailQueueViewChoice,
+ FirebaseModuleChooserConfiguration firebaseModuleChooserConfiguration,
+ LinagoraServicesDiscoveryModuleChooserConfiguration linagoraServicesDiscoveryModuleChooserConfiguration,
+ boolean jmapEnabled,
+ boolean rlsEnabled,
+ PropertiesProvider propertiesProvider,
+ PostgresJamesConfiguration.EventBusImpl eventBusImpl) implements Configuration {
+ public static class Builder {
+ private Optional mailboxConfiguration;
+ private Optional searchConfiguration;
+ private Optional blobStoreConfiguration;
+ private Optional rootDirectory;
+ private Optional configurationPath;
+ private Optional usersRepositoryImplementation;
+ private Optional mailQueueViewChoice;
+ private Optional firebaseModuleChooserConfiguration;
+ private Optional linagoraServicesDiscoveryModuleChooserConfiguration;
+ private Optional jmapEnabled;
+ private Optional rlsEnabled;
+ private Optional eventBusImpl;
+
+ private Builder() {
+ searchConfiguration = Optional.empty();
+ mailboxConfiguration = Optional.empty();
+ rootDirectory = Optional.empty();
+ configurationPath = Optional.empty();
+ blobStoreConfiguration = Optional.empty();
+ usersRepositoryImplementation = Optional.empty();
+ mailQueueViewChoice = Optional.empty();
+ firebaseModuleChooserConfiguration = Optional.empty();
+ linagoraServicesDiscoveryModuleChooserConfiguration = Optional.empty();
+ jmapEnabled = Optional.empty();
+ rlsEnabled = Optional.empty();
+ eventBusImpl = Optional.empty();
+ }
+
+ public Builder workingDirectory(String path) {
+ rootDirectory = Optional.of(path);
+ return this;
+ }
+
+ public Builder workingDirectory(File file) {
+ rootDirectory = Optional.of(file.getAbsolutePath());
+ return this;
+ }
+
+ public Builder useWorkingDirectoryEnvProperty() {
+ rootDirectory = Optional.ofNullable(System.getProperty(WORKING_DIRECTORY));
+ if (rootDirectory.isEmpty()) {
+ throw new MissingArgumentException("Server needs a working.directory env entry");
+ }
+ return this;
+ }
+
+ public Builder configurationPath(ConfigurationPath path) {
+ configurationPath = Optional.of(path);
+ return this;
+ }
+
+ public Builder configurationFromClasspath() {
+ configurationPath = Optional.of(new ConfigurationPath(FileSystem.CLASSPATH_PROTOCOL));
+ return this;
+ }
+
+ public Builder blobStore(BlobStoreConfiguration blobStoreConfiguration) {
+ this.blobStoreConfiguration = Optional.of(blobStoreConfiguration);
+ return this;
+ }
+
+ public Builder mailbox(MailboxConfiguration mailboxConfiguration) {
+ this.mailboxConfiguration = Optional.of(mailboxConfiguration);
+ return this;
+ }
+
+ public Builder searchConfiguration(SearchConfiguration searchConfiguration) {
+ this.searchConfiguration = Optional.of(searchConfiguration);
+ return this;
+ }
+
+ public Builder usersRepository(UsersRepositoryModuleChooser.Implementation implementation) {
+ this.usersRepositoryImplementation = Optional.of(implementation);
+ return this;
+ }
+
+ public Builder mailQueueViewChoice(MailQueueViewChoice mailQueueViewChoice) {
+ this.mailQueueViewChoice = Optional.of(mailQueueViewChoice);
+ return this;
+ }
+
+ public Builder firebaseModuleChooserConfiguration(FirebaseModuleChooserConfiguration firebaseModuleChooserConfiguration) {
+ this.firebaseModuleChooserConfiguration = Optional.of(firebaseModuleChooserConfiguration);
+ return this;
+ }
+
+ public Builder linagoraServicesDiscoveryModuleChooserConfiguration(LinagoraServicesDiscoveryModuleChooserConfiguration servicesDiscoveryModuleChooserConfiguration) {
+ this.linagoraServicesDiscoveryModuleChooserConfiguration = Optional.of(servicesDiscoveryModuleChooserConfiguration);
+ return this;
+ }
+
+ public Builder jmapEnabled(boolean enable) {
+ this.jmapEnabled = Optional.of(enable);
+ return this;
+ }
+
+ public Builder rlsEnabled(Optional rlsEnabled) {
+ this.rlsEnabled = rlsEnabled;
+ return this;
+ }
+
+ public Builder eventBusImpl(PostgresJamesConfiguration.EventBusImpl eventBusImpl) {
+ this.eventBusImpl = Optional.of(eventBusImpl);
+ return this;
+ }
+
+ public PostgresTmailConfiguration build() {
+ ConfigurationPath configurationPath = this.configurationPath.orElse(new ConfigurationPath(FileSystem.FILE_PROTOCOL_AND_CONF));
+ JamesServerResourceLoader directories = new JamesServerResourceLoader(rootDirectory
+ .orElseThrow(() -> new MissingArgumentException("Server needs a working.directory env entry")));
+
+ FileSystemImpl fileSystem = new FileSystemImpl(directories);
+ PropertiesProvider propertiesProvider = new PropertiesProvider(fileSystem, configurationPath);
+ BlobStoreConfiguration blobStoreConfiguration = this.blobStoreConfiguration.orElseGet(Throwing.supplier(
+ () -> BlobStoreConfiguration.parse(propertiesProvider)));
+
+ SearchConfiguration searchConfiguration = this.searchConfiguration.orElseGet(Throwing.supplier(
+ () -> SearchConfiguration.parse(propertiesProvider)));
+
+ MailboxConfiguration mailboxConfiguration = this.mailboxConfiguration.orElseGet(Throwing.supplier(
+ () -> MailboxConfiguration.parse(propertiesProvider)));
+
+ FileConfigurationProvider configurationProvider = new FileConfigurationProvider(fileSystem, Basic.builder()
+ .configurationPath(configurationPath)
+ .workingDirectory(directories.getRootDirectory())
+ .build());
+
+ UsersRepositoryModuleChooser.Implementation usersRepositoryChoice = usersRepositoryImplementation.orElseGet(
+ () -> UsersRepositoryModuleChooser.Implementation.parse(configurationProvider));
+
+ MailQueueViewChoice mailQueueViewChoice = this.mailQueueViewChoice.orElseGet(Throwing.supplier(
+ () -> MailQueueViewChoice.parse(propertiesProvider)));
+
+ FirebaseModuleChooserConfiguration firebaseModuleChooserConfiguration = this.firebaseModuleChooserConfiguration.orElseGet(Throwing.supplier(
+ () -> FirebaseModuleChooserConfiguration.parse(propertiesProvider)));
+
+ LinagoraServicesDiscoveryModuleChooserConfiguration servicesDiscoveryModuleChooserConfiguration = this.linagoraServicesDiscoveryModuleChooserConfiguration
+ .orElseGet(Throwing.supplier(() -> LinagoraServicesDiscoveryModuleChooserConfiguration.parse(propertiesProvider)));
+
+ boolean jmapEnabled = this.jmapEnabled.orElseGet(() -> {
+ try {
+ return JMAPModule.parseConfiguration(propertiesProvider).isEnabled();
+ } catch (FileNotFoundException e) {
+ return false;
+ } catch (ConfigurationException e) {
+ throw new RuntimeException(e);
+ }
+ });
+
+ boolean rlsEnabled = this.rlsEnabled.orElse(readRLSEnabledFromFile(propertiesProvider));
+
+ PostgresJamesConfiguration.EventBusImpl eventBusImpl = this.eventBusImpl.orElseGet(() -> PostgresJamesConfiguration.EventBusImpl.from(propertiesProvider));
+
+ return new PostgresTmailConfiguration(
+ configurationPath,
+ directories,
+ mailboxConfiguration,
+ blobStoreConfiguration,
+ searchConfiguration,
+ usersRepositoryChoice,
+ mailQueueViewChoice,
+ firebaseModuleChooserConfiguration,
+ servicesDiscoveryModuleChooserConfiguration,
+ jmapEnabled,
+ rlsEnabled,
+ propertiesProvider,
+ eventBusImpl);
+ }
+
+ private boolean readRLSEnabledFromFile(PropertiesProvider propertiesProvider) {
+ try {
+ return PostgresConfiguration.from(propertiesProvider.getConfiguration(PostgresConfiguration.POSTGRES_CONFIGURATION_NAME)).getRowLevelSecurity().isRowLevelSecurityEnabled();
+ } catch (FileNotFoundException | ConfigurationException e) {
+ return false;
+ }
+ }
+ }
+
+ public static PostgresTmailConfiguration.Builder builder() {
+ return new Builder();
+ }
+
+ public boolean hasConfigurationProperties(String configurationPropertiesName) {
+ try {
+ propertiesProvider().getConfiguration(configurationPropertiesName);
+ return true;
+ } catch (FileNotFoundException notFoundException) {
+ return false;
+ } catch (ConfigurationException e) {
+ throw new RuntimeException(e);
+ }
+ }
+}
diff --git a/tmail-backend/apps/postgres/src/main/java/com/linagora/tmail/james/app/PostgresTmailServer.java b/tmail-backend/apps/postgres/src/main/java/com/linagora/tmail/james/app/PostgresTmailServer.java
new file mode 100644
index 00000000000..8db56bc9a1b
--- /dev/null
+++ b/tmail-backend/apps/postgres/src/main/java/com/linagora/tmail/james/app/PostgresTmailServer.java
@@ -0,0 +1,464 @@
+package com.linagora.tmail.james.app;
+
+import static org.apache.james.PostgresJamesConfiguration.EventBusImpl.RABBITMQ;
+import static org.apache.james.PostgresJamesServerMain.JMAP;
+
+import java.util.List;
+import java.util.Set;
+import java.util.function.Function;
+
+import org.apache.commons.lang3.NotImplementedException;
+import org.apache.james.ExtraProperties;
+import org.apache.james.GuiceJamesServer;
+import org.apache.james.JamesServerMain;
+import org.apache.james.OpenSearchHighlightModule;
+import org.apache.james.backends.redis.RedisHealthCheck;
+import org.apache.james.core.healthcheck.HealthCheck;
+import org.apache.james.eventsourcing.eventstore.EventNestedTypes;
+import org.apache.james.jmap.JMAPListenerModule;
+import org.apache.james.json.DTO;
+import org.apache.james.json.DTOModule;
+import org.apache.james.mailbox.MailboxSession;
+import org.apache.james.mailbox.model.MessageId;
+import org.apache.james.mailbox.model.MultimailboxesSearchQuery;
+import org.apache.james.mailbox.searchhighligt.SearchHighlighter;
+import org.apache.james.mailbox.searchhighligt.SearchSnippet;
+import org.apache.james.mailbox.store.search.ListeningMessageSearchIndex;
+import org.apache.james.mailbox.store.search.MessageSearchIndex;
+import org.apache.james.mailbox.store.search.SimpleMessageSearchIndex;
+import org.apache.james.modules.BlobExportMechanismModule;
+import org.apache.james.modules.DistributedTaskSerializationModule;
+import org.apache.james.modules.MailboxModule;
+import org.apache.james.modules.MailetProcessingModule;
+import org.apache.james.modules.RunArgumentsModule;
+import org.apache.james.modules.blobstore.BlobStoreCacheModulesChooser;
+import org.apache.james.modules.data.PostgresDLPConfigurationStoreModule;
+import org.apache.james.modules.data.PostgresDataModule;
+import org.apache.james.modules.data.PostgresDelegationStoreModule;
+import org.apache.james.modules.data.PostgresEventStoreModule;
+import org.apache.james.modules.data.PostgresUsersRepositoryModule;
+import org.apache.james.modules.data.PostgresVacationModule;
+import org.apache.james.modules.data.SievePostgresRepositoryModules;
+import org.apache.james.modules.event.JMAPEventBusModule;
+import org.apache.james.modules.event.RabbitMQEventBusModule;
+import org.apache.james.modules.events.PostgresDeadLetterModule;
+import org.apache.james.modules.mailbox.DefaultEventModule;
+import org.apache.james.modules.mailbox.OpenSearchClientModule;
+import org.apache.james.modules.mailbox.OpenSearchDisabledModule;
+import org.apache.james.modules.mailbox.OpenSearchMailboxModule;
+import org.apache.james.modules.mailbox.PostgresDeletedMessageVaultModule;
+import org.apache.james.modules.mailbox.PostgresMailboxModule;
+import org.apache.james.modules.mailbox.RLSSupportPostgresMailboxModule;
+import org.apache.james.modules.mailbox.TikaMailboxModule;
+import org.apache.james.modules.plugins.QuotaMailingModule;
+import org.apache.james.modules.protocols.IMAPServerModule;
+import org.apache.james.modules.protocols.LMTPServerModule;
+import org.apache.james.modules.protocols.ManageSieveServerModule;
+import org.apache.james.modules.protocols.POP3ServerModule;
+import org.apache.james.modules.protocols.ProtocolHandlerModule;
+import org.apache.james.modules.protocols.SMTPServerModule;
+import org.apache.james.modules.queue.activemq.ActiveMQQueueModule;
+import org.apache.james.modules.queue.rabbitmq.FakeMailQueueViewModule;
+import org.apache.james.modules.queue.rabbitmq.RabbitMQMailQueueModule;
+import org.apache.james.modules.queue.rabbitmq.RabbitMQModule;
+import org.apache.james.modules.server.DKIMMailetModule;
+import org.apache.james.modules.server.DLPRoutesModule;
+import org.apache.james.modules.server.DataRoutesModules;
+import org.apache.james.modules.server.InconsistencyQuotasSolvingRoutesModule;
+import org.apache.james.modules.server.JMXServerModule;
+import org.apache.james.modules.server.JmapTasksModule;
+import org.apache.james.modules.server.JmapUploadCleanupModule;
+import org.apache.james.modules.server.MailQueueRoutesModule;
+import org.apache.james.modules.server.MailRepositoriesRoutesModule;
+import org.apache.james.modules.server.MailboxRoutesModule;
+import org.apache.james.modules.server.MailboxesExportRoutesModule;
+import org.apache.james.modules.server.MessagesRoutesModule;
+import org.apache.james.modules.server.RabbitMailQueueRoutesModule;
+import org.apache.james.modules.server.ReIndexingModule;
+import org.apache.james.modules.server.SieveRoutesModule;
+import org.apache.james.modules.server.TaskManagerModule;
+import org.apache.james.modules.server.UserIdentityModule;
+import org.apache.james.modules.server.WebAdminMailOverWebModule;
+import org.apache.james.modules.server.WebAdminReIndexingTaskSerializationModule;
+import org.apache.james.modules.server.WebAdminServerModule;
+import org.apache.james.modules.task.DistributedTaskManagerModule;
+import org.apache.james.modules.task.PostgresTaskExecutionDetailsProjectionGuiceModule;
+import org.apache.james.modules.vault.DeletedMessageVaultRoutesModule;
+import org.apache.james.quota.search.QuotaSearcher;
+import org.apache.james.quota.search.scanning.ScanningQuotaSearcher;
+import org.apache.james.rate.limiter.redis.RedisRateLimiterModule;
+import org.apache.james.user.postgres.PostgresUsersDAO;
+import org.reactivestreams.Publisher;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.inject.AbstractModule;
+import com.google.inject.Module;
+import com.google.inject.Scopes;
+import com.google.inject.TypeLiteral;
+import com.google.inject.multibindings.Multibinder;
+import com.google.inject.name.Names;
+import com.google.inject.util.Modules;
+import com.linagora.tmail.DatabaseCombinedUserRequireModule;
+import com.linagora.tmail.ScheduledReconnectionHandler;
+import com.linagora.tmail.UsersRepositoryModuleChooser;
+import com.linagora.tmail.blob.guice.BlobStoreModulesChooser;
+import com.linagora.tmail.contact.RabbitMQEmailAddressContactModule;
+import com.linagora.tmail.encrypted.MailboxConfiguration;
+import com.linagora.tmail.encrypted.postgres.PostgresEncryptedEmailContentStoreModule;
+import com.linagora.tmail.encrypted.postgres.PostgresEncryptedMailboxModule;
+import com.linagora.tmail.encrypted.postgres.PostgresKeystoreModule;
+import com.linagora.tmail.event.DistributedEmailAddressContactEventModule;
+import com.linagora.tmail.healthcheck.TasksHeathCheckModule;
+import com.linagora.tmail.james.jmap.TMailJMAPModule;
+import com.linagora.tmail.james.jmap.contact.MemoryEmailAddressContactModule;
+import com.linagora.tmail.james.jmap.firebase.FirebaseCommonModule;
+import com.linagora.tmail.james.jmap.firebase.FirebaseModuleChooserConfiguration;
+import com.linagora.tmail.james.jmap.firebase.PostgresFirebaseRepositoryModule;
+import com.linagora.tmail.james.jmap.label.PostgresLabelRepositoryModule;
+import com.linagora.tmail.james.jmap.mail.TMailMailboxSortOrderProviderModule;
+import com.linagora.tmail.james.jmap.method.CalendarEventMethodModule;
+import com.linagora.tmail.james.jmap.method.ContactAutocompleteMethodModule;
+import com.linagora.tmail.james.jmap.method.CustomMethodModule;
+import com.linagora.tmail.james.jmap.method.EmailRecoveryActionMethodModule;
+import com.linagora.tmail.james.jmap.method.EmailSendMethodModule;
+import com.linagora.tmail.james.jmap.method.EncryptedEmailDetailedViewGetMethodModule;
+import com.linagora.tmail.james.jmap.method.EncryptedEmailFastViewGetMethodModule;
+import com.linagora.tmail.james.jmap.method.FilterGetMethodModule;
+import com.linagora.tmail.james.jmap.method.FilterSetMethodModule;
+import com.linagora.tmail.james.jmap.method.ForwardGetMethodModule;
+import com.linagora.tmail.james.jmap.method.ForwardSetMethodModule;
+import com.linagora.tmail.james.jmap.method.JmapSettingsMethodModule;
+import com.linagora.tmail.james.jmap.method.KeystoreGetMethodModule;
+import com.linagora.tmail.james.jmap.method.KeystoreSetMethodModule;
+import com.linagora.tmail.james.jmap.method.LabelMethodModule;
+import com.linagora.tmail.james.jmap.module.OSContactAutoCompleteModule;
+import com.linagora.tmail.james.jmap.oidc.WebFingerModule;
+import com.linagora.tmail.james.jmap.publicAsset.PostgresPublicAssetRepositoryModule;
+import com.linagora.tmail.james.jmap.publicAsset.PublicAssetsModule;
+import com.linagora.tmail.james.jmap.service.discovery.LinagoraServicesDiscoveryModule;
+import com.linagora.tmail.james.jmap.service.discovery.LinagoraServicesDiscoveryModuleChooserConfiguration;
+import com.linagora.tmail.james.jmap.settings.PostgresJmapSettingsRepositoryModule;
+import com.linagora.tmail.james.jmap.team.mailboxes.TeamMailboxJmapModule;
+import com.linagora.tmail.james.jmap.ticket.PostgresTicketStoreModule;
+import com.linagora.tmail.james.jmap.ticket.TicketRoutesModule;
+import com.linagora.tmail.rate.limiter.api.postgres.module.PostgresRateLimitingModule;
+import com.linagora.tmail.rspamd.RspamdModule;
+import com.linagora.tmail.team.TeamMailboxModule;
+import com.linagora.tmail.webadmin.EmailAddressContactRoutesModule;
+import com.linagora.tmail.webadmin.RateLimitPlanRoutesModule;
+import com.linagora.tmail.webadmin.TeamMailboxRoutesModule;
+import com.linagora.tmail.webadmin.archival.InboxArchivalTaskModule;
+import com.linagora.tmail.webadmin.cleanup.MailboxesCleanupModule;
+
+import reactor.core.publisher.Mono;
+
+public class PostgresTmailServer {
+ private static class FakeSearchHighlighter implements SearchHighlighter {
+
+ @Override
+ public Publisher highlightSearch(List messageIds, MultimailboxesSearchQuery expression, MailboxSession session) {
+ return Mono.error(new NotImplementedException("not implemented"));
+ }
+ }
+
+ private static class FakeSearchHighlightModule extends AbstractModule {
+ @Override
+ protected void configure() {
+ bind(SearchHighlighter.class).toInstance(new FakeSearchHighlighter());
+ }
+ }
+
+ static final Logger LOGGER = LoggerFactory.getLogger("org.apache.james.CONFIGURATION");
+
+ private static final Module EVENT_STORE_JSON_SERIALIZATION_DEFAULT_MODULE = binder ->
+ binder.bind(new TypeLiteral>>() {
+ }).annotatedWith(Names.named(EventNestedTypes.EVENT_NESTED_TYPES_INJECTION_NAME))
+ .toInstance(Set.of());
+
+ public static void main(String[] args) throws Exception {
+ ExtraProperties.initialize();
+
+ PostgresTmailConfiguration configuration = PostgresTmailConfiguration.builder()
+ .useWorkingDirectoryEnvProperty()
+ .build();
+
+ LOGGER.info("Loading configuration {}", configuration.toString());
+ GuiceJamesServer server = createServer(configuration)
+ .combineWith(new JMXServerModule())
+ .overrideWith(new RunArgumentsModule(args));
+
+ JamesServerMain.main(server);
+ }
+
+ public static GuiceJamesServer createServer(PostgresTmailConfiguration configuration) {
+ return GuiceJamesServer.forConfiguration(configuration)
+ .combineWith(POSTGRES_MODULE_AGGREGATE.apply(configuration))
+ .combineWith(chooseUserRepositoryModule(configuration))
+ .combineWith(chooseBlobStoreModules(configuration))
+ .combineWith(chooseRedisRateLimiterModule(configuration))
+ .combineWith(chooseRspamdModule(configuration))
+ .combineWith(chooseLinagoraServicesDiscovery(configuration.linagoraServicesDiscoveryModuleChooserConfiguration()))
+ .combineWith(chooseFirebase(configuration.firebaseModuleChooserConfiguration()))
+ .combineWith(chooseRLSSupportPostgresMailboxModule(configuration))
+ .overrideWith(chooseSearchModules(configuration))
+ .overrideWith(chooseMailbox(configuration.mailboxConfiguration()))
+ .overrideWith(chooseJmapModule(configuration))
+ .overrideWith(chooseTaskManagerModules(configuration));
+ }
+
+ private static final Module WEBADMIN = Modules.combine(
+ new DataRoutesModules(),
+ new DeletedMessageVaultRoutesModule(),
+ new DLPRoutesModule(),
+ new EmailAddressContactRoutesModule(),
+ new InconsistencyQuotasSolvingRoutesModule(),
+ new InboxArchivalTaskModule(),
+ new JmapUploadCleanupModule(),
+ new JmapTasksModule(),
+ new MailboxRoutesModule(),
+ new MailboxesCleanupModule(),
+ new MailboxesExportRoutesModule(),
+ new MailQueueRoutesModule(),
+ new MailRepositoriesRoutesModule(),
+ new MessagesRoutesModule(),
+ new RateLimitPlanRoutesModule(),
+ new ReIndexingModule(),
+ new SieveRoutesModule(),
+ new TeamMailboxModule(),
+ new TeamMailboxRoutesModule(),
+ new UserIdentityModule(),
+ new WebAdminMailOverWebModule(),
+ new WebAdminReIndexingTaskSerializationModule(),
+ new WebAdminServerModule());
+
+ public static final Module JMAP_LINAGORA = Modules.override(
+ JMAP,
+ new TMailJMAPModule(),
+ new CalendarEventMethodModule(),
+ new ContactAutocompleteMethodModule(),
+ new CustomMethodModule(),
+ new EncryptedEmailDetailedViewGetMethodModule(),
+ new EncryptedEmailFastViewGetMethodModule(),
+ new EmailSendMethodModule(),
+ new FilterGetMethodModule(),
+ new FilterSetMethodModule(),
+ new ForwardGetMethodModule(),
+ new PostgresEncryptedEmailContentStoreModule(),
+ new PostgresKeystoreModule(),
+ new ForwardSetMethodModule(),
+ new KeystoreSetMethodModule(),
+ new KeystoreGetMethodModule(),
+ new TicketRoutesModule(),
+ new WebFingerModule(),
+ new EmailRecoveryActionMethodModule(),
+ new LabelMethodModule(),
+ new JmapSettingsMethodModule(),
+ new PublicAssetsModule())
+ .with(new TeamMailboxJmapModule());
+
+ private static final Module PROTOCOLS = Modules.combine(
+ JMAP_LINAGORA,
+ new IMAPServerModule(),
+ new LMTPServerModule(),
+ new ManageSieveServerModule(),
+ new POP3ServerModule(),
+ new ProtocolHandlerModule(),
+ new SMTPServerModule(),
+ WEBADMIN);
+
+ private static final Module POSTGRES_SERVER_MODULE = Modules.combine(
+ new BlobExportMechanismModule(),
+ new PostgresDelegationStoreModule(),
+ new PostgresMailboxModule(),
+ new PostgresDeadLetterModule(),
+ new PostgresDataModule(),
+ new MailboxModule(),
+ new SievePostgresRepositoryModules(),
+ new TaskManagerModule(),
+ new TikaMailboxModule(),
+ new PostgresVacationModule(),
+ new PostgresDLPConfigurationStoreModule(),
+ new PostgresDeletedMessageVaultModule(),
+ new PostgresEventStoreModule(),
+ EVENT_STORE_JSON_SERIALIZATION_DEFAULT_MODULE);
+
+ public static final Module PLUGINS = new QuotaMailingModule();
+
+ public static final Function POSTGRES_MODULE_AGGREGATE = configuration -> Modules
+ .override(Modules.combine(
+ new MailetProcessingModule(),
+ new DKIMMailetModule(),
+ POSTGRES_SERVER_MODULE,
+ PROTOCOLS,
+ PLUGINS))
+ .with(new TeamMailboxModule(),
+ new TMailMailboxSortOrderProviderModule(),
+ new PostgresRateLimitingModule(),
+ new RateLimitPlanRoutesModule(),
+ new EmailAddressContactRoutesModule(),
+ new PostgresLabelRepositoryModule(),
+ new PostgresJmapSettingsRepositoryModule(),
+ new PostgresPublicAssetRepositoryModule(),
+ new PostgresTicketStoreModule(),
+ new TasksHeathCheckModule(),
+ chooseEventBusModules(configuration));
+
+ private static final Module SCANNING_QUOTA_SEARCH_MODULE = new AbstractModule() {
+ @Override
+ protected void configure() {
+ bind(ScanningQuotaSearcher.class).in(Scopes.SINGLETON);
+ bind(QuotaSearcher.class).to(ScanningQuotaSearcher.class);
+ }
+ };
+
+ private static final Module SCANNING_SEARCH_MODULE = new AbstractModule() {
+ @Override
+ protected void configure() {
+ bind(MessageSearchIndex.class).to(SimpleMessageSearchIndex.class);
+ bind(FakeMessageSearchIndex.class).in(Scopes.SINGLETON);
+ bind(ListeningMessageSearchIndex.class).to(FakeMessageSearchIndex.class);
+ }
+ };
+
+ private static Module chooseBlobStoreModules(PostgresTmailConfiguration configuration) {
+ return Modules.combine(Modules.combine(BlobStoreModulesChooser.chooseModules(configuration.blobStoreConfiguration(),
+ BlobStoreModulesChooser.SingleSaveDeclarationModule.BackedStorage.POSTGRES)),
+ new BlobStoreCacheModulesChooser.CacheDisabledModule());
+ }
+
+ private static final Module IN_MEMORY_EVENT_BUS_FEATURE_MODULE = Modules.combine(
+ new MemoryEmailAddressContactModule()
+ );
+
+ private static final Module RABBITMQ_EVENT_BUS_FEATURE_MODULE = Modules.combine(
+ new DistributedEmailAddressContactEventModule(),
+ new PostgresEmailAddressContactEventDeadLettersModule());
+
+ public static Module chooseEventBusModules(PostgresTmailConfiguration configuration) {
+ return switch (configuration.eventBusImpl()) {
+ case IN_MEMORY -> Modules.combine(new DefaultEventModule(),
+ new ActiveMQQueueModule(),
+ IN_MEMORY_EVENT_BUS_FEATURE_MODULE);
+ case RABBITMQ -> Modules.combine(new RabbitMQModule(),
+ new RabbitMQMailQueueModule(),
+ new FakeMailQueueViewModule(),
+ new RabbitMailQueueRoutesModule(),
+ new RabbitMQEmailAddressContactModule(),
+ new ScheduledReconnectionHandler.Module(),
+ Modules.override(new DefaultEventModule())
+ .with(new RabbitMQEventBusModule()),
+ new DistributedTaskSerializationModule(),
+ RABBITMQ_EVENT_BUS_FEATURE_MODULE);
+ };
+ }
+
+ public static List chooseTaskManagerModules(PostgresTmailConfiguration configuration) {
+ switch (configuration.eventBusImpl()) {
+ case IN_MEMORY:
+ return List.of(new TaskManagerModule(), new PostgresTaskExecutionDetailsProjectionGuiceModule());
+ case RABBITMQ:
+ return List.of(new DistributedTaskManagerModule());
+ default:
+ throw new RuntimeException("Unsupported event-bus implementation " + configuration.eventBusImpl().name());
+ }
+ }
+
+ public static Module chooseUserRepositoryModule(PostgresTmailConfiguration configuration) {
+ return Modules.combine(PostgresUsersRepositoryModule.USER_CONFIGURATION_MODULE,
+ new UsersRepositoryModuleChooser(
+ DatabaseCombinedUserRequireModule.of(PostgresUsersDAO.class),
+ new PostgresUsersRepositoryModule())
+ .chooseModule(configuration.usersRepositoryImplementation()));
+ }
+
+ private static Module chooseRedisRateLimiterModule(PostgresTmailConfiguration configuration) {
+ if (configuration.hasConfigurationProperties("redis")) {
+ return Modules.combine(new RedisRateLimiterModule(), new AbstractModule() {
+ @Override
+ protected void configure() {
+ Multibinder.newSetBinder(binder(), HealthCheck.class)
+ .addBinding()
+ .to(RedisHealthCheck.class);
+ }
+ });
+ }
+ return Modules.EMPTY_MODULE;
+ }
+
+ private static Module chooseRspamdModule(PostgresTmailConfiguration configuration) {
+ if (configuration.hasConfigurationProperties("rspamd")) {
+ return new RspamdModule();
+ }
+ return Modules.EMPTY_MODULE;
+ }
+
+ private static Module chooseJmapModule(PostgresTmailConfiguration configuration) {
+ if (configuration.jmapEnabled()) {
+ return Modules.combine(chooseJmapEventBusModule(configuration), new JMAPListenerModule());
+ }
+ return binder -> {
+ };
+ }
+
+ public static Module chooseJmapEventBusModule(PostgresTmailConfiguration configuration) {
+ if (configuration.eventBusImpl().equals(RABBITMQ)) {
+ return new JMAPEventBusModule();
+ }
+ return Modules.EMPTY_MODULE;
+ }
+
+ private static List chooseFirebase(FirebaseModuleChooserConfiguration moduleChooserConfiguration) {
+ if (moduleChooserConfiguration.enable()) {
+ return List.of(new PostgresFirebaseRepositoryModule(), new FirebaseCommonModule());
+ }
+ return List.of();
+ }
+
+ public static List chooseSearchModules(PostgresTmailConfiguration configuration) {
+ switch (configuration.searchConfiguration().getImplementation()) {
+ case OpenSearch:
+ return List.of(
+ new OSContactAutoCompleteModule(),
+ new OpenSearchClientModule(),
+ new OpenSearchMailboxModule(),
+ new ReIndexingModule(),
+ new OpenSearchHighlightModule());
+ case Scanning:
+ return List.of(
+ new DisabledEmailAddressContactSearchEngineModule(),
+ SCANNING_SEARCH_MODULE,
+ SCANNING_QUOTA_SEARCH_MODULE,
+ new FakeSearchHighlightModule());
+ case OpenSearchDisabled:
+ return List.of(
+ new DisabledEmailAddressContactSearchEngineModule(),
+ new OpenSearchDisabledModule(),
+ SCANNING_QUOTA_SEARCH_MODULE,
+ new FakeSearchHighlightModule());
+ default:
+ throw new RuntimeException("Unsupported search implementation " + configuration.searchConfiguration().getImplementation());
+ }
+ }
+
+ private static List chooseLinagoraServicesDiscovery(LinagoraServicesDiscoveryModuleChooserConfiguration moduleChooserConfiguration) {
+ if (moduleChooserConfiguration.enable()) {
+ return List.of(new LinagoraServicesDiscoveryModule());
+ }
+ return List.of();
+ }
+
+ private static Module chooseMailbox(MailboxConfiguration mailboxConfiguration) {
+ if (mailboxConfiguration.isEncryptionEnabled()) {
+ return new PostgresEncryptedMailboxModule();
+ }
+ return Modules.EMPTY_MODULE;
+ }
+
+ private static Module chooseRLSSupportPostgresMailboxModule(PostgresTmailConfiguration configuration) {
+ if (configuration.rlsEnabled()) {
+ return new RLSSupportPostgresMailboxModule();
+ }
+ return Modules.EMPTY_MODULE;
+ }
+}
diff --git a/tmail-backend/apps/postgres/src/main/scripts/james-cli b/tmail-backend/apps/postgres/src/main/scripts/james-cli
new file mode 100755
index 00000000000..652b623405b
--- /dev/null
+++ b/tmail-backend/apps/postgres/src/main/scripts/james-cli
@@ -0,0 +1,4 @@
+#!/bin/bash
+
+unset JAVA_TOOL_OPTIONS
+java -cp /root/resources:/root/classes:/root/libs/* org.apache.james.cli.ServerCmd "$@"
\ No newline at end of file
diff --git a/tmail-backend/apps/postgres/src/test/java/com/linagora/tmail/james/app/CombinedIdentityTmailServerTest.java b/tmail-backend/apps/postgres/src/test/java/com/linagora/tmail/james/app/CombinedIdentityTmailServerTest.java
new file mode 100644
index 00000000000..bd436cf0ce3
--- /dev/null
+++ b/tmail-backend/apps/postgres/src/test/java/com/linagora/tmail/james/app/CombinedIdentityTmailServerTest.java
@@ -0,0 +1,90 @@
+/****************************************************************
+ * 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 com.linagora.tmail.james.app;
+
+import static org.apache.james.PostgresJamesConfiguration.EventBusImpl.IN_MEMORY;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatCode;
+
+import org.apache.james.GuiceJamesServer;
+import org.apache.james.JamesServerBuilder;
+import org.apache.james.JamesServerExtension;
+import org.apache.james.SearchConfiguration;
+import org.apache.james.backends.postgres.PostgresExtension;
+import org.apache.james.utils.DataProbeImpl;
+import org.apache.james.utils.GuiceProbe;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.RegisterExtension;
+
+import com.google.inject.multibindings.Multibinder;
+import com.linagora.tmail.UsersRepositoryModuleChooser;
+import com.linagora.tmail.blob.guice.BlobStoreConfiguration;
+import com.linagora.tmail.combined.identity.CombinedUsersRepository;
+import com.linagora.tmail.combined.identity.LdapExtension;
+import com.linagora.tmail.combined.identity.UsersRepositoryClassProbe;
+import com.linagora.tmail.encrypted.MailboxConfiguration;
+import com.linagora.tmail.encrypted.MailboxManagerClassProbe;
+import com.linagora.tmail.module.LinagoraTestJMAPServerModule;
+
+class CombinedIdentityTmailServerTest {
+
+ static PostgresExtension postgresExtension = PostgresExtension.empty();
+
+ @RegisterExtension
+ static JamesServerExtension testExtension = new JamesServerBuilder(tmpDir ->
+ PostgresTmailConfiguration.builder()
+ .workingDirectory(tmpDir)
+ .configurationFromClasspath()
+ .blobStore(BlobStoreConfiguration.builder()
+ .postgres()
+ .disableCache()
+ .deduplication()
+ .noCryptoConfig()
+ .disableSingleSave())
+ .searchConfiguration(SearchConfiguration.scanning())
+ .usersRepository(UsersRepositoryModuleChooser.Implementation.COMBINED)
+ .mailbox(new MailboxConfiguration(false))
+ .eventBusImpl(IN_MEMORY)
+ .build())
+ .server(configuration -> PostgresTmailServer.createServer(configuration)
+ .overrideWith(new LinagoraTestJMAPServerModule())
+ .overrideWith(binder -> Multibinder.newSetBinder(binder, GuiceProbe.class).addBinding().to(MailboxManagerClassProbe.class))
+ .overrideWith(binder -> Multibinder.newSetBinder(binder, GuiceProbe.class).addBinding().to(UsersRepositoryClassProbe.class)))
+ .extension(postgresExtension)
+ .extensions(new LdapExtension())
+ .lifeCycle(JamesServerExtension.Lifecycle.PER_CLASS)
+ .build();
+
+ @Test
+ void shouldUseCombinedUsersRepositoryWhenSpecified(GuiceJamesServer server) {
+ assertThat(server.getProbe(UsersRepositoryClassProbe.class).getUserRepositoryClass())
+ .isEqualTo(CombinedUsersRepository.class);
+ }
+
+ @Test
+ void shouldAllowUserSynchronisation(GuiceJamesServer server) {
+ assertThatCode(
+ () -> server.getProbe(DataProbeImpl.class)
+ .fluent()
+ .addDomain("james.org")
+ .addUser("james-user@james.org", "123456"))
+ .doesNotThrowAnyException();
+ }
+}
diff --git a/tmail-backend/apps/postgres/src/test/java/com/linagora/tmail/james/app/DistributedPostgresTmailServerTest.java b/tmail-backend/apps/postgres/src/test/java/com/linagora/tmail/james/app/DistributedPostgresTmailServerTest.java
new file mode 100644
index 00000000000..31486b764c2
--- /dev/null
+++ b/tmail-backend/apps/postgres/src/test/java/com/linagora/tmail/james/app/DistributedPostgresTmailServerTest.java
@@ -0,0 +1,143 @@
+/****************************************************************
+ * 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 com.linagora.tmail.james.app;
+
+import static org.apache.james.PostgresJamesConfiguration.EventBusImpl.RABBITMQ;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.awaitility.Durations.FIVE_HUNDRED_MILLISECONDS;
+import static org.awaitility.Durations.ONE_MINUTE;
+import static org.hamcrest.Matchers.equalTo;
+
+import org.apache.james.GuiceJamesServer;
+import org.apache.james.JamesServerBuilder;
+import org.apache.james.JamesServerConcreteContract;
+import org.apache.james.JamesServerExtension;
+import org.apache.james.SearchConfiguration;
+import org.apache.james.backends.postgres.PostgresExtension;
+import org.apache.james.core.healthcheck.ResultStatus;
+import org.apache.james.core.quota.QuotaSizeLimit;
+import org.apache.james.modules.AwsS3BlobStoreExtension;
+import org.apache.james.modules.QuotaProbesImpl;
+import org.apache.james.modules.protocols.ImapGuiceProbe;
+import org.apache.james.modules.protocols.SmtpGuiceProbe;
+import org.apache.james.utils.DataProbeImpl;
+import org.apache.james.utils.GuiceProbe;
+import org.apache.james.utils.SMTPMessageSender;
+import org.apache.james.utils.TestIMAPClient;
+import org.apache.james.utils.WebAdminGuiceProbe;
+import org.apache.james.webadmin.WebAdminUtils;
+import org.awaitility.Awaitility;
+import org.awaitility.core.ConditionFactory;
+import org.eclipse.jetty.http.HttpStatus;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.RegisterExtension;
+
+import com.google.common.base.Strings;
+import com.google.inject.multibindings.Multibinder;
+import com.linagora.tmail.blob.guice.BlobStoreConfiguration;
+import com.linagora.tmail.combined.identity.UsersRepositoryClassProbe;
+import com.linagora.tmail.encrypted.MailboxConfiguration;
+import com.linagora.tmail.encrypted.MailboxManagerClassProbe;
+import com.linagora.tmail.module.LinagoraTestJMAPServerModule;
+
+import io.restassured.specification.RequestSpecification;
+
+class DistributedPostgresTmailServerTest implements JamesServerConcreteContract {
+
+ @RegisterExtension
+ static JamesServerExtension testExtension = new JamesServerBuilder(tmpDir ->
+ PostgresTmailConfiguration.builder()
+ .workingDirectory(tmpDir)
+ .configurationFromClasspath()
+ .blobStore(BlobStoreConfiguration.builder()
+ .postgres()
+ .disableCache()
+ .deduplication()
+ .noCryptoConfig()
+ .enableSingleSave())
+ .searchConfiguration(SearchConfiguration.openSearch())
+ .mailbox(new MailboxConfiguration(false))
+ .eventBusImpl(RABBITMQ)
+ .build())
+ .server(configuration -> PostgresTmailServer.createServer(configuration)
+ .overrideWith(new LinagoraTestJMAPServerModule())
+ .overrideWith(binder -> Multibinder.newSetBinder(binder, GuiceProbe.class).addBinding().to(MailboxManagerClassProbe.class))
+ .overrideWith(binder -> Multibinder.newSetBinder(binder, GuiceProbe.class).addBinding().to(UsersRepositoryClassProbe.class)))
+ .extension(new RabbitMQExtension())
+ .extension(PostgresExtension.empty())
+ .extension(new AwsS3BlobStoreExtension())
+ .extension(new DockerOpenSearchExtension())
+ .lifeCycle(JamesServerExtension.Lifecycle.PER_CLASS)
+ .build();
+
+ private static final ConditionFactory AWAIT = Awaitility.await()
+ .atMost(ONE_MINUTE)
+ .with()
+ .pollInterval(FIVE_HUNDRED_MILLISECONDS);
+ static final String DOMAIN = "james.local";
+ private static final String USER = "toto@" + DOMAIN;
+ private static final String PASSWORD = "123456";
+
+ private TestIMAPClient testIMAPClient;
+ private SMTPMessageSender smtpMessageSender;
+ private RequestSpecification webAdminApi;
+ @BeforeEach
+ void setUp(GuiceJamesServer guiceJamesServer) {
+ this.testIMAPClient = new TestIMAPClient();
+ this.smtpMessageSender = new SMTPMessageSender(DOMAIN);
+ this.webAdminApi = WebAdminUtils.spec(guiceJamesServer.getProbe(WebAdminGuiceProbe.class).getWebAdminPort());
+ }
+
+ @Test
+ void guiceServerShouldUpdateQuota(GuiceJamesServer jamesServer) throws Exception {
+ jamesServer.getProbe(DataProbeImpl.class)
+ .fluent()
+ .addDomain(DOMAIN)
+ .addUser(USER, PASSWORD);
+ jamesServer.getProbe(QuotaProbesImpl.class).setGlobalMaxStorage(QuotaSizeLimit.size(50 * 1024));
+
+ // ~ 12 KB email
+ int imapPort = jamesServer.getProbe(ImapGuiceProbe.class).getImapPort();
+ smtpMessageSender.connect(JAMES_SERVER_HOST, jamesServer.getProbe(SmtpGuiceProbe.class).getSmtpPort())
+ .authenticate(USER, PASSWORD)
+ .sendMessageWithHeaders(USER, USER, "header: toto\\r\\n\\r\\n" + Strings.repeat("0123456789\n", 1024));
+ AWAIT.until(() -> testIMAPClient.connect(JAMES_SERVER_HOST, imapPort)
+ .login(USER, PASSWORD)
+ .select(TestIMAPClient.INBOX)
+ .hasAMessage());
+
+ AWAIT.untilAsserted(() -> assertThat(testIMAPClient.connect(JAMES_SERVER_HOST, imapPort)
+ .login(USER, PASSWORD)
+ .getQuotaRoot(TestIMAPClient.INBOX))
+ .startsWith("* QUOTAROOT \"INBOX\" #private&toto@james.local\r\n" +
+ "* QUOTA #private&toto@james.local (STORAGE 12 50)\r\n")
+ .endsWith("OK GETQUOTAROOT completed.\r\n"));
+ }
+
+ @Test
+ void healthCheckShouldBeHealthy() {
+ webAdminApi.when()
+ .get("/healthcheck")
+ .then()
+ .statusCode(HttpStatus.OK_200)
+ .body("status", equalTo(ResultStatus.HEALTHY.getValue()));
+ }
+}
diff --git a/tmail-backend/apps/postgres/src/test/java/com/linagora/tmail/james/app/EncryptedPostgresTmailServerTest.java b/tmail-backend/apps/postgres/src/test/java/com/linagora/tmail/james/app/EncryptedPostgresTmailServerTest.java
new file mode 100644
index 00000000000..1939fde7bc2
--- /dev/null
+++ b/tmail-backend/apps/postgres/src/test/java/com/linagora/tmail/james/app/EncryptedPostgresTmailServerTest.java
@@ -0,0 +1,105 @@
+package com.linagora.tmail.james.app;
+
+import static org.apache.james.PostgresJamesConfiguration.EventBusImpl.IN_MEMORY;
+import static org.assertj.core.api.Assertions.assertThat;
+
+import org.apache.james.ClockExtension;
+import org.apache.james.GuiceJamesServer;
+import org.apache.james.JamesServerBuilder;
+import org.apache.james.JamesServerConcreteContract;
+import org.apache.james.JamesServerExtension;
+import org.apache.james.SearchConfiguration;
+import org.apache.james.backends.postgres.PostgresExtension;
+import org.apache.james.blob.api.BlobStoreDAO;
+import org.apache.james.jmap.JmapJamesServerContract;
+import org.apache.james.jmap.rfc8621.contract.probe.DelegationProbeModule;
+import org.apache.james.user.postgres.PostgresUsersDAO;
+import org.apache.james.utils.GuiceProbe;
+import org.junit.jupiter.api.Disabled;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.RegisterExtension;
+
+import com.google.inject.Inject;
+import com.google.inject.multibindings.Multibinder;
+import com.linagora.tmail.blob.blobid.list.SingleSaveBlobStoreDAO;
+import com.linagora.tmail.blob.guice.BlobStoreConfiguration;
+import com.linagora.tmail.combined.identity.UsersRepositoryClassProbe;
+import com.linagora.tmail.encrypted.EncryptedMailboxManager;
+import com.linagora.tmail.encrypted.MailboxConfiguration;
+import com.linagora.tmail.encrypted.MailboxManagerClassProbe;
+import com.linagora.tmail.module.LinagoraTestJMAPServerModule;
+import com.linagora.tmail.team.TeamMailboxProbe;
+
+class EncryptedPostgresTmailServerTest implements JamesServerConcreteContract, JmapJamesServerContract {
+
+ static class BlobStoreDaoClassProbe implements GuiceProbe {
+ private final BlobStoreDAO blobStoreDAO;
+
+ @Inject
+ public BlobStoreDaoClassProbe(BlobStoreDAO blobStoreDAO) {
+ this.blobStoreDAO = blobStoreDAO;
+ }
+
+ public BlobStoreDAO getBlobStoreDAO() {
+ return blobStoreDAO;
+ }
+ }
+
+ @RegisterExtension
+ static JamesServerExtension testExtension = new JamesServerBuilder(tmpDir ->
+ PostgresTmailConfiguration.builder()
+ .workingDirectory(tmpDir)
+ .configurationFromClasspath()
+ .blobStore(BlobStoreConfiguration.builder()
+ .postgres()
+ .disableCache()
+ .deduplication()
+ .noCryptoConfig()
+ .enableSingleSave())
+ .searchConfiguration(SearchConfiguration.scanning())
+ .mailbox(new MailboxConfiguration(true))
+ .eventBusImpl(IN_MEMORY)
+ .build())
+ .server(configuration -> PostgresTmailServer.createServer(configuration)
+ .overrideWith(new LinagoraTestJMAPServerModule())
+ .overrideWith(binder -> Multibinder.newSetBinder(binder, GuiceProbe.class).addBinding().to(MailboxManagerClassProbe.class))
+ .overrideWith(binder -> Multibinder.newSetBinder(binder, GuiceProbe.class).addBinding().to(UsersRepositoryClassProbe.class))
+ .overrideWith(binder -> Multibinder.newSetBinder(binder, GuiceProbe.class).addBinding().to(TeamMailboxProbe.class))
+ .overrideWith(binder -> Multibinder.newSetBinder(binder, GuiceProbe.class).addBinding().to(BlobStoreDaoClassProbe.class))
+ .overrideWith(new DelegationProbeModule()))
+ .extension(PostgresExtension.empty())
+ .extension(new ClockExtension())
+ .build();
+
+ @Disabled("POP3 server is disabled")
+ @Test
+ public void connectPOP3ServerShouldSendShabangOnConnect(GuiceJamesServer jamesServer) {
+ // POP3 server is disabled
+ }
+
+ @Disabled("LMTP server is disabled")
+ @Test
+ public void connectLMTPServerShouldSendShabangOnConnect(GuiceJamesServer jamesServer) {
+ // LMTP server is disabled
+ }
+
+ @Test
+ public void shouldUseEncryptedMailboxManager(GuiceJamesServer jamesServer) {
+ assertThat(jamesServer.getProbe(MailboxManagerClassProbe.class).getMailboxManagerClass())
+ .isEqualTo(EncryptedMailboxManager.class);
+ }
+
+ @Test
+ public void shouldUseCassandraUsersDAOAsDefault(GuiceJamesServer jamesServer) {
+ assertThat(jamesServer.getProbe(UsersRepositoryClassProbe.class).getUsersDAOClass())
+ .isEqualTo(PostgresUsersDAO.class);
+ }
+
+ @Test
+ public void blobStoreShouldBindingCorrectWhenEncryptedBlobStoreAndSingleSave(GuiceJamesServer jamesServer) {
+ BlobStoreDAO blobStoreDAO = jamesServer.getProbe(BlobStoreDaoClassProbe.class).getBlobStoreDAO();
+ assertThat(blobStoreDAO.getClass())
+ .isEqualTo(SingleSaveBlobStoreDAO.class);
+ }
+
+}
\ No newline at end of file
diff --git a/tmail-backend/apps/postgres/src/test/java/com/linagora/tmail/james/app/PostgresTmailServerTest.java b/tmail-backend/apps/postgres/src/test/java/com/linagora/tmail/james/app/PostgresTmailServerTest.java
new file mode 100644
index 00000000000..7413e9f0845
--- /dev/null
+++ b/tmail-backend/apps/postgres/src/test/java/com/linagora/tmail/james/app/PostgresTmailServerTest.java
@@ -0,0 +1,46 @@
+package com.linagora.tmail.james.app;
+
+import static org.apache.james.PostgresJamesConfiguration.EventBusImpl.IN_MEMORY;
+
+import org.apache.james.JamesServerBuilder;
+import org.apache.james.JamesServerConcreteContract;
+import org.apache.james.JamesServerExtension;
+import org.apache.james.SearchConfiguration;
+import org.apache.james.backends.postgres.PostgresExtension;
+import org.apache.james.jmap.JmapJamesServerContract;
+import org.apache.james.utils.GuiceProbe;
+import org.junit.jupiter.api.extension.RegisterExtension;
+
+import com.google.inject.multibindings.Multibinder;
+import com.linagora.tmail.blob.guice.BlobStoreConfiguration;
+import com.linagora.tmail.combined.identity.UsersRepositoryClassProbe;
+import com.linagora.tmail.encrypted.MailboxConfiguration;
+import com.linagora.tmail.encrypted.MailboxManagerClassProbe;
+import com.linagora.tmail.module.LinagoraTestJMAPServerModule;
+
+class PostgresTmailServerTest implements JamesServerConcreteContract, JmapJamesServerContract {
+ static PostgresExtension postgresExtension = PostgresExtension.empty();
+
+ @RegisterExtension
+ static JamesServerExtension testExtension = new JamesServerBuilder(tmpDir ->
+ PostgresTmailConfiguration.builder()
+ .workingDirectory(tmpDir)
+ .configurationFromClasspath()
+ .blobStore(BlobStoreConfiguration.builder()
+ .postgres()
+ .disableCache()
+ .deduplication()
+ .noCryptoConfig()
+ .enableSingleSave())
+ .searchConfiguration(SearchConfiguration.scanning())
+ .mailbox(new MailboxConfiguration(false))
+ .eventBusImpl(IN_MEMORY)
+ .build())
+ .server(configuration -> PostgresTmailServer.createServer(configuration)
+ .overrideWith(new LinagoraTestJMAPServerModule())
+ .overrideWith(binder -> Multibinder.newSetBinder(binder, GuiceProbe.class).addBinding().to(MailboxManagerClassProbe.class))
+ .overrideWith(binder -> Multibinder.newSetBinder(binder, GuiceProbe.class).addBinding().to(UsersRepositoryClassProbe.class)))
+ .extension(postgresExtension)
+ .lifeCycle(JamesServerExtension.Lifecycle.PER_CLASS)
+ .build();
+}
diff --git a/tmail-backend/apps/postgres/src/test/resources/batchsizes.properties b/tmail-backend/apps/postgres/src/test/resources/batchsizes.properties
new file mode 100644
index 00000000000..1784f95d79e
--- /dev/null
+++ b/tmail-backend/apps/postgres/src/test/resources/batchsizes.properties
@@ -0,0 +1,9 @@
+# Those properties let you configure the number of messages queried at the same time.
+# IMAP FETCH command
+fetch.metadata=200
+fetch.headers=200
+fetch.full=50
+# IMAP COPY command
+copy=100
+# IMAP MOVE command
+move=100
diff --git a/tmail-backend/apps/postgres/src/test/resources/blob.properties b/tmail-backend/apps/postgres/src/test/resources/blob.properties
new file mode 100644
index 00000000000..78ea935448f
--- /dev/null
+++ b/tmail-backend/apps/postgres/src/test/resources/blob.properties
@@ -0,0 +1,66 @@
+# ============================================= BlobStore Implementation ==================================
+# Read https://james.apache.org/server/config-blobstore.html for further details
+
+# Choose your BlobStore implementation
+# Mandatory, allowed values are: file, s3, postgres.
+implementation=postgres
+
+# ========================================= Deduplication ========================================
+# If you choose to enable deduplication, the mails with the same content will be stored only once.
+# Warning: Once this feature is enabled, there is no turning back as turning it off will lead to the deletion of all
+# the mails sharing the same content once one is deleted.
+# Mandatory, Allowed values are: true, false
+deduplication.enable=true
+
+# deduplication.family needs to be incremented every time the deduplication.generation.duration is changed
+# Positive integer, defaults to 1
+# deduplication.gc.generation.family=1
+
+# Duration of generation.
+# Deduplication only takes place within a singe generation.
+# Only items two generation old can be garbage collected. (This prevent concurrent insertions issues and
+# accounts for a clock skew).
+# deduplication.family needs to be incremented everytime this parameter is changed.
+# Duration. Default unit: days. Defaults to 30 days.
+# deduplication.gc.generation.duration=30days
+
+# ========================================= Encryption ========================================
+# If you choose to enable encryption, the blob content will be encrypted before storing them in the BlobStore.
+# Warning: Once this feature is enabled, there is no turning back as turning it off will lead to all content being
+# encrypted. This comes at a performance impact but presents you from leaking data if, for instance the third party
+# offering you a S3 service is compromised.
+# Optional, Allowed values are: true, false, defaults to false
+encryption.aes.enable=false
+
+# Mandatory (if AES encryption is enabled) salt and password. Salt needs to be an hexadecimal encoded string
+#encryption.aes.password=xxx
+#encryption.aes.salt=73616c7479
+# Optional, defaults to PBKDF2WithHmacSHA512
+#encryption.aes.private.key.algorithm=PBKDF2WithHmacSHA512
+
+# ============================================ Blobs Exporting ==============================================
+# Read https://james.apache.org/server/config-blob-export.html for further details
+
+# Choosing blob exporting mechanism, allowed mechanism are: localFile, linshare
+# LinShare is a file sharing service, will be explained in the below section
+# Optional, default is localFile
+blob.export.implementation=localFile
+
+# ======================================= Local File Blobs Exporting ========================================
+# Optional, directory to store exported blob, directory path follows James file system format
+# default is file://var/blobExporting
+blob.export.localFile.directory=file://var/blobExporting
+
+# ======================================= LinShare File Blobs Exporting ========================================
+# LinShare is a sharing service where you can use james, connects to an existing LinShare server and shares files to
+# other mail addresses as long as those addresses available in LinShare. For example you can deploy James and LinShare
+# sharing the same LDAP repository
+# Mandatory if you choose LinShare, url to connect to LinShare service
+# blob.export.linshare.url=http://linshare:8080
+
+# ======================================= LinShare Configuration BasicAuthentication ===================================
+# Authentication is mandatory if you choose LinShare, TechnicalAccount is need to connect to LinShare specific service.
+# For Example: It will be formalized to 'Authorization: Basic {Credential of UUID/password}'
+
+# blob.export.linshare.technical.account.uuid=Technical_Account_UUID
+# blob.export.linshare.technical.account.password=password
\ No newline at end of file
diff --git a/tmail-backend/apps/postgres/src/test/resources/dnsservice.xml b/tmail-backend/apps/postgres/src/test/resources/dnsservice.xml
new file mode 100644
index 00000000000..6e4fbd2efb5
--- /dev/null
+++ b/tmail-backend/apps/postgres/src/test/resources/dnsservice.xml
@@ -0,0 +1,25 @@
+
+
+
+
+ true
+ false
+ 50000
+
diff --git a/tmail-backend/apps/postgres/src/test/resources/domainlist.xml b/tmail-backend/apps/postgres/src/test/resources/domainlist.xml
new file mode 100644
index 00000000000..fe17431a1ea
--- /dev/null
+++ b/tmail-backend/apps/postgres/src/test/resources/domainlist.xml
@@ -0,0 +1,24 @@
+
+
+
+
+ false
+ false
+
diff --git a/tmail-backend/apps/postgres/src/test/resources/imapserver.xml b/tmail-backend/apps/postgres/src/test/resources/imapserver.xml
new file mode 100644
index 00000000000..c99f956ce57
--- /dev/null
+++ b/tmail-backend/apps/postgres/src/test/resources/imapserver.xml
@@ -0,0 +1,56 @@
+
+
+
+
+
+
+ imapserver
+ 0.0.0.0:0
+ 200
+
+
+ classpath://keystore
+ james72laBalle
+ org.bouncycastle.jce.provider.BouncyCastleProvider
+
+ 0
+ 0
+ false
+ false
+
+
+ imapserver-ssl
+ 0.0.0.0:0
+ 200
+
+
+ classpath://keystore
+ james72laBalle
+ org.bouncycastle.jce.provider.BouncyCastleProvider
+
+ 0
+ 0
+ false
+
+
diff --git a/tmail-backend/apps/postgres/src/test/resources/keystore b/tmail-backend/apps/postgres/src/test/resources/keystore
new file mode 100644
index 00000000000..536a6c792b0
Binary files /dev/null and b/tmail-backend/apps/postgres/src/test/resources/keystore differ
diff --git a/tmail-backend/apps/postgres/src/test/resources/listeners.xml b/tmail-backend/apps/postgres/src/test/resources/listeners.xml
new file mode 100644
index 00000000000..ae5937f1fa6
--- /dev/null
+++ b/tmail-backend/apps/postgres/src/test/resources/listeners.xml
@@ -0,0 +1,22 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/tmail-backend/apps/postgres/src/test/resources/mailetcontainer.xml b/tmail-backend/apps/postgres/src/test/resources/mailetcontainer.xml
new file mode 100644
index 00000000000..d109eacd459
--- /dev/null
+++ b/tmail-backend/apps/postgres/src/test/resources/mailetcontainer.xml
@@ -0,0 +1,153 @@
+
+
+
+
+
+
+
+
+
+
+ postmaster
+
+
+
+ 20
+ postgres://var/mail/error/
+
+
+
+
+
+
+ postgres://var/mail/relay-limit-exceeded/
+
+
+ transport
+
+
+
+
+
+ mailetContainerErrors
+
+
+ ignore
+
+
+ postgres://var/mail/error/
+ propagate
+
+
+
+
+
+
+
+
+
+
+
+
+ rrt-error
+
+
+
+
+
+ local-address-error
+ 550 - Requested action not taken: no such user here
+
+
+
+ relay
+
+
+
+
+
+
+
+
+
+
+
+
+
+ outgoing
+ 5000, 100000, 500000
+ 3
+ 0
+ 10
+ true
+ bounces
+
+
+
+
+
+ mailetContainerLocalAddressError
+
+
+ none
+
+
+ postgres://var/mail/address-error/
+
+
+
+
+
+ mailetContainerRelayDenied
+
+
+ none
+
+
+ postgres://var/mail/relay-denied/
+ Warning: You are sending an e-mail to a remote server. You must be authenticated to perform such an operation
+
+
+
+
+
+ bounces
+
+
+ false
+
+
+
+
+
+ postgres://var/mail/rrt-error/
+ true
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/tmail-backend/apps/postgres/src/test/resources/mailrepositorystore.xml b/tmail-backend/apps/postgres/src/test/resources/mailrepositorystore.xml
new file mode 100644
index 00000000000..445f2727f29
--- /dev/null
+++ b/tmail-backend/apps/postgres/src/test/resources/mailrepositorystore.xml
@@ -0,0 +1,35 @@
+
+
+
+
+
+
+
+ postgres
+
+
+
+ postgres
+
+
+
+
+
+
diff --git a/tmail-backend/apps/postgres/src/test/resources/rabbitmq.properties b/tmail-backend/apps/postgres/src/test/resources/rabbitmq.properties
new file mode 100644
index 00000000000..5a2ca93ec05
--- /dev/null
+++ b/tmail-backend/apps/postgres/src/test/resources/rabbitmq.properties
@@ -0,0 +1,10 @@
+uri=amqp://james:james@rabbitmq_host:5672
+management.uri=http://james:james@rabbitmq_host:15672/api/
+
+# AMQP resources parameters for the subscriber email address contact messages. In order to synchronize contacts between Team-mail backend and 3rd.
+# AQMP uri
+address.contact.uri=amqp://rabbitmq:5672
+address.contact.user=guest
+address.contact.password=guest
+# Queue name
+address.contact.queue=AddressContactQueue1
\ No newline at end of file
diff --git a/tmail-backend/apps/postgres/src/test/resources/smtpserver.xml b/tmail-backend/apps/postgres/src/test/resources/smtpserver.xml
new file mode 100644
index 00000000000..5c8db4f9018
--- /dev/null
+++ b/tmail-backend/apps/postgres/src/test/resources/smtpserver.xml
@@ -0,0 +1,102 @@
+
+
+
+
+
+
+ smtpserver-global
+ 0.0.0.0:0
+ 200
+
+ file://conf/keystore
+ james72laBalle
+ org.bouncycastle.jce.provider.BouncyCastleProvider
+ SunX509
+
+ 360
+ 0
+ 0
+ false
+ false
+ 0
+ true
+ Apache JAMES awesome SMTP Server
+
+
+
+ false
+
+
+ smtpserver-TLS
+ 0.0.0.0:0
+ 200
+
+ file://conf/keystore
+ james72laBalle
+ org.bouncycastle.jce.provider.BouncyCastleProvider
+ SunX509
+
+ 360
+ 0
+ 0
+
+ true
+
+ false
+ 0
+ true
+ Apache JAMES awesome SMTP Server
+
+
+
+ false
+
+
+ smtpserver-authenticated
+ 0.0.0.0:0
+ 200
+
+ file://conf/keystore
+ james72laBalle
+ org.bouncycastle.jce.provider.BouncyCastleProvider
+ SunX509
+
+ 360
+ 0
+ 0
+
+ true
+
+ false
+ 0
+ true
+ Apache JAMES awesome SMTP Server
+
+
+
+ false
+
+
+
+
diff --git a/tmail-backend/apps/postgres/src/test/resources/webadmin.properties b/tmail-backend/apps/postgres/src/test/resources/webadmin.properties
new file mode 100644
index 00000000000..3386a14238a
--- /dev/null
+++ b/tmail-backend/apps/postgres/src/test/resources/webadmin.properties
@@ -0,0 +1,25 @@
+# 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.
+
+# This template file can be used as example for James Server configuration
+# DO NOT USE IT AS SUCH AND ADAPT IT TO YOUR NEEDS
+
+# Read https://james.apache.org/server/config-webadmin.html for further details
+
+enabled=true
+port=0
+host=127.0.0.1
\ No newline at end of file
diff --git a/tmail-backend/blob/blobid-list/pom.xml b/tmail-backend/blob/blobid-list/pom.xml
index a7dabec6a57..4c7a1fbd7a3 100644
--- a/tmail-backend/blob/blobid-list/pom.xml
+++ b/tmail-backend/blob/blobid-list/pom.xml
@@ -27,6 +27,16 @@
test-jar
test
+
+ ${james.groupId}
+ apache-james-backends-postgres
+
+
+ ${james.groupId}
+ apache-james-backends-postgres
+ test-jar
+ test
+
${james.groupId}
blob-api
@@ -94,6 +104,11 @@
io.projectreactor
reactor-scala-extensions_${scala.base}
+
+ org.testcontainers
+ postgresql
+ test
+
diff --git a/tmail-backend/blob/blobid-list/src/main/java/com/linagora/tmail/blob/blobid/list/SingleSaveBlobStoreModule.java b/tmail-backend/blob/blobid-list/src/main/java/com/linagora/tmail/blob/blobid/list/CassandraSingleSaveBlobStoreModule.java
similarity index 89%
rename from tmail-backend/blob/blobid-list/src/main/java/com/linagora/tmail/blob/blobid/list/SingleSaveBlobStoreModule.java
rename to tmail-backend/blob/blobid-list/src/main/java/com/linagora/tmail/blob/blobid/list/CassandraSingleSaveBlobStoreModule.java
index 6b900980421..bf4285f822d 100644
--- a/tmail-backend/blob/blobid-list/src/main/java/com/linagora/tmail/blob/blobid/list/SingleSaveBlobStoreModule.java
+++ b/tmail-backend/blob/blobid-list/src/main/java/com/linagora/tmail/blob/blobid/list/CassandraSingleSaveBlobStoreModule.java
@@ -7,7 +7,7 @@
import com.linagora.tmail.blob.blobid.list.cassandra.BlobIdListCassandraModule;
import com.linagora.tmail.blob.blobid.list.cassandra.CassandraBlobIdListModule;
-public class SingleSaveBlobStoreModule extends AbstractModule {
+public class CassandraSingleSaveBlobStoreModule extends AbstractModule {
@Override
protected void configure() {
diff --git a/tmail-backend/blob/blobid-list/src/main/scala/com/linagora/tmail/blob/blobid/list/postgres/PostgresBlobIdList.scala b/tmail-backend/blob/blobid-list/src/main/scala/com/linagora/tmail/blob/blobid/list/postgres/PostgresBlobIdList.scala
new file mode 100644
index 00000000000..59238ae08bd
--- /dev/null
+++ b/tmail-backend/blob/blobid-list/src/main/scala/com/linagora/tmail/blob/blobid/list/postgres/PostgresBlobIdList.scala
@@ -0,0 +1,19 @@
+package com.linagora.tmail.blob.blobid.list.postgres
+
+import java.lang
+
+import com.linagora.tmail.blob.blobid.list.BlobIdList
+import jakarta.inject.Inject
+import org.apache.james.blob.api.BlobId
+import org.reactivestreams.Publisher
+
+class PostgresBlobIdList @Inject()(postgresBlobIdListDAO: PostgresBlobIdListDAO) extends BlobIdList {
+ override def isStored(blobId: BlobId): Publisher[lang.Boolean] =
+ postgresBlobIdListDAO.isStored(blobId)
+
+ override def store(blobId: BlobId): Publisher[Unit] =
+ postgresBlobIdListDAO.insert(blobId)
+
+ override def remove(blobId: BlobId): Publisher[Unit] =
+ postgresBlobIdListDAO.remove(blobId)
+}
diff --git a/tmail-backend/blob/blobid-list/src/main/scala/com/linagora/tmail/blob/blobid/list/postgres/PostgresBlobIdListDAO.scala b/tmail-backend/blob/blobid-list/src/main/scala/com/linagora/tmail/blob/blobid/list/postgres/PostgresBlobIdListDAO.scala
new file mode 100644
index 00000000000..f6d16e45a88
--- /dev/null
+++ b/tmail-backend/blob/blobid-list/src/main/scala/com/linagora/tmail/blob/blobid/list/postgres/PostgresBlobIdListDAO.scala
@@ -0,0 +1,26 @@
+package com.linagora.tmail.blob.blobid.list.postgres
+
+import com.linagora.tmail.blob.blobid.list.postgres.PostgresBlobIdListModule.{BLOB_ID, TABLE_NAME}
+import jakarta.inject.Inject
+import org.apache.james.backends.postgres.utils.PostgresExecutor
+import org.apache.james.blob.api.BlobId
+import reactor.core.publisher.Mono
+import reactor.core.scala.publisher.SMono
+
+class PostgresBlobIdListDAO @Inject()(postgresExecutor: PostgresExecutor) {
+ def insert(blobId: BlobId): SMono[Unit] =
+ SMono.fromPublisher(postgresExecutor.executeVoid(dsl => Mono.from(dsl.insertInto(TABLE_NAME)
+ .set(BLOB_ID, blobId.asString())
+ .onConflictDoNothing())))
+ .`then`
+
+ def isStored(blobId: BlobId): SMono[java.lang.Boolean] =
+ SMono.fromPublisher(postgresExecutor.executeExists(dsl => dsl.selectOne()
+ .from(TABLE_NAME)
+ .where(BLOB_ID.eq(blobId.asString()))))
+
+ def remove(blobId: BlobId): SMono[Unit] =
+ SMono.fromPublisher(postgresExecutor.executeVoid(dsl => Mono.from(dsl.deleteFrom(TABLE_NAME)
+ .where(BLOB_ID.eq(blobId.asString())))))
+ .`then`
+}
diff --git a/tmail-backend/blob/blobid-list/src/main/scala/com/linagora/tmail/blob/blobid/list/postgres/PostgresBlobIdListModule.scala b/tmail-backend/blob/blobid-list/src/main/scala/com/linagora/tmail/blob/blobid/list/postgres/PostgresBlobIdListModule.scala
new file mode 100644
index 00000000000..e63e33020f3
--- /dev/null
+++ b/tmail-backend/blob/blobid-list/src/main/scala/com/linagora/tmail/blob/blobid/list/postgres/PostgresBlobIdListModule.scala
@@ -0,0 +1,23 @@
+package com.linagora.tmail.blob.blobid.list.postgres
+
+import org.apache.james.backends.postgres.{PostgresModule, PostgresTable}
+import org.jooq.impl.{DSL, SQLDataType}
+import org.jooq.{Field, Record, Table}
+
+object PostgresBlobIdListModule {
+ val TABLE_NAME: Table[Record] = DSL.table("blob_id_list")
+
+ val BLOB_ID: Field[String] = DSL.field("blob_id", SQLDataType.VARCHAR.notNull)
+
+ val TABLE: PostgresTable = PostgresTable.name(TABLE_NAME.getName)
+ .createTableStep((dsl, tableName) => dsl.createTableIfNotExists(tableName)
+ .column(BLOB_ID)
+ .constraint(DSL.primaryKey(BLOB_ID)))
+ .supportsRowLevelSecurity
+ .build
+
+ val MODULE: PostgresModule = PostgresModule
+ .builder
+ .addTable(TABLE)
+ .build
+}
diff --git a/tmail-backend/blob/blobid-list/src/main/scala/com/linagora/tmail/blob/blobid/list/postgres/PostgresSingleSaveBlobStoreModule.scala b/tmail-backend/blob/blobid-list/src/main/scala/com/linagora/tmail/blob/blobid/list/postgres/PostgresSingleSaveBlobStoreModule.scala
new file mode 100644
index 00000000000..43290f0eb79
--- /dev/null
+++ b/tmail-backend/blob/blobid-list/src/main/scala/com/linagora/tmail/blob/blobid/list/postgres/PostgresSingleSaveBlobStoreModule.scala
@@ -0,0 +1,25 @@
+package com.linagora.tmail.blob.blobid.list.postgres
+
+import com.google.inject.multibindings.Multibinder
+import com.google.inject.{AbstractModule, Scopes}
+import com.linagora.tmail.blob.blobid.list.BlobIdList
+import org.apache.james.backends.postgres.PostgresModule
+
+class PostgresBlobIdListGuiceModule extends AbstractModule {
+ override def configure(): Unit = {
+ Multibinder.newSetBinder(binder, classOf[PostgresModule])
+ .addBinding
+ .toInstance(PostgresBlobIdListModule.MODULE)
+
+ bind(classOf[PostgresBlobIdListDAO]).in(Scopes.SINGLETON)
+ bind(classOf[PostgresBlobIdList]).in(Scopes.SINGLETON)
+
+ bind(classOf[BlobIdList]).to(classOf[PostgresBlobIdList])
+ }
+}
+
+class PostgresSingleSaveBlobStoreModule extends AbstractModule {
+ override def configure(): Unit = {
+ install(new PostgresBlobIdListGuiceModule)
+ }
+}
diff --git a/tmail-backend/blob/blobid-list/src/test/java/com/linagora/tmail/blob/blobid/list/SingleSaveBlobStoreContract.scala b/tmail-backend/blob/blobid-list/src/test/java/com/linagora/tmail/blob/blobid/list/SingleSaveBlobStoreContract.scala
index 16266cf8aa8..f2aff1db16a 100644
--- a/tmail-backend/blob/blobid-list/src/test/java/com/linagora/tmail/blob/blobid/list/SingleSaveBlobStoreContract.scala
+++ b/tmail-backend/blob/blobid-list/src/test/java/com/linagora/tmail/blob/blobid/list/SingleSaveBlobStoreContract.scala
@@ -1,11 +1,13 @@
package com.linagora.tmail.blob.blobid.list
import java.io.ByteArrayInputStream
+import java.time.Duration
import java.util.UUID
import com.google.common.io.ByteSource
import org.apache.james.blob.api.BlobStoreDAOFixture.{SHORT_BYTEARRAY, TEST_BUCKET_NAME}
import org.apache.james.blob.api.{BlobId, BlobStoreDAOContract, BucketName, ObjectStoreException}
+import org.apache.james.util.concurrency.ConcurrentTestRunner
import org.assertj.core.api.Assertions.{assertThat, assertThatCode, assertThatThrownBy}
import org.assertj.core.api.ThrowableAssert.ThrowingCallable
import org.junit.jupiter.api.Test
@@ -53,6 +55,16 @@ trait SingleSaveBlobStoreContract extends BlobStoreDAOContract {
.doesNotThrowAnyException()
}
+ @Test
+ def saveInputStreamConcurrentlyWithTheSameBlobShouldNoop(): Unit = {
+ val blobId: BlobId = blobIdFactory.of(UUID.randomUUID.toString)
+
+ ConcurrentTestRunner.builder
+ .operation((a: Int, b: Int) => SMono.fromPublisher(testee.save(defaultBucketName, blobId, new ByteArrayInputStream(SHORT_BYTEARRAY))).block())
+ .threadCount(100)
+ .runSuccessfullyWithin(Duration.ofMinutes(1))
+ }
+
@Test
def saveByteSourceShouldSuccessIfBlobIdDoesNotExist(): Unit = {
val blobId: BlobId = blobIdFactory.of(UUID.randomUUID.toString)
diff --git a/tmail-backend/blob/blobid-list/src/test/java/com/linagora/tmail/blob/blobid/list/postgres/PostgresSingleSaveBlobStoreTest.java b/tmail-backend/blob/blobid-list/src/test/java/com/linagora/tmail/blob/blobid/list/postgres/PostgresSingleSaveBlobStoreTest.java
new file mode 100644
index 00000000000..39eaf041616
--- /dev/null
+++ b/tmail-backend/blob/blobid-list/src/test/java/com/linagora/tmail/blob/blobid/list/postgres/PostgresSingleSaveBlobStoreTest.java
@@ -0,0 +1,57 @@
+package com.linagora.tmail.blob.blobid.list.postgres;
+
+import org.apache.james.backends.postgres.PostgresExtension;
+import org.apache.james.backends.postgres.PostgresModule;
+import org.apache.james.blob.api.BlobId;
+import org.apache.james.blob.api.BlobStoreDAO;
+import org.apache.james.blob.api.BucketName;
+import org.apache.james.blob.api.PlainBlobId;
+import org.apache.james.blob.memory.MemoryBlobStoreDAO;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Disabled;
+import org.junit.jupiter.api.extension.RegisterExtension;
+
+import com.linagora.tmail.blob.blobid.list.BlobIdList;
+import com.linagora.tmail.blob.blobid.list.SingleSaveBlobStoreContract;
+import com.linagora.tmail.blob.blobid.list.SingleSaveBlobStoreDAO;
+
+public class PostgresSingleSaveBlobStoreTest implements SingleSaveBlobStoreContract {
+ @RegisterExtension
+ static PostgresExtension postgresExtension = PostgresExtension.withoutRowLevelSecurity(
+ PostgresModule.aggregateModules(PostgresBlobIdListModule.MODULE()));
+
+ private PostgresBlobIdList postgresBlobIdList;
+ private BlobStoreDAO blobStoreDAO;
+
+ @BeforeEach
+ void setUp() {
+ postgresBlobIdList = new PostgresBlobIdList(new PostgresBlobIdListDAO(postgresExtension.getDefaultPostgresExecutor()));
+ blobStoreDAO = new SingleSaveBlobStoreDAO(new MemoryBlobStoreDAO(), postgresBlobIdList, defaultBucketName());
+ }
+
+ @Override
+ public BlobStoreDAO testee() {
+ return blobStoreDAO;
+ }
+
+ @Override
+ public BlobIdList blobIdList() {
+ return postgresBlobIdList;
+ }
+
+ @Override
+ public BlobId.Factory blobIdFactory() {
+ return new PlainBlobId.Factory();
+ }
+
+ @Override
+ public BucketName defaultBucketName() {
+ return BucketName.DEFAULT;
+ }
+
+ @Override
+ @Disabled("Not supported")
+ public void listBucketsShouldReturnBucketsWithNoBlob() {
+
+ }
+}
diff --git a/tmail-backend/combined-identity/src/main/java/com/linagora/tmail/combined/identity/CombinedUserDAO.java b/tmail-backend/combined-identity/src/main/java/com/linagora/tmail/combined/identity/CombinedUserDAO.java
index 5a1a89b2059..936b0e0676f 100644
--- a/tmail-backend/combined-identity/src/main/java/com/linagora/tmail/combined/identity/CombinedUserDAO.java
+++ b/tmail-backend/combined-identity/src/main/java/com/linagora/tmail/combined/identity/CombinedUserDAO.java
@@ -4,11 +4,12 @@
import java.util.Optional;
import jakarta.inject.Inject;
+import jakarta.inject.Named;
+import jakarta.inject.Singleton;
import org.apache.james.core.Username;
import org.apache.james.user.api.UsersRepositoryException;
import org.apache.james.user.api.model.User;
-import org.apache.james.user.cassandra.CassandraUsersDAO;
import org.apache.james.user.ldap.ReadOnlyLDAPUsersDAO;
import org.apache.james.user.lib.UsersDAO;
import org.reactivestreams.Publisher;
@@ -16,21 +17,23 @@
public class CombinedUserDAO implements UsersDAO {
public static final org.slf4j.Logger LOGGER = LoggerFactory.getLogger(CombinedUserDAO.class);
+ public static final String DATABASE_INJECT_NAME = "database";
private final ReadOnlyLDAPUsersDAO readOnlyLDAPUsersDAO;
- private final CassandraUsersDAO cassandraUsersDAO;
+ private final UsersDAO usersDAO;
@Inject
+ @Singleton
public CombinedUserDAO(ReadOnlyLDAPUsersDAO readOnlyLDAPUsersDAO,
- CassandraUsersDAO cassandraUsersDAO) {
+ @Named(DATABASE_INJECT_NAME) UsersDAO usersDAO) {
this.readOnlyLDAPUsersDAO = readOnlyLDAPUsersDAO;
- this.cassandraUsersDAO = cassandraUsersDAO;
+ this.usersDAO = usersDAO;
}
@Override
public void addUser(Username username, String password) throws UsersRepositoryException {
if (readOnlyLDAPUsersDAO.contains(username)) {
- cassandraUsersDAO.addUser(username, password);
+ usersDAO.addUser(username, password);
} else {
throw new UsersRepositoryException("Can not add user as it does not exits in LDAP repository. " + username.asString());
}
@@ -44,40 +47,40 @@ public void updateUser(User user) throws UsersRepositoryException {
@Override
public void removeUser(Username name) throws UsersRepositoryException {
if (!readOnlyLDAPUsersDAO.contains(name)) {
- cassandraUsersDAO.removeUser(name);
+ usersDAO.removeUser(name);
} else {
throw new UsersRepositoryException("Can not remove user as it does still exit in LDAP repository. " + name.asString());
}
}
@Override
- public Optional extends User> getUserByName(Username name) {
- return cassandraUsersDAO.getUserByName(name);
+ public Optional extends User> getUserByName(Username name) throws UsersRepositoryException {
+ return usersDAO.getUserByName(name);
}
@Override
- public boolean contains(Username name) {
- return cassandraUsersDAO.contains(name);
+ public boolean contains(Username name) throws UsersRepositoryException {
+ return usersDAO.contains(name);
}
@Override
public Publisher containsReactive(Username name) {
- return cassandraUsersDAO.containsReactive(name);
+ return usersDAO.containsReactive(name);
}
@Override
- public int countUsers() {
- return cassandraUsersDAO.countUsers();
+ public int countUsers() throws UsersRepositoryException {
+ return usersDAO.countUsers();
}
@Override
- public Iterator list() {
- return cassandraUsersDAO.list();
+ public Iterator list() throws UsersRepositoryException {
+ return usersDAO.list();
}
@Override
public Publisher listReactive() {
- return cassandraUsersDAO.listReactive();
+ return usersDAO.listReactive();
}
public Optional test(Username name, String password) throws UsersRepositoryException {
diff --git a/tmail-backend/combined-identity/src/main/java/com/linagora/tmail/combined/identity/UsersRepositoryModuleChooser.java b/tmail-backend/combined-identity/src/main/java/com/linagora/tmail/combined/identity/UsersRepositoryModuleChooser.java
deleted file mode 100644
index 8bac8f621b2..00000000000
--- a/tmail-backend/combined-identity/src/main/java/com/linagora/tmail/combined/identity/UsersRepositoryModuleChooser.java
+++ /dev/null
@@ -1,81 +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. *
- ****************************************************************/
-
-/**
- * This class is copied & adapted from {@link org.apache.james.data.UsersRepositoryModuleChooser}
- */
-
-package com.linagora.tmail.combined.identity;
-
-import java.util.List;
-import java.util.Optional;
-
-import org.apache.commons.configuration2.HierarchicalConfiguration;
-import org.apache.commons.configuration2.ex.ConfigurationException;
-import org.apache.commons.configuration2.tree.ImmutableNode;
-import org.apache.james.data.LdapUsersRepositoryModule;
-import org.apache.james.modules.data.CassandraUsersRepositoryModule;
-import org.apache.james.server.core.configuration.FileConfigurationProvider;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import com.google.common.collect.ImmutableList;
-import com.google.inject.Module;
-
-public class UsersRepositoryModuleChooser {
-
- private static final Logger LOGGER = LoggerFactory.getLogger(UsersRepositoryModuleChooser.class);
-
- public static List chooseModules(Implementation implementation) {
- return switch (implementation) {
- case LDAP -> ImmutableList.of(new LdapUsersRepositoryModule());
- case COMBINED -> ImmutableList.of(new CombinedUsersRepositoryModule());
- case DEFAULT -> ImmutableList.of(new CassandraUsersRepositoryModule());
- };
- }
-
- public static List chooseModules(FileConfigurationProvider fileConfigurationProvider) {
- return chooseModules(Implementation.parse(fileConfigurationProvider));
- }
-
- public enum Implementation {
- LDAP,
- COMBINED,
- DEFAULT;
-
- public static Implementation parse(FileConfigurationProvider configurationProvider) {
- try {
- HierarchicalConfiguration userRepositoryConfig = configurationProvider.getConfiguration("usersrepository");
- return Optional.ofNullable(userRepositoryConfig.getString("[@ldapHost]", null))
- .map(anyHost -> userRepositoryConfig.getString("[@class]", "")
- .contains("CombinedUsersRepository"))
- .map(isCombined -> {
- if (isCombined) {
- return COMBINED;
- }
- return LDAP;
- })
- .orElse(DEFAULT);
- } catch (ConfigurationException e) {
- LOGGER.warn("Error reading usersrepository.xml, defaulting to default implementation", e);
- return Implementation.DEFAULT;
- }
- }
- }
-}
diff --git a/tmail-backend/deployment-tests/pom.xml b/tmail-backend/deployment-tests/pom.xml
index 1b8abb99867..0883201a4b3 100644
--- a/tmail-backend/deployment-tests/pom.xml
+++ b/tmail-backend/deployment-tests/pom.xml
@@ -19,6 +19,7 @@
distributed
distributed-ldap
memory
+ postgres
diff --git a/tmail-backend/deployment-tests/postgres/pom.xml b/tmail-backend/deployment-tests/postgres/pom.xml
new file mode 100644
index 00000000000..4c927c38893
--- /dev/null
+++ b/tmail-backend/deployment-tests/postgres/pom.xml
@@ -0,0 +1,32 @@
+
+
+ 4.0.0
+
+ com.linagora.tmail
+ deployment-tests
+ 1.0.0-SNAPSHOT
+
+
+ deployment-tests-postgres
+ Team-mail :: Deployment Tests :: Postgres
+
+
+
+ ${project.groupId}
+ deployment-tests-common
+
+
+ ${project.groupId}
+ postgres
+
+
+ ${project.groupId}
+ postgres
+ test-jar
+ test
+
+
+
+
\ No newline at end of file
diff --git a/tmail-backend/deployment-tests/postgres/src/test/java/com/linagora/tmail/deployment/PostgresImapAndSmtpTest.java b/tmail-backend/deployment-tests/postgres/src/test/java/com/linagora/tmail/deployment/PostgresImapAndSmtpTest.java
new file mode 100644
index 00000000000..4055045435f
--- /dev/null
+++ b/tmail-backend/deployment-tests/postgres/src/test/java/com/linagora/tmail/deployment/PostgresImapAndSmtpTest.java
@@ -0,0 +1,21 @@
+package com.linagora.tmail.deployment;
+
+import org.apache.james.mpt.imapmailbox.external.james.host.external.ExternalJamesConfiguration;
+import org.junit.jupiter.api.extension.RegisterExtension;
+import org.testcontainers.containers.GenericContainer;
+
+public class PostgresImapAndSmtpTest extends ImapAndSmtpContract {
+
+ @RegisterExtension
+ TmailPostgresExtension extension = new TmailPostgresExtension();
+
+ @Override
+ protected ExternalJamesConfiguration configuration() {
+ return extension.configuration();
+ }
+
+ @Override
+ protected GenericContainer> container() {
+ return extension.getContainer();
+ }
+}
diff --git a/tmail-backend/deployment-tests/postgres/src/test/java/com/linagora/tmail/deployment/PostgresLdapImapAndSmtpTest.java b/tmail-backend/deployment-tests/postgres/src/test/java/com/linagora/tmail/deployment/PostgresLdapImapAndSmtpTest.java
new file mode 100644
index 00000000000..5ee28accbd1
--- /dev/null
+++ b/tmail-backend/deployment-tests/postgres/src/test/java/com/linagora/tmail/deployment/PostgresLdapImapAndSmtpTest.java
@@ -0,0 +1,21 @@
+package com.linagora.tmail.deployment;
+
+import org.apache.james.mpt.imapmailbox.external.james.host.external.ExternalJamesConfiguration;
+import org.junit.jupiter.api.extension.RegisterExtension;
+import org.testcontainers.containers.GenericContainer;
+
+public class PostgresLdapImapAndSmtpTest extends ImapAndSmtpContract {
+
+ @RegisterExtension
+ TmailLdapPostgresExtension extension = new TmailLdapPostgresExtension();
+
+ @Override
+ protected ExternalJamesConfiguration configuration() {
+ return extension.configuration();
+ }
+
+ @Override
+ protected GenericContainer> container() {
+ return extension.getContainer();
+ }
+}
diff --git a/tmail-backend/deployment-tests/postgres/src/test/java/com/linagora/tmail/deployment/TmailLdapPostgresExtension.java b/tmail-backend/deployment-tests/postgres/src/test/java/com/linagora/tmail/deployment/TmailLdapPostgresExtension.java
new file mode 100644
index 00000000000..42b77f3b5ce
--- /dev/null
+++ b/tmail-backend/deployment-tests/postgres/src/test/java/com/linagora/tmail/deployment/TmailLdapPostgresExtension.java
@@ -0,0 +1,67 @@
+package com.linagora.tmail.deployment;
+
+import java.time.Duration;
+import java.util.UUID;
+
+import org.junit.jupiter.api.extension.ExtensionContext;
+import org.testcontainers.containers.GenericContainer;
+import org.testcontainers.containers.Network;
+import org.testcontainers.containers.startupcheck.MinimumDurationRunningStartupCheckStrategy;
+import org.testcontainers.containers.wait.strategy.LogMessageWaitStrategy;
+import org.testcontainers.images.builder.ImageFromDockerfile;
+import org.testcontainers.utility.MountableFile;
+
+public class TmailLdapPostgresExtension extends TmailPostgresExtension {
+ private GenericContainer> ldap;
+
+ public TmailLdapPostgresExtension() {
+ ldap = createLdap(network);
+ }
+
+ @SuppressWarnings("resource")
+ @Override
+ public GenericContainer> createTmailDistributed() {
+ if (ldap == null) {
+ ldap = createLdap(network);
+ }
+
+ return new GenericContainer<>("linagora/tmail-backend-postgresql-experimental:latest")
+ .withNetworkAliases("tmail-postgres")
+ .withNetwork(network)
+ .dependsOn(postgres, opensearch, s3, rabbitmq, ldap)
+ .withCopyFileToContainer(MountableFile.forClasspathResource("james-conf/imapserver.xml"), "/root/conf/")
+ .withCopyFileToContainer(MountableFile.forClasspathResource("james-conf/usersrepository.xml"), "/root/conf/")
+ .withCopyFileToContainer(MountableFile.forClasspathResource("james-conf/jwt_privatekey"), "/root/conf/")
+ .withCopyFileToContainer(MountableFile.forClasspathResource("james-conf/jwt_publickey"), "/root/conf/")
+ .withCopyFileToContainer(MountableFile.forClasspathResource("james-conf/keystore"), "/root/conf/")
+ .withCopyFileToContainer(MountableFile.forClasspathResource("james-conf/jmxremote.password"), "/root/conf/")
+ .withCopyFileToContainer(MountableFile.forClasspathResource("james-conf/blob.properties"), "/root/conf/")
+ .withCopyFileToContainer(MountableFile.forClasspathResource("james-conf/rabbitmq.properties"), "/root/conf/")
+ .withCopyFileToContainer(MountableFile.forClasspathResource("james-conf/opensearch.properties"), "/root/conf/")
+ .withCreateContainerCmdModifier(createContainerCmd -> createContainerCmd.withName("team-mail-backend-postgres-testing" + UUID.randomUUID()))
+ .waitingFor(TestContainerWaitStrategy.WAIT_STRATEGY)
+ .withExposedPorts(25, 143, 80, 8000);
+ }
+
+ private GenericContainer> createLdap(Network network) {
+ return new GenericContainer<>(
+ new ImageFromDockerfile()
+ .withFileFromClasspath("populate.ldif", "prepopulated-ldap/populate.ldif")
+ .withFileFromClasspath("Dockerfile", "prepopulated-ldap/Dockerfile"))
+ .withNetworkAliases("ldap")
+ .withNetwork(network)
+ .withEnv("LDAP_DOMAIN", "james.org")
+ .withEnv("LDAP_ADMIN_PASSWORD", "secret")
+ .withCommand("--copy-service --loglevel debug")
+ .withCreateContainerCmdModifier(createContainerCmd -> createContainerCmd.withName("team-mail-openldap-testing" + UUID.randomUUID()))
+ .waitingFor(new LogMessageWaitStrategy().withRegEx(".*slapd starting\\n").withTimes(1)
+ .withStartupTimeout(Duration.ofMinutes(3)))
+ .withStartupCheckStrategy(new MinimumDurationRunningStartupCheckStrategy(Duration.ofSeconds(10)));
+ }
+
+ @Override
+ public void afterEach(ExtensionContext extensionContext) throws Exception {
+ super.afterEach(extensionContext);
+ ldap.stop();
+ }
+}
diff --git a/tmail-backend/deployment-tests/postgres/src/test/java/com/linagora/tmail/deployment/TmailPostgresExtension.java b/tmail-backend/deployment-tests/postgres/src/test/java/com/linagora/tmail/deployment/TmailPostgresExtension.java
new file mode 100644
index 00000000000..3fbe7dd7001
--- /dev/null
+++ b/tmail-backend/deployment-tests/postgres/src/test/java/com/linagora/tmail/deployment/TmailPostgresExtension.java
@@ -0,0 +1,117 @@
+package com.linagora.tmail.deployment;
+
+import static com.linagora.tmail.deployment.ThirdPartyContainers.OS_IMAGE_NAME;
+import static com.linagora.tmail.deployment.ThirdPartyContainers.OS_NETWORK_ALIAS;
+import static com.linagora.tmail.deployment.ThirdPartyContainers.createRabbitMQ;
+import static com.linagora.tmail.deployment.ThirdPartyContainers.createS3;
+import static com.linagora.tmail.deployment.ThirdPartyContainers.createSearchContainer;
+
+import java.io.File;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.time.Duration;
+import java.time.temporal.ChronoUnit;
+import java.util.UUID;
+
+import org.apache.james.mpt.imapmailbox.external.james.host.external.ExternalJamesConfiguration;
+import org.apache.james.util.Port;
+import org.apache.james.util.Runnables;
+import org.junit.jupiter.api.extension.AfterEachCallback;
+import org.junit.jupiter.api.extension.BeforeEachCallback;
+import org.junit.jupiter.api.extension.ExtensionContext;
+import org.testcontainers.containers.GenericContainer;
+import org.testcontainers.containers.Network;
+import org.testcontainers.containers.wait.strategy.LogMessageWaitStrategy;
+import org.testcontainers.utility.MountableFile;
+
+public class TmailPostgresExtension implements BeforeEachCallback, AfterEachCallback {
+ protected final Network network;
+ protected final GenericContainer> postgres;
+ protected final GenericContainer> opensearch;
+ protected final GenericContainer> rabbitmq;
+ protected final GenericContainer> s3;
+ protected final GenericContainer> tmailBackend;
+
+ public TmailPostgresExtension() {
+ network = Network.newNetwork();
+ postgres = createPostgres(network);
+ opensearch = createSearchContainer(network, OS_IMAGE_NAME, OS_NETWORK_ALIAS);
+ rabbitmq = createRabbitMQ(network);
+ s3 = createS3(network);
+ tmailBackend = createTmailDistributed();
+ }
+
+ @SuppressWarnings("resource")
+ protected GenericContainer> createTmailDistributed() {
+ return new GenericContainer<>("linagora/tmail-backend-postgresql-experimental:latest")
+ .withNetworkAliases("tmail-postgres")
+ .withNetwork(network)
+ .dependsOn(postgres, opensearch, s3, rabbitmq)
+ .withCopyFileToContainer(MountableFile.forClasspathResource("james-conf/imapserver.xml"), "/root/conf/")
+ .withCopyFileToContainer(MountableFile.forClasspathResource("james-conf/jwt_privatekey"), "/root/conf/")
+ .withCopyFileToContainer(MountableFile.forClasspathResource("james-conf/jwt_publickey"), "/root/conf/")
+ .withCopyFileToContainer(MountableFile.forClasspathResource("james-conf/keystore"), "/root/conf/")
+ .withCopyFileToContainer(MountableFile.forClasspathResource("james-conf/jmxremote.password"), "/root/conf/")
+ .withCopyFileToContainer(MountableFile.forClasspathResource("james-conf/blob.properties"), "/root/conf/")
+ .withCopyFileToContainer(MountableFile.forClasspathResource("james-conf/rabbitmq.properties"), "/root/conf/")
+ .withCopyFileToContainer(MountableFile.forClasspathResource("james-conf/opensearch.properties"), "/root/conf/")
+ .withCreateContainerCmdModifier(createContainerCmd -> createContainerCmd.withName("team-mail-backend-postgres-testing" + UUID.randomUUID()))
+ .waitingFor(TestContainerWaitStrategy.WAIT_STRATEGY)
+ .withExposedPorts(25, 143, 80, 8000);
+ }
+
+ @SuppressWarnings("resource")
+ protected static GenericContainer> createPostgres(Network network) {
+ return new GenericContainer<>("postgres:16.1")
+ .withNetworkAliases("postgres")
+ .withNetwork(network)
+ .withEnv("POSTGRES_USER", "tmail")
+ .withEnv("POSTGRES_PASSWORD", "secret1")
+ .withEnv("POSTGRES_DB", "postgres")
+ .withCreateContainerCmdModifier(createContainerCmd -> createContainerCmd.withName("team-mail-postgres-database-testing" + UUID.randomUUID()))
+ .waitingFor((new LogMessageWaitStrategy()).withRegEx(".*database system is ready to accept connections.*\\s").withTimes(2).withStartupTimeout(Duration.of(60L, ChronoUnit.SECONDS)))
+ .withExposedPorts(5432);
+ }
+
+ @Override
+ public void beforeEach(ExtensionContext extensionContext) throws Exception {
+ String dockerSaveFileUrl = new File("").getAbsolutePath().replace(Paths.get("tmail-backend", "deployment-tests", "postgres").toString(),
+ Paths.get("tmail-backend", "apps", "postgres", "target", "jib-image.tar").toString());
+ tmailBackend.getDockerClient().loadImageCmd(Files.newInputStream(Paths.get(dockerSaveFileUrl))).exec();
+ tmailBackend.start();
+ }
+
+ @Override
+ public void afterEach(ExtensionContext extensionContext) throws Exception {
+ tmailBackend.stop();
+ Runnables.runParallel(
+ postgres::stop,
+ opensearch::stop,
+ rabbitmq::stop,
+ s3::stop);
+ }
+
+ public GenericContainer> getContainer() {
+ return tmailBackend;
+ }
+
+ ExternalJamesConfiguration configuration() {
+ return new ExternalJamesConfiguration() {
+ @Override
+ public String getAddress() {
+ return tmailBackend.getHost();
+ }
+
+ @Override
+ public Port getImapPort() {
+ return Port.of(tmailBackend.getMappedPort(143));
+ }
+
+ @Override
+ public Port getSmptPort() {
+ return Port.of(tmailBackend.getMappedPort(25));
+ }
+ };
+ }
+
+}
diff --git a/tmail-backend/deployment-tests/postgres/src/test/resources/james-conf/blob.properties b/tmail-backend/deployment-tests/postgres/src/test/resources/james-conf/blob.properties
new file mode 100644
index 00000000000..a02854b2313
--- /dev/null
+++ b/tmail-backend/deployment-tests/postgres/src/test/resources/james-conf/blob.properties
@@ -0,0 +1,104 @@
+# ============================================= BlobStore Implementation ==================================
+# Read https://james.apache.org/server/config-blobstore.html for further details
+
+# Choose your BlobStore implementation
+# Mandatory, allowed values are: file, s3, postgres.
+implementation=s3
+
+# ========================================= Deduplication ========================================
+# If you choose to enable deduplication, the mails with the same content will be stored only once.
+# Warning: Once this feature is enabled, there is no turning back as turning it off will lead to the deletion of all
+# the mails sharing the same content once one is deleted.
+# Mandatory, Allowed values are: true, false
+deduplication.enable=true
+
+# deduplication.family needs to be incremented every time the deduplication.generation.duration is changed
+# Positive integer, defaults to 1
+# deduplication.gc.generation.family=1
+
+# Duration of generation.
+# Deduplication only takes place within a singe generation.
+# Only items two generation old can be garbage collected. (This prevent concurrent insertions issues and
+# accounts for a clock skew).
+# deduplication.family needs to be incremented everytime this parameter is changed.
+# Duration. Default unit: days. Defaults to 30 days.
+# deduplication.gc.generation.duration=30days
+
+# ========================================= Encryption ========================================
+# If you choose to enable encryption, the blob content will be encrypted before storing them in the BlobStore.
+# Warning: Once this feature is enabled, there is no turning back as turning it off will lead to all content being
+# encrypted. This comes at a performance impact but presents you from leaking data if, for instance the third party
+# offering you a S3 service is compromised.
+# Optional, Allowed values are: true, false, defaults to false
+encryption.aes.enable=false
+
+# Mandatory (if AES encryption is enabled) salt and password. Salt needs to be an hexadecimal encoded string
+#encryption.aes.password=xxx
+#encryption.aes.salt=73616c7479
+# Optional, defaults to PBKDF2WithHmacSHA512
+#encryption.aes.private.key.algorithm=PBKDF2WithHmacSHA512
+
+# ============================================== ObjectStorage ============================================
+
+# ========================================= ObjectStorage Buckets ==========================================
+# bucket names prefix
+# Optional, default no prefix
+# objectstorage.bucketPrefix=prod-
+
+# Default bucket name
+# Optional, default is bucketPrefix + `default`
+# objectstorage.namespace=james
+
+# ========================================= ObjectStorage on S3 =============================================
+# Mandatory if you choose s3 storage service, S3 authentication endpoint
+objectstorage.s3.endPoint=${env:OBJECT_STORAGE_ENDPOINT:-http://s3.docker.test:8000/}
+
+# Mandatory if you choose s3 storage service, S3 region
+#objectstorage.s3.region=eu-west-1
+objectstorage.s3.region=${env:OBJECT_STORAGE_REGION:-eu-west-1}
+
+# Mandatory if you choose aws-s3 storage service, access key id configured in S3
+objectstorage.s3.accessKeyId=${env:OBJECT_STORAGE_ACCESS_KEY_ID:-accessKey1}
+
+# Mandatory if you choose s3 storage service, secret key configured in S3
+objectstorage.s3.secretKey=${env:OBJECT_STORAGE_SECRET_KEY:-secretKey1}
+
+# Optional if you choose s3 storage service: The trust store file, secret, and algorithm to use
+# when connecting to the storage service. If not specified falls back to Java defaults.
+#objectstorage.s3.truststore.path=
+#objectstorage.s3.truststore.type=JKS
+#objectstorage.s3.truststore.secret=
+#objectstorage.s3.truststore.algorithm=SunX509
+
+
+# optional: Object read in memory will be rejected if they exceed the size limit exposed here. Size, exemple `100M`.
+# Supported units: K, M, G, defaults to B if no unit is specified. If unspecified, big object won't be prevented
+# from being loaded in memory. This settings complements protocol limits.
+# objectstorage.s3.in.read.limit=50M
+
+# ============================================ Blobs Exporting ==============================================
+# Read https://james.apache.org/server/config-blob-export.html for further details
+
+# Choosing blob exporting mechanism, allowed mechanism are: localFile, linshare
+# LinShare is a file sharing service, will be explained in the below section
+# Optional, default is localFile
+blob.export.implementation=localFile
+
+# ======================================= Local File Blobs Exporting ========================================
+# Optional, directory to store exported blob, directory path follows James file system format
+# default is file://var/blobExporting
+blob.export.localFile.directory=file://var/blobExporting
+
+# ======================================= LinShare File Blobs Exporting ========================================
+# LinShare is a sharing service where you can use james, connects to an existing LinShare server and shares files to
+# other mail addresses as long as those addresses available in LinShare. For example you can deploy James and LinShare
+# sharing the same LDAP repository
+# Mandatory if you choose LinShare, url to connect to LinShare service
+# blob.export.linshare.url=http://linshare:8080
+
+# ======================================= LinShare Configuration BasicAuthentication ===================================
+# Authentication is mandatory if you choose LinShare, TechnicalAccount is need to connect to LinShare specific service.
+# For Example: It will be formalized to 'Authorization: Basic {Credential of UUID/password}'
+
+# blob.export.linshare.technical.account.uuid=Technical_Account_UUID
+# blob.export.linshare.technical.account.password=password
diff --git a/tmail-backend/deployment-tests/postgres/src/test/resources/james-conf/imapserver.xml b/tmail-backend/deployment-tests/postgres/src/test/resources/james-conf/imapserver.xml
new file mode 100644
index 00000000000..87b596d5dae
--- /dev/null
+++ b/tmail-backend/deployment-tests/postgres/src/test/resources/james-conf/imapserver.xml
@@ -0,0 +1,64 @@
+
+
+
+
+
+
+
+
+ imapserver
+ 0.0.0.0:143
+ 200
+
+
+ file://conf/keystore
+ james72laBalle
+ org.bouncycastle.jce.provider.BouncyCastleProvider
+
+ 0
+ 0
+ 120
+ SECONDS
+ true
+ false
+ false
+
+
+ imapserver-ssl
+ 0.0.0.0:993
+ 200
+
+
+ file://conf/keystore
+ james72laBalle
+ org.bouncycastle.jce.provider.BouncyCastleProvider
+
+ 0
+ 0
+ 120
+ SECONDS
+ true
+ false
+
+
diff --git a/tmail-backend/deployment-tests/postgres/src/test/resources/james-conf/jmxremote.password b/tmail-backend/deployment-tests/postgres/src/test/resources/james-conf/jmxremote.password
new file mode 100644
index 00000000000..cf0b7bf4224
--- /dev/null
+++ b/tmail-backend/deployment-tests/postgres/src/test/resources/james-conf/jmxremote.password
@@ -0,0 +1 @@
+james-admin 123456
\ No newline at end of file
diff --git a/tmail-backend/deployment-tests/postgres/src/test/resources/james-conf/jwt_privatekey b/tmail-backend/deployment-tests/postgres/src/test/resources/james-conf/jwt_privatekey
new file mode 100644
index 00000000000..a4aa851ff56
--- /dev/null
+++ b/tmail-backend/deployment-tests/postgres/src/test/resources/james-conf/jwt_privatekey
@@ -0,0 +1,28 @@
+-----BEGIN PRIVATE KEY-----
+MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDEV3BhLSBHuApH
+GNlquIczzcvDOc0HoqczATsM7CeRp2SUicioxlItL2q6tKGTsK1zCcM+uKw1ydxd
+AGDxpBD6kqIel/Quu/dRd8H6P/f4Hr0OOxGCgc78FYvjY6gcbPtaOmD9u/RMRqP6
+95s/DriPxPDYzA58yRH7NlhzJymthoZEtwIL/SuZ6e2huqnJdC11wEJgkwwkKO3x
+01Y84V2y2EdRCvb5j9EOkCC5C4TGpjvSffD/4mcj+Z2VNTxQV0KdrlNxhBwwFEbL
+TQ2B5hIkH2INDP6ubkxJeXmZ3iXyw+iNKtBUef9ZavIk4lZLKYNjZWX2eBg2cokT
+p759oRoHAgMBAAECggEAdmyrCuH2C2wVPubdFIKygeuKEHnHkehoYtpGLLgv8al+
+gB1PG4VrQXfNL0oN/w/cvntP+X/X1yWnNa0py/YCi7Bv+nX6wUl8lfXe2TtGLLEV
+pQS5vfbfyqqQUpnkZyjQvo5hvAlnA67D73bze6g8Z/MItir2PgvlPZl85g/kEpX3
+zt1yDTUFe+L8Ur+8Lied/0w2M53lUlebIYtsa2W7I05YzUBAVXkCIffnaIt3QtTC
+tppBcVZXUacRtqULBMcUE2zc/yUKNqHzBjlkBv4VQ7nQDOjjUfW+VtccnTr+mLnl
+R7VDy+POuZQ5u0gA8IwJNJFd5qdIm7l4tG7xa69rMQKBgQDmX1FF5Zty0Q7Acp1n
+G9TZBOTTehzQPYsJwMynLR/b8mUAL+FTCh7Q7OJkGhcBxVDgc34OVPvc9wlFSUuU
+ac0C0GtcD3BidatEfwMqVdpwcDnSEK47N13oSmaAL21mgC6/0ypV5TBgbkMRcxSb
+h1GdeBWEG1RluVx6n1TflSvw6QKBgQDaLvj3fNbcIfJubx5oP2kmA1rw5jcSShK4
+UgM4Ifj98LOmsiB6qfY+36p6D78XINV0KpS3THi8rWzf31OuTN1BoZ5UpcyOOrDb
+2pNnfGpC1VBP4rfWJMNpcGstj3YUNEV5pLyd5Cd6/gV8nRgiQ9ccEDJ1I5GXVWfV
++2a7/zddbwKBgHqWWi8xoWiVqp3p36yQeNEK86E9J7wAI86K09xZ/MwTzn8s+2Au
+0HsossfFwlxk3Uay7m899dB9fGdsO1W8fyVyNs8EQC+EoiCO3eZXTSfr8DjCO5Sz
+P7tua+DmW/bhWv8kpTCUBwwpYHMWo+6nMVz0G67yxBRlcLqnsohPXtSRAoGBAMMD
+MxJ6Kc1OJlMgzKve6YvJefJRwq19Oag33ZrBerz29IwtMCyTV36xCb3Z7zGr7j3L
+hWskVdJGrEaZZUEogKaV31/HZcNGoCeSASiBIrUj1ongmfI0n9jRW2q4jJDYe7ST
+UudJMySSgbL08spFmrIBpCfhJ9N8ybeP4i5smj7PAoGBANSfn+DzQPICp2rldw7y
+rPSCywIM6LzdoyykRMmX04sQAFVKgJwPei+oLrg8HcUjCZ1t8KJ4tHsvqFkQ0O4s
+q8eh2eli5Qppg/Qmx1zT1W7+JYxlPsiXfmViBcY2+yNjNOHQPzyJyE+pvybW0DC+
+k9EJZv81OGrKInhjB/Ep6C76
+-----END PRIVATE KEY-----
\ No newline at end of file
diff --git a/tmail-backend/deployment-tests/postgres/src/test/resources/james-conf/jwt_publickey b/tmail-backend/deployment-tests/postgres/src/test/resources/james-conf/jwt_publickey
new file mode 100644
index 00000000000..ea8beb9de08
--- /dev/null
+++ b/tmail-backend/deployment-tests/postgres/src/test/resources/james-conf/jwt_publickey
@@ -0,0 +1,9 @@
+-----BEGIN PUBLIC KEY-----
+MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxFdwYS0gR7gKRxjZariH
+M83LwznNB6KnMwE7DOwnkadklInIqMZSLS9qurShk7CtcwnDPrisNcncXQBg8aQQ
++pKiHpf0Lrv3UXfB+j/3+B69DjsRgoHO/BWL42OoHGz7Wjpg/bv0TEaj+vebPw64
+j8Tw2MwOfMkR+zZYcycprYaGRLcCC/0rmentobqpyXQtdcBCYJMMJCjt8dNWPOFd
+sthHUQr2+Y/RDpAguQuExqY70n3w/+JnI/mdlTU8UFdCna5TcYQcMBRGy00NgeYS
+JB9iDQz+rm5MSXl5md4l8sPojSrQVHn/WWryJOJWSymDY2Vl9ngYNnKJE6e+faEa
+BwIDAQAB
+-----END PUBLIC KEY-----
\ No newline at end of file
diff --git a/tmail-backend/deployment-tests/postgres/src/test/resources/james-conf/keystore b/tmail-backend/deployment-tests/postgres/src/test/resources/james-conf/keystore
new file mode 100644
index 00000000000..536a6c792b0
Binary files /dev/null and b/tmail-backend/deployment-tests/postgres/src/test/resources/james-conf/keystore differ
diff --git a/tmail-backend/deployment-tests/postgres/src/test/resources/james-conf/opensearch.properties b/tmail-backend/deployment-tests/postgres/src/test/resources/james-conf/opensearch.properties
new file mode 100644
index 00000000000..df261c5dee6
--- /dev/null
+++ b/tmail-backend/deployment-tests/postgres/src/test/resources/james-conf/opensearch.properties
@@ -0,0 +1,101 @@
+# 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.
+
+# This template file can be used as example for James Server configuration
+# DO NOT USE IT AS SUCH AND ADAPT IT TO YOUR NEEDS
+
+# Configuration file for OpenSearch
+# Read https://james.apache.org/server/config-opensearch.html for further details
+
+opensearch.masterHost=opensearch
+opensearch.port=9200
+
+# Optional. Only http or https are accepted, default is http
+# opensearch.hostScheme=http
+
+# Optional, default is `default`
+# Choosing the SSL check strategy when using https scheme
+# default: Use the default SSL TrustStore of the system.
+# ignore: Ignore SSL Validation check (not recommended).
+# override: Override the SSL Context to use a custom TrustStore containing ES server's certificate.
+# opensearch.hostScheme.https.sslValidationStrategy=default
+
+# Optional. Required when using 'https' scheme and 'override' sslValidationStrategy
+# Configure OpenSearch rest client to use this trustStore file to recognize nginx's ssl certificate.
+# You need to specify both trustStorePath and trustStorePassword
+# opensearch.hostScheme.https.trustStorePath=/file/to/trust/keystore.jks
+
+# Optional. Required when using 'https' scheme and 'override' sslValidationStrategy
+# Configure OpenSearch rest client to use this trustStore file with the specified password.
+# You need to specify both trustStorePath and trustStorePassword
+# opensearch.hostScheme.https.trustStorePassword=myJKSPassword
+
+# Optional. default is `default`
+# Configure OpenSearch rest client to use host name verifier during SSL handshake
+# default: using the default hostname verifier provided by apache http client.
+# accept_any_hostname: accept any host (not recommended).
+# opensearch.hostScheme.https.hostNameVerifier=default
+
+# Optional.
+# Basic auth username to access opensearch.
+# Ignore opensearch.user and opensearch.password to not be using authentication (default behaviour).
+# Otherwise, you need to specify both properties.
+# opensearch.user=elasticsearch
+
+# Optional.
+# Basic auth password to access opensearch.
+# Ignore opensearch.user and opensearch.password to not be using authentication (default behaviour).
+# Otherwise, you need to specify both properties.
+# opensearch.password=secret
+
+# You can alternatively provide a list of hosts following this format :
+# opensearch.hosts=host1:9200,host2:9200
+# opensearch.clusterName=cluster
+
+opensearch.nb.shards=5
+opensearch.nb.replica=1
+opensearch.index.waitForActiveShards=1
+opensearch.retryConnection.maxRetries=7
+opensearch.retryConnection.minDelay=3000
+# Index or not attachments (default value: true)
+# Note: Attachments not implemented yet for postgresql, false for now
+opensearch.indexAttachments=false
+
+# Search overrides allow resolution of predefined search queries against alternative sources of data
+# and allow bypassing opensearch. This is useful to handle most resynchronisation queries that
+# are simple enough to be resolved against Cassandra.
+#
+# Possible values are:
+# - `org.apache.james.mailbox.postgres.search.AllSearchOverride` Some IMAP clients uses SEARCH ALL to fully list messages in
+# a mailbox and detect deletions. This is typically done by clients not supporting QRESYNC and from an IMAP perspective
+# is considered an optimisation as less data is transmitted compared to a FETCH command. Resolving such requests against
+# Postgresql is enabled by this search override and likely desirable.
+# - `org.apache.james.mailbox.postgres.search.UidSearchOverride`. Same as above but restricted by ranges.
+# - `org.apache.james.mailbox.postgres.search.DeletedSearchOverride`. Find deleted messages by looking up in the relevant Postgresql
+# table.
+# - `org.apache.james.mailbox.postgres.search.DeletedWithRangeSearchOverride`. Same as above but limited by ranges.
+# - `org.apache.james.mailbox.postgres.search.NotDeletedWithRangeSearchOverride`. List non deleted messages in a given range.
+# Lists all messages and filters out deleted message thus this is based on the following heuristic: most messages are not marked as deleted.
+# - `org.apache.james.mailbox.postgres.search.UnseenSearchOverride`. List unseen messages in the corresponding Postgresql index.
+#
+# Please note that custom overrides can be defined here.
+#
+# opensearch.search.overrides=org.apache.james.mailbox.postgres.search.AllSearchOverride,org.apache.james.mailbox.postgres.search.DeletedSearchOverride, org.apache.james.mailbox.postgres.search.DeletedWithRangeSearchOverride,org.apache.james.mailbox.postgres.search.NotDeletedWithRangeSearchOverride,org.apache.james.mailbox.postgres.search.UidSearchOverride,org.apache.james.mailbox.postgres.search.UnseenSearchOverride
+
+# Optional. Default is `false`
+# When set to true, James will attempt to reindex from the indexed message when moved. If the message is not found, it will fall back to the old behavior (The message will be indexed from the blobStore source)
+# opensearch.message.index.optimize.move=false
\ No newline at end of file
diff --git a/tmail-backend/deployment-tests/postgres/src/test/resources/james-conf/rabbitmq.properties b/tmail-backend/deployment-tests/postgres/src/test/resources/james-conf/rabbitmq.properties
new file mode 100644
index 00000000000..c8b3f526026
--- /dev/null
+++ b/tmail-backend/deployment-tests/postgres/src/test/resources/james-conf/rabbitmq.properties
@@ -0,0 +1,103 @@
+# RabbitMQ configuration
+
+# Read https://james.apache.org/server/config-rabbitmq.html for further details
+
+# Mandatory
+uri=amqp://rabbitmq:5672
+# If you use a vhost, specify it as well at the end of the URI
+# uri=amqp://rabbitmq:5672/vhost
+
+# Vhost to use for creating queues and exchanges
+# Optional, only use this if you have invalid URIs containing characters like '_'
+# vhost=vhost1
+
+# Optional, default to the host specified as part of the URI.
+# Allow creating cluster aware connections.
+# hosts=ip1:5672,ip2:5672
+
+# RabbitMQ Administration Management
+# Mandatory
+management.uri=http://rabbitmq:15672
+# Mandatory
+management.user=guest
+# Mandatory
+management.password=guest
+
+# Configure retries count to retrieve a connection. Exponential backoff is performed between each retries.
+# Optional integer, defaults to 10
+#connection.pool.retries=10
+# Configure initial duration (in ms) between two connection retries. Exponential backoff is performed between each retries.
+# Optional integer, defaults to 100
+#connection.pool.min.delay.ms=100
+# Configure retries count to retrieve a channel. Exponential backoff is performed between each retries.
+# Optional integer, defaults to 3
+#channel.pool.retries=3
+# Configure timeout duration (in ms) to obtain a rabbitmq channel. Defaults to 30 seconds.
+# Optional integer, defaults to 30 seconds.
+#channel.pool.max.delay.ms=30000
+# Configure the size of the channel pool.
+# Optional integer, defaults to 3
+#channel.pool.size=3
+
+# Boolean. Whether to activate Quorum queue usage for use cases that benefits from it (work queue).
+# Quorum queues enables high availability.
+# False (default value) results in the usage of classic queues.
+#quorum.queues.enable=true
+
+# Strictly positive integer. The replication factor to use when creating quorum queues.
+#quorum.queues.replication.factor
+
+# Parameters for the Cassandra administrative view
+
+# Whether the Cassandra administrative view should be activated. Boolean value defaulting to true.
+# Not necessarily needed for MDA deployments, mail queue management adds significant complexity.
+# cassandra.view.enabled=true
+
+# Period of the window. Too large values will lead to wide rows while too little values might lead to many queries.
+# Use the number of mail per Cassandra row, along with your expected traffic, to determine this value
+# This value can only be decreased to a value dividing the current value
+# Optional, default 1h
+mailqueue.view.sliceWindow=1h
+
+# Use to distribute the emails of a given slice within your cassandra cluster
+# A good value is 2*cassandraNodeCount
+# This parameter can only be increased.
+# Optional, default 1
+mailqueue.view.bucketCount=1
+
+# Determine the probability to update the browse start pointer
+# Too little value will lead to unnecessary reads. Too big value will lead to more expensive browse.
+# Choose this parameter so that it get's update one time every one-two sliceWindow
+# Optional, default 1000
+mailqueue.view.updateBrowseStartPace=1000
+
+# Enables or disables the gauge metric on the mail queue size
+# Computing the size of the mail queue is currently implemented on top of browse operation and thus have a linear complexity
+# Metrics get exported periodically as configured in opensearch.properties, thus getSize is also called periodically
+# Choose to disable it when the mail queue size is getting too big
+# Note that this is as well a temporary workaround until we get 'getSize' method better optimized
+# Optional, default false
+mailqueue.size.metricsEnabled=false
+
+# Whether to enable task consumption on this node. Tasks are WebAdmin triggered long running jobs.
+# Disable with caution (this only makes sense in a distributed setup where other nodes consume tasks).
+# Defaults to true.
+task.consumption.enabled=true
+
+# Configure task queue consumer timeout. References: https://www.rabbitmq.com/consumers.html#acknowledgement-timeout. Required at least RabbitMQ version 3.12 to have effect.
+# This is used to avoid the task queue consumer (which could run very long tasks) being disconnected by RabbitMQ after the default acknowledgement timeout 30 minutes.
+# Optional. Duration (support multiple time units cf `DurationParser`), defaults to 1 day.
+#task.queue.consumer.timeout=1day
+
+# Configure queue ttl (in ms). References: https://www.rabbitmq.com/ttl.html#queue-ttl.
+# This is used only on queues used to share notification patterns, are exclusive to a node. If omitted, it will not add the TTL configure when declaring queues.
+# Optional integer, defaults is 3600000.
+#notification.queue.ttl=3600000
+
+# AMQP resources parameters for the subscriber email address contact messages. In order to synchronize contacts between Team-mail backend and 3rd.
+# AQMP uri
+address.contact.uri=amqp://rabbitmq:5672
+address.contact.user=guest
+address.contact.password=guest
+# Queue name
+address.contact.queue=AddressContactQueue1
\ No newline at end of file
diff --git a/tmail-backend/deployment-tests/postgres/src/test/resources/james-conf/usersrepository.xml b/tmail-backend/deployment-tests/postgres/src/test/resources/james-conf/usersrepository.xml
new file mode 100644
index 00000000000..8ba7bae5155
--- /dev/null
+++ b/tmail-backend/deployment-tests/postgres/src/test/resources/james-conf/usersrepository.xml
@@ -0,0 +1,13 @@
+
+
+ SHA-512
+ true
+ true
+
\ No newline at end of file
diff --git a/tmail-backend/deployment-tests/postgres/src/test/resources/prepopulated-ldap/Dockerfile b/tmail-backend/deployment-tests/postgres/src/test/resources/prepopulated-ldap/Dockerfile
new file mode 100644
index 00000000000..b76bb3da24c
--- /dev/null
+++ b/tmail-backend/deployment-tests/postgres/src/test/resources/prepopulated-ldap/Dockerfile
@@ -0,0 +1,3 @@
+FROM osixia/openldap:1.5.0
+
+COPY populate.ldif /container/service/slapd/assets/config/bootstrap/ldif/data.ldif
diff --git a/tmail-backend/deployment-tests/postgres/src/test/resources/prepopulated-ldap/populate.ldif b/tmail-backend/deployment-tests/postgres/src/test/resources/prepopulated-ldap/populate.ldif
new file mode 100644
index 00000000000..3d3b1e3421e
--- /dev/null
+++ b/tmail-backend/deployment-tests/postgres/src/test/resources/prepopulated-ldap/populate.ldif
@@ -0,0 +1,43 @@
+dn: ou=people, dc=james,dc=org
+ou: people
+objectClass: organizationalUnit
+
+dn: ou=empty, dc=james,dc=org
+ou: empty
+objectClass: organizationalUnit
+
+dn: uid=james-user, ou=people, dc=james,dc=org
+objectClass: inetOrgPerson
+uid: james-user
+cn: james-user
+sn: james-user
+mail: james-user@james.org
+userPassword: secret
+description: James user
+
+dn: uid=bob, ou=people, dc=james,dc=org
+objectClass: inetOrgPerson
+uid: bob
+cn: bob
+sn: bob
+mail: bob@domain.tld
+userPassword: bobpassword
+description: bob
+
+dn: uid=alice, ou=people, dc=james,dc=org
+objectClass: inetOrgPerson
+uid: alice
+cn: alice
+sn: alice
+mail: alice@domain.tld
+userPassword: alicepassword
+description: alice
+
+dn: uid=imapuser, ou=people, dc=james,dc=org
+objectClass: inetOrgPerson
+uid: imapuser
+cn: imapuser
+sn: imapuser
+mail: imapuser@domain
+userPassword: password
+description: imapuser
diff --git a/tmail-backend/guice/blob-guice/src/main/java/com/linagora/tmail/blob/guice/BlobStoreConfiguration.java b/tmail-backend/guice/blob-guice/src/main/java/com/linagora/tmail/blob/guice/BlobStoreConfiguration.java
index d122d928ee2..c4fcc7426db 100644
--- a/tmail-backend/guice/blob-guice/src/main/java/com/linagora/tmail/blob/guice/BlobStoreConfiguration.java
+++ b/tmail-backend/guice/blob-guice/src/main/java/com/linagora/tmail/blob/guice/BlobStoreConfiguration.java
@@ -7,6 +7,7 @@
import org.apache.commons.configuration2.Configuration;
import org.apache.commons.configuration2.ex.ConfigurationException;
+import org.apache.commons.lang3.StringUtils;
import org.apache.james.blob.aes.CryptoConfig;
import org.apache.james.blob.objectstorage.aws.S3BlobStoreConfiguration;
import org.apache.james.modules.mailbox.ConfigurationComponent;
@@ -38,6 +39,11 @@ default RequireCache file() {
default RequireSecondaryS3BlobStoreConfig s3() {
return implementation(BlobStoreImplName.S3);
}
+
+ default RequireCache postgres() {
+ return implementation(BlobStoreImplName.POSTGRES)
+ .noSecondaryS3BlobStore();
+ }
}
@FunctionalInterface
@@ -112,7 +118,8 @@ public static RequireImplementation builder() {
public enum BlobStoreImplName {
FILE("file"),
- S3("s3");
+ S3("s3"),
+ POSTGRES("postgres");
static String supportedImplNames() {
return Stream.of(BlobStoreImplName.values())
@@ -173,6 +180,12 @@ public static BlobStoreConfiguration parse(PropertiesProvider propertiesProvider
}
static BlobStoreConfiguration from(Configuration configuration) throws ConfigurationException {
+ BlobStoreImplName blobStoreImplName = Optional.ofNullable(configuration.getString(BLOBSTORE_IMPLEMENTATION_PROPERTY))
+ .filter(StringUtils::isNotBlank)
+ .map(StringUtils::trim)
+ .map(BlobStoreImplName::from)
+ .orElse(BlobStoreImplName.S3);
+
boolean cacheEnabled = configuration.getBoolean(CACHE_ENABLE_PROPERTY, false);
boolean deduplicationEnabled = Try.ofCallable(() -> configuration.getBoolean(DEDUPLICATION_ENABLE_PROPERTY))
.getOrElseThrow(() -> new IllegalStateException("""
diff --git a/tmail-backend/guice/blob-guice/src/main/java/com/linagora/tmail/blob/guice/BlobStoreModulesChooser.java b/tmail-backend/guice/blob-guice/src/main/java/com/linagora/tmail/blob/guice/BlobStoreModulesChooser.java
index d1e0230d4f3..0f434a636e0 100644
--- a/tmail-backend/guice/blob-guice/src/main/java/com/linagora/tmail/blob/guice/BlobStoreModulesChooser.java
+++ b/tmail-backend/guice/blob-guice/src/main/java/com/linagora/tmail/blob/guice/BlobStoreModulesChooser.java
@@ -10,10 +10,12 @@
import org.apache.james.blob.api.BlobStoreDAO;
import org.apache.james.blob.api.BucketName;
import org.apache.james.blob.cassandra.cache.CachedBlobStore;
+import org.apache.james.blob.file.FileBlobStoreDAO;
import org.apache.james.blob.objectstorage.aws.JamesS3MetricPublisher;
import org.apache.james.blob.objectstorage.aws.S3BlobStoreConfiguration;
import org.apache.james.blob.objectstorage.aws.S3BlobStoreDAO;
import org.apache.james.blob.objectstorage.aws.S3ClientFactory;
+import org.apache.james.blob.postgres.PostgresBlobStoreDAO;
import org.apache.james.events.EventBus;
import org.apache.james.eventsourcing.Event;
import org.apache.james.eventsourcing.eventstore.dto.EventDTO;
@@ -25,6 +27,7 @@
import org.apache.james.modules.blobstore.validation.EventsourcingStorageStrategy;
import org.apache.james.modules.blobstore.validation.StorageStrategyModule;
import org.apache.james.modules.mailbox.BlobStoreAPIModule;
+import org.apache.james.modules.mailbox.DefaultBucketModule;
import org.apache.james.modules.objectstorage.S3BlobStoreModule;
import org.apache.james.modules.objectstorage.S3BucketModule;
import org.apache.james.server.blob.deduplication.DeDuplicationBlobStore;
@@ -44,13 +47,16 @@
import com.google.inject.name.Named;
import com.google.inject.name.Names;
import com.linagora.tmail.blob.blobid.list.BlobIdList;
+import com.linagora.tmail.blob.blobid.list.CassandraSingleSaveBlobStoreModule;
import com.linagora.tmail.blob.blobid.list.SingleSaveBlobStoreDAO;
-import com.linagora.tmail.blob.blobid.list.SingleSaveBlobStoreModule;
+import com.linagora.tmail.blob.blobid.list.postgres.PostgresSingleSaveBlobStoreModule;
import com.linagora.tmail.blob.secondaryblobstore.FailedBlobOperationListener;
import com.linagora.tmail.blob.secondaryblobstore.SecondaryBlobStoreDAO;
import com.linagora.tmail.common.event.TmailInjectNameConstants;
import com.linagora.tmail.common.event.TmailReactiveGroupEventListener;
+import modules.BlobPostgresModule;
+
public class BlobStoreModulesChooser {
public static final String INITIAL_BLOBSTORE_DAO = "initial_blobstore_dao";
public static final String MAYBE_SECONDARY_BLOBSTORE = "maybe_secondary_blob_store_dao";
@@ -58,15 +64,50 @@ public class BlobStoreModulesChooser {
public static final String MAYBE_SINGLE_SAVE_BLOBSTORE = "maybe_single_save_blob_store_dao";
public static final String SECOND_BLOB_STORE_DAO = "second_blob_store_dao";
- static class BaseObjectStorageModule extends AbstractModule {
+ static class ObjectStorageBlobStoreDAODeclarationModule extends AbstractModule {
@Override
protected void configure() {
- install(new S3BucketModule());
install(new S3BlobStoreModule());
- bind(BlobStoreDAO.class).annotatedWith(Names.named(INITIAL_BLOBSTORE_DAO))
- .to(S3BlobStoreDAO.class);
+ install(new S3BucketModule());
+
+ bind(BlobStoreDAO.class)
+ .annotatedWith(Names.named(INITIAL_BLOBSTORE_DAO))
+ .to(S3BlobStoreDAO.class)
+ .in(Scopes.SINGLETON);
+ }
+ }
+
+ static class FileBlobStoreDAODeclarationModule extends AbstractModule {
+ @Override
+ protected void configure() {
+ install(new DefaultBucketModule());
+ bind(BucketName.class)
+ .toInstance(BucketName.DEFAULT);
+
+ bind(BlobStoreDAO.class)
+ .annotatedWith(Names.named(INITIAL_BLOBSTORE_DAO))
+ .to(FileBlobStoreDAO.class)
+ .in(Scopes.SINGLETON);
+ }
+ }
+
+ static class PostgresBlobStoreDAODeclarationModule extends AbstractModule {
+ @Override
+ protected void configure() {
+ install(new BlobPostgresModule());
+
+ install(new DefaultBucketModule());
+ bind(BucketName.class)
+ .toInstance(BucketName.DEFAULT);
+
+ bind(BlobStoreDAO.class)
+ .annotatedWith(Names.named(INITIAL_BLOBSTORE_DAO))
+ .to(PostgresBlobStoreDAO.class)
+ .in(Scopes.SINGLETON);
}
+ }
+ static class BaseObjectStorageModule extends AbstractModule {
@Provides
@Singleton
BlobStoreDAO provideFinalBlobStoreDAO(@Named(MAYBE_SINGLE_SAVE_BLOBSTORE) BlobStoreDAO blobStoreDAO) {
@@ -146,18 +187,33 @@ BlobStoreDAO provideNoEncryptBlobStoreDAO(@Named(MAYBE_SECONDARY_BLOBSTORE) Blob
}
}
- static class SingleSaveDeclarationModule extends AbstractModule {
+ public static class SingleSaveDeclarationModule extends AbstractModule {
+ public enum BackedStorage {
+ CASSANDRA,
+ POSTGRES
+ }
+
+ private final BackedStorage backedStorage;
+
+ public SingleSaveDeclarationModule(BackedStorage backedStorage) {
+ this.backedStorage = backedStorage;
+ }
+
@Override
protected void configure() {
- install(new SingleSaveBlobStoreModule());
+ if (backedStorage == BackedStorage.CASSANDRA) {
+ install(new CassandraSingleSaveBlobStoreModule());
+ } else {
+ install(new PostgresSingleSaveBlobStoreModule());
+ }
}
@Provides
@Singleton
@Named(MAYBE_SINGLE_SAVE_BLOBSTORE)
- public BlobStoreDAO provideSingleSaveBlobStoreDAO(@Named(MAYBE_ENCRYPTION_BLOBSTORE) BlobStoreDAO blobStoreDAO,
- BlobIdList blobIdList,
- BucketName defaultBucketName) {
+ BlobStoreDAO provideSingleSaveBlobStoreDAO(@Named(MAYBE_ENCRYPTION_BLOBSTORE) BlobStoreDAO blobStoreDAO,
+ BlobIdList blobIdList,
+ BucketName defaultBucketName) {
return new SingleSaveBlobStoreDAO(blobStoreDAO, blobIdList, defaultBucketName);
}
}
@@ -184,9 +240,9 @@ public static Module chooseEncryptionModule(Optional cryptoConfig)
.orElse(new NoEncryptionModule());
}
- public static Module chooseSaveDeclarationModule(boolean singleSaveEnabled) {
+ public static Module chooseSaveDeclarationModule(boolean singleSaveEnabled, SingleSaveDeclarationModule.BackedStorage backedSingleSaveStorage) {
if (singleSaveEnabled) {
- return new SingleSaveDeclarationModule();
+ return new SingleSaveDeclarationModule(backedSingleSaveStorage);
}
return new MultiSaveDeclarationModule();
}
@@ -227,15 +283,33 @@ protected void configure() {
}
}
- @VisibleForTesting
- public static List chooseModules(BlobStoreConfiguration blobStoreConfiguration) {
+ public static List chooseModules(BlobStoreConfiguration blobStoreConfiguration, SingleSaveDeclarationModule.BackedStorage backedSingleSaveStorage) {
return ImmutableList.builder()
- .add(new BaseObjectStorageModule())
+ .add(chooseBlobStoreDAOModule(blobStoreConfiguration.implementation()))
.add(chooseSecondaryObjectStorageModule(blobStoreConfiguration.maybeSecondaryS3BlobStoreConfiguration()))
.add(chooseEncryptionModule(blobStoreConfiguration.cryptoConfig()))
- .add(chooseSaveDeclarationModule(blobStoreConfiguration.singleSaveEnabled()))
+ .add(chooseSaveDeclarationModule(blobStoreConfiguration.singleSaveEnabled(), backedSingleSaveStorage))
+ .add(new BaseObjectStorageModule())
.addAll(chooseStoragePolicyModule(blobStoreConfiguration.storageStrategy()))
.add(new StoragePolicyConfigurationSanityEnforcementModule(blobStoreConfiguration))
.build();
}
+
+ @VisibleForTesting
+ public static List chooseModules(BlobStoreConfiguration choosingConfiguration) {
+ return chooseModules(choosingConfiguration, SingleSaveDeclarationModule.BackedStorage.CASSANDRA);
+ }
+
+ public static Module chooseBlobStoreDAOModule(BlobStoreConfiguration.BlobStoreImplName implementation) {
+ switch (implementation) {
+ case S3:
+ return new ObjectStorageBlobStoreDAODeclarationModule();
+ case FILE:
+ return new FileBlobStoreDAODeclarationModule();
+ case POSTGRES:
+ return new PostgresBlobStoreDAODeclarationModule();
+ default:
+ throw new RuntimeException("Unsupported blobStore implementation " + implementation);
+ }
+ }
}
\ No newline at end of file
diff --git a/tmail-backend/guice/blob-guice/src/test/java/com/linagora/tmail/blob/guice/BlobStoreConfigurationTest.java b/tmail-backend/guice/blob-guice/src/test/java/com/linagora/tmail/blob/guice/BlobStoreConfigurationTest.java
index a9ece01cb20..625b39497a0 100644
--- a/tmail-backend/guice/blob-guice/src/test/java/com/linagora/tmail/blob/guice/BlobStoreConfigurationTest.java
+++ b/tmail-backend/guice/blob-guice/src/test/java/com/linagora/tmail/blob/guice/BlobStoreConfigurationTest.java
@@ -211,4 +211,13 @@ void buildingConfigurationShouldThrowWhenSingleSavePropertyIsInvalid() {
assertThatThrownBy(() -> BlobStoreConfiguration.from(configuration))
.isInstanceOf(ConversionException.class);
}
+
+ @Test
+ void blobImplementationShouldDefaultToS3() throws ConfigurationException {
+ PropertiesConfiguration configuration = new PropertiesConfiguration();
+ configuration.addProperty("deduplication.enable", "true");
+
+ assertThat(BlobStoreConfiguration.from(configuration).implementation())
+ .isEqualTo(BlobStoreConfiguration.BlobStoreImplName.S3);
+ }
}
diff --git a/tmail-backend/guice/common/pom.xml b/tmail-backend/guice/common/pom.xml
new file mode 100644
index 00000000000..3c8ea278b27
--- /dev/null
+++ b/tmail-backend/guice/common/pom.xml
@@ -0,0 +1,41 @@
+
+
+ 4.0.0
+
+ com.linagora.tmail
+ tmail-backend
+ 1.0.0-SNAPSHOT
+ ../../pom.xml
+
+
+ tmail-guice-common
+ Team-mail :: Guice :: Common
+
+
+
+ ${project.groupId}
+ combined-identity
+
+
+ ${james.groupId}
+ james-server-guice-data-ldap
+
+
+ ${james.groupId}
+ james-server-guice-common
+
+
+ ${james.groupId}
+ james-server-guice-common
+ test-jar
+ test
+
+
+ ${james.groupId}
+ testing-base
+ test
+
+
+
\ No newline at end of file
diff --git a/tmail-backend/guice/common/src/main/java/com/linagora/tmail/DatabaseCombinedUserRequireModule.java b/tmail-backend/guice/common/src/main/java/com/linagora/tmail/DatabaseCombinedUserRequireModule.java
new file mode 100644
index 00000000000..d7dcc57248f
--- /dev/null
+++ b/tmail-backend/guice/common/src/main/java/com/linagora/tmail/DatabaseCombinedUserRequireModule.java
@@ -0,0 +1,49 @@
+/****************************************************************
+ * 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 com.linagora.tmail;
+
+import org.apache.james.user.lib.UsersDAO;
+
+import com.google.inject.AbstractModule;
+import com.google.inject.Injector;
+import com.google.inject.Provides;
+import com.google.inject.Singleton;
+import com.google.inject.name.Named;
+import com.linagora.tmail.combined.identity.CombinedUserDAO;
+
+public class DatabaseCombinedUserRequireModule extends AbstractModule {
+ public static DatabaseCombinedUserRequireModule> of(Class extends UsersDAO> typeUserDAOClass) {
+ return new DatabaseCombinedUserRequireModule<>(typeUserDAOClass);
+ }
+
+ final Class typeUserDAOClass;
+
+ public DatabaseCombinedUserRequireModule(Class typeUserDAOClass) {
+ this.typeUserDAOClass = typeUserDAOClass;
+ }
+
+ @Provides
+ @Singleton
+ @Named(CombinedUserDAO.DATABASE_INJECT_NAME)
+ public UsersDAO provideDatabaseCombinedUserDAO(Injector injector) {
+ return injector.getInstance(typeUserDAOClass);
+ }
+
+}
diff --git a/tmail-backend/guice/common/src/main/java/com/linagora/tmail/UsersRepositoryModuleChooser.java b/tmail-backend/guice/common/src/main/java/com/linagora/tmail/UsersRepositoryModuleChooser.java
new file mode 100644
index 00000000000..e3844e2dcca
--- /dev/null
+++ b/tmail-backend/guice/common/src/main/java/com/linagora/tmail/UsersRepositoryModuleChooser.java
@@ -0,0 +1,56 @@
+package com.linagora.tmail;
+
+import java.util.Optional;
+
+import org.apache.commons.configuration2.HierarchicalConfiguration;
+import org.apache.commons.configuration2.ex.ConfigurationException;
+import org.apache.commons.configuration2.tree.ImmutableNode;
+import org.apache.james.data.LdapUsersRepositoryModule;
+import org.apache.james.server.core.configuration.FileConfigurationProvider;
+
+import com.google.inject.Module;
+import com.google.inject.util.Modules;
+import com.linagora.tmail.combined.identity.CombinedUsersRepositoryModule;
+
+public class UsersRepositoryModuleChooser {
+ public enum Implementation {
+ LDAP,
+ COMBINED,
+ DEFAULT;
+
+ public static Implementation parse(FileConfigurationProvider configurationProvider) {
+ try {
+ HierarchicalConfiguration userRepositoryConfig = configurationProvider.getConfiguration("usersrepository");
+ return Optional.ofNullable(userRepositoryConfig.getString("[@ldapHost]", null))
+ .map(anyHost -> userRepositoryConfig.getString("[@class]", "")
+ .contains("CombinedUsersRepository"))
+ .map(isCombined -> {
+ if (isCombined) {
+ return COMBINED;
+ }
+ return LDAP;
+ })
+ .orElse(DEFAULT);
+ } catch (ConfigurationException e) {
+ throw new RuntimeException("Error reading usersrepository.xml", e);
+ }
+ }
+ }
+
+ private final DatabaseCombinedUserRequireModule> databaseCombinedUserRequireModule;
+
+ private final Module defaultModule;
+
+ public UsersRepositoryModuleChooser(DatabaseCombinedUserRequireModule> databaseCombinedUserRequireModule, Module defaultModule) {
+ this.databaseCombinedUserRequireModule = databaseCombinedUserRequireModule;
+ this.defaultModule = defaultModule;
+ }
+
+ public Module chooseModule(Implementation implementation) {
+ return switch (implementation) {
+ case LDAP -> new LdapUsersRepositoryModule();
+ case COMBINED -> Modules.override(defaultModule).with(Modules.combine(new CombinedUsersRepositoryModule(), databaseCombinedUserRequireModule));
+ case DEFAULT -> defaultModule;
+ };
+ }
+}
diff --git a/tmail-backend/integration-tests/healthcheck/postgres-healthcheck-integration-tests/pom.xml b/tmail-backend/integration-tests/healthcheck/postgres-healthcheck-integration-tests/pom.xml
new file mode 100644
index 00000000000..a0b6a9fc7ca
--- /dev/null
+++ b/tmail-backend/integration-tests/healthcheck/postgres-healthcheck-integration-tests/pom.xml
@@ -0,0 +1,134 @@
+
+
+ 4.0.0
+
+ com.linagora.tmail
+ integration-tests
+ 1.0.0-SNAPSHOT
+ ../../pom.xml
+
+
+ postgres-healthcheck-integration-tests
+ Team-mail :: Integration Tests :: Healthcheck :: Postgres
+
+
+
+ ${project.groupId}
+ postgres
+
+
+ ${project.groupId}
+ postgres
+ test-jar
+
+
+ ${project.groupId}
+ healthcheck-integration-tests-common
+ test
+
+
+ ${project.groupId}
+ webadmin-integration-tests-common
+ test
+
+
+ ${project.groupId}
+ tmail-guice-distributed
+ test-jar
+ test
+
+
+ ${project.groupId}
+ tmail-guice-jmap
+ test-jar
+ test
+
+
+ ${james.groupId}
+ apache-james-backends-postgres
+ test-jar
+ test
+
+
+ ${james.groupId}
+ apache-james-backends-opensearch
+ test-jar
+ test
+
+
+ org.testcontainers
+ postgresql
+ test
+
+
+ ${james.groupId}
+ james-server-guice-opensearch
+ test-jar
+ test
+
+
+ ${james.groupId}
+ james-server-guice-opensearch
+
+
+ ${james.groupId}
+ apache-james-backends-rabbitmq
+ test-jar
+ test
+
+
+ ${james.groupId}
+ apache-james-backends-redis
+ test-jar
+ test
+
+
+ ${james.groupId}
+ blob-s3
+ test-jar
+ test
+
+
+ ${james.groupId}
+ blob-s3-guice
+ test-jar
+ test
+
+
+ ${james.groupId}
+ james-server-postgres-app
+ test-jar
+ test
+
+
+ ${james.groupId}
+ james-server-postgres-app
+ test-jar
+ test
+
+
+ ${james.groupId}
+ james-server-guice-common
+ test-jar
+
+
+ ${james.groupId}
+ james-server-guice-jmap
+ test-jar
+
+
+ ${james.groupId}
+ apache-james-rspamd
+ test-jar
+ test
+
+
+ ${james.groupId}
+ james-server-rate-limiter-redis
+ test-jar
+ test
+
+
+
+
diff --git a/tmail-backend/integration-tests/healthcheck/postgres-healthcheck-integration-tests/src/test/java/com/linagora/tmail/integration/postgres/PostgresTMailHealthCheckIntegrationTests.java b/tmail-backend/integration-tests/healthcheck/postgres-healthcheck-integration-tests/src/test/java/com/linagora/tmail/integration/postgres/PostgresTMailHealthCheckIntegrationTests.java
new file mode 100644
index 00000000000..6485b0c5972
--- /dev/null
+++ b/tmail-backend/integration-tests/healthcheck/postgres-healthcheck-integration-tests/src/test/java/com/linagora/tmail/integration/postgres/PostgresTMailHealthCheckIntegrationTests.java
@@ -0,0 +1,57 @@
+package com.linagora.tmail.integration.postgres;
+
+import static org.apache.james.PostgresJamesConfiguration.EventBusImpl.RABBITMQ;
+
+import org.apache.james.JamesServerBuilder;
+import org.apache.james.JamesServerExtension;
+import org.apache.james.SearchConfiguration;
+import org.apache.james.backends.postgres.PostgresExtension;
+import org.apache.james.backends.redis.RedisExtension;
+import org.apache.james.modules.AwsS3BlobStoreExtension;
+import org.apache.james.utils.GuiceProbe;
+import org.junit.jupiter.api.extension.RegisterExtension;
+
+import com.google.inject.multibindings.Multibinder;
+import com.linagora.tmail.blob.guice.BlobStoreConfiguration;
+import com.linagora.tmail.combined.identity.UsersRepositoryClassProbe;
+import com.linagora.tmail.encrypted.MailboxConfiguration;
+import com.linagora.tmail.encrypted.MailboxManagerClassProbe;
+import com.linagora.tmail.integration.TMailHealthCheckIntegrationTests;
+import com.linagora.tmail.james.app.DockerOpenSearchExtension;
+import com.linagora.tmail.james.app.PostgresTmailConfiguration;
+import com.linagora.tmail.james.app.PostgresTmailServer;
+import com.linagora.tmail.james.app.RabbitMQExtension;
+import com.linagora.tmail.module.LinagoraTestJMAPServerModule;
+import com.linagora.tmail.rspamd.RspamdExtensionModule;
+
+public class PostgresTMailHealthCheckIntegrationTests extends TMailHealthCheckIntegrationTests {
+
+ @RegisterExtension
+ static JamesServerExtension testExtension = new JamesServerBuilder(tmpDir ->
+ PostgresTmailConfiguration.builder()
+ .workingDirectory(tmpDir)
+ .configurationFromClasspath()
+ .blobStore(BlobStoreConfiguration.builder()
+ .s3()
+ .noSecondaryS3BlobStore()
+ .disableCache()
+ .deduplication()
+ .noCryptoConfig()
+ .disableSingleSave())
+ .searchConfiguration(SearchConfiguration.openSearch())
+ .mailbox(new MailboxConfiguration(false))
+ .eventBusImpl(RABBITMQ)
+ .build())
+ .server(configuration -> PostgresTmailServer.createServer(configuration)
+ .overrideWith(new LinagoraTestJMAPServerModule())
+ .overrideWith(binder -> Multibinder.newSetBinder(binder, GuiceProbe.class).addBinding().to(MailboxManagerClassProbe.class))
+ .overrideWith(binder -> Multibinder.newSetBinder(binder, GuiceProbe.class).addBinding().to(UsersRepositoryClassProbe.class)))
+ .extension(PostgresExtension.empty())
+ .extension(new RabbitMQExtension())
+ .extension(new AwsS3BlobStoreExtension())
+ .extension(new DockerOpenSearchExtension())
+ .extension(new RspamdExtensionModule())
+ .extension(new RedisExtension())
+ .lifeCycle(JamesServerExtension.Lifecycle.PER_CLASS)
+ .build();
+}
diff --git a/tmail-backend/integration-tests/healthcheck/postgres-healthcheck-integration-tests/src/test/resources/batchsizes.properties b/tmail-backend/integration-tests/healthcheck/postgres-healthcheck-integration-tests/src/test/resources/batchsizes.properties
new file mode 100644
index 00000000000..1784f95d79e
--- /dev/null
+++ b/tmail-backend/integration-tests/healthcheck/postgres-healthcheck-integration-tests/src/test/resources/batchsizes.properties
@@ -0,0 +1,9 @@
+# Those properties let you configure the number of messages queried at the same time.
+# IMAP FETCH command
+fetch.metadata=200
+fetch.headers=200
+fetch.full=50
+# IMAP COPY command
+copy=100
+# IMAP MOVE command
+move=100
diff --git a/tmail-backend/integration-tests/healthcheck/postgres-healthcheck-integration-tests/src/test/resources/blob.properties b/tmail-backend/integration-tests/healthcheck/postgres-healthcheck-integration-tests/src/test/resources/blob.properties
new file mode 100644
index 00000000000..78ea935448f
--- /dev/null
+++ b/tmail-backend/integration-tests/healthcheck/postgres-healthcheck-integration-tests/src/test/resources/blob.properties
@@ -0,0 +1,66 @@
+# ============================================= BlobStore Implementation ==================================
+# Read https://james.apache.org/server/config-blobstore.html for further details
+
+# Choose your BlobStore implementation
+# Mandatory, allowed values are: file, s3, postgres.
+implementation=postgres
+
+# ========================================= Deduplication ========================================
+# If you choose to enable deduplication, the mails with the same content will be stored only once.
+# Warning: Once this feature is enabled, there is no turning back as turning it off will lead to the deletion of all
+# the mails sharing the same content once one is deleted.
+# Mandatory, Allowed values are: true, false
+deduplication.enable=true
+
+# deduplication.family needs to be incremented every time the deduplication.generation.duration is changed
+# Positive integer, defaults to 1
+# deduplication.gc.generation.family=1
+
+# Duration of generation.
+# Deduplication only takes place within a singe generation.
+# Only items two generation old can be garbage collected. (This prevent concurrent insertions issues and
+# accounts for a clock skew).
+# deduplication.family needs to be incremented everytime this parameter is changed.
+# Duration. Default unit: days. Defaults to 30 days.
+# deduplication.gc.generation.duration=30days
+
+# ========================================= Encryption ========================================
+# If you choose to enable encryption, the blob content will be encrypted before storing them in the BlobStore.
+# Warning: Once this feature is enabled, there is no turning back as turning it off will lead to all content being
+# encrypted. This comes at a performance impact but presents you from leaking data if, for instance the third party
+# offering you a S3 service is compromised.
+# Optional, Allowed values are: true, false, defaults to false
+encryption.aes.enable=false
+
+# Mandatory (if AES encryption is enabled) salt and password. Salt needs to be an hexadecimal encoded string
+#encryption.aes.password=xxx
+#encryption.aes.salt=73616c7479
+# Optional, defaults to PBKDF2WithHmacSHA512
+#encryption.aes.private.key.algorithm=PBKDF2WithHmacSHA512
+
+# ============================================ Blobs Exporting ==============================================
+# Read https://james.apache.org/server/config-blob-export.html for further details
+
+# Choosing blob exporting mechanism, allowed mechanism are: localFile, linshare
+# LinShare is a file sharing service, will be explained in the below section
+# Optional, default is localFile
+blob.export.implementation=localFile
+
+# ======================================= Local File Blobs Exporting ========================================
+# Optional, directory to store exported blob, directory path follows James file system format
+# default is file://var/blobExporting
+blob.export.localFile.directory=file://var/blobExporting
+
+# ======================================= LinShare File Blobs Exporting ========================================
+# LinShare is a sharing service where you can use james, connects to an existing LinShare server and shares files to
+# other mail addresses as long as those addresses available in LinShare. For example you can deploy James and LinShare
+# sharing the same LDAP repository
+# Mandatory if you choose LinShare, url to connect to LinShare service
+# blob.export.linshare.url=http://linshare:8080
+
+# ======================================= LinShare Configuration BasicAuthentication ===================================
+# Authentication is mandatory if you choose LinShare, TechnicalAccount is need to connect to LinShare specific service.
+# For Example: It will be formalized to 'Authorization: Basic {Credential of UUID/password}'
+
+# blob.export.linshare.technical.account.uuid=Technical_Account_UUID
+# blob.export.linshare.technical.account.password=password
\ No newline at end of file
diff --git a/tmail-backend/integration-tests/healthcheck/postgres-healthcheck-integration-tests/src/test/resources/dnsservice.xml b/tmail-backend/integration-tests/healthcheck/postgres-healthcheck-integration-tests/src/test/resources/dnsservice.xml
new file mode 100644
index 00000000000..6e4fbd2efb5
--- /dev/null
+++ b/tmail-backend/integration-tests/healthcheck/postgres-healthcheck-integration-tests/src/test/resources/dnsservice.xml
@@ -0,0 +1,25 @@
+
+
+
+
+ true
+ false
+ 50000
+
diff --git a/tmail-backend/integration-tests/healthcheck/postgres-healthcheck-integration-tests/src/test/resources/domainlist.xml b/tmail-backend/integration-tests/healthcheck/postgres-healthcheck-integration-tests/src/test/resources/domainlist.xml
new file mode 100644
index 00000000000..fe17431a1ea
--- /dev/null
+++ b/tmail-backend/integration-tests/healthcheck/postgres-healthcheck-integration-tests/src/test/resources/domainlist.xml
@@ -0,0 +1,24 @@
+
+
+
+
+ false
+ false
+
diff --git a/tmail-backend/integration-tests/healthcheck/postgres-healthcheck-integration-tests/src/test/resources/imapserver.xml b/tmail-backend/integration-tests/healthcheck/postgres-healthcheck-integration-tests/src/test/resources/imapserver.xml
new file mode 100644
index 00000000000..c99f956ce57
--- /dev/null
+++ b/tmail-backend/integration-tests/healthcheck/postgres-healthcheck-integration-tests/src/test/resources/imapserver.xml
@@ -0,0 +1,56 @@
+
+
+
+
+
+
+ imapserver
+ 0.0.0.0:0
+ 200
+
+
+ classpath://keystore
+ james72laBalle
+ org.bouncycastle.jce.provider.BouncyCastleProvider
+
+ 0
+ 0
+ false
+ false
+
+
+ imapserver-ssl
+ 0.0.0.0:0
+ 200
+
+
+ classpath://keystore
+ james72laBalle
+ org.bouncycastle.jce.provider.BouncyCastleProvider
+
+ 0
+ 0
+ false
+
+
diff --git a/tmail-backend/integration-tests/healthcheck/postgres-healthcheck-integration-tests/src/test/resources/keystore b/tmail-backend/integration-tests/healthcheck/postgres-healthcheck-integration-tests/src/test/resources/keystore
new file mode 100644
index 00000000000..536a6c792b0
Binary files /dev/null and b/tmail-backend/integration-tests/healthcheck/postgres-healthcheck-integration-tests/src/test/resources/keystore differ
diff --git a/tmail-backend/integration-tests/healthcheck/postgres-healthcheck-integration-tests/src/test/resources/listeners.xml b/tmail-backend/integration-tests/healthcheck/postgres-healthcheck-integration-tests/src/test/resources/listeners.xml
new file mode 100644
index 00000000000..ae5937f1fa6
--- /dev/null
+++ b/tmail-backend/integration-tests/healthcheck/postgres-healthcheck-integration-tests/src/test/resources/listeners.xml
@@ -0,0 +1,22 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/tmail-backend/integration-tests/healthcheck/postgres-healthcheck-integration-tests/src/test/resources/mailetcontainer.xml b/tmail-backend/integration-tests/healthcheck/postgres-healthcheck-integration-tests/src/test/resources/mailetcontainer.xml
new file mode 100644
index 00000000000..3eaaf58e6c8
--- /dev/null
+++ b/tmail-backend/integration-tests/healthcheck/postgres-healthcheck-integration-tests/src/test/resources/mailetcontainer.xml
@@ -0,0 +1,155 @@
+
+
+
+
+
+
+
+
+
+
+ postmaster
+
+
+
+ 20
+ postgres://var/mail/error/
+
+
+
+
+
+
+ postgres://var/mail/relay-limit-exceeded/
+
+
+ transport
+
+
+
+
+
+ mailetContainerErrors
+
+
+ ignore
+
+
+ postgres://var/mail/error/
+ propagate
+
+
+
+
+
+
+
+
+
+
+
+
+ rrt-error
+
+
+
+
+ local-delivery
+
+
+ local-address-error
+ 550 - Requested action not taken: no such user here
+
+
+
+ relay
+
+
+
+
+
+
+
+
+
+
+
+
+
+ outgoing
+ 5000, 100000, 500000
+ 3
+ 0
+ 10
+ true
+ bounces
+
+
+
+
+
+ mailetContainerLocalAddressError
+
+
+ none
+
+
+ postgres://var/mail/address-error/
+
+
+
+
+
+ mailetContainerRelayDenied
+
+
+ none
+
+
+ postgres://var/mail/relay-denied/
+ Warning: You are sending an e-mail to a remote server. You must be authenticated to perform such an operation
+
+
+
+
+
+ bounces
+
+
+ false
+
+
+
+
+
+ postgres://var/mail/rrt-error/
+ true
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/tmail-backend/integration-tests/healthcheck/postgres-healthcheck-integration-tests/src/test/resources/mailrepositorystore.xml b/tmail-backend/integration-tests/healthcheck/postgres-healthcheck-integration-tests/src/test/resources/mailrepositorystore.xml
new file mode 100644
index 00000000000..445f2727f29
--- /dev/null
+++ b/tmail-backend/integration-tests/healthcheck/postgres-healthcheck-integration-tests/src/test/resources/mailrepositorystore.xml
@@ -0,0 +1,35 @@
+
+
+
+
+
+
+
+ postgres
+
+
+
+ postgres
+
+
+
+
+
+
diff --git a/tmail-backend/integration-tests/healthcheck/postgres-healthcheck-integration-tests/src/test/resources/rabbitmq.properties b/tmail-backend/integration-tests/healthcheck/postgres-healthcheck-integration-tests/src/test/resources/rabbitmq.properties
new file mode 100644
index 00000000000..5a2ca93ec05
--- /dev/null
+++ b/tmail-backend/integration-tests/healthcheck/postgres-healthcheck-integration-tests/src/test/resources/rabbitmq.properties
@@ -0,0 +1,10 @@
+uri=amqp://james:james@rabbitmq_host:5672
+management.uri=http://james:james@rabbitmq_host:15672/api/
+
+# AMQP resources parameters for the subscriber email address contact messages. In order to synchronize contacts between Team-mail backend and 3rd.
+# AQMP uri
+address.contact.uri=amqp://rabbitmq:5672
+address.contact.user=guest
+address.contact.password=guest
+# Queue name
+address.contact.queue=AddressContactQueue1
\ No newline at end of file
diff --git a/tmail-backend/integration-tests/healthcheck/postgres-healthcheck-integration-tests/src/test/resources/redis.properties b/tmail-backend/integration-tests/healthcheck/postgres-healthcheck-integration-tests/src/test/resources/redis.properties
new file mode 100644
index 00000000000..449e9815497
--- /dev/null
+++ b/tmail-backend/integration-tests/healthcheck/postgres-healthcheck-integration-tests/src/test/resources/redis.properties
@@ -0,0 +1,2 @@
+redisURL=redis://redis:6379
+cluster.enabled=false
\ No newline at end of file
diff --git a/tmail-backend/integration-tests/healthcheck/postgres-healthcheck-integration-tests/src/test/resources/rspamd.properties b/tmail-backend/integration-tests/healthcheck/postgres-healthcheck-integration-tests/src/test/resources/rspamd.properties
new file mode 100644
index 00000000000..7382f27a966
--- /dev/null
+++ b/tmail-backend/integration-tests/healthcheck/postgres-healthcheck-integration-tests/src/test/resources/rspamd.properties
@@ -0,0 +1,4 @@
+rspamdUrl=http://rspamd:11334
+rspamdPassword=admin
+# Whether to scan/learn mails using per-user Bayes. Default to false.
+perUserBayes=false
\ No newline at end of file
diff --git a/tmail-backend/integration-tests/healthcheck/postgres-healthcheck-integration-tests/src/test/resources/smtpserver.xml b/tmail-backend/integration-tests/healthcheck/postgres-healthcheck-integration-tests/src/test/resources/smtpserver.xml
new file mode 100644
index 00000000000..5c8db4f9018
--- /dev/null
+++ b/tmail-backend/integration-tests/healthcheck/postgres-healthcheck-integration-tests/src/test/resources/smtpserver.xml
@@ -0,0 +1,102 @@
+
+
+
+
+
+
+ smtpserver-global
+ 0.0.0.0:0
+ 200
+
+ file://conf/keystore
+ james72laBalle
+ org.bouncycastle.jce.provider.BouncyCastleProvider
+ SunX509
+
+ 360
+ 0
+ 0
+ false
+ false
+ 0
+ true
+ Apache JAMES awesome SMTP Server
+
+
+
+ false
+
+
+ smtpserver-TLS
+ 0.0.0.0:0
+ 200
+
+ file://conf/keystore
+ james72laBalle
+ org.bouncycastle.jce.provider.BouncyCastleProvider
+ SunX509
+
+ 360
+ 0
+ 0
+
+ true
+
+ false
+ 0
+ true
+ Apache JAMES awesome SMTP Server
+
+
+
+ false
+
+
+ smtpserver-authenticated
+ 0.0.0.0:0
+ 200
+
+ file://conf/keystore
+ james72laBalle
+ org.bouncycastle.jce.provider.BouncyCastleProvider
+ SunX509
+
+ 360
+ 0
+ 0
+
+ true
+
+ false
+ 0
+ true
+ Apache JAMES awesome SMTP Server
+
+
+
+ false
+
+
+
+
diff --git a/tmail-backend/integration-tests/healthcheck/postgres-healthcheck-integration-tests/src/test/resources/webadmin.properties b/tmail-backend/integration-tests/healthcheck/postgres-healthcheck-integration-tests/src/test/resources/webadmin.properties
new file mode 100644
index 00000000000..3386a14238a
--- /dev/null
+++ b/tmail-backend/integration-tests/healthcheck/postgres-healthcheck-integration-tests/src/test/resources/webadmin.properties
@@ -0,0 +1,25 @@
+# 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.
+
+# This template file can be used as example for James Server configuration
+# DO NOT USE IT AS SUCH AND ADAPT IT TO YOUR NEEDS
+
+# Read https://james.apache.org/server/config-webadmin.html for further details
+
+enabled=true
+port=0
+host=127.0.0.1
\ No newline at end of file
diff --git a/tmail-backend/integration-tests/jmap/jmap-integration-tests-common/src/main/scala/com/linagora/tmail/james/common/LinagoraFilterStateChangeTest.scala b/tmail-backend/integration-tests/jmap/jmap-integration-tests-common/src/main/scala/com/linagora/tmail/james/common/LinagoraFilterStateChangeTest.scala
index b372e7a9718..521e9e32a43 100644
--- a/tmail-backend/integration-tests/jmap/jmap-integration-tests-common/src/main/scala/com/linagora/tmail/james/common/LinagoraFilterStateChangeTest.scala
+++ b/tmail-backend/integration-tests/jmap/jmap-integration-tests-common/src/main/scala/com/linagora/tmail/james/common/LinagoraFilterStateChangeTest.scala
@@ -7,6 +7,7 @@ import org.apache.james.GuiceJamesServer
import org.apache.james.jmap.JmapGuiceProbe
import org.apache.james.jmap.http.UserCredential
import org.apache.james.jmap.rfc8621.contract.Fixture.{ACCEPT_RFC8621_VERSION_HEADER, ACCOUNT_ID, BOB, BOB_PASSWORD, DOMAIN, authScheme, baseRequestSpecBuilder}
+import org.apache.james.jmap.rfc8621.contract.receiveMessageInTimespan
import org.apache.james.jmap.rfc8621.contract.tags.CategoryTags
import org.apache.james.utils.DataProbeImpl
import org.assertj.core.api.Assertions.assertThat
@@ -17,9 +18,7 @@ import sttp.client3.okhttp.OkHttpSyncBackend
import sttp.client3.{Identity, RequestT, SttpBackend, asWebSocket, basicRequest}
import sttp.model.Uri
import sttp.monad.MonadError
-import sttp.monad.syntax.MonadErrorOps
import sttp.ws.WebSocketFrame
-import sttp.ws.WebSocketFrame.Text
import scala.jdk.CollectionConverters._
@@ -86,15 +85,7 @@ trait LinagoraFilterStateChangeTest {
| ]
|}""".stripMargin))
- List(
- ws.receive()
- .map { case t: Text =>
- t.payload
- },
- ws.receive()
- .map { case t: Text =>
- t.payload
- })
+ ws.receiveMessageInTimespan()
})
.send(backend)
.body
@@ -150,15 +141,7 @@ trait LinagoraFilterStateChangeTest {
| ]
|}""".stripMargin))
- List(
- ws.receive()
- .map { case t: Text =>
- t.payload
- },
- ws.receive()
- .map { case t: Text =>
- t.payload
- })
+ ws.receiveMessageInTimespan()
})
.send(backend)
.body
diff --git a/tmail-backend/integration-tests/jmap/jmap-integration-tests-common/src/main/scala/com/linagora/tmail/james/common/TeamMailboxesContract.scala b/tmail-backend/integration-tests/jmap/jmap-integration-tests-common/src/main/scala/com/linagora/tmail/james/common/TeamMailboxesContract.scala
index 197e350e15d..e58eb164b43 100644
--- a/tmail-backend/integration-tests/jmap/jmap-integration-tests-common/src/main/scala/com/linagora/tmail/james/common/TeamMailboxesContract.scala
+++ b/tmail-backend/integration-tests/jmap/jmap-integration-tests-common/src/main/scala/com/linagora/tmail/james/common/TeamMailboxesContract.scala
@@ -31,7 +31,7 @@ import org.apache.james.jmap.core.{PushState, UTCDate, UuidState}
import org.apache.james.jmap.http.UserCredential
import org.apache.james.jmap.rfc8621.contract.DownloadContract.accountId
import org.apache.james.jmap.rfc8621.contract.Fixture.{ACCEPT_RFC8621_VERSION_HEADER, ACCOUNT_ID, BOB, BOB_PASSWORD, CEDRIC, DOMAIN, authScheme, baseRequestSpecBuilder}
-import org.apache.james.jmap.rfc8621.contract.asPayload
+import org.apache.james.jmap.rfc8621.contract.receiveMessageInTimespan
import org.apache.james.jmap.rfc8621.contract.tags.CategoryTags
import org.apache.james.mailbox.DefaultMailboxes
import org.apache.james.mailbox.MessageManager.AppendCommand
@@ -55,6 +55,8 @@ import sttp.model.Uri
import sttp.monad.MonadError
import sttp.ws.WebSocketFrame
+import scala.concurrent.duration.MILLISECONDS
+
object TeamMailboxesContract {
private var webAdminApi: RequestSpecification = _
}
@@ -2414,26 +2416,19 @@ trait TeamMailboxesContract {
.appendMessage(BOB.asString(), MailboxPath.inbox(BOB), AppendCommand.from(message))
.getMessageId.serialize()
- val request =
- s"""{
- | "using": ["urn:ietf:params:jmap:core", "urn:ietf:params:jmap:mail", "urn:apache:james:params:jmap:mail:shares"],
- | "methodCalls": [["Email/set", {
- | "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
- | "update": {
- | "$messageId": {
- | "mailboxIds": { "$id": true }
- | }
- | }
- | }, "c1"], ["Email/get", {
- | "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
- | "ids": ["$messageId"],
- | "properties":["mailboxIds"]
- | }, "c2"]]
- |}""".stripMargin
-
- val response: String = `given`()
+ val updateResponse: String = `given`()
.header(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
- .body(request)
+ .body(s"""{
+ | "using": ["urn:ietf:params:jmap:core", "urn:ietf:params:jmap:mail", "urn:apache:james:params:jmap:mail:shares"],
+ | "methodCalls": [["Email/set", {
+ | "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+ | "update": {
+ | "$messageId": {
+ | "mailboxIds": { "$id": true }
+ | }
+ | }
+ | }, "c1"]]
+ |}""".stripMargin)
.when()
.post()
.`then`
@@ -2443,7 +2438,7 @@ trait TeamMailboxesContract {
.body()
.asString()
- assertThatJson(response)
+ assertThatJson(updateResponse)
.whenIgnoringPaths("methodResponses[0][1].newState", "methodResponses[0][1].oldState", "methodResponses[1][1].state")
.isEqualTo(
s"""{
@@ -2456,23 +2451,54 @@ trait TeamMailboxesContract {
| "updated": {"$messageId": null}
| },
| "c1"
- | ],
- | [
- | "Email/get",
- | {
- | "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
- | "notFound": [],
- | "list": [
- | {
- | "mailboxIds":{"$id":true},
- | "id": "$messageId"
- | }
- | ]
- | },
- | "c2"
| ]
| ]
|}""".stripMargin)
+
+ awaitAtMostTenSeconds.untilAsserted(() => {
+ val getResponse: String = `given`()
+ .header(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
+ .body(s"""{
+ | "using": ["urn:ietf:params:jmap:core", "urn:ietf:params:jmap:mail", "urn:apache:james:params:jmap:mail:shares"],
+ | "methodCalls": [["Email/get", {
+ | "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+ | "ids": ["$messageId"],
+ | "properties":["mailboxIds"]
+ | }, "c2"]]
+ |}""".stripMargin)
+ .when()
+ .post()
+ .`then`
+ .statusCode(SC_OK)
+ .contentType(JSON)
+ .extract()
+ .body()
+ .asString()
+
+ assertThatJson(getResponse)
+ .whenIgnoringPaths("methodResponses[0][1].newState", "methodResponses[0][1].oldState")
+ .isEqualTo(
+ s"""{
+ | "sessionState": "2c9f1b12-b35a-43e6-9af2-0106fb53a943",
+ | "methodResponses": [
+ | [
+ | "Email/get",
+ | {
+ | "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+ | "notFound": [],
+ | "state": "$${json-unit.ignore}",
+ | "list": [
+ | {
+ | "mailboxIds":{"$id":true},
+ | "id": "$messageId"
+ | }
+ | ]
+ | },
+ | "c2"
+ | ]
+ | ]
+ |}""".stripMargin)
+ })
}
@Test
@@ -2727,11 +2753,7 @@ trait TeamMailboxesContract {
.appendMessage(BOB.asString(), teamMailbox.inboxPath, AppendCommand.from(message))
.getMessageId.serialize()
- Thread.sleep(100)
- List(
- ws.receive().asPayload,
- ws.receive().asPayload)
-
+ ws.receiveMessageInTimespan(scala.concurrent.duration.Duration(2000, MILLISECONDS))
})
.send(backend)
.body
diff --git a/tmail-backend/integration-tests/jmap/memory-jmap-integration-tests/src/test/java/com/linagora/tmail/james/MemoryMailboxSetMethodTest.java b/tmail-backend/integration-tests/jmap/memory-jmap-integration-tests/src/test/java/com/linagora/tmail/james/MemoryMailboxSetMethodTest.java
index 6588b3be055..b710e800730 100644
--- a/tmail-backend/integration-tests/jmap/memory-jmap-integration-tests/src/test/java/com/linagora/tmail/james/MemoryMailboxSetMethodTest.java
+++ b/tmail-backend/integration-tests/jmap/memory-jmap-integration-tests/src/test/java/com/linagora/tmail/james/MemoryMailboxSetMethodTest.java
@@ -23,11 +23,14 @@
import java.util.concurrent.ThreadLocalRandom;
+import org.apache.james.GuiceJamesServer;
import org.apache.james.JamesServerBuilder;
import org.apache.james.JamesServerExtension;
import org.apache.james.jmap.rfc8621.contract.MailboxSetMethodContract;
import org.apache.james.mailbox.inmemory.InMemoryId;
import org.apache.james.mailbox.model.MailboxId;
+import org.junit.jupiter.api.Disabled;
+import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
import com.linagora.tmail.james.app.MemoryConfiguration;
@@ -57,4 +60,11 @@ public MailboxId randomMailboxId() {
public String errorInvalidMailboxIdMessage(String value) {
return String.format("%s is not a mailboxId: For input string: \\\"%s\\\"", value, value);
}
+
+ @Override
+ @Test
+ @Disabled
+ // TODO Need to fix
+ public void webSocketShouldPushNewMessageWhenChangeSubscriptionOfMailbox(GuiceJamesServer server) {
+ }
}
diff --git a/tmail-backend/integration-tests/jmap/postgres-jmap-integration-tests/pom.xml b/tmail-backend/integration-tests/jmap/postgres-jmap-integration-tests/pom.xml
new file mode 100644
index 00000000000..7b307aa14c9
--- /dev/null
+++ b/tmail-backend/integration-tests/jmap/postgres-jmap-integration-tests/pom.xml
@@ -0,0 +1,143 @@
+
+
+
+ integration-tests
+ com.linagora.tmail
+ 1.0.0-SNAPSHOT
+ ../../pom.xml
+
+ 4.0.0
+
+ postgres-jmap-integration-tests
+ Team-mail :: Integration Tests :: JMAP :: Postgres
+
+
+
+ ${project.groupId}
+ blobid-list
+
+
+ ${project.groupId}
+ distributed
+ test-jar
+ test
+
+
+ ${project.groupId}
+ jmap-extensions-opensearch
+
+
+ ${project.groupId}
+ postgres
+ test
+
+
+ ${project.groupId}
+ postgres
+ test-jar
+ test
+
+
+ ${project.groupId}
+ tmail-guice-distributed
+ test-jar
+ test
+
+
+ ${project.groupId}
+ tmail-guice-jmap
+ test-jar
+ test
+
+
+ ${project.groupId}
+ webadmin-integration-tests-common
+ test
+
+
+ ${james.groupId}
+ james-server-mailbox-plugin-deleted-messages-vault-guice
+ test-jar
+ test
+
+
+ ${james.groupId}
+ apache-james-backends-opensearch
+ test-jar
+ test
+
+
+ ${james.groupId}
+ apache-james-backends-postgres
+ test-jar
+ test
+
+
+ ${james.groupId}
+ apache-james-backends-rabbitmq
+ test-jar
+ test
+
+
+ ${james.groupId}
+ apache-james-rspamd
+ test-jar
+ test
+
+
+ ${james.groupId}
+ james-server-guice-common
+ test-jar
+ test
+
+
+ ${james.groupId}
+ james-server-guice-jmap
+ test-jar
+ test
+
+
+ ${james.groupId}
+ james-server-guice-opensearch
+ test-jar
+ test
+
+
+ ${james.groupId}
+ james-server-postgres-app
+ test
+
+
+ ${james.groupId}
+ james-server-postgres-app
+ test-jar
+ test
+
+
+ ${james.groupId}
+ james-server-testing
+
+
+ org.testcontainers
+ postgresql
+ test
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-surefire-plugin
+
+ 2
+ 3600
+ true
+ 2
+
+
+
+
+
\ No newline at end of file
diff --git a/tmail-backend/integration-tests/jmap/postgres-jmap-integration-tests/src/test/java/com/linagora/tmail/james/PostgresEmailGetMethodTest.java b/tmail-backend/integration-tests/jmap/postgres-jmap-integration-tests/src/test/java/com/linagora/tmail/james/PostgresEmailGetMethodTest.java
new file mode 100644
index 00000000000..716b34564f6
--- /dev/null
+++ b/tmail-backend/integration-tests/jmap/postgres-jmap-integration-tests/src/test/java/com/linagora/tmail/james/PostgresEmailGetMethodTest.java
@@ -0,0 +1,22 @@
+package com.linagora.tmail.james;
+
+import static com.linagora.tmail.james.TmailJmapBase.JAMES_SERVER_EXTENSION_FUNCTION;
+import static com.linagora.tmail.james.TmailJmapBase.MESSAGE_ID_FACTORY;
+
+import org.apache.james.JamesServerExtension;
+import org.apache.james.jmap.rfc8621.contract.EmailGetMethodContract;
+import org.apache.james.jmap.rfc8621.contract.probe.DelegationProbeModule;
+import org.apache.james.mailbox.model.MessageId;
+import org.junit.jupiter.api.extension.RegisterExtension;
+
+public class PostgresEmailGetMethodTest implements EmailGetMethodContract {
+
+ @RegisterExtension
+ static JamesServerExtension testExtension = JAMES_SERVER_EXTENSION_FUNCTION.apply(new DelegationProbeModule())
+ .build();
+
+ @Override
+ public MessageId randomMessageId() {
+ return MESSAGE_ID_FACTORY.generate();
+ }
+}
diff --git a/tmail-backend/integration-tests/jmap/postgres-jmap-integration-tests/src/test/java/com/linagora/tmail/james/PostgresEmailQueryMethodTest.java b/tmail-backend/integration-tests/jmap/postgres-jmap-integration-tests/src/test/java/com/linagora/tmail/james/PostgresEmailQueryMethodTest.java
new file mode 100644
index 00000000000..7da02191bc1
--- /dev/null
+++ b/tmail-backend/integration-tests/jmap/postgres-jmap-integration-tests/src/test/java/com/linagora/tmail/james/PostgresEmailQueryMethodTest.java
@@ -0,0 +1,61 @@
+package com.linagora.tmail.james;
+
+import static org.apache.james.PostgresJamesConfiguration.EventBusImpl.IN_MEMORY;
+
+import org.apache.james.ClockExtension;
+import org.apache.james.DockerOpenSearchExtension;
+import org.apache.james.JamesServerBuilder;
+import org.apache.james.JamesServerExtension;
+import org.apache.james.SearchConfiguration;
+import org.apache.james.backends.postgres.PostgresExtension;
+import org.apache.james.jmap.rfc8621.contract.EmailQueryMethodContract;
+import org.apache.james.jmap.rfc8621.contract.probe.DelegationProbeModule;
+import org.apache.james.utils.GuiceProbe;
+import org.junit.jupiter.api.extension.RegisterExtension;
+
+import com.google.inject.multibindings.Multibinder;
+import com.linagora.tmail.blob.guice.BlobStoreConfiguration;
+import com.linagora.tmail.combined.identity.UsersRepositoryClassProbe;
+import com.linagora.tmail.encrypted.MailboxManagerClassProbe;
+import com.linagora.tmail.james.app.PostgresTmailConfiguration;
+import com.linagora.tmail.james.app.PostgresTmailServer;
+import com.linagora.tmail.james.common.LabelChangesMethodContract;
+import com.linagora.tmail.james.common.probe.JmapGuiceContactAutocompleteProbe;
+import com.linagora.tmail.james.common.probe.JmapSettingsProbe;
+import com.linagora.tmail.james.jmap.firebase.FirebaseModuleChooserConfiguration;
+import com.linagora.tmail.james.jmap.firebase.FirebasePushClient;
+import com.linagora.tmail.module.LinagoraTestJMAPServerModule;
+import com.linagora.tmail.team.TeamMailboxProbe;
+
+public class PostgresEmailQueryMethodTest implements EmailQueryMethodContract {
+
+ @RegisterExtension
+ static JamesServerExtension testExtension = new JamesServerBuilder(tmpDir ->
+ PostgresTmailConfiguration.builder()
+ .workingDirectory(tmpDir)
+ .configurationFromClasspath()
+ .blobStore(BlobStoreConfiguration.builder()
+ .postgres()
+ .disableCache()
+ .deduplication()
+ .noCryptoConfig()
+ .disableSingleSave())
+ .searchConfiguration(SearchConfiguration.openSearch())
+ .firebaseModuleChooserConfiguration(FirebaseModuleChooserConfiguration.ENABLED)
+ .eventBusImpl(IN_MEMORY)
+ .build())
+ .server(configuration -> PostgresTmailServer.createServer(configuration)
+ .overrideWith(new LinagoraTestJMAPServerModule())
+ .overrideWith(binder -> Multibinder.newSetBinder(binder, GuiceProbe.class).addBinding().to(MailboxManagerClassProbe.class))
+ .overrideWith(binder -> Multibinder.newSetBinder(binder, GuiceProbe.class).addBinding().to(UsersRepositoryClassProbe.class))
+ .overrideWith(binder -> Multibinder.newSetBinder(binder, GuiceProbe.class).addBinding().to(TeamMailboxProbe.class))
+ .overrideWith(binder -> Multibinder.newSetBinder(binder, GuiceProbe.class).addBinding().to(JmapGuiceContactAutocompleteProbe.class))
+ .overrideWith(binder -> Multibinder.newSetBinder(binder, GuiceProbe.class).addBinding().to(JmapSettingsProbe.class))
+ .overrideWith(binder -> binder.bind(FirebasePushClient.class).toInstance(LabelChangesMethodContract.firebasePushClient()))
+ .overrideWith(new DelegationProbeModule()))
+ .extension(PostgresExtension.empty())
+ .extension(new DockerOpenSearchExtension())
+ .extension(new ClockExtension())
+ .build();
+
+}
\ No newline at end of file
diff --git a/tmail-backend/integration-tests/jmap/postgres-jmap-integration-tests/src/test/java/com/linagora/tmail/james/PostgresEmailSetMethodTest.java b/tmail-backend/integration-tests/jmap/postgres-jmap-integration-tests/src/test/java/com/linagora/tmail/james/PostgresEmailSetMethodTest.java
new file mode 100644
index 00000000000..a70495f4158
--- /dev/null
+++ b/tmail-backend/integration-tests/jmap/postgres-jmap-integration-tests/src/test/java/com/linagora/tmail/james/PostgresEmailSetMethodTest.java
@@ -0,0 +1,38 @@
+package com.linagora.tmail.james;
+
+import static com.linagora.tmail.james.TmailJmapBase.JAMES_SERVER_EXTENSION_SUPPLIER;
+import static com.linagora.tmail.james.TmailJmapBase.MESSAGE_ID_FACTORY;
+
+import org.apache.james.GuiceJamesServer;
+import org.apache.james.JamesServerExtension;
+import org.apache.james.jmap.rfc8621.contract.EmailSetMethodContract;
+import org.apache.james.mailbox.model.MessageId;
+import org.junit.jupiter.api.Disabled;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.RegisterExtension;
+
+public class PostgresEmailSetMethodTest implements EmailSetMethodContract {
+
+ @RegisterExtension
+ static JamesServerExtension testExtension = JAMES_SERVER_EXTENSION_SUPPLIER.get().build();
+
+ @Override
+ public MessageId randomMessageId() {
+ return MESSAGE_ID_FACTORY.generate();
+ }
+
+ @Override
+ public String invalidMessageIdMessage(String invalid) {
+ return String.format("Invalid UUID string: %s", invalid);
+ }
+
+ @Override
+ @Test
+ @Disabled("Distributed event bus is asynchronous, we cannot expect the newState to be returned immediately after Email/set call")
+ public void newStateShouldBeUpToDate(GuiceJamesServer server) {}
+
+ @Override
+ @Test
+ @Disabled("Distributed event bus is asynchronous, we cannot expect the newState to be returned immediately after Email/set call")
+ public void oldStateShouldIncludeSetChanges(GuiceJamesServer server) {}
+}
\ No newline at end of file
diff --git a/tmail-backend/integration-tests/jmap/postgres-jmap-integration-tests/src/test/java/com/linagora/tmail/james/PostgresFirebasePushTest.java b/tmail-backend/integration-tests/jmap/postgres-jmap-integration-tests/src/test/java/com/linagora/tmail/james/PostgresFirebasePushTest.java
new file mode 100644
index 00000000000..6289a3e8f9e
--- /dev/null
+++ b/tmail-backend/integration-tests/jmap/postgres-jmap-integration-tests/src/test/java/com/linagora/tmail/james/PostgresFirebasePushTest.java
@@ -0,0 +1,25 @@
+package com.linagora.tmail.james;
+
+import static com.linagora.tmail.james.TmailJmapBase.JAMES_SERVER_EXTENSION_FUNCTION;
+
+import org.apache.james.JamesServerExtension;
+import org.junit.jupiter.api.extension.RegisterExtension;
+
+import com.google.inject.AbstractModule;
+import com.linagora.tmail.james.common.FirebasePushContract;
+import com.linagora.tmail.james.common.FirebaseSubscriptionProbeModule;
+import com.linagora.tmail.james.jmap.firebase.FirebasePushClient;
+
+public class PostgresFirebasePushTest implements FirebasePushContract {
+
+ @RegisterExtension
+ static JamesServerExtension testExtension = JAMES_SERVER_EXTENSION_FUNCTION
+ .apply(new AbstractModule() {
+ @Override
+ protected void configure() {
+ install(new FirebaseSubscriptionProbeModule());
+ bind(FirebasePushClient.class).toInstance(FirebasePushContract.firebasePushClient());
+ }
+ })
+ .build();
+}
\ No newline at end of file
diff --git a/tmail-backend/integration-tests/jmap/postgres-jmap-integration-tests/src/test/java/com/linagora/tmail/james/PostgresFirebaseSubscriptionGetMethodTest.java b/tmail-backend/integration-tests/jmap/postgres-jmap-integration-tests/src/test/java/com/linagora/tmail/james/PostgresFirebaseSubscriptionGetMethodTest.java
new file mode 100644
index 00000000000..1a152668cd8
--- /dev/null
+++ b/tmail-backend/integration-tests/jmap/postgres-jmap-integration-tests/src/test/java/com/linagora/tmail/james/PostgresFirebaseSubscriptionGetMethodTest.java
@@ -0,0 +1,25 @@
+package com.linagora.tmail.james;
+
+import static com.linagora.tmail.james.TmailJmapBase.JAMES_SERVER_EXTENSION_FUNCTION;
+
+import org.apache.james.JamesServerExtension;
+import org.junit.jupiter.api.extension.RegisterExtension;
+
+import com.google.inject.AbstractModule;
+import com.linagora.tmail.james.common.FirebaseSubscriptionGetMethodContract;
+import com.linagora.tmail.james.common.FirebaseSubscriptionProbeModule;
+import com.linagora.tmail.james.jmap.firebase.FirebasePushClient;
+
+public class PostgresFirebaseSubscriptionGetMethodTest implements FirebaseSubscriptionGetMethodContract {
+
+ @RegisterExtension
+ static JamesServerExtension testExtension = JAMES_SERVER_EXTENSION_FUNCTION
+ .apply(new AbstractModule() {
+ @Override
+ protected void configure() {
+ install(new FirebaseSubscriptionProbeModule());
+ bind(FirebasePushClient.class).toInstance(FirebaseSubscriptionGetMethodContract.firebasePushClient());
+ }
+ })
+ .build();
+}
\ No newline at end of file
diff --git a/tmail-backend/integration-tests/jmap/postgres-jmap-integration-tests/src/test/java/com/linagora/tmail/james/PostgresFirebaseSubscriptionSetMethodTest.java b/tmail-backend/integration-tests/jmap/postgres-jmap-integration-tests/src/test/java/com/linagora/tmail/james/PostgresFirebaseSubscriptionSetMethodTest.java
new file mode 100644
index 00000000000..89ba585e3b9
--- /dev/null
+++ b/tmail-backend/integration-tests/jmap/postgres-jmap-integration-tests/src/test/java/com/linagora/tmail/james/PostgresFirebaseSubscriptionSetMethodTest.java
@@ -0,0 +1,27 @@
+package com.linagora.tmail.james;
+
+import static com.linagora.tmail.james.TmailJmapBase.JAMES_SERVER_EXTENSION_FUNCTION;
+
+import org.apache.james.JamesServerExtension;
+import org.junit.jupiter.api.extension.RegisterExtension;
+
+import com.google.inject.AbstractModule;
+import com.google.inject.Module;
+import com.linagora.tmail.james.common.FirebaseSubscriptionProbeModule;
+import com.linagora.tmail.james.common.FirebaseSubscriptionSetMethodContract;
+import com.linagora.tmail.james.jmap.firebase.FirebasePushClient;
+
+public class PostgresFirebaseSubscriptionSetMethodTest implements FirebaseSubscriptionSetMethodContract {
+
+ static Module FIREBASE_TEST_MODULE = new AbstractModule() {
+ @Override
+ protected void configure() {
+ install(new FirebaseSubscriptionProbeModule());
+ bind(FirebasePushClient.class).toInstance(FirebaseSubscriptionSetMethodContract.firebasePushClient());
+ }
+ };
+
+ @RegisterExtension
+ static JamesServerExtension testExtension = JAMES_SERVER_EXTENSION_FUNCTION.apply(FIREBASE_TEST_MODULE)
+ .build();
+}
\ No newline at end of file
diff --git a/tmail-backend/integration-tests/jmap/postgres-jmap-integration-tests/src/test/java/com/linagora/tmail/james/PostgresLinagoraCalendarEventAcceptMethodTest.java b/tmail-backend/integration-tests/jmap/postgres-jmap-integration-tests/src/test/java/com/linagora/tmail/james/PostgresLinagoraCalendarEventAcceptMethodTest.java
new file mode 100644
index 00000000000..9a5c447df30
--- /dev/null
+++ b/tmail-backend/integration-tests/jmap/postgres-jmap-integration-tests/src/test/java/com/linagora/tmail/james/PostgresLinagoraCalendarEventAcceptMethodTest.java
@@ -0,0 +1,21 @@
+package com.linagora.tmail.james;
+
+import static com.linagora.tmail.james.TmailJmapBase.JAMES_SERVER_EXTENSION_FUNCTION;
+
+import org.apache.james.JamesServerExtension;
+import org.apache.james.jmap.rfc8621.contract.probe.DelegationProbeModule;
+import org.junit.jupiter.api.extension.RegisterExtension;
+
+import com.linagora.tmail.james.common.LinagoraCalendarEventAcceptMethodContract;
+
+public class PostgresLinagoraCalendarEventAcceptMethodTest implements LinagoraCalendarEventAcceptMethodContract {
+
+ @RegisterExtension
+ static JamesServerExtension testExtension = JAMES_SERVER_EXTENSION_FUNCTION.apply(new DelegationProbeModule())
+ .build();
+
+ @Override
+ public String randomBlobId() {
+ return TmailJmapBase.randomBlobId();
+ }
+}
\ No newline at end of file
diff --git a/tmail-backend/integration-tests/jmap/postgres-jmap-integration-tests/src/test/java/com/linagora/tmail/james/PostgresLinagoraCalendarEventMaybeMethodTest.java b/tmail-backend/integration-tests/jmap/postgres-jmap-integration-tests/src/test/java/com/linagora/tmail/james/PostgresLinagoraCalendarEventMaybeMethodTest.java
new file mode 100644
index 00000000000..828d2a84b3c
--- /dev/null
+++ b/tmail-backend/integration-tests/jmap/postgres-jmap-integration-tests/src/test/java/com/linagora/tmail/james/PostgresLinagoraCalendarEventMaybeMethodTest.java
@@ -0,0 +1,21 @@
+package com.linagora.tmail.james;
+
+import static com.linagora.tmail.james.TmailJmapBase.JAMES_SERVER_EXTENSION_FUNCTION;
+
+import org.apache.james.JamesServerExtension;
+import org.apache.james.jmap.rfc8621.contract.probe.DelegationProbeModule;
+import org.junit.jupiter.api.extension.RegisterExtension;
+
+import com.linagora.tmail.james.common.LinagoraCalendarEventMaybeMethodContract;
+
+public class PostgresLinagoraCalendarEventMaybeMethodTest implements LinagoraCalendarEventMaybeMethodContract {
+
+ @RegisterExtension
+ static JamesServerExtension testExtension = JAMES_SERVER_EXTENSION_FUNCTION.apply(new DelegationProbeModule())
+ .build();
+
+ @Override
+ public String randomBlobId() {
+ return TmailJmapBase.randomBlobId();
+ }
+}
\ No newline at end of file
diff --git a/tmail-backend/integration-tests/jmap/postgres-jmap-integration-tests/src/test/java/com/linagora/tmail/james/PostgresLinagoraCalendarEventParseMethodTest.java b/tmail-backend/integration-tests/jmap/postgres-jmap-integration-tests/src/test/java/com/linagora/tmail/james/PostgresLinagoraCalendarEventParseMethodTest.java
new file mode 100644
index 00000000000..da0113ec08e
--- /dev/null
+++ b/tmail-backend/integration-tests/jmap/postgres-jmap-integration-tests/src/test/java/com/linagora/tmail/james/PostgresLinagoraCalendarEventParseMethodTest.java
@@ -0,0 +1,21 @@
+package com.linagora.tmail.james;
+
+import static com.linagora.tmail.james.TmailJmapBase.JAMES_SERVER_EXTENSION_FUNCTION;
+
+import org.apache.james.JamesServerExtension;
+import org.apache.james.jmap.rfc8621.contract.probe.DelegationProbeModule;
+import org.junit.jupiter.api.extension.RegisterExtension;
+
+import com.linagora.tmail.james.common.LinagoraCalendarEventParseMethodContract;
+
+public class PostgresLinagoraCalendarEventParseMethodTest implements LinagoraCalendarEventParseMethodContract {
+
+ @RegisterExtension
+ static JamesServerExtension testExtension = JAMES_SERVER_EXTENSION_FUNCTION.apply(new DelegationProbeModule())
+ .build();
+
+ @Override
+ public String randomBlobId() {
+ return TmailJmapBase.randomBlobId();
+ }
+}
\ No newline at end of file
diff --git a/tmail-backend/integration-tests/jmap/postgres-jmap-integration-tests/src/test/java/com/linagora/tmail/james/PostgresLinagoraCalendarEventRejectMethodTest.java b/tmail-backend/integration-tests/jmap/postgres-jmap-integration-tests/src/test/java/com/linagora/tmail/james/PostgresLinagoraCalendarEventRejectMethodTest.java
new file mode 100644
index 00000000000..1c2bd7d8f80
--- /dev/null
+++ b/tmail-backend/integration-tests/jmap/postgres-jmap-integration-tests/src/test/java/com/linagora/tmail/james/PostgresLinagoraCalendarEventRejectMethodTest.java
@@ -0,0 +1,21 @@
+package com.linagora.tmail.james;
+
+import static com.linagora.tmail.james.TmailJmapBase.JAMES_SERVER_EXTENSION_FUNCTION;
+
+import org.apache.james.JamesServerExtension;
+import org.apache.james.jmap.rfc8621.contract.probe.DelegationProbeModule;
+import org.junit.jupiter.api.extension.RegisterExtension;
+
+import com.linagora.tmail.james.common.LinagoraCalendarEventRejectMethodContract;
+
+public class PostgresLinagoraCalendarEventRejectMethodTest implements LinagoraCalendarEventRejectMethodContract {
+
+ @RegisterExtension
+ static JamesServerExtension testExtension = JAMES_SERVER_EXTENSION_FUNCTION.apply(new DelegationProbeModule())
+ .build();
+
+ @Override
+ public String randomBlobId() {
+ return TmailJmapBase.randomBlobId();
+ }
+}
\ No newline at end of file
diff --git a/tmail-backend/integration-tests/jmap/postgres-jmap-integration-tests/src/test/java/com/linagora/tmail/james/PostgresLinagoraContactAutoCompleteMethodTest.java b/tmail-backend/integration-tests/jmap/postgres-jmap-integration-tests/src/test/java/com/linagora/tmail/james/PostgresLinagoraContactAutoCompleteMethodTest.java
new file mode 100644
index 00000000000..97451cda8ff
--- /dev/null
+++ b/tmail-backend/integration-tests/jmap/postgres-jmap-integration-tests/src/test/java/com/linagora/tmail/james/PostgresLinagoraContactAutoCompleteMethodTest.java
@@ -0,0 +1,140 @@
+package com.linagora.tmail.james;
+
+import static com.linagora.tmail.james.jmap.OpenSearchContactConfiguration.DEFAULT_CONFIGURATION;
+import static java.nio.charset.StandardCharsets.UTF_8;
+import static org.apache.james.PostgresJamesConfiguration.EventBusImpl.RABBITMQ;
+import static org.apache.james.backends.rabbitmq.Constants.EMPTY_ROUTING_KEY;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.awaitility.Durations.ONE_HUNDRED_MILLISECONDS;
+
+import java.io.IOException;
+
+import org.apache.james.JamesServerBuilder;
+import org.apache.james.JamesServerExtension;
+import org.apache.james.SearchConfiguration;
+import org.apache.james.backends.opensearch.ReactorOpenSearchClient;
+import org.apache.james.backends.postgres.PostgresExtension;
+import org.apache.james.backends.rabbitmq.RabbitMQExtension;
+import org.apache.james.jmap.rfc8621.contract.Fixture;
+import org.apache.james.jmap.rfc8621.contract.probe.DelegationProbeModule;
+import org.apache.james.utils.GuiceProbe;
+import org.awaitility.Awaitility;
+import org.awaitility.Durations;
+import org.awaitility.core.ConditionFactory;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.RegisterExtension;
+import org.opensearch.client.opensearch._types.query_dsl.QueryBuilders;
+import org.opensearch.client.opensearch.core.SearchRequest;
+
+import com.google.inject.multibindings.Multibinder;
+import com.linagora.tmail.blob.guice.BlobStoreConfiguration;
+import com.linagora.tmail.combined.identity.UsersRepositoryClassProbe;
+import com.linagora.tmail.encrypted.MailboxConfiguration;
+import com.linagora.tmail.encrypted.MailboxManagerClassProbe;
+import com.linagora.tmail.james.app.DockerOpenSearchExtension;
+import com.linagora.tmail.james.app.PostgresTmailConfiguration;
+import com.linagora.tmail.james.app.PostgresTmailServer;
+import com.linagora.tmail.james.common.LabelChangesMethodContract;
+import com.linagora.tmail.james.common.LinagoraContactAutocompleteMethodContract;
+import com.linagora.tmail.james.common.module.JmapGuiceContactAutocompleteModule;
+import com.linagora.tmail.james.common.probe.JmapGuiceContactAutocompleteProbe;
+import com.linagora.tmail.james.common.probe.JmapSettingsProbe;
+import com.linagora.tmail.james.jmap.firebase.FirebaseModuleChooserConfiguration;
+import com.linagora.tmail.james.jmap.firebase.FirebasePushClient;
+import com.linagora.tmail.module.LinagoraTestJMAPServerModule;
+import com.linagora.tmail.team.TeamMailboxProbe;
+
+import reactor.core.publisher.Mono;
+import reactor.rabbitmq.OutboundMessage;
+
+public class PostgresLinagoraContactAutoCompleteMethodTest implements LinagoraContactAutocompleteMethodContract {
+
+ private static final ConditionFactory CALMLY_AWAIT = Awaitility
+ .with().pollInterval(ONE_HUNDRED_MILLISECONDS)
+ .and().pollDelay(ONE_HUNDRED_MILLISECONDS)
+ .await();
+ private static final com.linagora.tmail.james.app.RabbitMQExtension rabbitMQExtensionModule = new com.linagora.tmail.james.app.RabbitMQExtension();
+
+ @RegisterExtension
+ static DockerOpenSearchExtension opensearchExtension = new DockerOpenSearchExtension();
+
+ @RegisterExtension
+ static RabbitMQExtension rabbitMQExtension = RabbitMQExtension.dockerRabbitMQ(rabbitMQExtensionModule.dockerRabbitMQ())
+ .restartPolicy(RabbitMQExtension.DockerRestartPolicy.PER_CLASS)
+ .isolationPolicy(RabbitMQExtension.IsolationPolicy.WEAK);
+
+ @RegisterExtension
+ static JamesServerExtension testExtension = new JamesServerBuilder(tmpDir ->
+ PostgresTmailConfiguration.builder()
+ .workingDirectory(tmpDir)
+ .configurationFromClasspath()
+ .blobStore(BlobStoreConfiguration.builder()
+ .postgres()
+ .disableCache()
+ .deduplication()
+ .noCryptoConfig()
+ .disableSingleSave())
+ .searchConfiguration(SearchConfiguration.openSearch())
+ .mailbox(new MailboxConfiguration(false))
+ .firebaseModuleChooserConfiguration(FirebaseModuleChooserConfiguration.ENABLED)
+ .eventBusImpl(RABBITMQ)
+ .build())
+ .server(configuration -> PostgresTmailServer.createServer(configuration)
+ .overrideWith(new LinagoraTestJMAPServerModule())
+ .overrideWith(binder -> Multibinder.newSetBinder(binder, GuiceProbe.class).addBinding().to(MailboxManagerClassProbe.class))
+ .overrideWith(binder -> Multibinder.newSetBinder(binder, GuiceProbe.class).addBinding().to(UsersRepositoryClassProbe.class))
+ .overrideWith(binder -> Multibinder.newSetBinder(binder, GuiceProbe.class).addBinding().to(TeamMailboxProbe.class))
+ .overrideWith(binder -> Multibinder.newSetBinder(binder, GuiceProbe.class).addBinding().to(JmapGuiceContactAutocompleteProbe.class))
+ .overrideWith(binder -> Multibinder.newSetBinder(binder, GuiceProbe.class).addBinding().to(JmapSettingsProbe.class))
+ .overrideWith(binder -> binder.bind(FirebasePushClient.class).toInstance(LabelChangesMethodContract.firebasePushClient()))
+ .overrideWith(new DelegationProbeModule())
+ .overrideWith(new JmapGuiceContactAutocompleteModule()))
+ .extension(PostgresExtension.empty())
+ .extension(opensearchExtension)
+ .extension(rabbitMQExtensionModule)
+ .build();
+
+
+ private final ReactorOpenSearchClient client = opensearchExtension.getDockerOS().clientProvider().get();
+
+ @AfterEach
+ void tearDown() throws IOException {
+ client.close();
+ }
+
+ @Override
+ public void awaitDocumentsIndexed(long documentCount) {
+ CALMLY_AWAIT.atMost(Durations.TEN_SECONDS)
+ .untilAsserted(() -> assertThat(client.search(
+ new SearchRequest.Builder()
+ .index(DEFAULT_CONFIGURATION.getUserContactIndexName().getValue(), DEFAULT_CONFIGURATION.getDomainContactIndexName().getValue())
+ .query(QueryBuilders.matchAll().build()._toQuery())
+ .build())
+ .block()
+ .hits().total().value()).isEqualTo(documentCount));
+ }
+
+ @Test
+ void contactShouldBeIndexedWhenAQMPUserAddedMessage() {
+ String aqmpUserAddedMessage = String.format("{ " +
+ " \"type\": \"addition\"," +
+ " \"scope\": \"user\", " +
+ " \"owner\" : \"%s\"," +
+ " \"entry\": {" +
+ " \"address\": \"%s\"," +
+ " \"firstname\": \"Alice\"," +
+ " \"surname\": \"Watson\"" +
+ " }" +
+ "}", Fixture.BOB().asString(), Fixture.ANDRE().asString());
+
+ rabbitMQExtension.getSender()
+ .send(Mono.just(new OutboundMessage(
+ "TmailExchange-AddressContactQueueForTesting",
+ EMPTY_ROUTING_KEY,
+ aqmpUserAddedMessage.getBytes(UTF_8))))
+ .block();
+
+ bobShouldHaveAndreContact();
+ }
+}
\ No newline at end of file
diff --git a/tmail-backend/integration-tests/jmap/postgres-jmap-integration-tests/src/test/java/com/linagora/tmail/james/PostgresLinagoraEchoMethodTest.java b/tmail-backend/integration-tests/jmap/postgres-jmap-integration-tests/src/test/java/com/linagora/tmail/james/PostgresLinagoraEchoMethodTest.java
new file mode 100644
index 00000000000..842f1ddb515
--- /dev/null
+++ b/tmail-backend/integration-tests/jmap/postgres-jmap-integration-tests/src/test/java/com/linagora/tmail/james/PostgresLinagoraEchoMethodTest.java
@@ -0,0 +1,23 @@
+package com.linagora.tmail.james;
+
+import org.apache.james.JamesServerExtension;
+import org.junit.jupiter.api.extension.RegisterExtension;
+
+import com.google.inject.AbstractModule;
+import com.linagora.tmail.james.common.FirebaseSubscriptionProbeModule;
+import com.linagora.tmail.james.common.LinagoraEchoMethodContract;
+import com.linagora.tmail.james.jmap.firebase.FirebasePushClient;
+
+public class PostgresLinagoraEchoMethodTest implements LinagoraEchoMethodContract {
+
+ @RegisterExtension
+ static JamesServerExtension testExtension = TmailJmapBase.JAMES_SERVER_EXTENSION_FUNCTION
+ .apply(new AbstractModule() {
+ @Override
+ protected void configure() {
+ install(new FirebaseSubscriptionProbeModule());
+ bind(FirebasePushClient.class).toInstance(LinagoraEchoMethodContract.firebasePushClient());
+ }
+ })
+ .build();
+}
\ No newline at end of file
diff --git a/tmail-backend/integration-tests/jmap/postgres-jmap-integration-tests/src/test/java/com/linagora/tmail/james/PostgresLinagoraEmailRecoveryActionGetMethodTest.java b/tmail-backend/integration-tests/jmap/postgres-jmap-integration-tests/src/test/java/com/linagora/tmail/james/PostgresLinagoraEmailRecoveryActionGetMethodTest.java
new file mode 100644
index 00000000000..a91fad94fb9
--- /dev/null
+++ b/tmail-backend/integration-tests/jmap/postgres-jmap-integration-tests/src/test/java/com/linagora/tmail/james/PostgresLinagoraEmailRecoveryActionGetMethodTest.java
@@ -0,0 +1,21 @@
+package com.linagora.tmail.james;
+
+import org.apache.james.JamesServerExtension;
+import org.apache.james.mailbox.model.MessageId;
+import org.junit.jupiter.api.extension.RegisterExtension;
+
+import com.linagora.tmail.james.common.DeletedMessageVaultProbeModule;
+import com.linagora.tmail.james.common.EmailRecoveryActionGetMethodContract;
+
+public class PostgresLinagoraEmailRecoveryActionGetMethodTest implements EmailRecoveryActionGetMethodContract {
+
+ @RegisterExtension
+ static JamesServerExtension testExtension = TmailJmapBase.JAMES_SERVER_EXTENSION_FUNCTION
+ .apply(new DeletedMessageVaultProbeModule())
+ .build();
+
+ @Override
+ public MessageId randomMessageId() {
+ return TmailJmapBase.MESSAGE_ID_FACTORY.generate();
+ }
+}
\ No newline at end of file
diff --git a/tmail-backend/integration-tests/jmap/postgres-jmap-integration-tests/src/test/java/com/linagora/tmail/james/PostgresLinagoraEmailRecoveryActionIntegrationTest.java b/tmail-backend/integration-tests/jmap/postgres-jmap-integration-tests/src/test/java/com/linagora/tmail/james/PostgresLinagoraEmailRecoveryActionIntegrationTest.java
new file mode 100644
index 00000000000..10c6288aa23
--- /dev/null
+++ b/tmail-backend/integration-tests/jmap/postgres-jmap-integration-tests/src/test/java/com/linagora/tmail/james/PostgresLinagoraEmailRecoveryActionIntegrationTest.java
@@ -0,0 +1,18 @@
+package com.linagora.tmail.james;
+
+import org.apache.james.JamesServerExtension;
+import org.apache.james.modules.vault.TestDeleteMessageVaultPreDeletionHookModule;
+import org.junit.jupiter.api.extension.RegisterExtension;
+
+import com.google.inject.util.Modules;
+import com.linagora.tmail.james.common.DeletedMessageVaultProbeModule;
+import com.linagora.tmail.james.common.EmailRecoveryActionIntegrationTest;
+
+public class PostgresLinagoraEmailRecoveryActionIntegrationTest implements EmailRecoveryActionIntegrationTest {
+
+ @RegisterExtension
+ static JamesServerExtension testExtension = TmailJmapBase.JAMES_SERVER_EXTENSION_FUNCTION
+ .apply(Modules.combine(new DeletedMessageVaultProbeModule(), new TestDeleteMessageVaultPreDeletionHookModule()))
+ .build();
+
+}
\ No newline at end of file
diff --git a/tmail-backend/integration-tests/jmap/postgres-jmap-integration-tests/src/test/java/com/linagora/tmail/james/PostgresLinagoraEmailRecoveryActionSetMethodTest.java b/tmail-backend/integration-tests/jmap/postgres-jmap-integration-tests/src/test/java/com/linagora/tmail/james/PostgresLinagoraEmailRecoveryActionSetMethodTest.java
new file mode 100644
index 00000000000..da70c19e856
--- /dev/null
+++ b/tmail-backend/integration-tests/jmap/postgres-jmap-integration-tests/src/test/java/com/linagora/tmail/james/PostgresLinagoraEmailRecoveryActionSetMethodTest.java
@@ -0,0 +1,30 @@
+package com.linagora.tmail.james;
+
+import org.apache.james.GuiceJamesServer;
+import org.apache.james.JamesServerExtension;
+import org.apache.james.mailbox.model.MessageId;
+import org.apache.james.modules.vault.TestDeleteMessageVaultPreDeletionHookModule;
+import org.junit.jupiter.api.Disabled;
+import org.junit.jupiter.api.extension.RegisterExtension;
+
+import com.google.inject.util.Modules;
+import com.linagora.tmail.james.common.DeletedMessageVaultProbeModule;
+import com.linagora.tmail.james.common.EmailRecoveryActionSetMethodContract;
+
+public class PostgresLinagoraEmailRecoveryActionSetMethodTest implements EmailRecoveryActionSetMethodContract {
+
+ @RegisterExtension
+ static JamesServerExtension testExtension = TmailJmapBase.JAMES_SERVER_EXTENSION_FUNCTION
+ .apply(Modules.combine(new DeletedMessageVaultProbeModule(), new TestDeleteMessageVaultPreDeletionHookModule()))
+ .build();
+
+ @Override
+ public MessageId randomMessageId() {
+ return TmailJmapBase.MESSAGE_ID_FACTORY.generate();
+ }
+
+ @Disabled("Difficult to implement serialize/deserialize MemoryReferenceTask with distributed James server")
+ @Override
+ public void updateStatusCanceledShouldCancelTask(GuiceJamesServer server) {
+ }
+}
\ No newline at end of file
diff --git a/tmail-backend/integration-tests/jmap/postgres-jmap-integration-tests/src/test/java/com/linagora/tmail/james/PostgresLinagoraEmailSendMethodTest.java b/tmail-backend/integration-tests/jmap/postgres-jmap-integration-tests/src/test/java/com/linagora/tmail/james/PostgresLinagoraEmailSendMethodTest.java
new file mode 100644
index 00000000000..7a7870e08fa
--- /dev/null
+++ b/tmail-backend/integration-tests/jmap/postgres-jmap-integration-tests/src/test/java/com/linagora/tmail/james/PostgresLinagoraEmailSendMethodTest.java
@@ -0,0 +1,18 @@
+package com.linagora.tmail.james;
+
+import org.apache.james.JamesServerExtension;
+import org.apache.james.mailbox.model.MessageId;
+import org.junit.jupiter.api.extension.RegisterExtension;
+
+import com.linagora.tmail.james.common.LinagoraEmailSendMethodContract;
+
+public class PostgresLinagoraEmailSendMethodTest implements LinagoraEmailSendMethodContract {
+
+ @RegisterExtension
+ static JamesServerExtension testExtension = TmailJmapBase.JAMES_SERVER_EXTENSION_SUPPLIER.get().build();
+
+ @Override
+ public MessageId randomMessageId() {
+ return TmailJmapBase.MESSAGE_ID_FACTORY.generate();
+ }
+}
\ No newline at end of file
diff --git a/tmail-backend/integration-tests/jmap/postgres-jmap-integration-tests/src/test/java/com/linagora/tmail/james/PostgresLinagoraEncryptedEmailDetailedViewGetMethodTest.java b/tmail-backend/integration-tests/jmap/postgres-jmap-integration-tests/src/test/java/com/linagora/tmail/james/PostgresLinagoraEncryptedEmailDetailedViewGetMethodTest.java
new file mode 100644
index 00000000000..9b51089e876
--- /dev/null
+++ b/tmail-backend/integration-tests/jmap/postgres-jmap-integration-tests/src/test/java/com/linagora/tmail/james/PostgresLinagoraEncryptedEmailDetailedViewGetMethodTest.java
@@ -0,0 +1,20 @@
+package com.linagora.tmail.james;
+
+import static com.linagora.tmail.james.PostgresLinagoraEncryptedEmailFastViewGetMethodTest.ENCRYPTED_JAMES_SERVER;
+
+import org.apache.james.JamesServerExtension;
+import org.apache.james.mailbox.model.MessageId;
+import org.junit.jupiter.api.extension.RegisterExtension;
+
+import com.linagora.tmail.james.common.LinagoraEncryptedEmailDetailedViewGetMethodContract;
+
+public class PostgresLinagoraEncryptedEmailDetailedViewGetMethodTest implements LinagoraEncryptedEmailDetailedViewGetMethodContract {
+
+ @RegisterExtension
+ static JamesServerExtension testExtension = ENCRYPTED_JAMES_SERVER.get();
+
+ @Override
+ public MessageId randomMessageId() {
+ return TmailJmapBase.MESSAGE_ID_FACTORY.generate();
+ }
+}
diff --git a/tmail-backend/integration-tests/jmap/postgres-jmap-integration-tests/src/test/java/com/linagora/tmail/james/PostgresLinagoraEncryptedEmailFastViewGetMethodTest.java b/tmail-backend/integration-tests/jmap/postgres-jmap-integration-tests/src/test/java/com/linagora/tmail/james/PostgresLinagoraEncryptedEmailFastViewGetMethodTest.java
new file mode 100644
index 00000000000..c0abcf7322e
--- /dev/null
+++ b/tmail-backend/integration-tests/jmap/postgres-jmap-integration-tests/src/test/java/com/linagora/tmail/james/PostgresLinagoraEncryptedEmailFastViewGetMethodTest.java
@@ -0,0 +1,63 @@
+package com.linagora.tmail.james;
+
+import static org.apache.james.PostgresJamesConfiguration.EventBusImpl.IN_MEMORY;
+
+import java.util.function.Supplier;
+
+import org.apache.james.ClockExtension;
+import org.apache.james.JamesServerBuilder;
+import org.apache.james.JamesServerExtension;
+import org.apache.james.SearchConfiguration;
+import org.apache.james.backends.postgres.PostgresExtension;
+import org.apache.james.jmap.rfc8621.contract.probe.DelegationProbeModule;
+import org.apache.james.mailbox.model.MessageId;
+import org.apache.james.utils.GuiceProbe;
+import org.junit.jupiter.api.extension.RegisterExtension;
+
+import com.google.inject.multibindings.Multibinder;
+import com.linagora.tmail.blob.guice.BlobStoreConfiguration;
+import com.linagora.tmail.combined.identity.UsersRepositoryClassProbe;
+import com.linagora.tmail.encrypted.MailboxConfiguration;
+import com.linagora.tmail.encrypted.MailboxManagerClassProbe;
+import com.linagora.tmail.james.app.PostgresTmailConfiguration;
+import com.linagora.tmail.james.app.PostgresTmailServer;
+import com.linagora.tmail.james.common.LinagoraEncryptedEmailFastViewGetMethodContract;
+import com.linagora.tmail.james.common.probe.JmapGuiceEncryptedEmailContentStoreProbe;
+import com.linagora.tmail.module.LinagoraTestJMAPServerModule;
+import com.linagora.tmail.team.TeamMailboxProbe;
+
+public class PostgresLinagoraEncryptedEmailFastViewGetMethodTest implements LinagoraEncryptedEmailFastViewGetMethodContract {
+
+ public static final Supplier ENCRYPTED_JAMES_SERVER = () -> new JamesServerBuilder(tmpDir ->
+ PostgresTmailConfiguration.builder()
+ .workingDirectory(tmpDir)
+ .configurationFromClasspath()
+ .blobStore(BlobStoreConfiguration.builder()
+ .postgres()
+ .disableCache()
+ .deduplication()
+ .noCryptoConfig()
+ .enableSingleSave())
+ .searchConfiguration(SearchConfiguration.scanning())
+ .mailbox(new MailboxConfiguration(true))
+ .eventBusImpl(IN_MEMORY)
+ .build())
+ .server(configuration -> PostgresTmailServer.createServer(configuration)
+ .overrideWith(new LinagoraTestJMAPServerModule())
+ .overrideWith(binder -> Multibinder.newSetBinder(binder, GuiceProbe.class).addBinding().to(MailboxManagerClassProbe.class))
+ .overrideWith(binder -> Multibinder.newSetBinder(binder, GuiceProbe.class).addBinding().to(UsersRepositoryClassProbe.class))
+ .overrideWith(binder -> Multibinder.newSetBinder(binder, GuiceProbe.class).addBinding().to(TeamMailboxProbe.class))
+ .overrideWith(binder -> Multibinder.newSetBinder(binder, GuiceProbe.class).addBinding().to(JmapGuiceEncryptedEmailContentStoreProbe.class))
+ .overrideWith(new DelegationProbeModule()))
+ .extension(PostgresExtension.empty())
+ .extension(new ClockExtension())
+ .build();
+
+ @RegisterExtension
+ static JamesServerExtension testExtension = ENCRYPTED_JAMES_SERVER.get();
+
+ @Override
+ public MessageId randomMessageId() {
+ return TmailJmapBase.MESSAGE_ID_FACTORY.generate();
+ }
+}
\ No newline at end of file
diff --git a/tmail-backend/integration-tests/jmap/postgres-jmap-integration-tests/src/test/java/com/linagora/tmail/james/PostgresLinagoraFilterGetMethodTest.java b/tmail-backend/integration-tests/jmap/postgres-jmap-integration-tests/src/test/java/com/linagora/tmail/james/PostgresLinagoraFilterGetMethodTest.java
new file mode 100644
index 00000000000..3df6678a4c2
--- /dev/null
+++ b/tmail-backend/integration-tests/jmap/postgres-jmap-integration-tests/src/test/java/com/linagora/tmail/james/PostgresLinagoraFilterGetMethodTest.java
@@ -0,0 +1,34 @@
+package com.linagora.tmail.james;
+
+import static org.apache.james.jmap.JMAPTestingConstants.BOB;
+
+import org.apache.james.JamesServerExtension;
+import org.apache.james.core.Username;
+import org.apache.james.mailbox.postgres.PostgresMailboxId;
+import org.junit.jupiter.api.extension.RegisterExtension;
+
+import com.linagora.tmail.james.common.LinagoraFilterGetMethodContract;
+import com.linagora.tmail.james.common.module.JmapGuiceCustomModule;
+
+public class PostgresLinagoraFilterGetMethodTest implements LinagoraFilterGetMethodContract {
+
+ @RegisterExtension
+ static JamesServerExtension testExtension = TmailJmapBase.JAMES_SERVER_EXTENSION_FUNCTION
+ .apply(new JmapGuiceCustomModule())
+ .build();
+
+ @Override
+ public String generateMailboxIdForUser() {
+ return PostgresMailboxId.of("123e4567-e89b-12d3-a456-426614174000").asUuid().toString();
+ }
+
+ @Override
+ public Username generateUsername() {
+ return BOB;
+ }
+
+ @Override
+ public String generateAccountIdAsString() {
+ return "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6";
+ }
+}
\ No newline at end of file
diff --git a/tmail-backend/integration-tests/jmap/postgres-jmap-integration-tests/src/test/java/com/linagora/tmail/james/PostgresLinagoraFilterSetMethodTest.java b/tmail-backend/integration-tests/jmap/postgres-jmap-integration-tests/src/test/java/com/linagora/tmail/james/PostgresLinagoraFilterSetMethodTest.java
new file mode 100644
index 00000000000..b7a833d9d08
--- /dev/null
+++ b/tmail-backend/integration-tests/jmap/postgres-jmap-integration-tests/src/test/java/com/linagora/tmail/james/PostgresLinagoraFilterSetMethodTest.java
@@ -0,0 +1,61 @@
+package com.linagora.tmail.james;
+
+import static org.apache.james.PostgresJamesConfiguration.EventBusImpl.IN_MEMORY;
+
+import org.apache.james.JamesServerBuilder;
+import org.apache.james.JamesServerExtension;
+import org.apache.james.SearchConfiguration;
+import org.apache.james.backends.postgres.PostgresExtension;
+import org.apache.james.jmap.rfc8621.contract.probe.DelegationProbeModule;
+import org.apache.james.mailbox.postgres.PostgresMailboxId;
+import org.junit.jupiter.api.extension.RegisterExtension;
+
+import com.linagora.tmail.blob.guice.BlobStoreConfiguration;
+import com.linagora.tmail.james.app.PostgresTmailConfiguration;
+import com.linagora.tmail.james.app.PostgresTmailServer;
+import com.linagora.tmail.james.common.LinagoraFilterSetMethodContract;
+import com.linagora.tmail.james.common.probe.JmapSettingsProbeModule;
+import com.linagora.tmail.james.jmap.firebase.FirebaseModuleChooserConfiguration;
+import com.linagora.tmail.james.jmap.firebase.FirebasePushClient;
+import com.linagora.tmail.module.LinagoraTestJMAPServerModule;
+
+public class PostgresLinagoraFilterSetMethodTest implements LinagoraFilterSetMethodContract {
+
+ @RegisterExtension
+ static JamesServerExtension testExtension = new JamesServerBuilder(tmpDir ->
+ PostgresTmailConfiguration.builder()
+ .workingDirectory(tmpDir)
+ .configurationFromClasspath()
+ .blobStore(BlobStoreConfiguration.builder()
+ .postgres()
+ .disableCache()
+ .deduplication()
+ .noCryptoConfig()
+ .disableSingleSave())
+ .searchConfiguration(SearchConfiguration.scanning())
+ .firebaseModuleChooserConfiguration(FirebaseModuleChooserConfiguration.ENABLED)
+ .eventBusImpl(IN_MEMORY)
+ .build())
+ .server(configuration -> PostgresTmailServer.createServer(configuration)
+ .overrideWith(new LinagoraTestJMAPServerModule())
+ .overrideWith(new JmapSettingsProbeModule())
+ .overrideWith(new DelegationProbeModule())
+ .overrideWith(binder -> binder.bind(FirebasePushClient.class).toInstance(LinagoraFilterSetMethodContract.firebasePushClient())))
+ .extension(PostgresExtension.empty())
+ .build();
+
+ @Override
+ public String generateMailboxIdForUser() {
+ return PostgresMailboxId.of("123e4567-e89b-12d3-a456-426614174000").asUuid().toString();
+ }
+
+ @Override
+ public String generateMailboxId2ForUser() {
+ return PostgresMailboxId.of("123e4567-e89b-12d3-a456-426614174001").asUuid().toString();
+ }
+
+ @Override
+ public String generateAccountIdAsString() {
+ return "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6";
+ }
+}
\ No newline at end of file
diff --git a/tmail-backend/integration-tests/jmap/postgres-jmap-integration-tests/src/test/java/com/linagora/tmail/james/PostgresLinagoraFilterStateChangeTest.java b/tmail-backend/integration-tests/jmap/postgres-jmap-integration-tests/src/test/java/com/linagora/tmail/james/PostgresLinagoraFilterStateChangeTest.java
new file mode 100644
index 00000000000..f343b2a9c84
--- /dev/null
+++ b/tmail-backend/integration-tests/jmap/postgres-jmap-integration-tests/src/test/java/com/linagora/tmail/james/PostgresLinagoraFilterStateChangeTest.java
@@ -0,0 +1,24 @@
+package com.linagora.tmail.james;
+
+import org.apache.james.JamesServerExtension;
+import org.apache.james.mailbox.postgres.PostgresMailboxId;
+import org.junit.jupiter.api.extension.RegisterExtension;
+
+import com.linagora.tmail.james.common.LinagoraFilterStateChangeTest;
+
+public class PostgresLinagoraFilterStateChangeTest implements LinagoraFilterStateChangeTest {
+
+ @RegisterExtension
+ static JamesServerExtension testExtension = TmailJmapBase.JAMES_SERVER_EXTENSION_SUPPLIER.get()
+ .build();
+
+ @Override
+ public String generateMailboxIdForUser() {
+ return PostgresMailboxId.of("123e4567-e89b-12d3-a456-426614174000").asUuid().toString();
+ }
+
+ @Override
+ public String generateAccountIdAsString() {
+ return "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6";
+ }
+}
\ No newline at end of file
diff --git a/tmail-backend/integration-tests/jmap/postgres-jmap-integration-tests/src/test/java/com/linagora/tmail/james/PostgresLinagoraForwardGetMethodTest.java b/tmail-backend/integration-tests/jmap/postgres-jmap-integration-tests/src/test/java/com/linagora/tmail/james/PostgresLinagoraForwardGetMethodTest.java
new file mode 100644
index 00000000000..0b33f51545e
--- /dev/null
+++ b/tmail-backend/integration-tests/jmap/postgres-jmap-integration-tests/src/test/java/com/linagora/tmail/james/PostgresLinagoraForwardGetMethodTest.java
@@ -0,0 +1,13 @@
+package com.linagora.tmail.james;
+
+import org.apache.james.JamesServerExtension;
+import org.junit.jupiter.api.extension.RegisterExtension;
+
+import com.linagora.tmail.james.common.LinagoraForwardGetMethodContract;
+
+public class PostgresLinagoraForwardGetMethodTest implements LinagoraForwardGetMethodContract {
+
+ @RegisterExtension
+ static JamesServerExtension testExtension = TmailJmapBase.JAMES_SERVER_EXTENSION_SUPPLIER.get()
+ .build();
+}
\ No newline at end of file
diff --git a/tmail-backend/integration-tests/jmap/postgres-jmap-integration-tests/src/test/java/com/linagora/tmail/james/PostgresLinagoraForwardSetMethodTest.java b/tmail-backend/integration-tests/jmap/postgres-jmap-integration-tests/src/test/java/com/linagora/tmail/james/PostgresLinagoraForwardSetMethodTest.java
new file mode 100644
index 00000000000..2953d4f46a6
--- /dev/null
+++ b/tmail-backend/integration-tests/jmap/postgres-jmap-integration-tests/src/test/java/com/linagora/tmail/james/PostgresLinagoraForwardSetMethodTest.java
@@ -0,0 +1,13 @@
+package com.linagora.tmail.james;
+
+import org.apache.james.JamesServerExtension;
+import org.junit.jupiter.api.extension.RegisterExtension;
+
+import com.linagora.tmail.james.common.LinagoraForwardSetMethodContract;
+
+public class PostgresLinagoraForwardSetMethodTest implements LinagoraForwardSetMethodContract {
+
+ @RegisterExtension
+ static JamesServerExtension testExtension = TmailJmapBase.JAMES_SERVER_EXTENSION_SUPPLIER.get()
+ .build();
+}
\ No newline at end of file
diff --git a/tmail-backend/integration-tests/jmap/postgres-jmap-integration-tests/src/test/java/com/linagora/tmail/james/PostgresLinagoraJmapSettingsGetMethodTest.java b/tmail-backend/integration-tests/jmap/postgres-jmap-integration-tests/src/test/java/com/linagora/tmail/james/PostgresLinagoraJmapSettingsGetMethodTest.java
new file mode 100644
index 00000000000..ce2138cd0c6
--- /dev/null
+++ b/tmail-backend/integration-tests/jmap/postgres-jmap-integration-tests/src/test/java/com/linagora/tmail/james/PostgresLinagoraJmapSettingsGetMethodTest.java
@@ -0,0 +1,17 @@
+package com.linagora.tmail.james;
+
+import static com.linagora.tmail.james.TmailJmapBase.JAMES_SERVER_EXTENSION_FUNCTION;
+
+import org.apache.james.JamesServerExtension;
+import org.junit.jupiter.api.extension.RegisterExtension;
+
+import com.linagora.tmail.james.common.JmapSettingsGetMethodContract;
+import com.linagora.tmail.james.common.probe.JmapSettingsProbeModule;
+
+public class PostgresLinagoraJmapSettingsGetMethodTest implements JmapSettingsGetMethodContract {
+
+ @RegisterExtension
+ static JamesServerExtension testExtension = JAMES_SERVER_EXTENSION_FUNCTION
+ .apply(new JmapSettingsProbeModule())
+ .build();
+}
\ No newline at end of file
diff --git a/tmail-backend/integration-tests/jmap/postgres-jmap-integration-tests/src/test/java/com/linagora/tmail/james/PostgresLinagoraJmapSettingsSetMethodTest.java b/tmail-backend/integration-tests/jmap/postgres-jmap-integration-tests/src/test/java/com/linagora/tmail/james/PostgresLinagoraJmapSettingsSetMethodTest.java
new file mode 100644
index 00000000000..066507b56ab
--- /dev/null
+++ b/tmail-backend/integration-tests/jmap/postgres-jmap-integration-tests/src/test/java/com/linagora/tmail/james/PostgresLinagoraJmapSettingsSetMethodTest.java
@@ -0,0 +1,49 @@
+package com.linagora.tmail.james;
+
+import static org.apache.james.PostgresJamesConfiguration.EventBusImpl.IN_MEMORY;
+
+import org.apache.james.ClockExtension;
+import org.apache.james.JamesServerBuilder;
+import org.apache.james.JamesServerExtension;
+import org.apache.james.SearchConfiguration;
+import org.apache.james.backends.postgres.PostgresExtension;
+import org.apache.james.jmap.rfc8621.contract.probe.DelegationProbeModule;
+import org.junit.jupiter.api.extension.RegisterExtension;
+
+import com.google.inject.util.Modules;
+import com.linagora.tmail.blob.guice.BlobStoreConfiguration;
+import com.linagora.tmail.james.app.PostgresTmailConfiguration;
+import com.linagora.tmail.james.app.PostgresTmailServer;
+import com.linagora.tmail.james.common.JmapSettingsSetMethodContract;
+import com.linagora.tmail.james.common.module.JmapGuiceLabelModule;
+import com.linagora.tmail.james.common.probe.JmapSettingsProbeModule;
+import com.linagora.tmail.james.jmap.firebase.FirebaseModuleChooserConfiguration;
+import com.linagora.tmail.james.jmap.firebase.FirebasePushClient;
+import com.linagora.tmail.module.LinagoraTestJMAPServerModule;
+
+public class PostgresLinagoraJmapSettingsSetMethodTest implements JmapSettingsSetMethodContract {
+
+ @RegisterExtension
+ static JamesServerExtension testExtension = new JamesServerBuilder(tmpDir ->
+ PostgresTmailConfiguration.builder()
+ .workingDirectory(tmpDir)
+ .configurationFromClasspath()
+ .blobStore(BlobStoreConfiguration.builder()
+ .postgres()
+ .disableCache()
+ .deduplication()
+ .noCryptoConfig()
+ .disableSingleSave())
+ .searchConfiguration(SearchConfiguration.scanning())
+ .firebaseModuleChooserConfiguration(FirebaseModuleChooserConfiguration.ENABLED)
+ .eventBusImpl(IN_MEMORY)
+ .build())
+ .server(configuration -> PostgresTmailServer.createServer(configuration)
+ .overrideWith(new LinagoraTestJMAPServerModule())
+ .overrideWith(new JmapSettingsProbeModule())
+ .overrideWith(new DelegationProbeModule())
+ .overrideWith(Modules.combine(new JmapGuiceLabelModule(), binder -> binder.bind(FirebasePushClient.class).toInstance(JmapSettingsSetMethodContract.firebasePushClient()))))
+ .extension(PostgresExtension.empty())
+ .extension(new ClockExtension())
+ .build();
+}
diff --git a/tmail-backend/integration-tests/jmap/postgres-jmap-integration-tests/src/test/java/com/linagora/tmail/james/PostgresLinagoraKeystoreGetMethodTest.java b/tmail-backend/integration-tests/jmap/postgres-jmap-integration-tests/src/test/java/com/linagora/tmail/james/PostgresLinagoraKeystoreGetMethodTest.java
new file mode 100644
index 00000000000..6bdfd7558c6
--- /dev/null
+++ b/tmail-backend/integration-tests/jmap/postgres-jmap-integration-tests/src/test/java/com/linagora/tmail/james/PostgresLinagoraKeystoreGetMethodTest.java
@@ -0,0 +1,17 @@
+package com.linagora.tmail.james;
+
+import static com.linagora.tmail.james.TmailJmapBase.JAMES_SERVER_EXTENSION_FUNCTION;
+
+import org.apache.james.JamesServerExtension;
+import org.junit.jupiter.api.extension.RegisterExtension;
+
+import com.linagora.tmail.james.common.LinagoraKeystoreGetMethodContract;
+import com.linagora.tmail.james.common.module.JmapGuiceKeystoreManagerModule;
+
+public class PostgresLinagoraKeystoreGetMethodTest implements LinagoraKeystoreGetMethodContract {
+
+ @RegisterExtension
+ static JamesServerExtension testExtension = JAMES_SERVER_EXTENSION_FUNCTION
+ .apply(new JmapGuiceKeystoreManagerModule())
+ .build();
+}
\ No newline at end of file
diff --git a/tmail-backend/integration-tests/jmap/postgres-jmap-integration-tests/src/test/java/com/linagora/tmail/james/PostgresLinagoraKeystoreSetMethodTest.java b/tmail-backend/integration-tests/jmap/postgres-jmap-integration-tests/src/test/java/com/linagora/tmail/james/PostgresLinagoraKeystoreSetMethodTest.java
new file mode 100644
index 00000000000..0e6196cfadd
--- /dev/null
+++ b/tmail-backend/integration-tests/jmap/postgres-jmap-integration-tests/src/test/java/com/linagora/tmail/james/PostgresLinagoraKeystoreSetMethodTest.java
@@ -0,0 +1,17 @@
+package com.linagora.tmail.james;
+
+import static com.linagora.tmail.james.TmailJmapBase.JAMES_SERVER_EXTENSION_FUNCTION;
+
+import org.apache.james.JamesServerExtension;
+import org.junit.jupiter.api.extension.RegisterExtension;
+
+import com.linagora.tmail.james.common.LinagoraKeystoreSetMethodContract;
+import com.linagora.tmail.james.common.module.JmapGuiceKeystoreManagerModule;
+
+class PostgresLinagoraKeystoreSetMethodTest implements LinagoraKeystoreSetMethodContract {
+
+ @RegisterExtension
+ static JamesServerExtension testExtension = JAMES_SERVER_EXTENSION_FUNCTION
+ .apply(new JmapGuiceKeystoreManagerModule())
+ .build();
+}
\ No newline at end of file
diff --git a/tmail-backend/integration-tests/jmap/postgres-jmap-integration-tests/src/test/java/com/linagora/tmail/james/PostgresLinagoraLabelChangesMethodTest.java b/tmail-backend/integration-tests/jmap/postgres-jmap-integration-tests/src/test/java/com/linagora/tmail/james/PostgresLinagoraLabelChangesMethodTest.java
new file mode 100644
index 00000000000..85e56a80791
--- /dev/null
+++ b/tmail-backend/integration-tests/jmap/postgres-jmap-integration-tests/src/test/java/com/linagora/tmail/james/PostgresLinagoraLabelChangesMethodTest.java
@@ -0,0 +1,24 @@
+package com.linagora.tmail.james;
+
+import static com.linagora.tmail.james.TmailJmapBase.JAMES_SERVER_EXTENSION_FUNCTION;
+
+import org.apache.james.JamesServerExtension;
+import org.apache.james.jmap.api.change.State;
+import org.apache.james.jmap.postgres.change.PostgresStateFactory;
+import org.junit.jupiter.api.extension.RegisterExtension;
+
+import com.linagora.tmail.james.common.LabelChangesMethodContract;
+import com.linagora.tmail.james.common.module.JmapGuiceLabelModule;
+
+public class PostgresLinagoraLabelChangesMethodTest implements LabelChangesMethodContract {
+
+ @RegisterExtension
+ static JamesServerExtension testExtension = JAMES_SERVER_EXTENSION_FUNCTION
+ .apply(new JmapGuiceLabelModule())
+ .build();
+
+ @Override
+ public State.Factory stateFactory() {
+ return new PostgresStateFactory();
+ }
+}
\ No newline at end of file
diff --git a/tmail-backend/integration-tests/jmap/postgres-jmap-integration-tests/src/test/java/com/linagora/tmail/james/PostgresLinagoraLabelGetMethodTest.java b/tmail-backend/integration-tests/jmap/postgres-jmap-integration-tests/src/test/java/com/linagora/tmail/james/PostgresLinagoraLabelGetMethodTest.java
new file mode 100644
index 00000000000..1aad6059ee8
--- /dev/null
+++ b/tmail-backend/integration-tests/jmap/postgres-jmap-integration-tests/src/test/java/com/linagora/tmail/james/PostgresLinagoraLabelGetMethodTest.java
@@ -0,0 +1,17 @@
+package com.linagora.tmail.james;
+
+import static com.linagora.tmail.james.TmailJmapBase.JAMES_SERVER_EXTENSION_FUNCTION;
+
+import org.apache.james.JamesServerExtension;
+import org.junit.jupiter.api.extension.RegisterExtension;
+
+import com.linagora.tmail.james.common.LabelGetMethodContract;
+import com.linagora.tmail.james.common.module.JmapGuiceLabelModule;
+
+public class PostgresLinagoraLabelGetMethodTest implements LabelGetMethodContract {
+
+ @RegisterExtension
+ static JamesServerExtension testExtension = JAMES_SERVER_EXTENSION_FUNCTION
+ .apply(new JmapGuiceLabelModule())
+ .build();
+}
\ No newline at end of file
diff --git a/tmail-backend/integration-tests/jmap/postgres-jmap-integration-tests/src/test/java/com/linagora/tmail/james/PostgresLinagoraLabelSetMethodTest.java b/tmail-backend/integration-tests/jmap/postgres-jmap-integration-tests/src/test/java/com/linagora/tmail/james/PostgresLinagoraLabelSetMethodTest.java
new file mode 100644
index 00000000000..0918c2a719d
--- /dev/null
+++ b/tmail-backend/integration-tests/jmap/postgres-jmap-integration-tests/src/test/java/com/linagora/tmail/james/PostgresLinagoraLabelSetMethodTest.java
@@ -0,0 +1,17 @@
+package com.linagora.tmail.james;
+
+import static com.linagora.tmail.james.TmailJmapBase.JAMES_SERVER_EXTENSION_FUNCTION;
+
+import org.apache.james.JamesServerExtension;
+import org.junit.jupiter.api.extension.RegisterExtension;
+
+import com.linagora.tmail.james.common.LabelSetMethodContract;
+import com.linagora.tmail.james.common.module.JmapGuiceLabelModule;
+
+public class PostgresLinagoraLabelSetMethodTest implements LabelSetMethodContract {
+
+ @RegisterExtension
+ static JamesServerExtension testExtension = JAMES_SERVER_EXTENSION_FUNCTION
+ .apply(new JmapGuiceLabelModule())
+ .build();
+}
\ No newline at end of file
diff --git a/tmail-backend/integration-tests/jmap/postgres-jmap-integration-tests/src/test/java/com/linagora/tmail/james/PostgresMailboxGetMethodTest.java b/tmail-backend/integration-tests/jmap/postgres-jmap-integration-tests/src/test/java/com/linagora/tmail/james/PostgresMailboxGetMethodTest.java
new file mode 100644
index 00000000000..2e14266d6a5
--- /dev/null
+++ b/tmail-backend/integration-tests/jmap/postgres-jmap-integration-tests/src/test/java/com/linagora/tmail/james/PostgresMailboxGetMethodTest.java
@@ -0,0 +1,21 @@
+package com.linagora.tmail.james;
+
+import static com.linagora.tmail.james.TmailJmapBase.JAMES_SERVER_EXTENSION_SUPPLIER;
+
+import org.apache.james.JamesServerExtension;
+import org.apache.james.jmap.rfc8621.contract.MailboxGetMethodContract;
+import org.apache.james.mailbox.model.MailboxId;
+import org.apache.james.mailbox.postgres.PostgresMailboxId;
+import org.junit.jupiter.api.extension.RegisterExtension;
+
+import com.datastax.oss.driver.api.core.uuid.Uuids;
+
+public class PostgresMailboxGetMethodTest implements MailboxGetMethodContract {
+ @RegisterExtension
+ static JamesServerExtension testExtension = JAMES_SERVER_EXTENSION_SUPPLIER.get().build();
+
+ @Override
+ public MailboxId randomMailboxId() {
+ return PostgresMailboxId.of(Uuids.timeBased());
+ }
+}
\ No newline at end of file
diff --git a/tmail-backend/integration-tests/jmap/postgres-jmap-integration-tests/src/test/java/com/linagora/tmail/james/PostgresMailboxQueryMethodTest.java b/tmail-backend/integration-tests/jmap/postgres-jmap-integration-tests/src/test/java/com/linagora/tmail/james/PostgresMailboxQueryMethodTest.java
new file mode 100644
index 00000000000..66d9df7f17a
--- /dev/null
+++ b/tmail-backend/integration-tests/jmap/postgres-jmap-integration-tests/src/test/java/com/linagora/tmail/james/PostgresMailboxQueryMethodTest.java
@@ -0,0 +1,14 @@
+package com.linagora.tmail.james;
+
+import static com.linagora.tmail.james.TmailJmapBase.JAMES_SERVER_EXTENSION_SUPPLIER;
+
+import org.apache.james.JamesServerExtension;
+import org.apache.james.jmap.rfc8621.contract.MailboxQueryMethodContract;
+import org.junit.jupiter.api.extension.RegisterExtension;
+
+public class PostgresMailboxQueryMethodTest implements MailboxQueryMethodContract {
+
+ @RegisterExtension
+ static JamesServerExtension testExtension = JAMES_SERVER_EXTENSION_SUPPLIER.get().build();
+
+}
\ No newline at end of file
diff --git a/tmail-backend/integration-tests/jmap/postgres-jmap-integration-tests/src/test/java/com/linagora/tmail/james/PostgresMailboxSetMethodTest.java b/tmail-backend/integration-tests/jmap/postgres-jmap-integration-tests/src/test/java/com/linagora/tmail/james/PostgresMailboxSetMethodTest.java
new file mode 100644
index 00000000000..25237cdb3e8
--- /dev/null
+++ b/tmail-backend/integration-tests/jmap/postgres-jmap-integration-tests/src/test/java/com/linagora/tmail/james/PostgresMailboxSetMethodTest.java
@@ -0,0 +1,42 @@
+package com.linagora.tmail.james;
+
+import static com.linagora.tmail.james.TmailJmapBase.JAMES_SERVER_EXTENSION_SUPPLIER;
+
+import org.apache.james.GuiceJamesServer;
+import org.apache.james.JamesServerExtension;
+import org.apache.james.jmap.rfc8621.contract.MailboxSetMethodContract;
+import org.apache.james.mailbox.model.MailboxId;
+import org.apache.james.mailbox.postgres.PostgresMailboxId;
+import org.junit.jupiter.api.Disabled;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.RegisterExtension;
+
+import com.datastax.oss.driver.api.core.uuid.Uuids;
+
+public class PostgresMailboxSetMethodTest implements MailboxSetMethodContract {
+
+ @RegisterExtension
+ static JamesServerExtension testExtension = JAMES_SERVER_EXTENSION_SUPPLIER.get().build();
+
+ @Override
+ public MailboxId randomMailboxId() {
+ return PostgresMailboxId.of(Uuids.timeBased());
+ }
+
+ @Override
+ public String errorInvalidMailboxIdMessage(String value) {
+ return String.format("%s is not a mailboxId: Invalid UUID string: %s", value, value);
+ }
+
+ @Override
+ @Test
+ @Disabled("Distributed event bus is asynchronous, we cannot expect the newState to be returned immediately after Mailbox/set call")
+ public void newStateShouldBeUpToDate(GuiceJamesServer server) {
+ }
+
+ @Override
+ @Test
+ @Disabled("Distributed event bus is asynchronous, we cannot expect the newState to be returned immediately after Mailbox/set call")
+ public void oldStateShouldIncludeSetChanges(GuiceJamesServer server) {
+ }
+}
diff --git a/tmail-backend/integration-tests/jmap/postgres-jmap-integration-tests/src/test/java/com/linagora/tmail/james/PostgresTeamMailboxRevokeAccessTest.java b/tmail-backend/integration-tests/jmap/postgres-jmap-integration-tests/src/test/java/com/linagora/tmail/james/PostgresTeamMailboxRevokeAccessTest.java
new file mode 100644
index 00000000000..82a8a1862aa
--- /dev/null
+++ b/tmail-backend/integration-tests/jmap/postgres-jmap-integration-tests/src/test/java/com/linagora/tmail/james/PostgresTeamMailboxRevokeAccessTest.java
@@ -0,0 +1,28 @@
+package com.linagora.tmail.james;
+
+import static com.linagora.tmail.james.TmailJmapBase.JAMES_SERVER_EXTENSION_FUNCTION;
+
+import org.apache.james.JamesServerExtension;
+import org.apache.james.utils.GuiceProbe;
+import org.apache.james.webadmin.RandomPortSupplier;
+import org.apache.james.webadmin.WebAdminConfiguration;
+import org.junit.jupiter.api.extension.RegisterExtension;
+
+import com.google.inject.multibindings.Multibinder;
+import com.google.inject.util.Modules;
+import com.linagora.tmail.james.common.TeamMailboxRevokeAccessMethodContract;
+import com.linagora.tmail.team.TeamMailboxProbe;
+
+public class PostgresTeamMailboxRevokeAccessTest implements TeamMailboxRevokeAccessMethodContract {
+
+ @RegisterExtension
+ static JamesServerExtension testExtension = JAMES_SERVER_EXTENSION_FUNCTION
+ .apply(Modules.combine(binder -> binder.bind(WebAdminConfiguration.class).toInstance(WebAdminConfiguration.builder()
+ .port(new RandomPortSupplier())
+ .enabled()
+ .host("127.0.0.1")
+ .build()),
+ binder -> Multibinder.newSetBinder(binder, GuiceProbe.class)
+ .addBinding().to(TeamMailboxProbe.class)))
+ .build();
+}
\ No newline at end of file
diff --git a/tmail-backend/integration-tests/jmap/postgres-jmap-integration-tests/src/test/java/com/linagora/tmail/james/PostgresTeamMailboxesQuotaExtensionsTest.java b/tmail-backend/integration-tests/jmap/postgres-jmap-integration-tests/src/test/java/com/linagora/tmail/james/PostgresTeamMailboxesQuotaExtensionsTest.java
new file mode 100644
index 00000000000..32fc0bf4e72
--- /dev/null
+++ b/tmail-backend/integration-tests/jmap/postgres-jmap-integration-tests/src/test/java/com/linagora/tmail/james/PostgresTeamMailboxesQuotaExtensionsTest.java
@@ -0,0 +1,78 @@
+package com.linagora.tmail.james;
+
+import static org.apache.james.PostgresJamesConfiguration.EventBusImpl.RABBITMQ;
+
+import org.apache.james.ClockExtension;
+import org.apache.james.GuiceJamesServer;
+import org.apache.james.JamesServerBuilder;
+import org.apache.james.JamesServerExtension;
+import org.apache.james.SearchConfiguration;
+import org.apache.james.backends.postgres.PostgresExtension;
+import org.apache.james.jmap.rfc8621.contract.probe.DelegationProbeModule;
+import org.apache.james.utils.GuiceProbe;
+import org.apache.james.webadmin.RandomPortSupplier;
+import org.apache.james.webadmin.WebAdminConfiguration;
+import org.junit.jupiter.api.Disabled;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.RegisterExtension;
+
+import com.google.inject.multibindings.Multibinder;
+import com.google.inject.util.Modules;
+import com.linagora.tmail.blob.guice.BlobStoreConfiguration;
+import com.linagora.tmail.combined.identity.UsersRepositoryClassProbe;
+import com.linagora.tmail.encrypted.MailboxManagerClassProbe;
+import com.linagora.tmail.james.app.DockerOpenSearchExtension;
+import com.linagora.tmail.james.app.PostgresTmailConfiguration;
+import com.linagora.tmail.james.app.PostgresTmailServer;
+import com.linagora.tmail.james.app.RabbitMQExtension;
+import com.linagora.tmail.james.common.TeamMailboxesQuotaExtensionsContract;
+import com.linagora.tmail.james.common.probe.JmapGuiceContactAutocompleteProbe;
+import com.linagora.tmail.james.common.probe.JmapSettingsProbe;
+import com.linagora.tmail.module.LinagoraTestJMAPServerModule;
+import com.linagora.tmail.team.TeamMailboxProbe;
+
+public class PostgresTeamMailboxesQuotaExtensionsTest extends TeamMailboxesQuotaExtensionsContract {
+
+ @RegisterExtension
+ static JamesServerExtension testExtension = new JamesServerBuilder(tmpDir ->
+ PostgresTmailConfiguration.builder()
+ .workingDirectory(tmpDir)
+ .configurationFromClasspath()
+ .blobStore(BlobStoreConfiguration.builder()
+ .postgres()
+ .disableCache()
+ .deduplication()
+ .noCryptoConfig()
+ .disableSingleSave())
+ .searchConfiguration(SearchConfiguration.openSearch())
+ .eventBusImpl(RABBITMQ)
+ .build())
+ .server(configuration -> PostgresTmailServer.createServer(configuration)
+ .overrideWith(new LinagoraTestJMAPServerModule())
+ .overrideWith(binder -> Multibinder.newSetBinder(binder, GuiceProbe.class).addBinding().to(MailboxManagerClassProbe.class))
+ .overrideWith(binder -> Multibinder.newSetBinder(binder, GuiceProbe.class).addBinding().to(UsersRepositoryClassProbe.class))
+ .overrideWith(binder -> Multibinder.newSetBinder(binder, GuiceProbe.class).addBinding().to(TeamMailboxProbe.class))
+ .overrideWith(binder -> Multibinder.newSetBinder(binder, GuiceProbe.class).addBinding().to(JmapGuiceContactAutocompleteProbe.class))
+ .overrideWith(binder -> Multibinder.newSetBinder(binder, GuiceProbe.class).addBinding().to(JmapSettingsProbe.class))
+ .overrideWith(binder -> Multibinder.newSetBinder(binder, GuiceProbe.class).addBinding().to(JmapSettingsProbe.class))
+ .overrideWith(new DelegationProbeModule())
+ .overrideWith(Modules.combine(binder -> binder.bind(WebAdminConfiguration.class).toInstance(WebAdminConfiguration.builder()
+ .port(new RandomPortSupplier())
+ .enabled()
+ .host("127.0.0.1")
+ .build()),
+ binder -> Multibinder.newSetBinder(binder, GuiceProbe.class)
+ .addBinding().to(TeamMailboxProbe.class))))
+ .extension(PostgresExtension.empty())
+ .extension(new RabbitMQExtension())
+ .extension(new DockerOpenSearchExtension())
+ .extension(new ClockExtension())
+ .build();
+
+
+ @Test
+ @Override
+ @Disabled("Unstable test. The current quota update procedure does not guarantee concurrency when the previous record is empty.")
+ public void teamMailboxesShouldSendOverQuotaEmails(GuiceJamesServer server) {
+ }
+}
diff --git a/tmail-backend/integration-tests/jmap/postgres-jmap-integration-tests/src/test/java/com/linagora/tmail/james/PostgresTeamMailboxesTest.java b/tmail-backend/integration-tests/jmap/postgres-jmap-integration-tests/src/test/java/com/linagora/tmail/james/PostgresTeamMailboxesTest.java
new file mode 100644
index 00000000000..be5c28641b4
--- /dev/null
+++ b/tmail-backend/integration-tests/jmap/postgres-jmap-integration-tests/src/test/java/com/linagora/tmail/james/PostgresTeamMailboxesTest.java
@@ -0,0 +1,71 @@
+package com.linagora.tmail.james;
+
+import static org.apache.james.PostgresJamesConfiguration.EventBusImpl.RABBITMQ;
+
+import org.apache.james.ClockExtension;
+import org.apache.james.GuiceJamesServer;
+import org.apache.james.JamesServerBuilder;
+import org.apache.james.JamesServerExtension;
+import org.apache.james.SearchConfiguration;
+import org.apache.james.backends.postgres.PostgresExtension;
+import org.apache.james.jmap.rfc8621.contract.probe.DelegationProbeModule;
+import org.apache.james.utils.GuiceProbe;
+import org.apache.james.webadmin.RandomPortSupplier;
+import org.apache.james.webadmin.WebAdminConfiguration;
+import org.junit.jupiter.api.Disabled;
+import org.junit.jupiter.api.extension.RegisterExtension;
+
+import com.google.inject.multibindings.Multibinder;
+import com.google.inject.util.Modules;
+import com.linagora.tmail.blob.guice.BlobStoreConfiguration;
+import com.linagora.tmail.combined.identity.UsersRepositoryClassProbe;
+import com.linagora.tmail.encrypted.MailboxManagerClassProbe;
+import com.linagora.tmail.james.app.DockerOpenSearchExtension;
+import com.linagora.tmail.james.app.PostgresTmailConfiguration;
+import com.linagora.tmail.james.app.PostgresTmailServer;
+import com.linagora.tmail.james.app.RabbitMQExtension;
+import com.linagora.tmail.james.common.TeamMailboxesContract;
+import com.linagora.tmail.james.jmap.firebase.FirebaseModuleChooserConfiguration;
+import com.linagora.tmail.module.LinagoraTestJMAPServerModule;
+import com.linagora.tmail.team.TeamMailboxProbe;
+
+public class PostgresTeamMailboxesTest implements TeamMailboxesContract {
+
+ @RegisterExtension
+ static JamesServerExtension testExtension = new JamesServerBuilder(tmpDir ->
+ PostgresTmailConfiguration.builder()
+ .workingDirectory(tmpDir)
+ .configurationFromClasspath()
+ .blobStore(BlobStoreConfiguration.builder()
+ .postgres()
+ .disableCache()
+ .deduplication()
+ .noCryptoConfig()
+ .disableSingleSave())
+ .searchConfiguration(SearchConfiguration.openSearch())
+ .eventBusImpl(RABBITMQ)
+ .firebaseModuleChooserConfiguration(FirebaseModuleChooserConfiguration.DISABLED)
+ .build())
+ .server(configuration -> PostgresTmailServer.createServer(configuration)
+ .overrideWith(new LinagoraTestJMAPServerModule())
+ .overrideWith(binder -> Multibinder.newSetBinder(binder, GuiceProbe.class).addBinding().to(MailboxManagerClassProbe.class))
+ .overrideWith(binder -> Multibinder.newSetBinder(binder, GuiceProbe.class).addBinding().to(UsersRepositoryClassProbe.class))
+ .overrideWith(binder -> Multibinder.newSetBinder(binder, GuiceProbe.class).addBinding().to(TeamMailboxProbe.class))
+ .overrideWith(new DelegationProbeModule())
+ .overrideWith(Modules.combine(binder -> binder.bind(WebAdminConfiguration.class).toInstance(WebAdminConfiguration.builder()
+ .port(new RandomPortSupplier())
+ .enabled()
+ .host("127.0.0.1")
+ .build()),
+ binder -> Multibinder.newSetBinder(binder, GuiceProbe.class).addBinding().to(TeamMailboxProbe.class))))
+ .extension(PostgresExtension.empty())
+ .extension(new RabbitMQExtension())
+ .extension(new DockerOpenSearchExtension())
+ .extension(new ClockExtension())
+ .build();
+
+ @Override
+ @Disabled("unstable")
+ public void webSocketShouldPushTeamMailboxStateChanges(GuiceJamesServer server) {
+ }
+}
\ No newline at end of file
diff --git a/tmail-backend/integration-tests/jmap/postgres-jmap-integration-tests/src/test/java/com/linagora/tmail/james/PostgresTicketRoutesTest.java b/tmail-backend/integration-tests/jmap/postgres-jmap-integration-tests/src/test/java/com/linagora/tmail/james/PostgresTicketRoutesTest.java
new file mode 100644
index 00000000000..42d11a4503f
--- /dev/null
+++ b/tmail-backend/integration-tests/jmap/postgres-jmap-integration-tests/src/test/java/com/linagora/tmail/james/PostgresTicketRoutesTest.java
@@ -0,0 +1,18 @@
+package com.linagora.tmail.james;
+
+import static com.linagora.tmail.james.TmailJmapBase.JAMES_SERVER_EXTENSION_FUNCTION;
+
+import org.apache.james.JamesServerExtension;
+import org.apache.james.jmap.core.JmapRfc8621Configuration;
+import org.junit.jupiter.api.extension.RegisterExtension;
+
+import com.linagora.tmail.james.common.LinagoraTicketAuthenticationContract;
+
+public class PostgresTicketRoutesTest implements LinagoraTicketAuthenticationContract {
+
+ @RegisterExtension
+ static JamesServerExtension testExtension = JAMES_SERVER_EXTENSION_FUNCTION
+ .apply(binder -> binder.bind(JmapRfc8621Configuration.class)
+ .toInstance(LinagoraTicketAuthenticationContract.jmapConfiguration()))
+ .build();
+}
\ No newline at end of file
diff --git a/tmail-backend/integration-tests/jmap/postgres-jmap-integration-tests/src/test/java/com/linagora/tmail/james/TmailJmapBase.java b/tmail-backend/integration-tests/jmap/postgres-jmap-integration-tests/src/test/java/com/linagora/tmail/james/TmailJmapBase.java
new file mode 100644
index 00000000000..fbb56480d39
--- /dev/null
+++ b/tmail-backend/integration-tests/jmap/postgres-jmap-integration-tests/src/test/java/com/linagora/tmail/james/TmailJmapBase.java
@@ -0,0 +1,93 @@
+/****************************************************************
+ * 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 com.linagora.tmail.james;
+
+import static org.apache.james.PostgresJamesConfiguration.EventBusImpl.IN_MEMORY;
+
+import java.util.function.Function;
+import java.util.function.Supplier;
+
+import org.apache.james.ClockExtension;
+import org.apache.james.JamesServerBuilder;
+import org.apache.james.SearchConfiguration;
+import org.apache.james.backends.postgres.PostgresExtension;
+import org.apache.james.jmap.rfc8621.contract.probe.DelegationProbeModule;
+import org.apache.james.mailbox.postgres.PostgresMessageId;
+import org.apache.james.utils.GuiceProbe;
+import org.junit.jupiter.api.extension.RegisterExtension;
+
+import com.github.f4b6a3.uuid.UuidCreator;
+import com.google.inject.Module;
+import com.google.inject.multibindings.Multibinder;
+import com.google.inject.util.Modules;
+import com.linagora.tmail.blob.guice.BlobStoreConfiguration;
+import com.linagora.tmail.combined.identity.UsersRepositoryClassProbe;
+import com.linagora.tmail.encrypted.MailboxConfiguration;
+import com.linagora.tmail.encrypted.MailboxManagerClassProbe;
+import com.linagora.tmail.james.app.PostgresTmailConfiguration;
+import com.linagora.tmail.james.app.PostgresTmailServer;
+import com.linagora.tmail.james.common.LabelChangesMethodContract;
+import com.linagora.tmail.james.common.probe.JmapGuiceContactAutocompleteProbe;
+import com.linagora.tmail.james.common.probe.JmapSettingsProbe;
+import com.linagora.tmail.james.jmap.firebase.FirebaseModuleChooserConfiguration;
+import com.linagora.tmail.james.jmap.firebase.FirebasePushClient;
+import com.linagora.tmail.module.LinagoraTestJMAPServerModule;
+import com.linagora.tmail.team.TeamMailboxProbe;
+
+public class TmailJmapBase {
+ @RegisterExtension
+ static PostgresExtension postgresExtension = PostgresExtension.empty();
+
+ public static Function> JAMES_SERVER_EXTENSION_FUNCTION = overrideWithModule -> new JamesServerBuilder(tmpDir ->
+ PostgresTmailConfiguration.builder()
+ .workingDirectory(tmpDir)
+ .configurationFromClasspath()
+ .blobStore(BlobStoreConfiguration.builder()
+ .postgres()
+ .disableCache()
+ .deduplication()
+ .noCryptoConfig()
+ .disableSingleSave())
+ .searchConfiguration(SearchConfiguration.scanning())
+ .mailbox(new MailboxConfiguration(false))
+ .firebaseModuleChooserConfiguration(FirebaseModuleChooserConfiguration.ENABLED)
+ .eventBusImpl(IN_MEMORY)
+ .build())
+ .server(configuration -> PostgresTmailServer.createServer(configuration)
+ .overrideWith(new LinagoraTestJMAPServerModule())
+ .overrideWith(binder -> Multibinder.newSetBinder(binder, GuiceProbe.class).addBinding().to(MailboxManagerClassProbe.class))
+ .overrideWith(binder -> Multibinder.newSetBinder(binder, GuiceProbe.class).addBinding().to(UsersRepositoryClassProbe.class))
+ .overrideWith(binder -> Multibinder.newSetBinder(binder, GuiceProbe.class).addBinding().to(TeamMailboxProbe.class))
+ .overrideWith(binder -> Multibinder.newSetBinder(binder, GuiceProbe.class).addBinding().to(JmapGuiceContactAutocompleteProbe.class))
+ .overrideWith(binder -> Multibinder.newSetBinder(binder, GuiceProbe.class).addBinding().to(JmapSettingsProbe.class))
+ .overrideWith(binder -> binder.bind(FirebasePushClient.class).toInstance(LabelChangesMethodContract.firebasePushClient()))
+ .overrideWith(new DelegationProbeModule())
+ .overrideWith(overrideWithModule))
+ .extension(postgresExtension)
+ .extension(new ClockExtension());
+
+ public static Supplier> JAMES_SERVER_EXTENSION_SUPPLIER = () -> JAMES_SERVER_EXTENSION_FUNCTION.apply(Modules.EMPTY_MODULE);
+
+ public static PostgresMessageId.Factory MESSAGE_ID_FACTORY = new PostgresMessageId.Factory();
+
+ public static String randomBlobId() {
+ return UuidCreator.getTimeOrderedEpoch().toString();
+ }
+}
diff --git a/tmail-backend/integration-tests/jmap/postgres-jmap-integration-tests/src/test/resources/dnsservice.xml b/tmail-backend/integration-tests/jmap/postgres-jmap-integration-tests/src/test/resources/dnsservice.xml
new file mode 100644
index 00000000000..6e4fbd2efb5
--- /dev/null
+++ b/tmail-backend/integration-tests/jmap/postgres-jmap-integration-tests/src/test/resources/dnsservice.xml
@@ -0,0 +1,25 @@
+
+
+
+
+ true
+ false
+ 50000
+
diff --git a/tmail-backend/integration-tests/jmap/postgres-jmap-integration-tests/src/test/resources/domainlist.xml b/tmail-backend/integration-tests/jmap/postgres-jmap-integration-tests/src/test/resources/domainlist.xml
new file mode 100644
index 00000000000..fe17431a1ea
--- /dev/null
+++ b/tmail-backend/integration-tests/jmap/postgres-jmap-integration-tests/src/test/resources/domainlist.xml
@@ -0,0 +1,24 @@
+
+
+
+
+ false
+ false
+
diff --git a/tmail-backend/integration-tests/jmap/postgres-jmap-integration-tests/src/test/resources/eml/calendar_reply_accepted-en.eml b/tmail-backend/integration-tests/jmap/postgres-jmap-integration-tests/src/test/resources/eml/calendar_reply_accepted-en.eml
new file mode 100644
index 00000000000..d3e89e8556f
--- /dev/null
+++ b/tmail-backend/integration-tests/jmap/postgres-jmap-integration-tests/src/test/resources/eml/calendar_reply_accepted-en.eml
@@ -0,0 +1,5 @@
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Subject: ACCEPTED: {{EVENT_TITLE}} @ {{EVENT_START_DATE}} ({{ATTENDEE}})
+
+{{ATTENDEE}} has accepted this invitation.
diff --git a/tmail-backend/integration-tests/jmap/postgres-jmap-integration-tests/src/test/resources/eml/calendar_reply_accepted-fr.eml b/tmail-backend/integration-tests/jmap/postgres-jmap-integration-tests/src/test/resources/eml/calendar_reply_accepted-fr.eml
new file mode 100644
index 00000000000..476719d6cc9
--- /dev/null
+++ b/tmail-backend/integration-tests/jmap/postgres-jmap-integration-tests/src/test/resources/eml/calendar_reply_accepted-fr.eml
@@ -0,0 +1,5 @@
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Subject: =?ISO-8859-1?Q?ACCEPT=C9?=: {{EVENT_TITLE}} @ {{EVENT_START_DATE}} ({{ATTENDEE}})
+
+{{ATTENDEE}} a accepté cette invitation.
diff --git a/tmail-backend/integration-tests/jmap/postgres-jmap-integration-tests/src/test/resources/eml/calendar_reply_declined-en.eml b/tmail-backend/integration-tests/jmap/postgres-jmap-integration-tests/src/test/resources/eml/calendar_reply_declined-en.eml
new file mode 100644
index 00000000000..52bb30af4ec
--- /dev/null
+++ b/tmail-backend/integration-tests/jmap/postgres-jmap-integration-tests/src/test/resources/eml/calendar_reply_declined-en.eml
@@ -0,0 +1,5 @@
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Subject: Declined: {{EVENT_TITLE}} @ {{EVENT_START_DATE}} ({{ATTENDEE}})
+
+{{ATTENDEE}} has declined this invitation.
diff --git a/tmail-backend/integration-tests/jmap/postgres-jmap-integration-tests/src/test/resources/eml/calendar_reply_declined-fr.eml b/tmail-backend/integration-tests/jmap/postgres-jmap-integration-tests/src/test/resources/eml/calendar_reply_declined-fr.eml
new file mode 100644
index 00000000000..b733953f820
--- /dev/null
+++ b/tmail-backend/integration-tests/jmap/postgres-jmap-integration-tests/src/test/resources/eml/calendar_reply_declined-fr.eml
@@ -0,0 +1,5 @@
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Subject: =?ISO-8859-1?Q?D=E9clin=E9?=: {{EVENT_TITLE}} @ {{EVENT_START_DATE}} ({{ATTENDEE}})
+
+{{ATTENDEE}} a décliné cette invitation.
diff --git a/tmail-backend/integration-tests/jmap/postgres-jmap-integration-tests/src/test/resources/eml/calendar_reply_tentative-en.eml b/tmail-backend/integration-tests/jmap/postgres-jmap-integration-tests/src/test/resources/eml/calendar_reply_tentative-en.eml
new file mode 100644
index 00000000000..954a905d652
--- /dev/null
+++ b/tmail-backend/integration-tests/jmap/postgres-jmap-integration-tests/src/test/resources/eml/calendar_reply_tentative-en.eml
@@ -0,0 +1,5 @@
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Subject: Tentatively Accepted: {{EVENT_TITLE}} @ {{EVENT_START_DATE}} ({{ATTENDEE}})
+
+{{ATTENDEE}} has replied Maybe to this invitation.
diff --git a/tmail-backend/integration-tests/jmap/postgres-jmap-integration-tests/src/test/resources/eml/calendar_reply_tentative-fr.eml b/tmail-backend/integration-tests/jmap/postgres-jmap-integration-tests/src/test/resources/eml/calendar_reply_tentative-fr.eml
new file mode 100644
index 00000000000..df613e1a1b3
--- /dev/null
+++ b/tmail-backend/integration-tests/jmap/postgres-jmap-integration-tests/src/test/resources/eml/calendar_reply_tentative-fr.eml
@@ -0,0 +1,5 @@
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Subject: =?ISO-8859-1?Q?Accept=E9_provisoirement?=: {{EVENT_TITLE}} @ {{EVENT_START_DATE}} ({{ATTENDEE}})
+
+{{ATTENDEE}} a répondu Peut-être à cette invitation.
diff --git a/tmail-backend/integration-tests/jmap/postgres-jmap-integration-tests/src/test/resources/firebase.properties b/tmail-backend/integration-tests/jmap/postgres-jmap-integration-tests/src/test/resources/firebase.properties
new file mode 100644
index 00000000000..57d27706b30
--- /dev/null
+++ b/tmail-backend/integration-tests/jmap/postgres-jmap-integration-tests/src/test/resources/firebase.properties
@@ -0,0 +1,10 @@
+privatekey.url=/root/conf/dummy.json
+
+api.key=key123
+app.id=firebase123
+messaging.sender.id=sender123
+project.id=project123
+database.url=http://database.com
+storage.bucket=bucket123
+auth.domain=domain123
+vapid.public.key=vapidkey123
diff --git a/tmail-backend/integration-tests/jmap/postgres-jmap-integration-tests/src/test/resources/imapserver.xml b/tmail-backend/integration-tests/jmap/postgres-jmap-integration-tests/src/test/resources/imapserver.xml
new file mode 100644
index 00000000000..ead2b342f34
--- /dev/null
+++ b/tmail-backend/integration-tests/jmap/postgres-jmap-integration-tests/src/test/resources/imapserver.xml
@@ -0,0 +1,24 @@
+
+
+
+
+
+
+
diff --git a/tmail-backend/integration-tests/jmap/postgres-jmap-integration-tests/src/test/resources/jmap.properties b/tmail-backend/integration-tests/jmap/postgres-jmap-integration-tests/src/test/resources/jmap.properties
new file mode 100644
index 00000000000..4f8b45ff3f9
--- /dev/null
+++ b/tmail-backend/integration-tests/jmap/postgres-jmap-integration-tests/src/test/resources/jmap.properties
@@ -0,0 +1,4 @@
+enabled=true
+emailRecoveryAction.maxEmailRecoveryPerRequest=6
+calendarEvent.reply.mailTemplateLocation=classpath://eml/
+calendarEvent.reply.supportedLanguages=en,fr
\ No newline at end of file
diff --git a/tmail-backend/integration-tests/jmap/postgres-jmap-integration-tests/src/test/resources/keystore b/tmail-backend/integration-tests/jmap/postgres-jmap-integration-tests/src/test/resources/keystore
new file mode 100644
index 00000000000..536a6c792b0
Binary files /dev/null and b/tmail-backend/integration-tests/jmap/postgres-jmap-integration-tests/src/test/resources/keystore differ
diff --git a/tmail-backend/integration-tests/jmap/postgres-jmap-integration-tests/src/test/resources/listeners.xml b/tmail-backend/integration-tests/jmap/postgres-jmap-integration-tests/src/test/resources/listeners.xml
new file mode 100644
index 00000000000..947e743528d
--- /dev/null
+++ b/tmail-backend/integration-tests/jmap/postgres-jmap-integration-tests/src/test/resources/listeners.xml
@@ -0,0 +1,18 @@
+
+
+ org.apache.james.jmap.event.PopulateEmailQueryViewListener
+ true
+
+
+ org.apache.james.mailbox.quota.mailing.listeners.QuotaThresholdCrossingListener
+ QuotaThresholdCrossingListener
+
+
+
+ 0.9
+
+
+ first
+
+
+
\ No newline at end of file
diff --git a/tmail-backend/integration-tests/jmap/postgres-jmap-integration-tests/src/test/resources/mailetcontainer.xml b/tmail-backend/integration-tests/jmap/postgres-jmap-integration-tests/src/test/resources/mailetcontainer.xml
new file mode 100644
index 00000000000..86e3ecf564e
--- /dev/null
+++ b/tmail-backend/integration-tests/jmap/postgres-jmap-integration-tests/src/test/resources/mailetcontainer.xml
@@ -0,0 +1,132 @@
+
+
+
+
+
+
+
+ postmaster
+
+
+
+ 2
+ postgres://var/mail/error/
+
+
+
+
+
+
+
+ transport
+
+
+
+
+
+
+ postgres://var/mail/error/
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ rrt-error
+
+
+ local-delivery
+
+
+ local-address-error
+ 550 - Requested action not taken: no such user here
+
+
+ outgoing
+ 5000, 100000, 500000
+ 3
+ 0
+ 10
+ true
+ bounces
+
+
+ relay-denied
+
+
+
+
+
+
+
+
+
+ ContactAttribute1
+
+
+
+
+
+
+ none
+
+
+ postgres://var/mail/address-error/
+
+
+
+
+
+ none
+
+
+ postgres://var/mail/relay-denied/
+ Warning: You are sending an e-mail to a remote server. You must be authentified to perform such an operation
+
+
+
+
+
+ false
+
+
+
+
+
+ postgres://var/mail/rrt-error/
+ true
+
+
+
+
+
+
+
+
diff --git a/tmail-backend/integration-tests/jmap/postgres-jmap-integration-tests/src/test/resources/mailrepositorystore.xml b/tmail-backend/integration-tests/jmap/postgres-jmap-integration-tests/src/test/resources/mailrepositorystore.xml
new file mode 100644
index 00000000000..573ec24ad3e
--- /dev/null
+++ b/tmail-backend/integration-tests/jmap/postgres-jmap-integration-tests/src/test/resources/mailrepositorystore.xml
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+
+
+ postgres
+
+
+
+
diff --git a/tmail-backend/integration-tests/jmap/postgres-jmap-integration-tests/src/test/resources/managesieveserver.xml b/tmail-backend/integration-tests/jmap/postgres-jmap-integration-tests/src/test/resources/managesieveserver.xml
new file mode 100644
index 00000000000..f136a432b8a
--- /dev/null
+++ b/tmail-backend/integration-tests/jmap/postgres-jmap-integration-tests/src/test/resources/managesieveserver.xml
@@ -0,0 +1,32 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/tmail-backend/integration-tests/jmap/postgres-jmap-integration-tests/src/test/resources/pop3server.xml b/tmail-backend/integration-tests/jmap/postgres-jmap-integration-tests/src/test/resources/pop3server.xml
new file mode 100644
index 00000000000..bec385ae306
--- /dev/null
+++ b/tmail-backend/integration-tests/jmap/postgres-jmap-integration-tests/src/test/resources/pop3server.xml
@@ -0,0 +1,23 @@
+
+
+
+
+
+
diff --git a/tmail-backend/integration-tests/jmap/postgres-jmap-integration-tests/src/test/resources/rabbitmq.properties b/tmail-backend/integration-tests/jmap/postgres-jmap-integration-tests/src/test/resources/rabbitmq.properties
new file mode 100644
index 00000000000..25d0dd6a976
--- /dev/null
+++ b/tmail-backend/integration-tests/jmap/postgres-jmap-integration-tests/src/test/resources/rabbitmq.properties
@@ -0,0 +1,2 @@
+uri=amqp://james:james@rabbitmq_host:5672
+management.uri=http://james:james@rabbitmq_host:15672/api/
\ No newline at end of file
diff --git a/tmail-backend/integration-tests/jmap/postgres-jmap-integration-tests/src/test/resources/smtpserver.xml b/tmail-backend/integration-tests/jmap/postgres-jmap-integration-tests/src/test/resources/smtpserver.xml
new file mode 100644
index 00000000000..21dc0a9af9c
--- /dev/null
+++ b/tmail-backend/integration-tests/jmap/postgres-jmap-integration-tests/src/test/resources/smtpserver.xml
@@ -0,0 +1,53 @@
+
+
+
+
+
+
+ smtpserver-global
+ 0.0.0.0:0
+ 200
+
+ file://conf/keystore
+ james72laBalle
+ org.bouncycastle.jce.provider.BouncyCastleProvider
+ SunX509
+
+ 360
+ 0
+ 0
+ false
+
+ never
+ false
+ true
+
+ 0
+ true
+ Apache JAMES awesome SMTP Server
+
+
+
+
+ false
+
+
+
+
diff --git a/tmail-backend/integration-tests/jmap/postgres-jmap-integration-tests/src/test/resources/usersrepository.xml b/tmail-backend/integration-tests/jmap/postgres-jmap-integration-tests/src/test/resources/usersrepository.xml
new file mode 100644
index 00000000000..f8c8a258722
--- /dev/null
+++ b/tmail-backend/integration-tests/jmap/postgres-jmap-integration-tests/src/test/resources/usersrepository.xml
@@ -0,0 +1,25 @@
+
+
+
+
+
+ true
+ SHA-1
+
diff --git a/tmail-backend/integration-tests/jmap/postgres-jmap-integration-tests/src/test/resources/webadmin.properties b/tmail-backend/integration-tests/jmap/postgres-jmap-integration-tests/src/test/resources/webadmin.properties
new file mode 100644
index 00000000000..cafc6f0fb0c
--- /dev/null
+++ b/tmail-backend/integration-tests/jmap/postgres-jmap-integration-tests/src/test/resources/webadmin.properties
@@ -0,0 +1,26 @@
+# 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.
+
+# This template file can be used as example for James Server configuration
+# DO NOT USE IT AS SUCH AND ADAPT IT TO YOUR NEEDS
+
+# Read https://james.apache.org/server/config-webadmin.html for further details
+
+enabled=true
+port=0
+host=127.0.0.1
+
diff --git a/tmail-backend/integration-tests/pom.xml b/tmail-backend/integration-tests/pom.xml
index 9343a80d00f..dd443d6fb52 100644
--- a/tmail-backend/integration-tests/pom.xml
+++ b/tmail-backend/integration-tests/pom.xml
@@ -19,21 +19,28 @@
jmap/distributed-jmap-integration-tests
jmap/jmap-integration-tests-common
jmap/memory-jmap-integration-tests
+ jmap/postgres-jmap-integration-tests
smtp/distributed-smtp-integration-tests
smtp/memory-smtp-integration-tests
+ smtp/postgres-smtp-integration-tests
smtp/smtp-integration-tests-common
webadmin/webadmin-integration-tests-common
webadmin/distributed-webadmin-integration-tests
+ webadmin/postgres-webadmin-integration-tests
rate-limiter/rate-limiter-integration-tests-common
rate-limiter/distributed-rate-limiter-integration-tests
+ rate-limiter/postgres-rate-limiter-integration-tests
rspamd/rspamd-integration-tests-common
rspamd/distributed-rspamd-integration-tests
+ rspamd/postgres-rspamd-integration-tests
+
healthcheck/healthcheck-integration-tests-common
healthcheck/distributed-healthcheck-integration-tests
+ healthcheck/postgres-healthcheck-integration-tests
diff --git a/tmail-backend/integration-tests/rate-limiter/postgres-rate-limiter-integration-tests/pom.xml b/tmail-backend/integration-tests/rate-limiter/postgres-rate-limiter-integration-tests/pom.xml
new file mode 100644
index 00000000000..7dca3ea2bfb
--- /dev/null
+++ b/tmail-backend/integration-tests/rate-limiter/postgres-rate-limiter-integration-tests/pom.xml
@@ -0,0 +1,72 @@
+
+
+
+ com.linagora.tmail
+ integration-tests
+ 1.0.0-SNAPSHOT
+ ../../pom.xml
+
+ 4.0.0
+ postgres-rate-limiter-integration-tests
+ Team-mail :: Integration Tests :: Rate Limiter :: Postgres
+
+
+
+ ${project.groupId}
+ blobid-list
+ test
+
+
+ ${project.groupId}
+ postgres
+ test
+
+
+ ${project.groupId}
+ postgres
+ test-jar
+ test
+
+
+ ${project.groupId}
+ tmail-guice-distributed
+ test-jar
+ test
+
+
+ ${project.groupId}
+ tmail-guice-jmap
+ test
+ test-jar
+
+
+ ${project.groupId}
+ rate-limiter-integration-tests-common
+ test
+
+
+ ${james.groupId}
+ apache-james-backends-postgres
+ test-jar
+ test
+
+
+ ${james.groupId}
+ apache-james-backends-rabbitmq
+ test
+ test-jar
+
+
+ ${james.groupId}
+ apache-james-backends-redis
+ test
+ test-jar
+
+
+ org.testcontainers
+ postgresql
+ test
+
+
+
diff --git a/tmail-backend/integration-tests/rate-limiter/postgres-rate-limiter-integration-tests/src/test/java/com/linagora/tmail/integration/postgres/PostgresRateLimitingPlanIntegrationTest.java b/tmail-backend/integration-tests/rate-limiter/postgres-rate-limiter-integration-tests/src/test/java/com/linagora/tmail/integration/postgres/PostgresRateLimitingPlanIntegrationTest.java
new file mode 100644
index 00000000000..c90d527750f
--- /dev/null
+++ b/tmail-backend/integration-tests/rate-limiter/postgres-rate-limiter-integration-tests/src/test/java/com/linagora/tmail/integration/postgres/PostgresRateLimitingPlanIntegrationTest.java
@@ -0,0 +1,49 @@
+package com.linagora.tmail.integration.postgres;
+
+import org.apache.james.JamesServerBuilder;
+import org.apache.james.JamesServerExtension;
+import org.apache.james.PostgresJamesConfiguration;
+import org.apache.james.SearchConfiguration;
+import org.apache.james.backends.postgres.PostgresExtension;
+import org.apache.james.backends.redis.RedisExtension;
+import org.apache.james.mailrepository.api.MailRepositoryUrl;
+import org.apache.james.rate.limiter.redis.RedisRateLimiterModule;
+import org.junit.jupiter.api.extension.RegisterExtension;
+
+import com.linagora.tmail.blob.guice.BlobStoreConfiguration;
+import com.linagora.tmail.integration.RateLimitingPlanIntegrationContract;
+import com.linagora.tmail.james.app.PostgresTmailConfiguration;
+import com.linagora.tmail.james.app.PostgresTmailServer;
+import com.linagora.tmail.james.app.RabbitMQExtension;
+import com.linagora.tmail.module.LinagoraTestJMAPServerModule;
+
+public class PostgresRateLimitingPlanIntegrationTest implements RateLimitingPlanIntegrationContract {
+ private static final MailRepositoryUrl ERROR_REPOSITORY = MailRepositoryUrl.from("postgres://var/mail/error/");
+
+ @Override
+ public MailRepositoryUrl getErrorRepository() {
+ return ERROR_REPOSITORY;
+ }
+
+ @RegisterExtension
+ static JamesServerExtension testExtension = new JamesServerBuilder(tmpDir ->
+ PostgresTmailConfiguration.builder()
+ .workingDirectory(tmpDir)
+ .configurationFromClasspath()
+ .searchConfiguration(SearchConfiguration.scanning())
+ .eventBusImpl(PostgresJamesConfiguration.EventBusImpl.RABBITMQ)
+ .blobStore(BlobStoreConfiguration.builder()
+ .postgres()
+ .disableCache()
+ .deduplication()
+ .noCryptoConfig()
+ .disableSingleSave())
+ .build())
+ .extension(PostgresExtension.empty())
+ .extension(new RabbitMQExtension())
+ .extension(new RedisExtension())
+ .server(configuration -> PostgresTmailServer.createServer(configuration)
+ .overrideWith(new LinagoraTestJMAPServerModule())
+ .overrideWith(new RedisRateLimiterModule()))
+ .build();
+}
diff --git a/tmail-backend/integration-tests/rate-limiter/postgres-rate-limiter-integration-tests/src/test/resources/dnsservice.xml b/tmail-backend/integration-tests/rate-limiter/postgres-rate-limiter-integration-tests/src/test/resources/dnsservice.xml
new file mode 100644
index 00000000000..6e4fbd2efb5
--- /dev/null
+++ b/tmail-backend/integration-tests/rate-limiter/postgres-rate-limiter-integration-tests/src/test/resources/dnsservice.xml
@@ -0,0 +1,25 @@
+
+
+
+
+ true
+ false
+ 50000
+
diff --git a/tmail-backend/integration-tests/rate-limiter/postgres-rate-limiter-integration-tests/src/test/resources/domainlist.xml b/tmail-backend/integration-tests/rate-limiter/postgres-rate-limiter-integration-tests/src/test/resources/domainlist.xml
new file mode 100644
index 00000000000..fe17431a1ea
--- /dev/null
+++ b/tmail-backend/integration-tests/rate-limiter/postgres-rate-limiter-integration-tests/src/test/resources/domainlist.xml
@@ -0,0 +1,24 @@
+
+
+
+
+ false
+ false
+
diff --git a/tmail-backend/integration-tests/rate-limiter/postgres-rate-limiter-integration-tests/src/test/resources/fakemailrepositorystore.xml b/tmail-backend/integration-tests/rate-limiter/postgres-rate-limiter-integration-tests/src/test/resources/fakemailrepositorystore.xml
new file mode 100644
index 00000000000..2d19a802da9
--- /dev/null
+++ b/tmail-backend/integration-tests/rate-limiter/postgres-rate-limiter-integration-tests/src/test/resources/fakemailrepositorystore.xml
@@ -0,0 +1,31 @@
+
+
+
+
+
+
+
+
+ file
+
+
+
+
+
diff --git a/tmail-backend/integration-tests/rate-limiter/postgres-rate-limiter-integration-tests/src/test/resources/imapserver.xml b/tmail-backend/integration-tests/rate-limiter/postgres-rate-limiter-integration-tests/src/test/resources/imapserver.xml
new file mode 100644
index 00000000000..3434dbce390
--- /dev/null
+++ b/tmail-backend/integration-tests/rate-limiter/postgres-rate-limiter-integration-tests/src/test/resources/imapserver.xml
@@ -0,0 +1,57 @@
+
+
+
+
+
+
+
+ imapserver
+ 0.0.0.0:0
+ 200
+
+
+ classpath://keystore
+ james72laBalle
+ org.bouncycastle.jce.provider.BouncyCastleProvider
+
+ 0
+ 0
+ false
+ false
+
+
+ imapserver-ssl
+ 0.0.0.0:0
+ 200
+
+
+ classpath://keystore
+ james72laBalle
+ org.bouncycastle.jce.provider.BouncyCastleProvider
+
+ 0
+ 0
+ false
+
+
diff --git a/tmail-backend/integration-tests/rate-limiter/postgres-rate-limiter-integration-tests/src/test/resources/keystore b/tmail-backend/integration-tests/rate-limiter/postgres-rate-limiter-integration-tests/src/test/resources/keystore
new file mode 100644
index 00000000000..536a6c792b0
Binary files /dev/null and b/tmail-backend/integration-tests/rate-limiter/postgres-rate-limiter-integration-tests/src/test/resources/keystore differ
diff --git a/tmail-backend/integration-tests/rate-limiter/postgres-rate-limiter-integration-tests/src/test/resources/listeners.xml b/tmail-backend/integration-tests/rate-limiter/postgres-rate-limiter-integration-tests/src/test/resources/listeners.xml
new file mode 100644
index 00000000000..74e79d927f2
--- /dev/null
+++ b/tmail-backend/integration-tests/rate-limiter/postgres-rate-limiter-integration-tests/src/test/resources/listeners.xml
@@ -0,0 +1,6 @@
+
+
+ org.apache.james.jmap.event.PopulateEmailQueryViewListener
+ true
+
+
\ No newline at end of file
diff --git a/tmail-backend/integration-tests/rate-limiter/postgres-rate-limiter-integration-tests/src/test/resources/mailetcontainer.xml b/tmail-backend/integration-tests/rate-limiter/postgres-rate-limiter-integration-tests/src/test/resources/mailetcontainer.xml
new file mode 100644
index 00000000000..e06ea9b4d8a
--- /dev/null
+++ b/tmail-backend/integration-tests/rate-limiter/postgres-rate-limiter-integration-tests/src/test/resources/mailetcontainer.xml
@@ -0,0 +1,76 @@
+
+
+
+ postmaster@localhost
+
+
+ 5
+
+
+
+
+ transport
+
+
+
+
+ TransitLimitations
+
+
+
+
+ rrt-error
+
+
+
+
+
+
+ local-delivery
+
+
+ outgoing
+ 5000, 100000, 500000
+ 3
+ 0
+ 10
+ true
+ bounces
+
+
+ error
+
+
+
+
+ DeliveryLimitations
+
+
+
+
+
+
+ false
+
+
+
+
+
+ postgres://var/mail/error
+
+
+
+
+
+ postgres://var/mail/rrt-error/
+
+
+
+
+
+
\ No newline at end of file
diff --git a/tmail-backend/integration-tests/rate-limiter/postgres-rate-limiter-integration-tests/src/test/resources/mailrepositorystore.xml b/tmail-backend/integration-tests/rate-limiter/postgres-rate-limiter-integration-tests/src/test/resources/mailrepositorystore.xml
new file mode 100644
index 00000000000..573ec24ad3e
--- /dev/null
+++ b/tmail-backend/integration-tests/rate-limiter/postgres-rate-limiter-integration-tests/src/test/resources/mailrepositorystore.xml
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+
+
+ postgres
+
+
+
+
diff --git a/tmail-backend/integration-tests/rate-limiter/postgres-rate-limiter-integration-tests/src/test/resources/managesieveserver.xml b/tmail-backend/integration-tests/rate-limiter/postgres-rate-limiter-integration-tests/src/test/resources/managesieveserver.xml
new file mode 100644
index 00000000000..f136a432b8a
--- /dev/null
+++ b/tmail-backend/integration-tests/rate-limiter/postgres-rate-limiter-integration-tests/src/test/resources/managesieveserver.xml
@@ -0,0 +1,32 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/tmail-backend/integration-tests/rate-limiter/postgres-rate-limiter-integration-tests/src/test/resources/pop3server.xml b/tmail-backend/integration-tests/rate-limiter/postgres-rate-limiter-integration-tests/src/test/resources/pop3server.xml
new file mode 100644
index 00000000000..2a62f6f789c
--- /dev/null
+++ b/tmail-backend/integration-tests/rate-limiter/postgres-rate-limiter-integration-tests/src/test/resources/pop3server.xml
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
diff --git a/tmail-backend/integration-tests/rate-limiter/postgres-rate-limiter-integration-tests/src/test/resources/rabbitmq.properties b/tmail-backend/integration-tests/rate-limiter/postgres-rate-limiter-integration-tests/src/test/resources/rabbitmq.properties
new file mode 100644
index 00000000000..25d0dd6a976
--- /dev/null
+++ b/tmail-backend/integration-tests/rate-limiter/postgres-rate-limiter-integration-tests/src/test/resources/rabbitmq.properties
@@ -0,0 +1,2 @@
+uri=amqp://james:james@rabbitmq_host:5672
+management.uri=http://james:james@rabbitmq_host:15672/api/
\ No newline at end of file
diff --git a/tmail-backend/integration-tests/rate-limiter/postgres-rate-limiter-integration-tests/src/test/resources/smtpserver.xml b/tmail-backend/integration-tests/rate-limiter/postgres-rate-limiter-integration-tests/src/test/resources/smtpserver.xml
new file mode 100644
index 00000000000..07178db1002
--- /dev/null
+++ b/tmail-backend/integration-tests/rate-limiter/postgres-rate-limiter-integration-tests/src/test/resources/smtpserver.xml
@@ -0,0 +1,51 @@
+
+
+
+
+
+
+ smtpserver-global
+ 0.0.0.0:0
+ 200
+
+ file://conf/keystore
+ james72laBalle
+ org.bouncycastle.jce.provider.BouncyCastleProvider
+ SunX509
+
+ 360
+ 0
+ 0
+ false
+
+ never
+ false
+ true
+
+ 0
+ true
+ Apache JAMES awesome SMTP Server
+
+
+
+
+ false
+
+
\ No newline at end of file
diff --git a/tmail-backend/integration-tests/rate-limiter/postgres-rate-limiter-integration-tests/src/test/resources/usersrepository.xml b/tmail-backend/integration-tests/rate-limiter/postgres-rate-limiter-integration-tests/src/test/resources/usersrepository.xml
new file mode 100644
index 00000000000..f8c8a258722
--- /dev/null
+++ b/tmail-backend/integration-tests/rate-limiter/postgres-rate-limiter-integration-tests/src/test/resources/usersrepository.xml
@@ -0,0 +1,25 @@
+
+
+
+
+
+ true
+ SHA-1
+
diff --git a/tmail-backend/integration-tests/rate-limiter/postgres-rate-limiter-integration-tests/src/test/resources/webadmin.properties b/tmail-backend/integration-tests/rate-limiter/postgres-rate-limiter-integration-tests/src/test/resources/webadmin.properties
new file mode 100644
index 00000000000..3386a14238a
--- /dev/null
+++ b/tmail-backend/integration-tests/rate-limiter/postgres-rate-limiter-integration-tests/src/test/resources/webadmin.properties
@@ -0,0 +1,25 @@
+# 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.
+
+# This template file can be used as example for James Server configuration
+# DO NOT USE IT AS SUCH AND ADAPT IT TO YOUR NEEDS
+
+# Read https://james.apache.org/server/config-webadmin.html for further details
+
+enabled=true
+port=0
+host=127.0.0.1
\ No newline at end of file
diff --git a/tmail-backend/integration-tests/rspamd/postgres-rspamd-integration-tests/pom.xml b/tmail-backend/integration-tests/rspamd/postgres-rspamd-integration-tests/pom.xml
new file mode 100644
index 00000000000..1733088017d
--- /dev/null
+++ b/tmail-backend/integration-tests/rspamd/postgres-rspamd-integration-tests/pom.xml
@@ -0,0 +1,78 @@
+
+
+
+ com.linagora.tmail
+ integration-tests
+ 1.0.0-SNAPSHOT
+ ../../pom.xml
+
+ 4.0.0
+ postgres-rspamd-integration-tests
+ Team-mail :: Integration Tests :: Rspamd :: Postgres
+
+
+
+ ${project.groupId}
+ blobid-list
+ test
+
+
+ ${project.groupId}
+ postgres
+ test
+
+
+ ${project.groupId}
+ postgres
+ test-jar
+ test
+
+
+ ${project.groupId}
+ tmail-guice-distributed
+ test-jar
+ test
+
+
+ ${project.groupId}
+ tmail-guice-jmap
+ test
+ test-jar
+
+
+ ${project.groupId}
+ rspamd-integration-tests-common
+ test
+
+
+ ${james.groupId}
+ apache-james-backends-postgres
+ test-jar
+ test
+
+
+ ${james.groupId}
+ apache-james-backends-rabbitmq
+ test
+ test-jar
+
+
+ ${james.groupId}
+ apache-james-backends-redis
+ test
+ test-jar
+
+
+ ${james.groupId}
+ apache-james-rspamd
+ test-jar
+ test
+
+
+ org.testcontainers
+ postgresql
+ test
+
+
+
diff --git a/tmail-backend/integration-tests/rspamd/postgres-rspamd-integration-tests/src/test/java/com/linagora/tmail/integration/postgres/PostgresRspamdScannerIntegrationTest.java b/tmail-backend/integration-tests/rspamd/postgres-rspamd-integration-tests/src/test/java/com/linagora/tmail/integration/postgres/PostgresRspamdScannerIntegrationTest.java
new file mode 100644
index 00000000000..78b198aa785
--- /dev/null
+++ b/tmail-backend/integration-tests/rspamd/postgres-rspamd-integration-tests/src/test/java/com/linagora/tmail/integration/postgres/PostgresRspamdScannerIntegrationTest.java
@@ -0,0 +1,39 @@
+package com.linagora.tmail.integration.postgres;
+
+import org.apache.james.JamesServerBuilder;
+import org.apache.james.JamesServerExtension;
+import org.apache.james.PostgresJamesConfiguration;
+import org.apache.james.SearchConfiguration;
+import org.apache.james.backends.postgres.PostgresExtension;
+import org.junit.jupiter.api.extension.RegisterExtension;
+
+import com.linagora.tmail.blob.guice.BlobStoreConfiguration;
+import com.linagora.tmail.integration.RspamdScannerIntegrationContract;
+import com.linagora.tmail.james.app.PostgresTmailConfiguration;
+import com.linagora.tmail.james.app.PostgresTmailServer;
+import com.linagora.tmail.james.app.RabbitMQExtension;
+import com.linagora.tmail.module.LinagoraTestJMAPServerModule;
+import com.linagora.tmail.rspamd.RspamdExtensionModule;
+
+public class PostgresRspamdScannerIntegrationTest extends RspamdScannerIntegrationContract {
+ @RegisterExtension
+ static JamesServerExtension testExtension = new JamesServerBuilder(tmpDir ->
+ PostgresTmailConfiguration.builder()
+ .workingDirectory(tmpDir)
+ .configurationFromClasspath()
+ .searchConfiguration(SearchConfiguration.scanning())
+ .eventBusImpl(PostgresJamesConfiguration.EventBusImpl.RABBITMQ)
+ .blobStore(BlobStoreConfiguration.builder()
+ .postgres()
+ .disableCache()
+ .deduplication()
+ .noCryptoConfig()
+ .disableSingleSave())
+ .build())
+ .extension(PostgresExtension.empty())
+ .extension(new RabbitMQExtension())
+ .extension(new RspamdExtensionModule())
+ .server(configuration -> PostgresTmailServer.createServer(configuration)
+ .overrideWith(new LinagoraTestJMAPServerModule()))
+ .build();
+}
diff --git a/tmail-backend/integration-tests/rspamd/postgres-rspamd-integration-tests/src/test/resources/dnsservice.xml b/tmail-backend/integration-tests/rspamd/postgres-rspamd-integration-tests/src/test/resources/dnsservice.xml
new file mode 100644
index 00000000000..6e4fbd2efb5
--- /dev/null
+++ b/tmail-backend/integration-tests/rspamd/postgres-rspamd-integration-tests/src/test/resources/dnsservice.xml
@@ -0,0 +1,25 @@
+
+
+
+
+ true
+ false
+ 50000
+
diff --git a/tmail-backend/integration-tests/rspamd/postgres-rspamd-integration-tests/src/test/resources/domainlist.xml b/tmail-backend/integration-tests/rspamd/postgres-rspamd-integration-tests/src/test/resources/domainlist.xml
new file mode 100644
index 00000000000..fe17431a1ea
--- /dev/null
+++ b/tmail-backend/integration-tests/rspamd/postgres-rspamd-integration-tests/src/test/resources/domainlist.xml
@@ -0,0 +1,24 @@
+
+
+
+
+ false
+ false
+
diff --git a/tmail-backend/integration-tests/rspamd/postgres-rspamd-integration-tests/src/test/resources/imapserver.xml b/tmail-backend/integration-tests/rspamd/postgres-rspamd-integration-tests/src/test/resources/imapserver.xml
new file mode 100644
index 00000000000..bec47e988c0
--- /dev/null
+++ b/tmail-backend/integration-tests/rspamd/postgres-rspamd-integration-tests/src/test/resources/imapserver.xml
@@ -0,0 +1,57 @@
+
+
+
+
+
+
+
+ imapserver
+ 0.0.0.0:0
+ 200
+
+
+ classpath://keystore
+ james72laBalle
+ org.bouncycastle.jce.provider.BouncyCastleProvider
+
+ 0
+ 0
+ false
+ false
+
+
+ imapserver-ssl
+ 0.0.0.0:0
+ 200
+
+
+ classpath://keystore
+ james72laBalle
+ org.bouncycastle.jce.provider.BouncyCastleProvider
+
+ 0
+ 0
+ false
+
+
\ No newline at end of file
diff --git a/tmail-backend/integration-tests/rspamd/postgres-rspamd-integration-tests/src/test/resources/jmap.properties b/tmail-backend/integration-tests/rspamd/postgres-rspamd-integration-tests/src/test/resources/jmap.properties
new file mode 100644
index 00000000000..4f8b45ff3f9
--- /dev/null
+++ b/tmail-backend/integration-tests/rspamd/postgres-rspamd-integration-tests/src/test/resources/jmap.properties
@@ -0,0 +1,4 @@
+enabled=true
+emailRecoveryAction.maxEmailRecoveryPerRequest=6
+calendarEvent.reply.mailTemplateLocation=classpath://eml/
+calendarEvent.reply.supportedLanguages=en,fr
\ No newline at end of file
diff --git a/tmail-backend/integration-tests/rspamd/postgres-rspamd-integration-tests/src/test/resources/keystore b/tmail-backend/integration-tests/rspamd/postgres-rspamd-integration-tests/src/test/resources/keystore
new file mode 100644
index 00000000000..536a6c792b0
Binary files /dev/null and b/tmail-backend/integration-tests/rspamd/postgres-rspamd-integration-tests/src/test/resources/keystore differ
diff --git a/tmail-backend/integration-tests/rspamd/postgres-rspamd-integration-tests/src/test/resources/listeners.xml b/tmail-backend/integration-tests/rspamd/postgres-rspamd-integration-tests/src/test/resources/listeners.xml
new file mode 100644
index 00000000000..69f85827b1c
--- /dev/null
+++ b/tmail-backend/integration-tests/rspamd/postgres-rspamd-integration-tests/src/test/resources/listeners.xml
@@ -0,0 +1,10 @@
+
+
+ org.apache.james.jmap.event.PopulateEmailQueryViewListener
+ true
+
+
+ org.apache.james.rspamd.RspamdListener
+ true
+
+
\ No newline at end of file
diff --git a/tmail-backend/integration-tests/rspamd/postgres-rspamd-integration-tests/src/test/resources/mailetcontainer.xml b/tmail-backend/integration-tests/rspamd/postgres-rspamd-integration-tests/src/test/resources/mailetcontainer.xml
new file mode 100644
index 00000000000..43699fbc837
--- /dev/null
+++ b/tmail-backend/integration-tests/rspamd/postgres-rspamd-integration-tests/src/test/resources/mailetcontainer.xml
@@ -0,0 +1,83 @@
+
+
+
+ postmaster@localhost
+
+
+ 5
+
+
+
+
+ transport
+
+
+
+
+
+
+ rrt-error
+
+
+
+
+
+
+ local-delivery
+
+
+ outgoing
+ 5000, 100000, 500000
+ 3
+ 0
+ 10
+ true
+ bounces
+
+
+ error
+
+
+
+
+ true
+ virus
+
+
+ Spam
+
+
+
+
+
+
+ false
+
+
+
+
+
+ postgres://var/mail/error
+
+
+
+
+
+ postgres://var/mail/virus/
+
+
+
+
+
+ postgres://var/mail/rrt-error/
+
+
+
+
+
+
\ No newline at end of file
diff --git a/tmail-backend/integration-tests/rspamd/postgres-rspamd-integration-tests/src/test/resources/mailrepositorystore.xml b/tmail-backend/integration-tests/rspamd/postgres-rspamd-integration-tests/src/test/resources/mailrepositorystore.xml
new file mode 100644
index 00000000000..573ec24ad3e
--- /dev/null
+++ b/tmail-backend/integration-tests/rspamd/postgres-rspamd-integration-tests/src/test/resources/mailrepositorystore.xml
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+
+
+ postgres
+
+
+
+
diff --git a/tmail-backend/integration-tests/rspamd/postgres-rspamd-integration-tests/src/test/resources/managesieveserver.xml b/tmail-backend/integration-tests/rspamd/postgres-rspamd-integration-tests/src/test/resources/managesieveserver.xml
new file mode 100644
index 00000000000..f136a432b8a
--- /dev/null
+++ b/tmail-backend/integration-tests/rspamd/postgres-rspamd-integration-tests/src/test/resources/managesieveserver.xml
@@ -0,0 +1,32 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/tmail-backend/integration-tests/rspamd/postgres-rspamd-integration-tests/src/test/resources/pop3server.xml b/tmail-backend/integration-tests/rspamd/postgres-rspamd-integration-tests/src/test/resources/pop3server.xml
new file mode 100644
index 00000000000..2a62f6f789c
--- /dev/null
+++ b/tmail-backend/integration-tests/rspamd/postgres-rspamd-integration-tests/src/test/resources/pop3server.xml
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
diff --git a/tmail-backend/integration-tests/rspamd/postgres-rspamd-integration-tests/src/test/resources/rabbitmq.properties b/tmail-backend/integration-tests/rspamd/postgres-rspamd-integration-tests/src/test/resources/rabbitmq.properties
new file mode 100644
index 00000000000..25d0dd6a976
--- /dev/null
+++ b/tmail-backend/integration-tests/rspamd/postgres-rspamd-integration-tests/src/test/resources/rabbitmq.properties
@@ -0,0 +1,2 @@
+uri=amqp://james:james@rabbitmq_host:5672
+management.uri=http://james:james@rabbitmq_host:15672/api/
\ No newline at end of file
diff --git a/tmail-backend/integration-tests/rspamd/postgres-rspamd-integration-tests/src/test/resources/rspamd-config/actions.conf b/tmail-backend/integration-tests/rspamd/postgres-rspamd-integration-tests/src/test/resources/rspamd-config/actions.conf
new file mode 100644
index 00000000000..d14e7c6a1a4
--- /dev/null
+++ b/tmail-backend/integration-tests/rspamd/postgres-rspamd-integration-tests/src/test/resources/rspamd-config/actions.conf
@@ -0,0 +1,3 @@
+reject = 10; # Reject when reaching this score
+add_header = 6; # Add header when reaching this score
+greylist = 4; # Apply greylisting when reaching this score (will emit `soft reject action`)
\ No newline at end of file
diff --git a/tmail-backend/integration-tests/rspamd/postgres-rspamd-integration-tests/src/test/resources/smtpserver.xml b/tmail-backend/integration-tests/rspamd/postgres-rspamd-integration-tests/src/test/resources/smtpserver.xml
new file mode 100644
index 00000000000..5bc25bcff7b
--- /dev/null
+++ b/tmail-backend/integration-tests/rspamd/postgres-rspamd-integration-tests/src/test/resources/smtpserver.xml
@@ -0,0 +1,54 @@
+
+
+
+
+
+
+ smtpserver-global
+ 0.0.0.0:0
+ 200
+
+ file://conf/keystore
+ james72laBalle
+ org.bouncycastle.jce.provider.BouncyCastleProvider
+ SunX509
+
+ 360
+ 0
+ 0
+ false
+ false
+
+ never
+ false
+ true
+
+ 0
+ true
+ Apache JAMES awesome SMTP Server
+
+
+
+
+ false
+
+
+
+
diff --git a/tmail-backend/integration-tests/rspamd/postgres-rspamd-integration-tests/src/test/resources/usersrepository.xml b/tmail-backend/integration-tests/rspamd/postgres-rspamd-integration-tests/src/test/resources/usersrepository.xml
new file mode 100644
index 00000000000..f8c8a258722
--- /dev/null
+++ b/tmail-backend/integration-tests/rspamd/postgres-rspamd-integration-tests/src/test/resources/usersrepository.xml
@@ -0,0 +1,25 @@
+
+
+
+
+
+ true
+ SHA-1
+
diff --git a/tmail-backend/integration-tests/rspamd/postgres-rspamd-integration-tests/src/test/resources/webadmin.properties b/tmail-backend/integration-tests/rspamd/postgres-rspamd-integration-tests/src/test/resources/webadmin.properties
new file mode 100644
index 00000000000..cafc6f0fb0c
--- /dev/null
+++ b/tmail-backend/integration-tests/rspamd/postgres-rspamd-integration-tests/src/test/resources/webadmin.properties
@@ -0,0 +1,26 @@
+# 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.
+
+# This template file can be used as example for James Server configuration
+# DO NOT USE IT AS SUCH AND ADAPT IT TO YOUR NEEDS
+
+# Read https://james.apache.org/server/config-webadmin.html for further details
+
+enabled=true
+port=0
+host=127.0.0.1
+
diff --git a/tmail-backend/integration-tests/smtp/postgres-smtp-integration-tests/pom.xml b/tmail-backend/integration-tests/smtp/postgres-smtp-integration-tests/pom.xml
new file mode 100644
index 00000000000..4032d7d6fe5
--- /dev/null
+++ b/tmail-backend/integration-tests/smtp/postgres-smtp-integration-tests/pom.xml
@@ -0,0 +1,84 @@
+
+
+
+ integration-tests
+ com.linagora.tmail
+ 1.0.0-SNAPSHOT
+ ../../pom.xml
+
+ 4.0.0
+
+ postgres-smtp-integration-tests
+ Team-mail :: Integration Tests :: SMTP :: Postgres
+
+
+
+ ${project.groupId}
+ blobid-list
+ test
+
+
+ ${project.groupId}
+ postgres
+ test
+
+
+ ${project.groupId}
+ postgres
+ test-jar
+ test
+
+
+ ${project.groupId}
+ smtp-integration-tests-common
+
+
+ ${project.groupId}
+ tmail-guice-distributed
+ test-jar
+ test
+
+
+ ${project.groupId}
+ tmail-guice-jmap
+ test
+ test-jar
+
+
+ ${james.groupId}
+ apache-james-backends-postgres
+ test-jar
+ test
+
+
+ ${james.groupId}
+ apache-james-backends-rabbitmq
+ test
+ test-jar
+
+
+ ${james.groupId}
+ apache-james-backends-redis
+ test
+ test-jar
+
+
+ ${james.groupId}
+ james-server-guice-common
+ test
+ test-jar
+
+
+ ${james.groupId}
+ james-server-guice-jmap
+ test-jar
+
+
+ org.testcontainers
+ postgresql
+ test
+
+
+
diff --git a/tmail-backend/integration-tests/smtp/postgres-smtp-integration-tests/src/test/java/com/linagora/tmail/james/PostgresTeamMailboxSmtpTest.java b/tmail-backend/integration-tests/smtp/postgres-smtp-integration-tests/src/test/java/com/linagora/tmail/james/PostgresTeamMailboxSmtpTest.java
new file mode 100644
index 00000000000..5c453f2faab
--- /dev/null
+++ b/tmail-backend/integration-tests/smtp/postgres-smtp-integration-tests/src/test/java/com/linagora/tmail/james/PostgresTeamMailboxSmtpTest.java
@@ -0,0 +1,63 @@
+/****************************************************************
+ * 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 com.linagora.tmail.james;
+
+import org.apache.james.JamesServerBuilder;
+import org.apache.james.JamesServerExtension;
+import org.apache.james.PostgresJamesConfiguration;
+import org.apache.james.SearchConfiguration;
+import org.apache.james.backends.postgres.PostgresExtension;
+import org.apache.james.backends.redis.RedisExtension;
+import org.apache.james.utils.GuiceProbe;
+import org.junit.jupiter.api.extension.RegisterExtension;
+
+import com.google.inject.multibindings.Multibinder;
+import com.linagora.tmail.blob.guice.BlobStoreConfiguration;
+import com.linagora.tmail.james.app.PostgresTmailConfiguration;
+import com.linagora.tmail.james.app.PostgresTmailServer;
+import com.linagora.tmail.james.app.RabbitMQExtension;
+import com.linagora.tmail.james.common.TeamMailboxSmtpContract;
+import com.linagora.tmail.module.LinagoraTestJMAPServerModule;
+import com.linagora.tmail.team.TeamMailboxProbe;
+
+public class PostgresTeamMailboxSmtpTest extends TeamMailboxSmtpContract {
+ @RegisterExtension
+ static JamesServerExtension testExtension = new JamesServerBuilder(tmpDir ->
+ PostgresTmailConfiguration.builder()
+ .workingDirectory(tmpDir)
+ .configurationFromClasspath()
+ .searchConfiguration(SearchConfiguration.scanning())
+ .eventBusImpl(PostgresJamesConfiguration.EventBusImpl.RABBITMQ)
+ .blobStore(BlobStoreConfiguration.builder()
+ .postgres()
+ .disableCache()
+ .deduplication()
+ .noCryptoConfig()
+ .disableSingleSave())
+ .build())
+ .extension(PostgresExtension.empty())
+ .extension(new RabbitMQExtension())
+ .extension(new RedisExtension())
+ .server(configuration -> PostgresTmailServer.createServer(configuration)
+ .overrideWith(new LinagoraTestJMAPServerModule())
+ .overrideWith(binder -> Multibinder.newSetBinder(binder, GuiceProbe.class)
+ .addBinding().to(TeamMailboxProbe.class)))
+ .build();
+}
diff --git a/tmail-backend/integration-tests/smtp/postgres-smtp-integration-tests/src/test/resources/dnsservice.xml b/tmail-backend/integration-tests/smtp/postgres-smtp-integration-tests/src/test/resources/dnsservice.xml
new file mode 100644
index 00000000000..6e4fbd2efb5
--- /dev/null
+++ b/tmail-backend/integration-tests/smtp/postgres-smtp-integration-tests/src/test/resources/dnsservice.xml
@@ -0,0 +1,25 @@
+
+
+
+
+ true
+ false
+ 50000
+
diff --git a/tmail-backend/integration-tests/smtp/postgres-smtp-integration-tests/src/test/resources/domainlist.xml b/tmail-backend/integration-tests/smtp/postgres-smtp-integration-tests/src/test/resources/domainlist.xml
new file mode 100644
index 00000000000..fe17431a1ea
--- /dev/null
+++ b/tmail-backend/integration-tests/smtp/postgres-smtp-integration-tests/src/test/resources/domainlist.xml
@@ -0,0 +1,24 @@
+
+
+
+
+ false
+ false
+
diff --git a/tmail-backend/integration-tests/smtp/postgres-smtp-integration-tests/src/test/resources/fakemailrepositorystore.xml b/tmail-backend/integration-tests/smtp/postgres-smtp-integration-tests/src/test/resources/fakemailrepositorystore.xml
new file mode 100644
index 00000000000..2d19a802da9
--- /dev/null
+++ b/tmail-backend/integration-tests/smtp/postgres-smtp-integration-tests/src/test/resources/fakemailrepositorystore.xml
@@ -0,0 +1,31 @@
+
+
+
+
+
+
+
+
+ file
+
+
+
+
+
diff --git a/tmail-backend/integration-tests/smtp/postgres-smtp-integration-tests/src/test/resources/imapserver.xml b/tmail-backend/integration-tests/smtp/postgres-smtp-integration-tests/src/test/resources/imapserver.xml
new file mode 100644
index 00000000000..3434dbce390
--- /dev/null
+++ b/tmail-backend/integration-tests/smtp/postgres-smtp-integration-tests/src/test/resources/imapserver.xml
@@ -0,0 +1,57 @@
+
+
+
+
+
+
+
+ imapserver
+ 0.0.0.0:0
+ 200
+
+
+ classpath://keystore
+ james72laBalle
+ org.bouncycastle.jce.provider.BouncyCastleProvider
+
+ 0
+ 0
+ false
+ false
+
+
+ imapserver-ssl
+ 0.0.0.0:0
+ 200
+
+
+ classpath://keystore
+ james72laBalle
+ org.bouncycastle.jce.provider.BouncyCastleProvider
+
+ 0
+ 0
+ false
+
+
diff --git a/tmail-backend/integration-tests/smtp/postgres-smtp-integration-tests/src/test/resources/keystore b/tmail-backend/integration-tests/smtp/postgres-smtp-integration-tests/src/test/resources/keystore
new file mode 100644
index 00000000000..536a6c792b0
Binary files /dev/null and b/tmail-backend/integration-tests/smtp/postgres-smtp-integration-tests/src/test/resources/keystore differ
diff --git a/tmail-backend/integration-tests/smtp/postgres-smtp-integration-tests/src/test/resources/listeners.xml b/tmail-backend/integration-tests/smtp/postgres-smtp-integration-tests/src/test/resources/listeners.xml
new file mode 100644
index 00000000000..74e79d927f2
--- /dev/null
+++ b/tmail-backend/integration-tests/smtp/postgres-smtp-integration-tests/src/test/resources/listeners.xml
@@ -0,0 +1,6 @@
+
+
+ org.apache.james.jmap.event.PopulateEmailQueryViewListener
+ true
+
+
\ No newline at end of file
diff --git a/tmail-backend/integration-tests/smtp/postgres-smtp-integration-tests/src/test/resources/mailetcontainer.xml b/tmail-backend/integration-tests/smtp/postgres-smtp-integration-tests/src/test/resources/mailetcontainer.xml
new file mode 100644
index 00000000000..6323ae71c71
--- /dev/null
+++ b/tmail-backend/integration-tests/smtp/postgres-smtp-integration-tests/src/test/resources/mailetcontainer.xml
@@ -0,0 +1,129 @@
+
+
+
+
+
+
+
+ postmaster
+
+
+
+ 20
+ postgres://var/mail/error/
+
+
+
+
+
+
+
+ transport
+
+
+
+
+
+
+ postgres://var/mail/error/
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ rrt-error
+
+
+ local-delivery
+
+
+ local-address-error
+ 550 - Requested action not taken: no such user here
+
+
+ outgoing
+ 5000, 100000, 500000
+ 3
+ 0
+ 10
+ true
+ bounces
+
+
+ relay-denied
+
+
+
+
+
+
+
+
+
+
+
+
+
+ none
+
+
+ postgres://var/mail/address-error/
+
+
+
+
+
+ none
+
+
+ postgres://var/mail/relay-denied/
+ Warning: You are sending an e-mail to a remote server. You must be authentified to perform such an operation
+
+
+
+
+
+ false
+
+
+
+
+
+ postgres://var/mail/rrt-error/
+
+
+
+
+
+
+
+
+
diff --git a/tmail-backend/integration-tests/smtp/postgres-smtp-integration-tests/src/test/resources/mailrepositorystore.xml b/tmail-backend/integration-tests/smtp/postgres-smtp-integration-tests/src/test/resources/mailrepositorystore.xml
new file mode 100644
index 00000000000..573ec24ad3e
--- /dev/null
+++ b/tmail-backend/integration-tests/smtp/postgres-smtp-integration-tests/src/test/resources/mailrepositorystore.xml
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+
+
+ postgres
+
+
+
+
diff --git a/tmail-backend/integration-tests/smtp/postgres-smtp-integration-tests/src/test/resources/managesieveserver.xml b/tmail-backend/integration-tests/smtp/postgres-smtp-integration-tests/src/test/resources/managesieveserver.xml
new file mode 100644
index 00000000000..f136a432b8a
--- /dev/null
+++ b/tmail-backend/integration-tests/smtp/postgres-smtp-integration-tests/src/test/resources/managesieveserver.xml
@@ -0,0 +1,32 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/tmail-backend/integration-tests/smtp/postgres-smtp-integration-tests/src/test/resources/pop3server.xml b/tmail-backend/integration-tests/smtp/postgres-smtp-integration-tests/src/test/resources/pop3server.xml
new file mode 100644
index 00000000000..2a62f6f789c
--- /dev/null
+++ b/tmail-backend/integration-tests/smtp/postgres-smtp-integration-tests/src/test/resources/pop3server.xml
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
diff --git a/tmail-backend/integration-tests/smtp/postgres-smtp-integration-tests/src/test/resources/rabbitmq.properties b/tmail-backend/integration-tests/smtp/postgres-smtp-integration-tests/src/test/resources/rabbitmq.properties
new file mode 100644
index 00000000000..25d0dd6a976
--- /dev/null
+++ b/tmail-backend/integration-tests/smtp/postgres-smtp-integration-tests/src/test/resources/rabbitmq.properties
@@ -0,0 +1,2 @@
+uri=amqp://james:james@rabbitmq_host:5672
+management.uri=http://james:james@rabbitmq_host:15672/api/
\ No newline at end of file
diff --git a/tmail-backend/integration-tests/smtp/postgres-smtp-integration-tests/src/test/resources/smtpserver.xml b/tmail-backend/integration-tests/smtp/postgres-smtp-integration-tests/src/test/resources/smtpserver.xml
new file mode 100644
index 00000000000..278c76e0a42
--- /dev/null
+++ b/tmail-backend/integration-tests/smtp/postgres-smtp-integration-tests/src/test/resources/smtpserver.xml
@@ -0,0 +1,103 @@
+
+
+
+
+
+
+ smtpserver-global
+ 0.0.0.0:0
+ 200
+
+ file://conf/keystore
+ james72laBalle
+ org.bouncycastle.jce.provider.BouncyCastleProvider
+ SunX509
+
+ 360
+ 0
+ 0
+ false
+ false
+ 0
+ true
+ Apache JAMES awesome SMTP Server
+
+
+
+
+ false
+
+
+ smtpserver-TLS
+ 0.0.0.0:0
+ 200
+
+ file://conf/keystore
+ james72laBalle
+ org.bouncycastle.jce.provider.BouncyCastleProvider
+ SunX509
+
+ 360
+ 0
+ 0
+
+ true
+
+ false
+ 0
+ true
+ Apache JAMES awesome SMTP Server
+
+
+
+
+ false
+
+
+ smtpserver-authenticated
+ 0.0.0.0:0
+ 200
+
+ file://conf/keystore
+ james72laBalle
+ org.bouncycastle.jce.provider.BouncyCastleProvider
+ SunX509
+
+ 360
+ 0
+ 0
+
+ true
+
+ false
+ 0
+ true
+ Apache JAMES awesome SMTP Server
+
+
+
+
+ false
+
+
\ No newline at end of file
diff --git a/tmail-backend/integration-tests/smtp/postgres-smtp-integration-tests/src/test/resources/usersrepository.xml b/tmail-backend/integration-tests/smtp/postgres-smtp-integration-tests/src/test/resources/usersrepository.xml
new file mode 100644
index 00000000000..f8c8a258722
--- /dev/null
+++ b/tmail-backend/integration-tests/smtp/postgres-smtp-integration-tests/src/test/resources/usersrepository.xml
@@ -0,0 +1,25 @@
+
+
+
+
+
+ true
+ SHA-1
+
diff --git a/tmail-backend/integration-tests/smtp/postgres-smtp-integration-tests/src/test/resources/webadmin.properties b/tmail-backend/integration-tests/smtp/postgres-smtp-integration-tests/src/test/resources/webadmin.properties
new file mode 100644
index 00000000000..3386a14238a
--- /dev/null
+++ b/tmail-backend/integration-tests/smtp/postgres-smtp-integration-tests/src/test/resources/webadmin.properties
@@ -0,0 +1,25 @@
+# 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.
+
+# This template file can be used as example for James Server configuration
+# DO NOT USE IT AS SUCH AND ADAPT IT TO YOUR NEEDS
+
+# Read https://james.apache.org/server/config-webadmin.html for further details
+
+enabled=true
+port=0
+host=127.0.0.1
\ No newline at end of file
diff --git a/tmail-backend/integration-tests/webadmin/postgres-webadmin-integration-tests/pom.xml b/tmail-backend/integration-tests/webadmin/postgres-webadmin-integration-tests/pom.xml
new file mode 100644
index 00000000000..db934238408
--- /dev/null
+++ b/tmail-backend/integration-tests/webadmin/postgres-webadmin-integration-tests/pom.xml
@@ -0,0 +1,117 @@
+
+
+ 4.0.0
+
+ com.linagora.tmail
+ integration-tests
+ 1.0.0-SNAPSHOT
+ ../../pom.xml
+
+
+ postgres-webadmin-integration-tests
+ Team-mail :: Integration Tests :: Web Admin :: Postgres
+
+
+
+ ${project.groupId}
+ jmap-extensions-opensearch
+
+
+ ${project.groupId}
+ postgres
+ test
+
+
+ ${project.groupId}
+ postgres
+ test-jar
+ test
+
+
+ ${project.groupId}
+ tmail-guice-distributed
+ test-jar
+ test
+
+
+ ${project.groupId}
+ tmail-guice-jmap
+ test-jar
+ test
+
+
+ ${project.groupId}
+ webadmin-integration-tests-common
+ test
+
+
+ ${project.groupId}
+ tmail-guice-distributed
+ test-jar
+ test
+
+
+ ${james.groupId}
+ apache-james-backends-opensearch
+ test-jar
+ test
+
+
+ ${james.groupId}
+ apache-james-backends-postgres
+ test-jar
+ test
+
+
+ ${james.groupId}
+ apache-james-backends-rabbitmq
+ test-jar
+ test
+
+
+ ${james.groupId}
+ apache-james-rspamd
+ test-jar
+ test
+
+
+ ${james.groupId}
+ james-server-guice-common
+ test-jar
+ test
+
+
+ ${james.groupId}
+ james-server-guice-jmap
+ test-jar
+ test
+
+
+ ${james.groupId}
+ james-server-guice-opensearch
+ test-jar
+ test
+
+
+ ${james.groupId}
+ james-server-postgres-app
+ test
+
+
+ ${james.groupId}
+ james-server-postgres-app
+ test-jar
+ test
+
+
+ ${james.groupId}
+ james-server-testing
+
+
+ org.testcontainers
+ postgresql
+ test
+
+
+
diff --git a/tmail-backend/integration-tests/webadmin/postgres-webadmin-integration-tests/src/test/java/com/linagora/tmail/integration/postgres/PostgresCleanupIntegrationContract.java b/tmail-backend/integration-tests/webadmin/postgres-webadmin-integration-tests/src/test/java/com/linagora/tmail/integration/postgres/PostgresCleanupIntegrationContract.java
new file mode 100644
index 00000000000..aedaa36c8be
--- /dev/null
+++ b/tmail-backend/integration-tests/webadmin/postgres-webadmin-integration-tests/src/test/java/com/linagora/tmail/integration/postgres/PostgresCleanupIntegrationContract.java
@@ -0,0 +1,14 @@
+package com.linagora.tmail.integration.postgres;
+
+import static com.linagora.tmail.integration.postgres.PostgresWebAdminBase.JAMES_SERVER_EXTENSION_SUPPLIER;
+
+import org.apache.james.JamesServerExtension;
+import org.junit.jupiter.api.extension.RegisterExtension;
+
+import com.linagora.tmail.integration.CleanupIntegrationContract;
+
+public class PostgresCleanupIntegrationContract extends CleanupIntegrationContract {
+
+ @RegisterExtension
+ static JamesServerExtension testExtension = JAMES_SERVER_EXTENSION_SUPPLIER.get().build();
+}
diff --git a/tmail-backend/integration-tests/webadmin/postgres-webadmin-integration-tests/src/test/java/com/linagora/tmail/integration/postgres/PostgresInboxArchivalIntegrationTest.java b/tmail-backend/integration-tests/webadmin/postgres-webadmin-integration-tests/src/test/java/com/linagora/tmail/integration/postgres/PostgresInboxArchivalIntegrationTest.java
new file mode 100644
index 00000000000..4d7501e5d8b
--- /dev/null
+++ b/tmail-backend/integration-tests/webadmin/postgres-webadmin-integration-tests/src/test/java/com/linagora/tmail/integration/postgres/PostgresInboxArchivalIntegrationTest.java
@@ -0,0 +1,14 @@
+package com.linagora.tmail.integration.postgres;
+
+import static com.linagora.tmail.integration.postgres.PostgresWebAdminBase.JAMES_SERVER_EXTENSION_SUPPLIER;
+
+import org.apache.james.JamesServerExtension;
+import org.junit.jupiter.api.extension.RegisterExtension;
+
+import com.linagora.tmail.integration.InboxArchivalIntegrationContract;
+
+public class PostgresInboxArchivalIntegrationTest extends InboxArchivalIntegrationContract {
+
+ @RegisterExtension
+ static JamesServerExtension testExtension = JAMES_SERVER_EXTENSION_SUPPLIER.get().build();
+}
diff --git a/tmail-backend/integration-tests/webadmin/postgres-webadmin-integration-tests/src/test/java/com/linagora/tmail/integration/postgres/PostgresJmapUploadCleanRouteIntegrationTest.java b/tmail-backend/integration-tests/webadmin/postgres-webadmin-integration-tests/src/test/java/com/linagora/tmail/integration/postgres/PostgresJmapUploadCleanRouteIntegrationTest.java
new file mode 100644
index 00000000000..99bb3adadfe
--- /dev/null
+++ b/tmail-backend/integration-tests/webadmin/postgres-webadmin-integration-tests/src/test/java/com/linagora/tmail/integration/postgres/PostgresJmapUploadCleanRouteIntegrationTest.java
@@ -0,0 +1,11 @@
+package com.linagora.tmail.integration.postgres;
+
+import org.apache.james.JamesServerExtension;
+import org.junit.jupiter.api.extension.RegisterExtension;
+
+import com.linagora.tmail.integration.JmapUploadCleanRouteIntegrationContract;
+
+public class PostgresJmapUploadCleanRouteIntegrationTest extends JmapUploadCleanRouteIntegrationContract {
+ @RegisterExtension
+ static JamesServerExtension testExtension = PostgresWebAdminBase.JAMES_SERVER_EXTENSION_SUPPLIER.get().build();
+}
diff --git a/tmail-backend/integration-tests/webadmin/postgres-webadmin-integration-tests/src/test/java/com/linagora/tmail/integration/postgres/PostgresRecomputeQuotaTeamMailboxesRouteIntegrationTest.java b/tmail-backend/integration-tests/webadmin/postgres-webadmin-integration-tests/src/test/java/com/linagora/tmail/integration/postgres/PostgresRecomputeQuotaTeamMailboxesRouteIntegrationTest.java
new file mode 100644
index 00000000000..cbcb24ac27f
--- /dev/null
+++ b/tmail-backend/integration-tests/webadmin/postgres-webadmin-integration-tests/src/test/java/com/linagora/tmail/integration/postgres/PostgresRecomputeQuotaTeamMailboxesRouteIntegrationTest.java
@@ -0,0 +1,13 @@
+package com.linagora.tmail.integration.postgres;
+
+import org.apache.james.JamesServerExtension;
+import org.junit.jupiter.api.extension.RegisterExtension;
+
+import com.linagora.tmail.integration.RecomputeQuotaTeamMailboxesRouteIntegrationContract;
+
+public class PostgresRecomputeQuotaTeamMailboxesRouteIntegrationTest extends RecomputeQuotaTeamMailboxesRouteIntegrationContract {
+
+ @RegisterExtension
+ static JamesServerExtension testExtension = PostgresWebAdminBase.JAMES_SERVER_EXTENSION_SUPPLIER.get().build();
+
+}
diff --git a/tmail-backend/integration-tests/webadmin/postgres-webadmin-integration-tests/src/test/java/com/linagora/tmail/integration/postgres/PostgresRspamdFeedMessageRouteIntegrationTest.java b/tmail-backend/integration-tests/webadmin/postgres-webadmin-integration-tests/src/test/java/com/linagora/tmail/integration/postgres/PostgresRspamdFeedMessageRouteIntegrationTest.java
new file mode 100644
index 00000000000..807478aab6d
--- /dev/null
+++ b/tmail-backend/integration-tests/webadmin/postgres-webadmin-integration-tests/src/test/java/com/linagora/tmail/integration/postgres/PostgresRspamdFeedMessageRouteIntegrationTest.java
@@ -0,0 +1,14 @@
+package com.linagora.tmail.integration.postgres;
+
+import org.apache.james.JamesServerExtension;
+import org.junit.jupiter.api.extension.RegisterExtension;
+import com.linagora.tmail.rspamd.RspamdExtensionModule;
+import com.linagora.tmail.integration.RspamdFeedMessageRouteIntegrationContract;
+
+public class PostgresRspamdFeedMessageRouteIntegrationTest extends RspamdFeedMessageRouteIntegrationContract {
+
+ @RegisterExtension
+ static JamesServerExtension testExtension = PostgresWebAdminBase.JAMES_SERVER_EXTENSION_SUPPLIER.get()
+ .extension(new RspamdExtensionModule())
+ .build();
+}
diff --git a/tmail-backend/integration-tests/webadmin/postgres-webadmin-integration-tests/src/test/java/com/linagora/tmail/integration/postgres/PostgresUserDeletionIntegrationTest.java b/tmail-backend/integration-tests/webadmin/postgres-webadmin-integration-tests/src/test/java/com/linagora/tmail/integration/postgres/PostgresUserDeletionIntegrationTest.java
new file mode 100644
index 00000000000..bfc4e79422e
--- /dev/null
+++ b/tmail-backend/integration-tests/webadmin/postgres-webadmin-integration-tests/src/test/java/com/linagora/tmail/integration/postgres/PostgresUserDeletionIntegrationTest.java
@@ -0,0 +1,57 @@
+package com.linagora.tmail.integration.postgres;
+
+import static com.linagora.tmail.james.jmap.OpenSearchContactConfiguration.DEFAULT_CONFIGURATION;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.awaitility.Durations.ONE_HUNDRED_MILLISECONDS;
+
+import java.io.IOException;
+
+import org.apache.james.JamesServerExtension;
+import org.apache.james.backends.opensearch.ReactorOpenSearchClient;
+import org.awaitility.Awaitility;
+import org.awaitility.Durations;
+import org.awaitility.core.ConditionFactory;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.Disabled;
+import org.junit.jupiter.api.extension.RegisterExtension;
+import org.opensearch.client.opensearch._types.query_dsl.QueryBuilders;
+import org.opensearch.client.opensearch.core.SearchRequest;
+
+import com.linagora.tmail.integration.UserDeletionIntegrationContract;
+import com.linagora.tmail.james.app.DockerOpenSearchExtension;
+
+@Disabled("TODO https://github.com/linagora/james-project/issues/5143")
+public class PostgresUserDeletionIntegrationTest extends UserDeletionIntegrationContract {
+ private static final ConditionFactory CALMLY_AWAIT = Awaitility
+ .with().pollInterval(ONE_HUNDRED_MILLISECONDS)
+ .and().pollDelay(ONE_HUNDRED_MILLISECONDS)
+ .await();
+
+ @RegisterExtension
+ static DockerOpenSearchExtension opensearchExtension = new DockerOpenSearchExtension();
+
+ @RegisterExtension
+ static JamesServerExtension testExtension = PostgresWebAdminBase.JAMES_SERVER_EXTENSION_SUPPLIER.get()
+ .extensions(opensearchExtension)
+ .build();
+
+ private final ReactorOpenSearchClient client = opensearchExtension.getDockerOS().clientProvider().get();
+
+ @AfterEach
+ void tearDown() throws IOException {
+ client.close();
+ }
+
+ @Override
+ public void awaitDocumentsIndexed(Long documentCount) {
+ CALMLY_AWAIT.atMost(Durations.TEN_SECONDS)
+ .untilAsserted(() -> assertThat(client.search(
+ new SearchRequest.Builder()
+ .index(DEFAULT_CONFIGURATION.getUserContactIndexName().getValue(), DEFAULT_CONFIGURATION.getDomainContactIndexName().getValue())
+ .query(QueryBuilders.matchAll().build()._toQuery())
+ .build())
+ .block()
+ .hits().total().value()).isEqualTo(documentCount));
+ }
+
+}
diff --git a/tmail-backend/integration-tests/webadmin/postgres-webadmin-integration-tests/src/test/java/com/linagora/tmail/integration/postgres/PostgresUsernameChangeIntegrationTest.java b/tmail-backend/integration-tests/webadmin/postgres-webadmin-integration-tests/src/test/java/com/linagora/tmail/integration/postgres/PostgresUsernameChangeIntegrationTest.java
new file mode 100644
index 00000000000..d22b7c62e0a
--- /dev/null
+++ b/tmail-backend/integration-tests/webadmin/postgres-webadmin-integration-tests/src/test/java/com/linagora/tmail/integration/postgres/PostgresUsernameChangeIntegrationTest.java
@@ -0,0 +1,99 @@
+package com.linagora.tmail.integration.postgres;
+
+import static com.linagora.tmail.james.jmap.OpenSearchContactConfiguration.DEFAULT_CONFIGURATION;
+import static org.apache.james.PostgresJamesConfiguration.EventBusImpl.IN_MEMORY;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.awaitility.Durations.ONE_HUNDRED_MILLISECONDS;
+
+import java.io.IOException;
+
+import org.apache.james.ClockExtension;
+import org.apache.james.JamesServerBuilder;
+import org.apache.james.JamesServerExtension;
+import org.apache.james.SearchConfiguration;
+import org.apache.james.backends.opensearch.ReactorOpenSearchClient;
+import org.apache.james.backends.postgres.PostgresExtension;
+import org.apache.james.utils.GuiceProbe;
+import org.awaitility.Awaitility;
+import org.awaitility.Durations;
+import org.awaitility.core.ConditionFactory;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.extension.RegisterExtension;
+import org.opensearch.client.opensearch._types.query_dsl.QueryBuilders;
+import org.opensearch.client.opensearch.core.SearchRequest;
+
+import com.google.inject.multibindings.Multibinder;
+import com.linagora.tmail.blob.guice.BlobStoreConfiguration;
+import com.linagora.tmail.combined.identity.UsersRepositoryClassProbe;
+import com.linagora.tmail.encrypted.MailboxConfiguration;
+import com.linagora.tmail.encrypted.MailboxManagerClassProbe;
+import com.linagora.tmail.integration.UsernameChangeIntegrationContract;
+import com.linagora.tmail.integration.probe.RateLimitingProbe;
+import com.linagora.tmail.james.app.DockerOpenSearchExtension;
+import com.linagora.tmail.james.app.PostgresTmailConfiguration;
+import com.linagora.tmail.james.app.PostgresTmailServer;
+import com.linagora.tmail.james.common.probe.JmapGuiceContactAutocompleteProbe;
+import com.linagora.tmail.james.common.probe.JmapGuiceKeystoreManagerProbe;
+import com.linagora.tmail.james.common.probe.JmapGuiceLabelProbe;
+import com.linagora.tmail.james.common.probe.JmapSettingsProbe;
+import com.linagora.tmail.module.LinagoraTestJMAPServerModule;
+import com.linagora.tmail.team.TeamMailboxProbe;
+
+public class PostgresUsernameChangeIntegrationTest extends UsernameChangeIntegrationContract {
+ private static final ConditionFactory CALMLY_AWAIT = Awaitility
+ .with().pollInterval(ONE_HUNDRED_MILLISECONDS)
+ .and().pollDelay(ONE_HUNDRED_MILLISECONDS)
+ .await();
+
+ @RegisterExtension
+ static DockerOpenSearchExtension opensearchExtension = new DockerOpenSearchExtension();
+
+ @RegisterExtension
+ static JamesServerExtension testExtension = new JamesServerBuilder(tmpDir ->
+ PostgresTmailConfiguration.builder()
+ .workingDirectory(tmpDir)
+ .configurationFromClasspath()
+ .blobStore(BlobStoreConfiguration.builder()
+ .postgres()
+ .disableCache()
+ .deduplication()
+ .noCryptoConfig()
+ .disableSingleSave())
+ .searchConfiguration(SearchConfiguration.openSearch())
+ .mailbox(new MailboxConfiguration(false))
+ .eventBusImpl(IN_MEMORY)
+ .build())
+ .server(configuration -> PostgresTmailServer.createServer(configuration)
+ .overrideWith(new LinagoraTestJMAPServerModule())
+ .overrideWith(binder -> Multibinder.newSetBinder(binder, GuiceProbe.class).addBinding().to(MailboxManagerClassProbe.class))
+ .overrideWith(binder -> Multibinder.newSetBinder(binder, GuiceProbe.class).addBinding().to(UsersRepositoryClassProbe.class))
+ .overrideWith(binder -> Multibinder.newSetBinder(binder, GuiceProbe.class).addBinding().to(TeamMailboxProbe.class))
+ .overrideWith(binder -> Multibinder.newSetBinder(binder, GuiceProbe.class).addBinding().to(JmapGuiceContactAutocompleteProbe.class))
+ .overrideWith(binder -> Multibinder.newSetBinder(binder, GuiceProbe.class).addBinding().to(JmapSettingsProbe.class))
+ .overrideWith(binder -> Multibinder.newSetBinder(binder, GuiceProbe.class).addBinding().to(JmapGuiceLabelProbe.class))
+ .overrideWith(binder -> Multibinder.newSetBinder(binder, GuiceProbe.class).addBinding().to(RateLimitingProbe.class))
+ .overrideWith(binder -> Multibinder.newSetBinder(binder, GuiceProbe.class).addBinding().to(JmapGuiceKeystoreManagerProbe.class)))
+ .extension(PostgresExtension.empty())
+ .extension(new ClockExtension())
+ .extensions(opensearchExtension)
+ .build();
+
+ private final ReactorOpenSearchClient client = opensearchExtension.getDockerOS().clientProvider().get();
+
+ @AfterEach
+ void tearDown() throws IOException {
+ client.close();
+ }
+
+ @Override
+ public void awaitDocumentsIndexed(Long documentCount) {
+ CALMLY_AWAIT.atMost(Durations.TEN_SECONDS)
+ .untilAsserted(() -> assertThat(client.search(
+ new SearchRequest.Builder()
+ .index(DEFAULT_CONFIGURATION.getUserContactIndexName().getValue(), DEFAULT_CONFIGURATION.getDomainContactIndexName().getValue())
+ .query(QueryBuilders.matchAll().build()._toQuery())
+ .build())
+ .block()
+ .hits().total().value()).isEqualTo(documentCount));
+ }
+}
diff --git a/tmail-backend/integration-tests/webadmin/postgres-webadmin-integration-tests/src/test/java/com/linagora/tmail/integration/postgres/PostgresWebAdminBase.java b/tmail-backend/integration-tests/webadmin/postgres-webadmin-integration-tests/src/test/java/com/linagora/tmail/integration/postgres/PostgresWebAdminBase.java
new file mode 100644
index 00000000000..3b4b7ee2719
--- /dev/null
+++ b/tmail-backend/integration-tests/webadmin/postgres-webadmin-integration-tests/src/test/java/com/linagora/tmail/integration/postgres/PostgresWebAdminBase.java
@@ -0,0 +1,62 @@
+package com.linagora.tmail.integration.postgres;
+
+import static org.apache.james.PostgresJamesConfiguration.EventBusImpl.IN_MEMORY;
+
+import java.util.function.Function;
+import java.util.function.Supplier;
+
+import org.apache.james.ClockExtension;
+import org.apache.james.JamesServerBuilder;
+import org.apache.james.SearchConfiguration;
+import org.apache.james.backends.postgres.PostgresExtension;
+import org.apache.james.utils.GuiceProbe;
+
+import com.google.inject.Module;
+import com.google.inject.multibindings.Multibinder;
+import com.google.inject.util.Modules;
+import com.linagora.tmail.blob.guice.BlobStoreConfiguration;
+import com.linagora.tmail.combined.identity.UsersRepositoryClassProbe;
+import com.linagora.tmail.encrypted.MailboxConfiguration;
+import com.linagora.tmail.encrypted.MailboxManagerClassProbe;
+import com.linagora.tmail.integration.probe.RateLimitingProbe;
+import com.linagora.tmail.james.app.PostgresTmailConfiguration;
+import com.linagora.tmail.james.app.PostgresTmailServer;
+import com.linagora.tmail.james.common.probe.JmapGuiceContactAutocompleteProbe;
+import com.linagora.tmail.james.common.probe.JmapGuiceKeystoreManagerProbe;
+import com.linagora.tmail.james.common.probe.JmapGuiceLabelProbe;
+import com.linagora.tmail.james.common.probe.JmapSettingsProbe;
+import com.linagora.tmail.module.LinagoraTestJMAPServerModule;
+import com.linagora.tmail.team.TeamMailboxProbe;
+
+public class PostgresWebAdminBase {
+
+ public static Function> JAMES_SERVER_EXTENSION_FUNCTION = overrideWithModule -> new JamesServerBuilder(tmpDir ->
+ PostgresTmailConfiguration.builder()
+ .workingDirectory(tmpDir)
+ .configurationFromClasspath()
+ .blobStore(BlobStoreConfiguration.builder()
+ .postgres()
+ .disableCache()
+ .deduplication()
+ .noCryptoConfig()
+ .disableSingleSave())
+ .searchConfiguration(SearchConfiguration.scanning())
+ .mailbox(new MailboxConfiguration(false))
+ .eventBusImpl(IN_MEMORY)
+ .build())
+ .server(configuration -> PostgresTmailServer.createServer(configuration)
+ .overrideWith(new LinagoraTestJMAPServerModule())
+ .overrideWith(binder -> Multibinder.newSetBinder(binder, GuiceProbe.class).addBinding().to(MailboxManagerClassProbe.class))
+ .overrideWith(binder -> Multibinder.newSetBinder(binder, GuiceProbe.class).addBinding().to(UsersRepositoryClassProbe.class))
+ .overrideWith(binder -> Multibinder.newSetBinder(binder, GuiceProbe.class).addBinding().to(TeamMailboxProbe.class))
+ .overrideWith(binder -> Multibinder.newSetBinder(binder, GuiceProbe.class).addBinding().to(JmapGuiceContactAutocompleteProbe.class))
+ .overrideWith(binder -> Multibinder.newSetBinder(binder, GuiceProbe.class).addBinding().to(JmapSettingsProbe.class))
+ .overrideWith(binder -> Multibinder.newSetBinder(binder, GuiceProbe.class).addBinding().to(JmapGuiceLabelProbe.class))
+ .overrideWith(binder -> Multibinder.newSetBinder(binder, GuiceProbe.class).addBinding().to(RateLimitingProbe.class))
+ .overrideWith(binder -> Multibinder.newSetBinder(binder, GuiceProbe.class).addBinding().to(JmapGuiceKeystoreManagerProbe.class))
+ .overrideWith(overrideWithModule))
+ .extension(PostgresExtension.empty())
+ .extension(new ClockExtension());
+
+ public static Supplier> JAMES_SERVER_EXTENSION_SUPPLIER = () -> JAMES_SERVER_EXTENSION_FUNCTION.apply(Modules.EMPTY_MODULE);
+}
diff --git a/tmail-backend/integration-tests/webadmin/postgres-webadmin-integration-tests/src/test/resources/dnsservice.xml b/tmail-backend/integration-tests/webadmin/postgres-webadmin-integration-tests/src/test/resources/dnsservice.xml
new file mode 100644
index 00000000000..6e4fbd2efb5
--- /dev/null
+++ b/tmail-backend/integration-tests/webadmin/postgres-webadmin-integration-tests/src/test/resources/dnsservice.xml
@@ -0,0 +1,25 @@
+
+
+
+
+ true
+ false
+ 50000
+
diff --git a/tmail-backend/integration-tests/webadmin/postgres-webadmin-integration-tests/src/test/resources/domainlist.xml b/tmail-backend/integration-tests/webadmin/postgres-webadmin-integration-tests/src/test/resources/domainlist.xml
new file mode 100644
index 00000000000..fe17431a1ea
--- /dev/null
+++ b/tmail-backend/integration-tests/webadmin/postgres-webadmin-integration-tests/src/test/resources/domainlist.xml
@@ -0,0 +1,24 @@
+
+
+
+
+ false
+ false
+
diff --git a/tmail-backend/integration-tests/webadmin/postgres-webadmin-integration-tests/src/test/resources/imapserver.xml b/tmail-backend/integration-tests/webadmin/postgres-webadmin-integration-tests/src/test/resources/imapserver.xml
new file mode 100644
index 00000000000..ead2b342f34
--- /dev/null
+++ b/tmail-backend/integration-tests/webadmin/postgres-webadmin-integration-tests/src/test/resources/imapserver.xml
@@ -0,0 +1,24 @@
+
+
+
+
+
+
+
diff --git a/tmail-backend/integration-tests/webadmin/postgres-webadmin-integration-tests/src/test/resources/jmap.properties b/tmail-backend/integration-tests/webadmin/postgres-webadmin-integration-tests/src/test/resources/jmap.properties
new file mode 100644
index 00000000000..519703e204c
--- /dev/null
+++ b/tmail-backend/integration-tests/webadmin/postgres-webadmin-integration-tests/src/test/resources/jmap.properties
@@ -0,0 +1,7 @@
+# Configuration urlPrefix for JMAP routes.
+url.prefix=http://domain.com
+websocket.url.prefix=ws://domain.com
+upload.max.size=20M
+webpush.maxTimeoutSeconds=10
+webpush.maxConnections=10
+dynamic.jmap.prefix.resolution.enabled=true
\ No newline at end of file
diff --git a/tmail-backend/integration-tests/webadmin/postgres-webadmin-integration-tests/src/test/resources/keystore b/tmail-backend/integration-tests/webadmin/postgres-webadmin-integration-tests/src/test/resources/keystore
new file mode 100644
index 00000000000..536a6c792b0
Binary files /dev/null and b/tmail-backend/integration-tests/webadmin/postgres-webadmin-integration-tests/src/test/resources/keystore differ
diff --git a/tmail-backend/integration-tests/webadmin/postgres-webadmin-integration-tests/src/test/resources/listeners.xml b/tmail-backend/integration-tests/webadmin/postgres-webadmin-integration-tests/src/test/resources/listeners.xml
new file mode 100644
index 00000000000..ddc4d9d1522
--- /dev/null
+++ b/tmail-backend/integration-tests/webadmin/postgres-webadmin-integration-tests/src/test/resources/listeners.xml
@@ -0,0 +1,26 @@
+
+
+
+
+
+ org.apache.james.jmap.event.PopulateEmailQueryViewListener
+ true
+
+
\ No newline at end of file
diff --git a/tmail-backend/integration-tests/webadmin/postgres-webadmin-integration-tests/src/test/resources/mailetcontainer.xml b/tmail-backend/integration-tests/webadmin/postgres-webadmin-integration-tests/src/test/resources/mailetcontainer.xml
new file mode 100644
index 00000000000..f429a43156b
--- /dev/null
+++ b/tmail-backend/integration-tests/webadmin/postgres-webadmin-integration-tests/src/test/resources/mailetcontainer.xml
@@ -0,0 +1,98 @@
+
+
+
+
+
+
+
+ postmaster
+
+
+
+ 2
+ postgres://var/mail/error/
+
+
+
+
+
+ transport
+
+
+
+
+
+ ignore
+
+
+
+
+
+ ignore
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ error
+
+
+ ignore
+
+
+
+ ignore
+
+
+
+
+ outgoing
+ 5000, 100000, 500000
+ 3
+ 0
+ 10
+ true
+ error
+
+
+
+ error
+
+
+
+
+
+
diff --git a/tmail-backend/integration-tests/webadmin/postgres-webadmin-integration-tests/src/test/resources/mailrepositorystore.xml b/tmail-backend/integration-tests/webadmin/postgres-webadmin-integration-tests/src/test/resources/mailrepositorystore.xml
new file mode 100644
index 00000000000..573ec24ad3e
--- /dev/null
+++ b/tmail-backend/integration-tests/webadmin/postgres-webadmin-integration-tests/src/test/resources/mailrepositorystore.xml
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+
+
+ postgres
+
+
+
+
diff --git a/tmail-backend/integration-tests/webadmin/postgres-webadmin-integration-tests/src/test/resources/managesieveserver.xml b/tmail-backend/integration-tests/webadmin/postgres-webadmin-integration-tests/src/test/resources/managesieveserver.xml
new file mode 100644
index 00000000000..f136a432b8a
--- /dev/null
+++ b/tmail-backend/integration-tests/webadmin/postgres-webadmin-integration-tests/src/test/resources/managesieveserver.xml
@@ -0,0 +1,32 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/tmail-backend/integration-tests/webadmin/postgres-webadmin-integration-tests/src/test/resources/pop3server.xml b/tmail-backend/integration-tests/webadmin/postgres-webadmin-integration-tests/src/test/resources/pop3server.xml
new file mode 100644
index 00000000000..4cd372d7f78
--- /dev/null
+++ b/tmail-backend/integration-tests/webadmin/postgres-webadmin-integration-tests/src/test/resources/pop3server.xml
@@ -0,0 +1,3 @@
+
+
+
diff --git a/tmail-backend/integration-tests/webadmin/postgres-webadmin-integration-tests/src/test/resources/rabbitmq.properties b/tmail-backend/integration-tests/webadmin/postgres-webadmin-integration-tests/src/test/resources/rabbitmq.properties
new file mode 100644
index 00000000000..25d0dd6a976
--- /dev/null
+++ b/tmail-backend/integration-tests/webadmin/postgres-webadmin-integration-tests/src/test/resources/rabbitmq.properties
@@ -0,0 +1,2 @@
+uri=amqp://james:james@rabbitmq_host:5672
+management.uri=http://james:james@rabbitmq_host:15672/api/
\ No newline at end of file
diff --git a/tmail-backend/integration-tests/webadmin/postgres-webadmin-integration-tests/src/test/resources/rspamd.properties b/tmail-backend/integration-tests/webadmin/postgres-webadmin-integration-tests/src/test/resources/rspamd.properties
new file mode 100644
index 00000000000..7382f27a966
--- /dev/null
+++ b/tmail-backend/integration-tests/webadmin/postgres-webadmin-integration-tests/src/test/resources/rspamd.properties
@@ -0,0 +1,4 @@
+rspamdUrl=http://rspamd:11334
+rspamdPassword=admin
+# Whether to scan/learn mails using per-user Bayes. Default to false.
+perUserBayes=false
\ No newline at end of file
diff --git a/tmail-backend/integration-tests/webadmin/postgres-webadmin-integration-tests/src/test/resources/smtpserver.xml b/tmail-backend/integration-tests/webadmin/postgres-webadmin-integration-tests/src/test/resources/smtpserver.xml
new file mode 100644
index 00000000000..21dc0a9af9c
--- /dev/null
+++ b/tmail-backend/integration-tests/webadmin/postgres-webadmin-integration-tests/src/test/resources/smtpserver.xml
@@ -0,0 +1,53 @@
+
+
+
+
+
+
+ smtpserver-global
+ 0.0.0.0:0
+ 200
+
+ file://conf/keystore
+ james72laBalle
+ org.bouncycastle.jce.provider.BouncyCastleProvider
+ SunX509
+
+ 360
+ 0
+ 0
+ false
+
+ never
+ false
+ true
+
+ 0
+ true
+ Apache JAMES awesome SMTP Server
+
+
+
+
+ false
+
+
+
+
diff --git a/tmail-backend/integration-tests/webadmin/postgres-webadmin-integration-tests/src/test/resources/usersrepository.xml b/tmail-backend/integration-tests/webadmin/postgres-webadmin-integration-tests/src/test/resources/usersrepository.xml
new file mode 100644
index 00000000000..f8c8a258722
--- /dev/null
+++ b/tmail-backend/integration-tests/webadmin/postgres-webadmin-integration-tests/src/test/resources/usersrepository.xml
@@ -0,0 +1,25 @@
+
+
+
+
+
+ true
+ SHA-1
+
diff --git a/tmail-backend/integration-tests/webadmin/postgres-webadmin-integration-tests/src/test/resources/webadmin.properties b/tmail-backend/integration-tests/webadmin/postgres-webadmin-integration-tests/src/test/resources/webadmin.properties
new file mode 100644
index 00000000000..cafc6f0fb0c
--- /dev/null
+++ b/tmail-backend/integration-tests/webadmin/postgres-webadmin-integration-tests/src/test/resources/webadmin.properties
@@ -0,0 +1,26 @@
+# 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.
+
+# This template file can be used as example for James Server configuration
+# DO NOT USE IT AS SUCH AND ADAPT IT TO YOUR NEEDS
+
+# Read https://james.apache.org/server/config-webadmin.html for further details
+
+enabled=true
+port=0
+host=127.0.0.1
+
diff --git a/tmail-backend/jmap/extensions-postgres/pom.xml b/tmail-backend/jmap/extensions-postgres/pom.xml
new file mode 100644
index 00000000000..63b9b808846
--- /dev/null
+++ b/tmail-backend/jmap/extensions-postgres/pom.xml
@@ -0,0 +1,91 @@
+
+
+ 4.0.0
+
+ com.linagora.tmail
+ tmail-backend
+ 1.0.0-SNAPSHOT
+ ../../pom.xml
+
+
+ jmap-extensions-postgres
+ Team-mail :: JMAP :: Extensions :: PostgreSQL
+
+
+
+ ${project.groupId}
+ jmap-extensions
+
+
+ ${project.groupId}
+ jmap-extensions
+ test-jar
+ test
+
+
+
+ ${james.groupId}
+ apache-james-backends-postgres
+
+
+ ${james.groupId}
+ apache-james-backends-postgres
+ test-jar
+ test
+
+
+ ${james.groupId}
+ blob-memory
+ test
+
+
+ ${james.groupId}
+ blob-storage-strategy
+ test
+
+
+ ${james.groupId}
+ james-server-data-jmap-postgres
+ test
+
+
+ ${james.groupId}
+ james-server-guice-common
+ test-jar
+ test
+
+
+ ${james.groupId}
+ james-server-testing
+ test
+
+
+ ${james.groupId}
+ testing-base
+ test
+
+
+ org.testcontainers
+ postgresql
+ test
+
+
+
+
+
+
+ net.alchim31.maven
+ scala-maven-plugin
+
+
+ io.github.evis
+ scalafix-maven-plugin_2.13
+
+ ${project.parent.parent.basedir}/.scalafix.conf
+
+
+
+
+
\ No newline at end of file
diff --git a/tmail-backend/jmap/extensions-postgres/src/main/java/com/linagora/tmail/james/jmap/firebase/PostgresFirebaseModule.java b/tmail-backend/jmap/extensions-postgres/src/main/java/com/linagora/tmail/james/jmap/firebase/PostgresFirebaseModule.java
new file mode 100644
index 00000000000..a4ea6886098
--- /dev/null
+++ b/tmail-backend/jmap/extensions-postgres/src/main/java/com/linagora/tmail/james/jmap/firebase/PostgresFirebaseModule.java
@@ -0,0 +1,59 @@
+package com.linagora.tmail.james.jmap.firebase;
+
+import java.time.OffsetDateTime;
+import java.util.UUID;
+
+import org.apache.james.backends.postgres.PostgresCommons;
+import org.apache.james.backends.postgres.PostgresIndex;
+import org.apache.james.backends.postgres.PostgresModule;
+import org.apache.james.backends.postgres.PostgresTable;
+import org.jooq.Field;
+import org.jooq.Record;
+import org.jooq.Table;
+import org.jooq.impl.DSL;
+import org.jooq.impl.SQLDataType;
+
+public interface PostgresFirebaseModule {
+
+ interface FirebaseSubscriptionTable {
+ Table TABLE_NAME = DSL.table("firebase_subscription");
+ String PRIMARY_KEY_CONSTRAINT = "firebase_subscription_primary_key_constraint";
+ String FCM_TOKEN_UNIQUE_CONSTRAINT = "fcm_token_unique_constraint";
+
+ Field USER = DSL.field("username", SQLDataType.VARCHAR.notNull());
+ Field DEVICE_CLIENT_ID = DSL.field("device_client_id", SQLDataType.VARCHAR.notNull());
+ Field ID = DSL.field("id", SQLDataType.UUID.notNull());
+ Field EXPIRES = DSL.field("expires", SQLDataType.TIMESTAMPWITHTIMEZONE(6));
+ Field TYPES = DSL.field("types", PostgresCommons.DataTypes.STRING_ARRAY.notNull());
+ Field FCM_TOKEN = DSL.field("fcm_token", SQLDataType.VARCHAR.notNull());
+
+ PostgresTable TABLE = PostgresTable.name(TABLE_NAME.getName())
+ .createTableStep(((dsl, tableName) -> dsl.createTableIfNotExists(tableName)
+ .column(USER)
+ .column(DEVICE_CLIENT_ID)
+ .column(ID)
+ .column(EXPIRES)
+ .column(TYPES)
+ .column(FCM_TOKEN)
+ .constraint(DSL.constraint(PRIMARY_KEY_CONSTRAINT)
+ .primaryKey(USER, DEVICE_CLIENT_ID))
+ .constraint(DSL.constraint(FCM_TOKEN_UNIQUE_CONSTRAINT)
+ .unique(FCM_TOKEN))
+ .comment("Hold user firebase push subscriptions data")))
+ .supportsRowLevelSecurity()
+ .build();
+
+
+ PostgresIndex USERNAME_INDEX = PostgresIndex.name("firebase_subscription_username_index")
+ .createIndexStep((dslContext, indexName) -> dslContext.createIndexIfNotExists(indexName)
+ .on(TABLE_NAME, USER));
+ PostgresIndex USERNAME_ID_INDEX = PostgresIndex.name("firebase_subscription_username_id_index")
+ .createIndexStep((dslContext, indexName) -> dslContext.createIndexIfNotExists(indexName)
+ .on(TABLE_NAME, USER, ID));
+ }
+
+ PostgresModule MODULE = PostgresModule.builder()
+ .addTable(FirebaseSubscriptionTable.TABLE)
+ .addIndex(FirebaseSubscriptionTable.USERNAME_INDEX, FirebaseSubscriptionTable.USERNAME_ID_INDEX)
+ .build();
+}
diff --git a/tmail-backend/jmap/extensions-postgres/src/main/java/com/linagora/tmail/james/jmap/firebase/PostgresFirebaseRepositoryModule.java b/tmail-backend/jmap/extensions-postgres/src/main/java/com/linagora/tmail/james/jmap/firebase/PostgresFirebaseRepositoryModule.java
new file mode 100644
index 00000000000..3a090bb8b79
--- /dev/null
+++ b/tmail-backend/jmap/extensions-postgres/src/main/java/com/linagora/tmail/james/jmap/firebase/PostgresFirebaseRepositoryModule.java
@@ -0,0 +1,21 @@
+package com.linagora.tmail.james.jmap.firebase;
+
+import org.apache.james.backends.postgres.PostgresModule;
+import org.apache.james.user.api.DeleteUserDataTaskStep;
+
+import com.google.inject.AbstractModule;
+import com.google.inject.Scopes;
+import com.google.inject.multibindings.Multibinder;
+
+public class PostgresFirebaseRepositoryModule extends AbstractModule {
+ @Override
+ protected void configure() {
+ Multibinder postgresDataDefinitions = Multibinder.newSetBinder(binder(), PostgresModule.class);
+ postgresDataDefinitions.addBinding().toInstance(PostgresFirebaseModule.MODULE);
+
+ bind(FirebaseSubscriptionRepository.class).to(PostgresFirebaseSubscriptionRepository.class);
+ bind(PostgresFirebaseSubscriptionRepository.class).in(Scopes.SINGLETON);
+
+ Multibinder.newSetBinder(binder(), DeleteUserDataTaskStep.class).addBinding().to(FirebaseSubscriptionUserDeletionTaskStep.class);
+ }
+}
\ No newline at end of file
diff --git a/tmail-backend/jmap/extensions-postgres/src/main/java/com/linagora/tmail/james/jmap/firebase/PostgresFirebaseSubscriptionDAO.java b/tmail-backend/jmap/extensions-postgres/src/main/java/com/linagora/tmail/james/jmap/firebase/PostgresFirebaseSubscriptionDAO.java
new file mode 100644
index 00000000000..fbd54b4b401
--- /dev/null
+++ b/tmail-backend/jmap/extensions-postgres/src/main/java/com/linagora/tmail/james/jmap/firebase/PostgresFirebaseSubscriptionDAO.java
@@ -0,0 +1,148 @@
+package com.linagora.tmail.james.jmap.firebase;
+
+import static com.linagora.tmail.james.jmap.firebase.PostgresFirebaseModule.FirebaseSubscriptionTable.DEVICE_CLIENT_ID;
+import static com.linagora.tmail.james.jmap.firebase.PostgresFirebaseModule.FirebaseSubscriptionTable.EXPIRES;
+import static com.linagora.tmail.james.jmap.firebase.PostgresFirebaseModule.FirebaseSubscriptionTable.FCM_TOKEN;
+import static com.linagora.tmail.james.jmap.firebase.PostgresFirebaseModule.FirebaseSubscriptionTable.FCM_TOKEN_UNIQUE_CONSTRAINT;
+import static com.linagora.tmail.james.jmap.firebase.PostgresFirebaseModule.FirebaseSubscriptionTable.ID;
+import static com.linagora.tmail.james.jmap.firebase.PostgresFirebaseModule.FirebaseSubscriptionTable.PRIMARY_KEY_CONSTRAINT;
+import static com.linagora.tmail.james.jmap.firebase.PostgresFirebaseModule.FirebaseSubscriptionTable.TABLE_NAME;
+import static com.linagora.tmail.james.jmap.firebase.PostgresFirebaseModule.FirebaseSubscriptionTable.TYPES;
+import static com.linagora.tmail.james.jmap.firebase.PostgresFirebaseModule.FirebaseSubscriptionTable.USER;
+import static org.apache.james.backends.postgres.PostgresCommons.IN_CLAUSE_MAX_SIZE;
+import static org.apache.james.backends.postgres.utils.PostgresUtils.UNIQUE_CONSTRAINT_VIOLATION_PREDICATE;
+
+import java.time.OffsetDateTime;
+import java.time.ZoneId;
+import java.time.ZonedDateTime;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Optional;
+import java.util.Set;
+import java.util.function.Function;
+import java.util.function.Predicate;
+import java.util.stream.Collectors;
+
+import org.apache.james.backends.postgres.utils.PostgresExecutor;
+import org.apache.james.core.Username;
+import org.apache.james.jmap.api.change.TypeStateFactory;
+import org.apache.james.jmap.api.model.TypeName;
+import org.jooq.Record;
+
+import com.google.common.base.Preconditions;
+import com.google.common.collect.Iterables;
+import com.linagora.tmail.james.jmap.model.DeviceClientIdInvalidException;
+import com.linagora.tmail.james.jmap.model.FirebaseSubscription;
+import com.linagora.tmail.james.jmap.model.FirebaseSubscriptionExpiredTime;
+import com.linagora.tmail.james.jmap.model.FirebaseSubscriptionId;
+import com.linagora.tmail.james.jmap.model.TokenInvalidException;
+
+import reactor.core.publisher.Flux;
+import reactor.core.publisher.Mono;
+import scala.jdk.javaapi.CollectionConverters;
+
+public class PostgresFirebaseSubscriptionDAO {
+ private static final Function OFFSET_DATE_TIME_ZONED_DATE_TIME_FUNCTION = offsetDateTime ->
+ Optional.ofNullable(offsetDateTime)
+ .map(value -> value.atZoneSameInstant(ZoneId.of("UTC")))
+ .orElse(null);
+
+ private static final Predicate IS_PRIMARY_KEY_UNIQUE_CONSTRAINT = throwable -> throwable.getMessage().contains(PRIMARY_KEY_CONSTRAINT);
+ private static final Predicate IS_FCM_TOKEN_UNIQUE_CONSTRAINT = throwable -> throwable.getMessage().contains(FCM_TOKEN_UNIQUE_CONSTRAINT);
+
+ private final PostgresExecutor postgresExecutor;
+ private final TypeStateFactory typeStateFactory;
+
+ public PostgresFirebaseSubscriptionDAO(PostgresExecutor postgresExecutor, TypeStateFactory typeStateFactory) {
+ this.postgresExecutor = postgresExecutor;
+ this.typeStateFactory = typeStateFactory;
+ }
+
+ public Mono save(Username username, FirebaseSubscription subscription) {
+ return postgresExecutor.executeVoid(dslContext -> Mono.from(dslContext.insertInto(TABLE_NAME)
+ .set(USER, username.asString())
+ .set(DEVICE_CLIENT_ID, subscription.deviceClientId())
+ .set(ID, subscription.id().value())
+ .set(EXPIRES, subscription.expires().value().toOffsetDateTime())
+ .set(TYPES, CollectionConverters.asJava(subscription.types())
+ .stream().map(TypeName::asString).toArray(String[]::new))
+ .set(FCM_TOKEN, subscription.token())))
+ .onErrorMap(UNIQUE_CONSTRAINT_VIOLATION_PREDICATE.and(IS_PRIMARY_KEY_UNIQUE_CONSTRAINT),
+ e -> new DeviceClientIdInvalidException(subscription.deviceClientId(), "deviceClientId must be unique"))
+ .onErrorMap(UNIQUE_CONSTRAINT_VIOLATION_PREDICATE.and(IS_FCM_TOKEN_UNIQUE_CONSTRAINT),
+ e -> new TokenInvalidException("deviceToken must be unique"));
+ }
+
+ public Flux listByUsername(Username username) {
+ return postgresExecutor.executeRows(dslContext -> Flux.from(dslContext.select(DEVICE_CLIENT_ID, ID, EXPIRES, TYPES, FCM_TOKEN)
+ .from(TABLE_NAME)
+ .where(USER.eq(username.asString()))))
+ .map(this::toSubscription);
+ }
+
+ public Flux getByUsernameAndIds(Username username, Collection ids) {
+ Function, Flux> queryPublisherFunction = idsMatching -> postgresExecutor.executeRows(dslContext ->
+ Flux.from(dslContext.select(DEVICE_CLIENT_ID, ID, EXPIRES, TYPES, FCM_TOKEN)
+ .from(TABLE_NAME)
+ .where(USER.eq(username.asString()))
+ .and(ID.in(idsMatching.stream().map(FirebaseSubscriptionId::value)
+ .toList()))))
+ .map(this::toSubscription);
+
+ if (ids.size() <= IN_CLAUSE_MAX_SIZE) {
+ return queryPublisherFunction.apply(ids);
+ } else {
+ return Flux.fromIterable(Iterables.partition(ids, IN_CLAUSE_MAX_SIZE))
+ .flatMap(queryPublisherFunction);
+ }
+ }
+
+ public Mono deleteByUsername(Username username) {
+ return postgresExecutor.executeVoid(dslContext -> Mono.from(dslContext.deleteFrom(TABLE_NAME)
+ .where(USER.eq(username.asString()))));
+ }
+
+ public Mono deleteByUsernameAndId(Username username, FirebaseSubscriptionId id) {
+ return postgresExecutor.executeVoid(dslContext -> Mono.from(dslContext.deleteFrom(TABLE_NAME)
+ .where(USER.eq(username.asString()))
+ .and(ID.eq(id.value()))));
+ }
+
+ public Mono> updateType(Username username, FirebaseSubscriptionId id, Set newTypes) {
+ Preconditions.checkNotNull(newTypes, "newTypes should not be null");
+ return postgresExecutor.executeRow(dslContext -> Mono.from(dslContext.update(TABLE_NAME)
+ .set(TYPES, newTypes.stream().map(TypeName::asString).toArray(String[]::new))
+ .where(USER.eq(username.asString()))
+ .and(ID.eq(id.value()))
+ .returning(TYPES)))
+ .map(this::extractTypes);
+ }
+
+ public Mono updateExpireTime(Username username, FirebaseSubscriptionId id, ZonedDateTime newExpire) {
+ Preconditions.checkNotNull(newExpire, "newExpire should not be null");
+ return postgresExecutor.executeRow(dslContext -> Mono.from(dslContext.update(TABLE_NAME)
+ .set(EXPIRES, newExpire.toOffsetDateTime())
+ .where(USER.eq(username.asString()))
+ .and(ID.eq(id.value()))
+ .returning(EXPIRES)))
+ .map(record -> OFFSET_DATE_TIME_ZONED_DATE_TIME_FUNCTION.apply(record.get(EXPIRES)));
+ }
+
+ private FirebaseSubscription toSubscription(Record record) {
+ return new FirebaseSubscription(new FirebaseSubscriptionId(record.get(ID)),
+ record.get(DEVICE_CLIENT_ID),
+ record.get(FCM_TOKEN),
+ toExpires(record),
+ CollectionConverters.asScala(extractTypes(record)).toSeq());
+ }
+
+ private FirebaseSubscriptionExpiredTime toExpires(Record record) {
+ return new FirebaseSubscriptionExpiredTime(OFFSET_DATE_TIME_ZONED_DATE_TIME_FUNCTION.apply(record.get(EXPIRES)));
+ }
+
+ private Set extractTypes(Record record) {
+ return Arrays.stream(record.get(TYPES))
+ .map(string -> typeStateFactory.strictParse(string).right().get())
+ .collect(Collectors.toSet());
+ }
+}
\ No newline at end of file
diff --git a/tmail-backend/jmap/extensions-postgres/src/main/java/com/linagora/tmail/james/jmap/firebase/PostgresFirebaseSubscriptionRepository.java b/tmail-backend/jmap/extensions-postgres/src/main/java/com/linagora/tmail/james/jmap/firebase/PostgresFirebaseSubscriptionRepository.java
new file mode 100644
index 00000000000..75f6d41eeb7
--- /dev/null
+++ b/tmail-backend/jmap/extensions-postgres/src/main/java/com/linagora/tmail/james/jmap/firebase/PostgresFirebaseSubscriptionRepository.java
@@ -0,0 +1,114 @@
+package com.linagora.tmail.james.jmap.firebase;
+
+import static com.linagora.tmail.james.jmap.firebase.FirebaseSubscriptionHelper.evaluateExpiresTime;
+import static com.linagora.tmail.james.jmap.firebase.FirebaseSubscriptionHelper.isInThePast;
+
+import java.time.Clock;
+import java.time.ZonedDateTime;
+import java.util.Optional;
+import java.util.Set;
+
+import jakarta.inject.Inject;
+import jakarta.inject.Singleton;
+
+import org.apache.james.backends.postgres.utils.PostgresExecutor;
+import org.apache.james.core.Username;
+import org.apache.james.jmap.api.change.TypeStateFactory;
+import org.apache.james.jmap.api.model.TypeName;
+
+import com.linagora.tmail.james.jmap.model.ExpireTimeInvalidException;
+import com.linagora.tmail.james.jmap.model.FirebaseSubscription;
+import com.linagora.tmail.james.jmap.model.FirebaseSubscriptionCreationRequest;
+import com.linagora.tmail.james.jmap.model.FirebaseSubscriptionExpiredTime;
+import com.linagora.tmail.james.jmap.model.FirebaseSubscriptionId;
+import com.linagora.tmail.james.jmap.model.FirebaseSubscriptionNotFoundException;
+
+import reactor.core.publisher.Flux;
+import reactor.core.publisher.Mono;
+import scala.jdk.javaapi.OptionConverters;
+
+public class PostgresFirebaseSubscriptionRepository implements FirebaseSubscriptionRepository {
+ private final Clock clock;
+ private final TypeStateFactory typeStateFactory;
+ private final PostgresExecutor.Factory executorFactory;
+
+ @Inject
+ @Singleton
+ public PostgresFirebaseSubscriptionRepository(Clock clock, TypeStateFactory typeStateFactory, PostgresExecutor.Factory executorFactory) {
+ this.clock = clock;
+ this.typeStateFactory = typeStateFactory;
+ this.executorFactory = executorFactory;
+ }
+
+ @Override
+ public Mono save(Username username, FirebaseSubscriptionCreationRequest request) {
+ PostgresFirebaseSubscriptionDAO subscriptionDAO = firebaseSubscriptionDAO(username);
+
+ return validateInputExpires(request)
+ .then(Mono.defer(() -> {
+ FirebaseSubscription subscription = FirebaseSubscription.from(request,
+ evaluateExpiresTime(OptionConverters.toJava(request.expires().map(FirebaseSubscriptionExpiredTime::value)), clock));
+ return subscriptionDAO.save(username, subscription)
+ .thenReturn(subscription);
+ }));
+ }
+
+ private Mono