This is an automated email from the ASF dual-hosted git repository.
rcordier pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/james-project.git
The following commit(s) were added to refs/heads/master by this push:
new dc51178a3c JAMES-4047 DeconnectionRight mailet (#2325)
dc51178a3c is described below
commit dc51178a3c11053d9c5c733decdce2ae2a7c1396
Author: Benoit TELLIER <[email protected]>
AuthorDate: Wed Jul 3 05:37:39 2024 +0200
JAMES-4047 DeconnectionRight mailet (#2325)
---
.../pages/distributed/configure/mailets.adoc | 2 +
.../servers/partials/DeconnectionRight.adoc | 17 ++
.../james/transport/mailets/DeconnectionRight.java | 149 ++++++++++++++++
.../transport/mailets/DeconnectionRightTest.java | 195 +++++++++++++++++++++
4 files changed, 363 insertions(+)
diff --git a/docs/modules/servers/pages/distributed/configure/mailets.adoc
b/docs/modules/servers/pages/distributed/configure/mailets.adoc
index 1d7af110ab..d8ac82fc0c 100644
--- a/docs/modules/servers/pages/distributed/configure/mailets.adoc
+++ b/docs/modules/servers/pages/distributed/configure/mailets.adoc
@@ -20,6 +20,8 @@ include::partial$ContactExtractor.adoc[]
include::partial$ConvertTo7Bit.adoc[]
+include::partial$DeconnectionRight.adoc[]
+
include::partial$DKIMSign.adoc[]
include::partial$DKIMVerify.adoc[]
diff --git a/docs/modules/servers/partials/DeconnectionRight.adoc
b/docs/modules/servers/partials/DeconnectionRight.adoc
new file mode 100644
index 0000000000..b10df5652f
--- /dev/null
+++ b/docs/modules/servers/partials/DeconnectionRight.adoc
@@ -0,0 +1,17 @@
+=== DeconnectionRight
+
+Mailet to avoid people to receive emails outside working hour.
+
+Working hours are defined by a starting time and an end time, on weekdays from
monday to friday.
+
+ Emails received outside working hours are to be delayed to next working hours.
+
+Example:
+
+....xml
+<mailet match="all" class="DeconnectionRight">
+ <zoneId>Europe/Paris</zoneId>
+ <workDayStart>07:00:00</workDayStart>
+ <workDayEnd>20:00:00</metricName>
+</mailet>
+....
diff --git
a/server/mailet/mailets/src/main/java/org/apache/james/transport/mailets/DeconnectionRight.java
b/server/mailet/mailets/src/main/java/org/apache/james/transport/mailets/DeconnectionRight.java
new file mode 100644
index 0000000000..13b8d8dd8c
--- /dev/null
+++
b/server/mailet/mailets/src/main/java/org/apache/james/transport/mailets/DeconnectionRight.java
@@ -0,0 +1,149 @@
+/****************************************************************
+ * 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.transport.mailets;
+
+import java.time.Clock;
+import java.time.DayOfWeek;
+import java.time.Duration;
+import java.time.LocalDate;
+import java.time.LocalTime;
+import java.time.ZoneId;
+import java.time.ZonedDateTime;
+import java.util.Optional;
+import java.util.concurrent.TimeUnit;
+
+import jakarta.inject.Inject;
+import jakarta.mail.MessagingException;
+
+import org.apache.mailet.Mail;
+import org.apache.mailet.base.GenericMailet;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Preconditions;
+
+/**
+ * Mailet to avoid people to receive emails outside working hour.<br/>
+ *
+ * Working hours are defined by a starting time and an end time, on weekdays
from monday to friday.<br/>
+ *
+ * Emails received outside working hours are to be delayed to next working
hours.<br/>
+ *
+ * Example:<br/>
+ *
+ * <pre>
+ * <code>
+ * <mailet match="all" class="DeconnectionRight">
+ * <zoneId>Europe/Paris</zoneId>
+ * <workDayStart>07:00:00</workDayStart>
+ * <workDayEnd>20:00:00</metricName>
+ * </mailet>
+ * </code>
+ * </pre>
+ */
+public class DeconnectionRight extends GenericMailet {
+ private static final Logger LOGGER =
LoggerFactory.getLogger(DeconnectionRight.class);
+ private Clock clock;
+ private ZoneId zoneId;
+ private LocalTime workDayStart;
+ private LocalTime workDayEnd;
+
+ @Inject
+ public DeconnectionRight(Clock clock) {
+ this.clock = clock;
+ }
+
+ @VisibleForTesting
+ Optional<Duration> timeToWorkingHour(ZonedDateTime pointInTime) {
+ LocalTime localTime = pointInTime.toLocalTime();
+ DayOfWeek dayOfWeek = pointInTime.getDayOfWeek();
+ LocalDate localDate = pointInTime.toLocalDate();
+
+ if (dayOfWeek == DayOfWeek.SATURDAY) {
+ ZonedDateTime deliveryTime = localDate.plusDays(2)
+ .atTime(workDayStart)
+ .atZone(zoneId);
+
+ return Optional.of(Duration.between(pointInTime, deliveryTime));
+ }
+
+ if (dayOfWeek == DayOfWeek.SUNDAY) {
+ ZonedDateTime deliveryTime = localDate.plusDays(1)
+ .atTime(workDayStart)
+ .atZone(zoneId);
+
+ return Optional.of(Duration.between(pointInTime, deliveryTime));
+ }
+ if (localTime.equals(workDayStart) || localTime.equals(workDayEnd)) {
+ return Optional.empty();
+ }
+ if (localTime.isAfter(workDayStart) && localTime.isBefore(workDayEnd))
{
+ return Optional.empty();
+ }
+ if (localTime.isBefore(workDayStart)) {
+ ZonedDateTime deliveryTime = localDate
+ .atTime(workDayStart)
+ .atZone(zoneId);
+ return Optional.of(Duration.between(pointInTime, deliveryTime));
+ }
+ if (localTime.isAfter(workDayEnd) &&
dayOfWeek.equals(DayOfWeek.FRIDAY)) {
+ ZonedDateTime deliveryTime = localDate.plusDays(3)
+ .atTime(workDayStart)
+ .atZone(zoneId);
+ return Optional.of(Duration.between(pointInTime, deliveryTime));
+ }
+ if (localTime.isAfter(workDayEnd)) {
+ ZonedDateTime deliveryTime = localDate.plusDays(1)
+ .atTime(workDayStart)
+ .atZone(zoneId);
+
+ return Optional.of(Duration.between(pointInTime, deliveryTime));
+ }
+ LOGGER.error("Time at which mail was processed ({}) was not handled by
{}", pointInTime, DeconnectionRight.class);
+ return Optional.empty();
+ }
+
+ @Override
+ public void service(Mail mail) throws MessagingException {
+ ZonedDateTime now = ZonedDateTime.now(clock)
+ .withZoneSameInstant(zoneId);
+
+ timeToWorkingHour(now)
+ .ifPresent(duration -> {
+ try {
+ getMailetContext().sendMail(mail, Mail.DEFAULT,
duration.getSeconds(), TimeUnit.SECONDS);
+ } catch (MessagingException e) {
+ throw new RuntimeException(e);
+ }
+ // discard this mail and keep the scheduled copy
+ mail.setState(Mail.GHOST);
+ });
+ }
+
+ @Override
+ public void init() throws MessagingException {
+ zoneId = ZoneId.of(getInitParameter("zoneId"));
+ workDayStart = LocalTime.parse(getInitParameter("workDayStart"));
+ workDayEnd = LocalTime.parse(getInitParameter("workDayEnd"));
+
+ Preconditions.checkArgument(workDayEnd.isAfter(workDayStart));
+ }
+}
diff --git
a/server/mailet/mailets/src/test/java/org/apache/james/transport/mailets/DeconnectionRightTest.java
b/server/mailet/mailets/src/test/java/org/apache/james/transport/mailets/DeconnectionRightTest.java
new file mode 100644
index 0000000000..3aab478d44
--- /dev/null
+++
b/server/mailet/mailets/src/test/java/org/apache/james/transport/mailets/DeconnectionRightTest.java
@@ -0,0 +1,195 @@
+/****************************************************************
+ * 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.transport.mailets;
+
+import java.time.Clock;
+import java.time.Duration;
+import java.time.Instant;
+import java.time.ZoneId;
+import java.time.ZonedDateTime;
+import java.util.concurrent.TimeUnit;
+
+import org.apache.mailet.Mail;
+import org.apache.mailet.base.test.FakeMail;
+import org.apache.mailet.base.test.FakeMailContext;
+import org.apache.mailet.base.test.FakeMailetConfig;
+import org.assertj.core.api.SoftAssertions;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.ValueSource;
+
+class DeconnectionRightTest {
+ @ParameterizedTest
+ @ValueSource(strings = {
+ "2024-07-01T07:00:00.00Z",
+ "2024-07-01T10:15:30.00Z",
+ "2024-07-01T18:00:00.00Z",
+ "2024-07-01T18:00:00.01Z",
+ "2024-07-01T20:00:00.00Z"})
+ // 2024-07-01 => monday
+ void noDelaywithUTC(String dateTime) throws Exception {
+ Clock clock = Clock.fixed(Instant.parse(dateTime), ZoneId.of("UTC"));
+ DeconnectionRight testee = new DeconnectionRight(clock);
+ FakeMailContext mailetContext = FakeMailContext.defaultContext();
+ testee.init(FakeMailetConfig.builder()
+ .mailetContext(mailetContext)
+ .setProperty("zoneId", "UTC")
+ .setProperty("workDayStart", "07:00:00")
+ .setProperty("workDayEnd", "20:00:00")
+ .build());
+
+ FakeMail mail =
FakeMail.builder().name("aMail").state(Mail.DEFAULT).build();
+ testee.service(mail);
+
+ SoftAssertions.assertSoftly(softly -> {
+ softly.assertThat(mail.getState()).isEqualTo(Mail.DEFAULT);
+ softly.assertThat(mailetContext.getSentMails()).isEmpty();
+ });
+ }
+
+ @ParameterizedTest
+ @ValueSource(strings = {
+ "2024-07-01T20:00:00.01Z",
+ "2024-07-01T21:00:00.01Z",
+ "2024-07-02T03:00:00.01Z",
+ "2024-07-02T06:59:59.99Z"
+ })
+ // 2024-07-01 => monday
+ // 2024-07-01 => tuesday
+ void tomorrowWithUTC(String dateTime) throws Exception {
+ Instant now = Instant.parse(dateTime);
+ ZoneId zone = ZoneId.of("UTC");
+ Clock clock = Clock.fixed(now, zone);
+
+ DeconnectionRight testee = new DeconnectionRight(clock);
+ FakeMailContext mailetContext = FakeMailContext.defaultContext();
+ testee.init(FakeMailetConfig.builder()
+ .mailetContext(mailetContext)
+ .setProperty("zoneId", "UTC")
+ .setProperty("workDayStart", "07:00:00")
+ .setProperty("workDayEnd", "20:00:00")
+ .build());
+
+ FakeMail mail =
FakeMail.builder().name("aMail").state(Mail.DEFAULT).build();
+ testee.service(mail);
+
+ SoftAssertions.assertSoftly(softly -> {
+ softly.assertThat(mail.getState()).isEqualTo(Mail.GHOST);
+ softly.assertThat(mailetContext.getSentMails()).hasSize(1);
+
softly.assertThat(mailetContext.getSentMails().get(0).getDelay().get()).isEqualTo(new
FakeMailContext.Delay(
+ Duration.between(ZonedDateTime.ofInstant(now, zone),
+
ZonedDateTime.ofInstant(Instant.parse("2024-07-02T07:00:00.00Z"), zone))
+ .toSeconds(), TimeUnit.SECONDS
+ ));
+ });
+ }
+
+ @ParameterizedTest
+ @ValueSource(strings = {
+ "2024-07-05T20:00:00.01Z",
+ "2024-07-05T21:00:00.01Z",
+ "2024-07-06T03:00:00.01Z",
+ "2024-07-07T03:00:00.01Z",
+ "2024-07-08T06:59:59.99Z"
+ })
+ // 2024-07-01 => monday
+ void afterWeekendWithUTC(String dateTime) throws Exception {
+ Instant now = Instant.parse(dateTime);
+ ZoneId zone = ZoneId.of("UTC");
+ Clock clock = Clock.fixed(now, zone);
+
+ DeconnectionRight testee = new DeconnectionRight(clock);
+ FakeMailContext mailetContext = FakeMailContext.defaultContext();
+ testee.init(FakeMailetConfig.builder()
+ .mailetContext(mailetContext)
+ .setProperty("zoneId", "UTC")
+ .setProperty("workDayStart", "07:00:00")
+ .setProperty("workDayEnd", "20:00:00")
+ .build());
+
+ FakeMail mail =
FakeMail.builder().name("aMail").state(Mail.DEFAULT).build();
+ testee.service(mail);
+
+ SoftAssertions.assertSoftly(softly -> {
+ softly.assertThat(mail.getState()).isEqualTo(Mail.GHOST);
+ softly.assertThat(mailetContext.getSentMails()).hasSize(1);
+
softly.assertThat(mailetContext.getSentMails().get(0).getDelay().get()).isEqualTo(new
FakeMailContext.Delay(
+ Duration.between(ZonedDateTime.ofInstant(now, zone),
+
ZonedDateTime.ofInstant(Instant.parse("2024-07-08T07:00:00.00Z"), zone))
+ .toSeconds(), TimeUnit.SECONDS
+ ));
+ });
+ }
+
+ @ParameterizedTest
+ @ValueSource(strings = {
+ "2024-07-01T05:00:00.00Z",
+ "2024-07-01T10:15:30.00Z",
+ "2024-07-01T18:00:00.00Z"})
+ // 2024-07-01 => monday
+ void noDelaywithUTCPlus2(String dateTime) throws Exception {
+ Clock clock = Clock.fixed(Instant.parse(dateTime), ZoneId.of("UTC"));
+ DeconnectionRight testee = new DeconnectionRight(clock);
+ FakeMailContext mailetContext = FakeMailContext.defaultContext();
+ testee.init(FakeMailetConfig.builder()
+ .mailetContext(mailetContext)
+ .setProperty("zoneId", "Europe/Paris")
+ .setProperty("workDayStart", "07:00:00")
+ .setProperty("workDayEnd", "20:00:00")
+ .build());
+
+ FakeMail mail =
FakeMail.builder().name("aMail").state(Mail.DEFAULT).build();
+ testee.service(mail);
+
+ SoftAssertions.assertSoftly(softly -> {
+ softly.assertThat(mail.getState()).isEqualTo(Mail.DEFAULT);
+ softly.assertThat(mailetContext.getSentMails()).isEmpty();
+ });
+ }
+
+ @Test// 2024-03-30 => saturday before time change
+ void handleTimeChange() throws Exception {
+ Instant now = Instant.parse("2024-03-30T05:00:00.00Z");
+ ZoneId zone = ZoneId.of("UTC");
+ Clock clock = Clock.fixed(now, ZoneId.of("UTC"));
+ DeconnectionRight testee = new DeconnectionRight(clock);
+ FakeMailContext mailetContext = FakeMailContext.defaultContext();
+ testee.init(FakeMailetConfig.builder()
+ .mailetContext(mailetContext)
+ .setProperty("zoneId", "Europe/Paris")
+ .setProperty("workDayStart", "07:00:00")
+ .setProperty("workDayEnd", "20:00:00")
+ .build());
+
+ FakeMail mail =
FakeMail.builder().name("aMail").state(Mail.DEFAULT).build();
+ testee.service(mail);
+
+ SoftAssertions.assertSoftly(softly -> {
+ softly.assertThat(mail.getState()).isEqualTo(Mail.GHOST);
+ softly.assertThat(mailetContext.getSentMails()).hasSize(1);
+
softly.assertThat(mailetContext.getSentMails().get(0).getDelay().get()).isEqualTo(new
FakeMailContext.Delay(
+ Duration.between(ZonedDateTime.ofInstant(now, zone),
+ // 7am on next monday besides time change
+
ZonedDateTime.ofInstant(Instant.parse("2024-04-01T05:00:00.00Z"),
ZoneId.of("UTC")))
+ .toSeconds(), TimeUnit.SECONDS
+ ));
+ });
+ }
+}
\ No newline at end of file
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]