This is an automated email from the ASF dual-hosted git repository. rcordier pushed a commit to branch postgresql in repository https://gitbox.apache.org/repos/asf/james-project.git
commit 7dc4891429e775f42ffd798200af5031510f6a89 Author: Quan Tran <[email protected]> AuthorDate: Wed Feb 7 15:16:16 2024 +0700 JAMES-2586 Implement PostgresNotificationRegistry --- .../postgres/PostgresNotificationRegistry.java | 79 ++++++++++++++++++++++ .../postgres/PostgresNotificationRegistryDAO.java | 72 ++++++++++++++++++++ .../vacation/postgres/PostgresVacationModule.java | 32 ++++++++- .../postgres/PostgresNotificationRegistryTest.java | 52 ++++++++++++++ 4 files changed, 232 insertions(+), 3 deletions(-) diff --git a/server/data/data-postgres/src/main/java/org/apache/james/vacation/postgres/PostgresNotificationRegistry.java b/server/data/data-postgres/src/main/java/org/apache/james/vacation/postgres/PostgresNotificationRegistry.java new file mode 100644 index 0000000000..7dd3238ac8 --- /dev/null +++ b/server/data/data-postgres/src/main/java/org/apache/james/vacation/postgres/PostgresNotificationRegistry.java @@ -0,0 +1,79 @@ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + ****************************************************************/ + +package org.apache.james.vacation.postgres; + +import java.time.ZonedDateTime; +import java.util.Optional; + +import javax.inject.Inject; + +import org.apache.james.backends.postgres.utils.PostgresExecutor; +import org.apache.james.core.Username; +import org.apache.james.util.date.ZonedDateTimeProvider; +import org.apache.james.vacation.api.AccountId; +import org.apache.james.vacation.api.NotificationRegistry; +import org.apache.james.vacation.api.RecipientId; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import reactor.core.publisher.Mono; + +public class PostgresNotificationRegistry implements NotificationRegistry { + private static final Logger LOGGER = LoggerFactory.getLogger(PostgresNotificationRegistry.class); + + private final ZonedDateTimeProvider zonedDateTimeProvider; + private final PostgresExecutor.Factory executorFactory; + + @Inject + public PostgresNotificationRegistry(ZonedDateTimeProvider zonedDateTimeProvider, + PostgresExecutor.Factory executorFactory) { + this.zonedDateTimeProvider = zonedDateTimeProvider; + this.executorFactory = executorFactory; + } + + @Override + public Mono<Void> register(AccountId accountId, RecipientId recipientId, Optional<ZonedDateTime> expiryDate) { + if (isValid(expiryDate)) { + return notificationRegistryDAO(accountId).register(accountId, recipientId, expiryDate); + } else { + LOGGER.warn("Invalid vacation notification expiry date for {} {} : {}", accountId, recipientId, expiryDate); + return Mono.empty(); + } + } + + @Override + public Mono<Boolean> isRegistered(AccountId accountId, RecipientId recipientId) { + return notificationRegistryDAO(accountId).isRegistered(accountId, recipientId); + } + + @Override + public Mono<Void> flush(AccountId accountId) { + return notificationRegistryDAO(accountId).flush(accountId); + } + + private boolean isValid(Optional<ZonedDateTime> expiryDate) { + return expiryDate.isEmpty() || expiryDate.get().isAfter(zonedDateTimeProvider.get()); + } + + private PostgresNotificationRegistryDAO notificationRegistryDAO(AccountId accountId) { + return new PostgresNotificationRegistryDAO(executorFactory.create(Username.of(accountId.getIdentifier()).getDomainPart()), + zonedDateTimeProvider); + } +} diff --git a/server/data/data-postgres/src/main/java/org/apache/james/vacation/postgres/PostgresNotificationRegistryDAO.java b/server/data/data-postgres/src/main/java/org/apache/james/vacation/postgres/PostgresNotificationRegistryDAO.java new file mode 100644 index 0000000000..4638ad9805 --- /dev/null +++ b/server/data/data-postgres/src/main/java/org/apache/james/vacation/postgres/PostgresNotificationRegistryDAO.java @@ -0,0 +1,72 @@ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + ****************************************************************/ + +package org.apache.james.vacation.postgres; + +import static org.apache.james.vacation.postgres.PostgresVacationModule.PostgresVacationNotificationRegistryTable.ACCOUNT_ID; +import static org.apache.james.vacation.postgres.PostgresVacationModule.PostgresVacationNotificationRegistryTable.EXPIRY_DATE; +import static org.apache.james.vacation.postgres.PostgresVacationModule.PostgresVacationNotificationRegistryTable.RECIPIENT_ID; +import static org.apache.james.vacation.postgres.PostgresVacationModule.PostgresVacationNotificationRegistryTable.TABLE_NAME; + +import java.time.LocalDateTime; +import java.time.ZoneOffset; +import java.time.ZonedDateTime; +import java.util.Optional; + +import org.apache.james.backends.postgres.utils.PostgresExecutor; +import org.apache.james.util.date.ZonedDateTimeProvider; +import org.apache.james.vacation.api.AccountId; +import org.apache.james.vacation.api.RecipientId; + +import reactor.core.publisher.Mono; + +public class PostgresNotificationRegistryDAO { + private final PostgresExecutor postgresExecutor; + private final ZonedDateTimeProvider zonedDateTimeProvider; + + public PostgresNotificationRegistryDAO(PostgresExecutor postgresExecutor, + ZonedDateTimeProvider zonedDateTimeProvider) { + this.postgresExecutor = postgresExecutor; + this.zonedDateTimeProvider = zonedDateTimeProvider; + } + + public Mono<Void> register(AccountId accountId, RecipientId recipientId, Optional<ZonedDateTime> expiryDate) { + return postgresExecutor.executeVoid(dsl -> Mono.from(dsl.insertInto(TABLE_NAME) + .set(ACCOUNT_ID, accountId.getIdentifier()) + .set(RECIPIENT_ID, recipientId.getAsString()) + .set(EXPIRY_DATE, expiryDate.map(zonedDateTime -> zonedDateTime.withZoneSameInstant(ZoneOffset.UTC).toLocalDateTime()) + .orElse(null)))); + } + + public Mono<Boolean> isRegistered(AccountId accountId, RecipientId recipientId) { + LocalDateTime currentUTCTime = zonedDateTimeProvider.get().withZoneSameInstant(ZoneOffset.UTC).toLocalDateTime(); + + return postgresExecutor.executeRow(dsl -> Mono.from(dsl.select(ACCOUNT_ID) + .from(TABLE_NAME) + .where(ACCOUNT_ID.eq(accountId.getIdentifier()), + RECIPIENT_ID.eq(recipientId.getAsString()), + EXPIRY_DATE.ge(currentUTCTime).or(EXPIRY_DATE.isNull())))) + .hasElement(); + } + + public Mono<Void> flush(AccountId accountId) { + return postgresExecutor.executeVoid(dsl -> Mono.from(dsl.deleteFrom(TABLE_NAME) + .where(ACCOUNT_ID.eq(accountId.getIdentifier())))); + } +} diff --git a/server/data/data-postgres/src/main/java/org/apache/james/vacation/postgres/PostgresVacationModule.java b/server/data/data-postgres/src/main/java/org/apache/james/vacation/postgres/PostgresVacationModule.java index 8149ed53a7..f306651822 100644 --- a/server/data/data-postgres/src/main/java/org/apache/james/vacation/postgres/PostgresVacationModule.java +++ b/server/data/data-postgres/src/main/java/org/apache/james/vacation/postgres/PostgresVacationModule.java @@ -19,11 +19,10 @@ package org.apache.james.vacation.postgres; -import static org.apache.james.vacation.postgres.PostgresVacationModule.PostgresVacationResponseTable.TABLE; - import java.time.LocalDateTime; 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; @@ -59,7 +58,34 @@ public interface PostgresVacationModule { .build(); } + interface PostgresVacationNotificationRegistryTable { + Table<Record> TABLE_NAME = DSL.table("vacation_notification_registry"); + + Field<String> ACCOUNT_ID = DSL.field("account_id", SQLDataType.VARCHAR.notNull()); + Field<String> RECIPIENT_ID = DSL.field("recipient_id", SQLDataType.VARCHAR.notNull()); + Field<LocalDateTime> EXPIRY_DATE = DSL.field("expiry_date", PostgresCommons.DataTypes.TIMESTAMP); + + PostgresTable TABLE = PostgresTable.name(TABLE_NAME.getName()) + .createTableStep(((dsl, tableName) -> dsl.createTableIfNotExists(tableName) + .column(ACCOUNT_ID) + .column(RECIPIENT_ID) + .column(EXPIRY_DATE) + .constraint(DSL.primaryKey(ACCOUNT_ID, RECIPIENT_ID)))) + .supportsRowLevelSecurity() + .build(); + + PostgresIndex ACCOUNT_ID_INDEX = PostgresIndex.name("vacation_notification_registry_accountId_index") + .createIndexStep((dsl, indexName) -> dsl.createIndexIfNotExists(indexName) + .on(TABLE_NAME, ACCOUNT_ID)); + + PostgresIndex FULL_COMPOSITE_INDEX = PostgresIndex.name("vacation_notification_registry_accountId_recipientId_expiryDate_index") + .createIndexStep((dsl, indexName) -> dsl.createIndexIfNotExists(indexName) + .on(TABLE_NAME, ACCOUNT_ID, RECIPIENT_ID, EXPIRY_DATE)); + } + PostgresModule MODULE = PostgresModule.builder() - .addTable(TABLE) + .addTable(PostgresVacationResponseTable.TABLE) + .addTable(PostgresVacationNotificationRegistryTable.TABLE) + .addIndex(PostgresVacationNotificationRegistryTable.ACCOUNT_ID_INDEX, PostgresVacationNotificationRegistryTable.FULL_COMPOSITE_INDEX) .build(); } diff --git a/server/data/data-postgres/src/test/java/org/apache/james/vacation/postgres/PostgresNotificationRegistryTest.java b/server/data/data-postgres/src/test/java/org/apache/james/vacation/postgres/PostgresNotificationRegistryTest.java new file mode 100644 index 0000000000..19c55dbf2a --- /dev/null +++ b/server/data/data-postgres/src/test/java/org/apache/james/vacation/postgres/PostgresNotificationRegistryTest.java @@ -0,0 +1,52 @@ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + ****************************************************************/ + +package org.apache.james.vacation.postgres; + +import org.apache.james.backends.postgres.PostgresExtension; +import org.apache.james.backends.postgres.PostgresModule; +import org.apache.james.core.MailAddress; +import org.apache.james.vacation.api.NotificationRegistry; +import org.apache.james.vacation.api.NotificationRegistryContract; +import org.apache.james.vacation.api.RecipientId; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.extension.RegisterExtension; + +class PostgresNotificationRegistryTest implements NotificationRegistryContract { + @RegisterExtension + static PostgresExtension postgresExtension = PostgresExtension.withRowLevelSecurity(PostgresModule.aggregateModules(PostgresVacationModule.MODULE)); + + NotificationRegistry notificationRegistry; + RecipientId recipientId; + + @BeforeEach + public void setUp() throws Exception { + notificationRegistry = new PostgresNotificationRegistry(zonedDateTimeProvider, postgresExtension.getExecutorFactory()); + recipientId = RecipientId.fromMailAddress(new MailAddress("[email protected]")); + } + @Override + public NotificationRegistry notificationRegistry() { + return notificationRegistry; + } + + @Override + public RecipientId recipientId() { + return recipientId; + } +} --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
