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]

Reply via email to