diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
index 96ef44f..30d17ce 100644
--- a/.github/workflows/main.yml
+++ b/.github/workflows/main.yml
@@ -11,10 +11,10 @@ jobs:
runs-on: ubuntu-24.04
timeout-minutes: 10
steps:
- - uses: actions/checkout@v5
+ - uses: actions/checkout@v7
- uses: aboutbits/github-actions-java/setup-with-maven@v4
with:
- java-version: 21
+ java-version: 25
- name: Test
env:
GITHUB_USER_NAME: ${{ github.actor }}
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index a2b0643..935be73 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -13,13 +13,13 @@ jobs:
runs-on: ubuntu-24.04
timeout-minutes: 15
steps:
- - uses: actions/checkout@v5
+ - uses: actions/checkout@v7
with:
token: ${{ secrets.GH_PERSONAL_ACCESS_TOKEN }}
- uses: aboutbits/github-actions-base/git-setup@v2
- uses: aboutbits/github-actions-java/setup-with-maven@v4
with:
- java-version: 21
+ java-version: 25
- uses: aboutbits/github-actions-java/set-version-with-maven@v4
with:
version: "${{ github.event.inputs.version }}"
diff --git a/.idea/misc.xml b/.idea/misc.xml
index 68a40a6..cc6aff1 100644
--- a/.idea/misc.xml
+++ b/.idea/misc.xml
@@ -8,8 +8,8 @@
-
+
-
\ No newline at end of file
+
diff --git a/pom.xml b/pom.xml
index 6de6955..214ad04 100644
--- a/pom.xml
+++ b/pom.xml
@@ -6,7 +6,7 @@
org.springframework.boot
spring-boot-starter-parent
- 3.2.2
+ 4.0.4
@@ -16,10 +16,22 @@
Spring Boot Email Service
- 21
- 1.5.5.Final
+ 25
+ 1.6.3
+ 0.13.2
+ 2.49.0
+
+
+
+ it.aboutbits
+ archunit-toolbox
+ 1.1.0
+
+
+
+
org.springframework.boot
@@ -57,41 +69,51 @@
org.postgresql
postgresql
-
- io.hypersistence
- hypersistence-utils-hibernate-63
- 3.7.0
-
com.fasterxml.jackson.core
jackson-databind
-
- com.github.javafaker
- javafaker
- 1.0.2
+ net.datafaker
+ datafaker
+ 2.7.0
test
org.testcontainers
testcontainers
- 1.18.0
+ 2.0.5
test
org.testcontainers
- junit-jupiter
- 1.18.0
+ testcontainers-junit-jupiter
+ 2.0.5
test
org.testcontainers
- postgresql
- 1.18.0
+ testcontainers-postgresql
+ 2.0.5
test
+
+ com.tngtech.archunit
+ archunit-junit5
+ 1.4.2
+ test
+
+
+ it.aboutbits
+ archunit-toolbox
+ test
+
+
+ it.aboutbits
+ spring-boot-testing
+ 2.5.0
+
@@ -99,7 +121,7 @@
org.apache.maven.plugins
maven-compiler-plugin
- 3.11.0
+ 3.15.0
${java.version}
${java.version}
@@ -119,17 +141,43 @@
lombok-mapstruct-binding
0.2.0
+
+ com.google.errorprone
+ error_prone_core
+ ${errorprone.version}
+
+
+ com.uber.nullaway
+ nullaway
+ ${nullaway.version}
+
-Amapstruct.defaultComponentModel=spring
-Amapstruct.unmappedTargetPolicy=ERROR
+ -XDcompilePolicy=simple
+ --should-stop=ifError=FLOW
+
+ -Xplugin:ErrorProne -XepDisableAllChecks -XepOpt:NullAway:AnnotatedPackages=it.aboutbits.springboot.emailservice -XepOpt:NullAway:JSpecifyMode=true -Xep:NullAway:ERROR -XepOpt:NullAway:ExcludedFieldAnnotations=org.mockito.InjectMocks,org.mockito.Mock
+
+ -J--add-exports=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED
+ -J--add-exports=jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED
+ -J--add-exports=jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED
+ -J--add-exports=jdk.compiler/com.sun.tools.javac.model=ALL-UNNAMED
+ -J--add-exports=jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED
+ -J--add-exports=jdk.compiler/com.sun.tools.javac.processing=ALL-UNNAMED
+ -J--add-exports=jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED
+ -J--add-exports=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED
+ -J--add-opens=jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED
+ -J--add-opens=jdk.compiler/com.sun.tools.javac.comp=ALL-UNNAMED
+ true
org.apache.maven.plugins
maven-checkstyle-plugin
- 3.2.1
+ 3.6.0
checkstyle.xml
@@ -154,7 +202,7 @@
com.puppycrawl.tools
checkstyle
- 10.3.4
+ 13.5.0
diff --git a/readme.md b/readme.md
index f0aa6b3..19c5434 100644
--- a/readme.md
+++ b/readme.md
@@ -4,7 +4,7 @@ A reusable mailer service to send emails.
## Setup
-Add the mailer service to the classpath by adding the following maven dependency. Versions haven be found [here](../../packages)
+Add the mailer service to the classpath by adding the following maven dependency. Versions can be found [here](../../packages)
```xml
diff --git a/src/main/java/it/aboutbits/springboot/emailservice/EmailServiceConfiguration.java b/src/main/java/it/aboutbits/springboot/emailservice/EmailServiceConfiguration.java
index ae58265..535054e 100644
--- a/src/main/java/it/aboutbits/springboot/emailservice/EmailServiceConfiguration.java
+++ b/src/main/java/it/aboutbits/springboot/emailservice/EmailServiceConfiguration.java
@@ -15,6 +15,7 @@
import it.aboutbits.springboot.emailservice.lib.application.UnavailableAttachmentDataSource;
import it.aboutbits.springboot.emailservice.lib.jpa.EmailRepository;
import jakarta.persistence.EntityManager;
+import org.jspecify.annotations.NullMarked;
import org.springframework.boot.autoconfigure.AutoConfigurationPackage;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
@@ -25,6 +26,7 @@
import java.util.List;
@AutoConfigurationPackage
+@NullMarked
public class EmailServiceConfiguration {
@Bean(initMethod = "migrate")
@ConditionalOnProperty(value = "aboutbits.emailservice.migrations.enabled", matchIfMissing = true)
diff --git a/src/main/java/it/aboutbits/springboot/emailservice/lib/AttachmentCleanerCallback.java b/src/main/java/it/aboutbits/springboot/emailservice/lib/AttachmentCleanerCallback.java
index 680804e..ef23561 100644
--- a/src/main/java/it/aboutbits/springboot/emailservice/lib/AttachmentCleanerCallback.java
+++ b/src/main/java/it/aboutbits/springboot/emailservice/lib/AttachmentCleanerCallback.java
@@ -1,5 +1,8 @@
package it.aboutbits.springboot.emailservice.lib;
+import org.jspecify.annotations.NullMarked;
+
+@NullMarked
public interface AttachmentCleanerCallback {
void report(Report report);
diff --git a/src/main/java/it/aboutbits/springboot/emailservice/lib/AttachmentDataSource.java b/src/main/java/it/aboutbits/springboot/emailservice/lib/AttachmentDataSource.java
index f05b85a..813e1e4 100644
--- a/src/main/java/it/aboutbits/springboot/emailservice/lib/AttachmentDataSource.java
+++ b/src/main/java/it/aboutbits/springboot/emailservice/lib/AttachmentDataSource.java
@@ -1,9 +1,11 @@
package it.aboutbits.springboot.emailservice.lib;
import it.aboutbits.springboot.emailservice.lib.exception.AttachmentException;
+import org.jspecify.annotations.NullMarked;
import java.io.InputStream;
+@NullMarked
public interface AttachmentDataSource {
InputStream getAttachmentPayload(long fileReference) throws AttachmentException;
diff --git a/src/main/java/it/aboutbits/springboot/emailservice/lib/EmailAttachmentDto.java b/src/main/java/it/aboutbits/springboot/emailservice/lib/EmailAttachmentDto.java
index 3f138df..dbf6f2d 100644
--- a/src/main/java/it/aboutbits/springboot/emailservice/lib/EmailAttachmentDto.java
+++ b/src/main/java/it/aboutbits/springboot/emailservice/lib/EmailAttachmentDto.java
@@ -1,7 +1,9 @@
package it.aboutbits.springboot.emailservice.lib;
import it.aboutbits.springboot.emailservice.lib.model.Email;
+import org.jspecify.annotations.NullMarked;
+@NullMarked
public record EmailAttachmentDto(
long id,
diff --git a/src/main/java/it/aboutbits/springboot/emailservice/lib/EmailDto.java b/src/main/java/it/aboutbits/springboot/emailservice/lib/EmailDto.java
index d910922..11ff3ac 100644
--- a/src/main/java/it/aboutbits/springboot/emailservice/lib/EmailDto.java
+++ b/src/main/java/it/aboutbits/springboot/emailservice/lib/EmailDto.java
@@ -1,11 +1,13 @@
package it.aboutbits.springboot.emailservice.lib;
-import org.springframework.lang.Nullable;
+import org.jspecify.annotations.NullMarked;
+import org.jspecify.annotations.Nullable;
import java.time.OffsetDateTime;
import java.util.List;
import java.util.Set;
+@NullMarked
public record EmailDto(
long id,
@@ -29,9 +31,12 @@ public record EmailDto(
Set attachments,
OffsetDateTime scheduledAt,
+ @Nullable
OffsetDateTime sentAt,
+ @Nullable
OffsetDateTime errorAt,
+ @Nullable
String errorMessage,
OffsetDateTime createdAt,
diff --git a/src/main/java/it/aboutbits/springboot/emailservice/lib/EmailSchedulerCallback.java b/src/main/java/it/aboutbits/springboot/emailservice/lib/EmailSchedulerCallback.java
index 675b6ca..2db9acd 100644
--- a/src/main/java/it/aboutbits/springboot/emailservice/lib/EmailSchedulerCallback.java
+++ b/src/main/java/it/aboutbits/springboot/emailservice/lib/EmailSchedulerCallback.java
@@ -1,5 +1,8 @@
package it.aboutbits.springboot.emailservice.lib;
+import org.jspecify.annotations.NullMarked;
+
+@NullMarked
public interface EmailSchedulerCallback {
void report(Report report);
diff --git a/src/main/java/it/aboutbits/springboot/emailservice/lib/EmailState.java b/src/main/java/it/aboutbits/springboot/emailservice/lib/EmailState.java
index f053425..4d2ed5d 100644
--- a/src/main/java/it/aboutbits/springboot/emailservice/lib/EmailState.java
+++ b/src/main/java/it/aboutbits/springboot/emailservice/lib/EmailState.java
@@ -1,5 +1,8 @@
package it.aboutbits.springboot.emailservice.lib;
+import org.jspecify.annotations.NullMarked;
+
+@NullMarked
public enum EmailState {
PENDING,
SENT,
diff --git a/src/main/java/it/aboutbits/springboot/emailservice/lib/application/CleanupAttachmentFiles.java b/src/main/java/it/aboutbits/springboot/emailservice/lib/application/CleanupAttachmentFiles.java
index 7476eb6..df7209d 100644
--- a/src/main/java/it/aboutbits/springboot/emailservice/lib/application/CleanupAttachmentFiles.java
+++ b/src/main/java/it/aboutbits/springboot/emailservice/lib/application/CleanupAttachmentFiles.java
@@ -5,6 +5,7 @@
import it.aboutbits.springboot.emailservice.lib.exception.AttachmentException;
import lombok.RequiredArgsConstructor;
import lombok.extern.log4j.Log4j2;
+import org.jspecify.annotations.NullMarked;
import org.springframework.scheduling.annotation.Scheduled;
import java.time.Duration;
@@ -12,6 +13,7 @@
@RequiredArgsConstructor
@Log4j2
+@NullMarked
public class CleanupAttachmentFiles {
private static final String JOB_DESCRIPTION = "Cleanup attachments of sent Emails.";
diff --git a/src/main/java/it/aboutbits/springboot/emailservice/lib/application/EmailAttachmentMapper.java b/src/main/java/it/aboutbits/springboot/emailservice/lib/application/EmailAttachmentMapper.java
index e8d572a..8fc8f8b 100644
--- a/src/main/java/it/aboutbits/springboot/emailservice/lib/application/EmailAttachmentMapper.java
+++ b/src/main/java/it/aboutbits/springboot/emailservice/lib/application/EmailAttachmentMapper.java
@@ -2,11 +2,16 @@
import it.aboutbits.springboot.emailservice.lib.EmailAttachmentDto;
import it.aboutbits.springboot.emailservice.lib.model.EmailAttachment;
+import org.jspecify.annotations.NullMarked;
+import org.jspecify.annotations.NullUnmarked;
+import org.mapstruct.AnnotateWith;
import org.mapstruct.Mapper;
import java.util.List;
@Mapper
+@AnnotateWith(NullUnmarked.class)
+@NullMarked
public interface EmailAttachmentMapper {
EmailAttachmentDto toDto(EmailAttachment model);
diff --git a/src/main/java/it/aboutbits/springboot/emailservice/lib/application/EmailMapper.java b/src/main/java/it/aboutbits/springboot/emailservice/lib/application/EmailMapper.java
index c5aa008..48aa8c9 100644
--- a/src/main/java/it/aboutbits/springboot/emailservice/lib/application/EmailMapper.java
+++ b/src/main/java/it/aboutbits/springboot/emailservice/lib/application/EmailMapper.java
@@ -2,6 +2,9 @@
import it.aboutbits.springboot.emailservice.lib.EmailDto;
import it.aboutbits.springboot.emailservice.lib.model.Email;
+import org.jspecify.annotations.NullMarked;
+import org.jspecify.annotations.NullUnmarked;
+import org.mapstruct.AnnotateWith;
import org.mapstruct.Mapper;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
@@ -9,6 +12,8 @@
import java.util.List;
@Mapper(uses = EmailAttachmentMapper.class)
+@AnnotateWith(NullUnmarked.class)
+@NullMarked
public interface EmailMapper {
EmailDto toDto(Email model);
diff --git a/src/main/java/it/aboutbits/springboot/emailservice/lib/application/EmailParameter.java b/src/main/java/it/aboutbits/springboot/emailservice/lib/application/EmailParameter.java
index 21cd008..841032d 100644
--- a/src/main/java/it/aboutbits/springboot/emailservice/lib/application/EmailParameter.java
+++ b/src/main/java/it/aboutbits/springboot/emailservice/lib/application/EmailParameter.java
@@ -3,9 +3,9 @@
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotEmpty;
import lombok.Builder;
-import lombok.NonNull;
import lombok.Singular;
-import org.springframework.lang.Nullable;
+import org.jspecify.annotations.NullMarked;
+import org.jspecify.annotations.Nullable;
import java.io.InputStream;
import java.time.OffsetDateTime;
@@ -13,31 +13,25 @@
import java.util.Set;
@Builder
+@NullMarked
public record EmailParameter(
- @NonNull
OffsetDateTime scheduledAt,
- @NonNull
Email email
) {
-
@Builder
public record Email(
- @NonNull
@NotBlank
String subject,
@Singular
- @NonNull
@NotEmpty
List recipients,
- @NonNull String textBody,
- @NonNull String htmlBody,
+ String textBody,
+ String htmlBody,
- @NonNull
@NotBlank
String fromAddress,
- @NonNull
@NotBlank
String fromName,
@@ -47,17 +41,13 @@ public record Email(
String replyToName,
@Singular
- @NonNull
Set attachments
) {
@Builder
public record Attachment(
- @NonNull
InputStream payload,
- @NonNull
@NotBlank
String fileName,
- @NonNull
@NotBlank
String contentType
) {
diff --git a/src/main/java/it/aboutbits/springboot/emailservice/lib/application/EmailServiceMigrator.java b/src/main/java/it/aboutbits/springboot/emailservice/lib/application/EmailServiceMigrator.java
index 07d6795..617732a 100644
--- a/src/main/java/it/aboutbits/springboot/emailservice/lib/application/EmailServiceMigrator.java
+++ b/src/main/java/it/aboutbits/springboot/emailservice/lib/application/EmailServiceMigrator.java
@@ -1,9 +1,11 @@
package it.aboutbits.springboot.emailservice.lib.application;
import lombok.extern.slf4j.Slf4j;
+import org.jspecify.annotations.NullMarked;
import org.springframework.jdbc.core.JdbcTemplate;
@Slf4j
+@NullMarked
public class EmailServiceMigrator {
private final JdbcTemplate jdbcTemplate;
diff --git a/src/main/java/it/aboutbits/springboot/emailservice/lib/application/ManageEmail.java b/src/main/java/it/aboutbits/springboot/emailservice/lib/application/ManageEmail.java
index 1fe4c24..3346aac 100644
--- a/src/main/java/it/aboutbits/springboot/emailservice/lib/application/ManageEmail.java
+++ b/src/main/java/it/aboutbits/springboot/emailservice/lib/application/ManageEmail.java
@@ -11,10 +11,10 @@
import it.aboutbits.springboot.emailservice.lib.model.EmailAttachment;
import jakarta.mail.MessagingException;
import jakarta.validation.Valid;
-import lombok.NonNull;
import lombok.extern.slf4j.Slf4j;
+import org.jspecify.annotations.NullMarked;
+import org.jspecify.annotations.Nullable;
import org.springframework.core.io.ByteArrayResource;
-import org.springframework.lang.Nullable;
import org.springframework.mail.MailException;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.mail.javamail.MimeMessageHelper;
@@ -29,6 +29,7 @@
@Validated
@Slf4j
+@NullMarked
public class ManageEmail {
private final EmailRepository emailRepository;
private final JavaMailSender mailSender;
@@ -41,14 +42,13 @@ public ManageEmail(
AttachmentDataSource attachmentDataSource,
final EmailMapper emailMapper
) {
-
this.emailRepository = emailRepository;
this.mailSender = mailSender;
this.attachmentDataSource = attachmentDataSource;
this.emailMapper = emailMapper;
}
- public EmailDto schedule(@NonNull @Valid EmailParameter parameter) throws EmailException {
+ public EmailDto schedule(@Valid EmailParameter parameter) throws EmailException {
Email email;
try {
email = fromParameter(parameter);
@@ -61,7 +61,7 @@ public EmailDto schedule(@NonNull @Valid EmailParameter parameter) throws EmailE
return emailMapper.toDto(savedEmail);
}
- public EmailDto sendOrFail(@NonNull @Valid EmailParameter parameter) throws EmailException {
+ public EmailDto sendOrFail(@Valid EmailParameter parameter) throws EmailException {
Email email;
try {
email = fromParameter(parameter);
@@ -72,7 +72,8 @@ public EmailDto sendOrFail(@NonNull @Valid EmailParameter parameter) throws Emai
var savedEmail = send(email);
if (savedEmail.hasFailed()) {
- throw new EmailException(savedEmail.getErrorMessage());
+ throw new EmailException("Failed to send email [id=%s, providerMessage=%s]"
+ .formatted(savedEmail.getId(), savedEmail.getErrorMessage()));
}
return emailMapper.toDto(savedEmail);
diff --git a/src/main/java/it/aboutbits/springboot/emailservice/lib/application/QueryEmail.java b/src/main/java/it/aboutbits/springboot/emailservice/lib/application/QueryEmail.java
index 9714345..7c43588 100644
--- a/src/main/java/it/aboutbits/springboot/emailservice/lib/application/QueryEmail.java
+++ b/src/main/java/it/aboutbits/springboot/emailservice/lib/application/QueryEmail.java
@@ -7,6 +7,7 @@
import it.aboutbits.springboot.emailservice.lib.model.Email;
import jakarta.persistence.EntityManager;
import lombok.RequiredArgsConstructor;
+import org.jspecify.annotations.NullMarked;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort;
@@ -18,13 +19,18 @@
import java.util.Optional;
@RequiredArgsConstructor
+@NullMarked
public class QueryEmail {
private final EmailRepository emailRepository;
private final EmailMapper emailMapper;
private final EntityManager entityManager;
public Page paginatedByState(EmailState state, PageRequest pageParameter) {
- var pageRequest = PageRequest.of(pageParameter.getPageNumber(), pageParameter.getPageSize(), Sort.by("updatedAt"));
+ var pageRequest = PageRequest.of(
+ pageParameter.getPageNumber(),
+ pageParameter.getPageSize(),
+ Sort.by("updatedAt")
+ );
return emailMapper.toDto(emailRepository.findByState(state, pageRequest));
}
@@ -38,12 +44,14 @@ public List byIds(Collection ids) {
List readyToSend() {
var entityGraph = entityManager.getEntityGraph("email_service_emails-entity-graph");
- return entityManager.createQuery("""
- SELECT e from Email e WHERE e.scheduledAt < :scheduledBefore AND e.state IN (
- it.aboutbits.springboot.emailservice.lib.EmailState.PENDING,
- it.aboutbits.springboot.emailservice.lib.EmailState.ERROR
- )
- """, Email.class)
+ return entityManager.createQuery(
+ """
+ SELECT e from Email e WHERE e.scheduledAt < :scheduledBefore AND e.state IN (
+ it.aboutbits.springboot.emailservice.lib.EmailState.PENDING,
+ it.aboutbits.springboot.emailservice.lib.EmailState.ERROR
+ )
+ """, Email.class
+ )
.setParameter("scheduledBefore", OffsetDateTime.now())
.setHint("jakarta.persistence.fetchgraph", entityGraph)
.getResultList();
@@ -51,9 +59,11 @@ List readyToSend() {
List readyToCleanup() {
var entityGraph = entityManager.getEntityGraph("email_service_emails-entity-graph");
- return entityManager.createQuery("""
- SELECT e from Email e WHERE e.attachmentsCleaned=false AND e.state=it.aboutbits.springboot.emailservice.lib.EmailState.SENT
- """, Email.class)
+ return entityManager.createQuery(
+ """
+ SELECT e from Email e WHERE e.attachmentsCleaned=false AND e.state=it.aboutbits.springboot.emailservice.lib.EmailState.SENT
+ """, Email.class
+ )
.setHint("jakarta.persistence.fetchgraph", entityGraph)
.getResultList();
}
diff --git a/src/main/java/it/aboutbits/springboot/emailservice/lib/application/SendScheduledEmails.java b/src/main/java/it/aboutbits/springboot/emailservice/lib/application/SendScheduledEmails.java
index 107512b..f1789d0 100644
--- a/src/main/java/it/aboutbits/springboot/emailservice/lib/application/SendScheduledEmails.java
+++ b/src/main/java/it/aboutbits/springboot/emailservice/lib/application/SendScheduledEmails.java
@@ -4,6 +4,7 @@
import it.aboutbits.springboot.emailservice.lib.EmailSchedulerCallback;
import lombok.RequiredArgsConstructor;
import lombok.extern.log4j.Log4j2;
+import org.jspecify.annotations.NullMarked;
import org.springframework.scheduling.annotation.Scheduled;
import java.time.Duration;
@@ -11,6 +12,7 @@
@RequiredArgsConstructor
@Log4j2
+@NullMarked
public class SendScheduledEmails {
private static final String JOB_DESCRIPTION = "Sending open and failed email notifications.";
diff --git a/src/main/java/it/aboutbits/springboot/emailservice/lib/application/UnavailableAttachmentDataSource.java b/src/main/java/it/aboutbits/springboot/emailservice/lib/application/UnavailableAttachmentDataSource.java
index 6d7bd52..f2ba789 100644
--- a/src/main/java/it/aboutbits/springboot/emailservice/lib/application/UnavailableAttachmentDataSource.java
+++ b/src/main/java/it/aboutbits/springboot/emailservice/lib/application/UnavailableAttachmentDataSource.java
@@ -2,9 +2,11 @@
import it.aboutbits.springboot.emailservice.lib.AttachmentDataSource;
import it.aboutbits.springboot.emailservice.lib.exception.AttachmentException;
+import org.jspecify.annotations.NullMarked;
import java.io.InputStream;
+@NullMarked
public final class UnavailableAttachmentDataSource implements AttachmentDataSource {
@Override
public InputStream getAttachmentPayload(long fileReference) throws AttachmentException {
diff --git a/src/main/java/it/aboutbits/springboot/emailservice/lib/application/package-info.java b/src/main/java/it/aboutbits/springboot/emailservice/lib/application/package-info.java
deleted file mode 100644
index 20c8675..0000000
--- a/src/main/java/it/aboutbits/springboot/emailservice/lib/application/package-info.java
+++ /dev/null
@@ -1,6 +0,0 @@
-@NonNullApi
-@NonNullFields
-package it.aboutbits.springboot.emailservice.lib.application;
-
-import org.springframework.lang.NonNullApi;
-import org.springframework.lang.NonNullFields;
diff --git a/src/main/java/it/aboutbits/springboot/emailservice/lib/exception/AttachmentException.java b/src/main/java/it/aboutbits/springboot/emailservice/lib/exception/AttachmentException.java
index f836e33..837bcb2 100644
--- a/src/main/java/it/aboutbits/springboot/emailservice/lib/exception/AttachmentException.java
+++ b/src/main/java/it/aboutbits/springboot/emailservice/lib/exception/AttachmentException.java
@@ -1,23 +1,31 @@
package it.aboutbits.springboot.emailservice.lib.exception;
+import org.jspecify.annotations.NullMarked;
+
+@NullMarked
public class AttachmentException extends Exception {
public AttachmentException() {
super();
}
- public AttachmentException(final String message) {
+ public AttachmentException(String message) {
super(message);
}
- public AttachmentException(final String message, final Throwable cause) {
+ public AttachmentException(String message, Throwable cause) {
super(message, cause);
}
- public AttachmentException(final Throwable cause) {
+ public AttachmentException(Throwable cause) {
super(cause);
}
- protected AttachmentException(final String message, final Throwable cause, final boolean enableSuppression, final boolean writableStackTrace) {
+ protected AttachmentException(
+ String message,
+ Throwable cause,
+ boolean enableSuppression,
+ boolean writableStackTrace
+ ) {
super(message, cause, enableSuppression, writableStackTrace);
}
}
diff --git a/src/main/java/it/aboutbits/springboot/emailservice/lib/exception/EmailException.java b/src/main/java/it/aboutbits/springboot/emailservice/lib/exception/EmailException.java
index 9bc9855..8b890cb 100644
--- a/src/main/java/it/aboutbits/springboot/emailservice/lib/exception/EmailException.java
+++ b/src/main/java/it/aboutbits/springboot/emailservice/lib/exception/EmailException.java
@@ -1,5 +1,8 @@
package it.aboutbits.springboot.emailservice.lib.exception;
+import org.jspecify.annotations.NullMarked;
+
+@NullMarked
public class EmailException extends Exception {
public EmailException() {
super();
diff --git a/src/main/java/it/aboutbits/springboot/emailservice/lib/jpa/EmailRepository.java b/src/main/java/it/aboutbits/springboot/emailservice/lib/jpa/EmailRepository.java
index 718edf8..ba3100c 100644
--- a/src/main/java/it/aboutbits/springboot/emailservice/lib/jpa/EmailRepository.java
+++ b/src/main/java/it/aboutbits/springboot/emailservice/lib/jpa/EmailRepository.java
@@ -3,29 +3,27 @@
import it.aboutbits.springboot.emailservice.lib.EmailState;
import it.aboutbits.springboot.emailservice.lib.model.Email;
+import org.jspecify.annotations.NullMarked;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.jpa.repository.EntityGraph;
import org.springframework.data.jpa.repository.JpaRepository;
-import org.springframework.lang.NonNull;
import java.util.Collection;
import java.util.List;
import java.util.Optional;
+@NullMarked
public interface EmailRepository extends JpaRepository {
@Override
- @NonNull
@EntityGraph(value = Email.DEFAULT_ENTITY_GRAPH)
- Optional findById(@NonNull Long id);
+ Optional findById(Long id);
@Override
- @NonNull
@EntityGraph(value = Email.DEFAULT_ENTITY_GRAPH)
- List findAllById(@NonNull Iterable ids);
+ List findAllById(Iterable ids);
@Override
- @NonNull
@EntityGraph(value = Email.DEFAULT_ENTITY_GRAPH)
List findAll();
diff --git a/src/main/java/it/aboutbits/springboot/emailservice/lib/model/Email.java b/src/main/java/it/aboutbits/springboot/emailservice/lib/model/Email.java
index 20ea001..88a990e 100644
--- a/src/main/java/it/aboutbits/springboot/emailservice/lib/model/Email.java
+++ b/src/main/java/it/aboutbits/springboot/emailservice/lib/model/Email.java
@@ -21,7 +21,8 @@
import org.hibernate.annotations.JdbcTypeCode;
import org.hibernate.annotations.UpdateTimestamp;
import org.hibernate.type.SqlTypes;
-import org.springframework.lang.Nullable;
+import org.jspecify.annotations.NullUnmarked;
+import org.jspecify.annotations.Nullable;
import java.time.OffsetDateTime;
import java.util.List;
@@ -43,6 +44,7 @@
@NoArgsConstructor
@Table(name = "email_service_emails")
@NamedEntityGraph(name = DEFAULT_ENTITY_GRAPH, attributeNodes = @NamedAttributeNode("attachments"))
+@NullUnmarked
public class Email {
public static final String DEFAULT_ENTITY_GRAPH = "graph.EmailServiceEmail.default";
@@ -76,9 +78,12 @@ public class Email {
private boolean attachmentsCleaned = false;
private OffsetDateTime scheduledAt;
+ @Nullable
private OffsetDateTime sentAt;
+ @Nullable
private OffsetDateTime errorAt;
+ @Nullable
private String errorMessage;
@CreationTimestamp
diff --git a/src/main/java/it/aboutbits/springboot/emailservice/lib/model/EmailAttachment.java b/src/main/java/it/aboutbits/springboot/emailservice/lib/model/EmailAttachment.java
index 9de7abb..b089bb9 100644
--- a/src/main/java/it/aboutbits/springboot/emailservice/lib/model/EmailAttachment.java
+++ b/src/main/java/it/aboutbits/springboot/emailservice/lib/model/EmailAttachment.java
@@ -9,11 +9,13 @@
import jakarta.persistence.Table;
import lombok.Getter;
import lombok.Setter;
+import org.jspecify.annotations.NullUnmarked;
@Entity
@Getter
@Setter
@Table(name = "email_service_email_attachments")
+@NullUnmarked
public class EmailAttachment {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
diff --git a/src/main/java/it/aboutbits/springboot/emailservice/lib/package-info.java b/src/main/java/it/aboutbits/springboot/emailservice/lib/package-info.java
deleted file mode 100644
index 48744f3..0000000
--- a/src/main/java/it/aboutbits/springboot/emailservice/lib/package-info.java
+++ /dev/null
@@ -1,6 +0,0 @@
-@NonNullApi
-@NonNullFields
-package it.aboutbits.springboot.emailservice.lib;
-
-import org.springframework.lang.NonNullApi;
-import org.springframework.lang.NonNullFields;
diff --git a/src/test/java/it/aboutbits/springboot/emailservice/ArchitectureTest.java b/src/test/java/it/aboutbits/springboot/emailservice/ArchitectureTest.java
new file mode 100644
index 0000000..48caab5
--- /dev/null
+++ b/src/test/java/it/aboutbits/springboot/emailservice/ArchitectureTest.java
@@ -0,0 +1,15 @@
+package it.aboutbits.springboot.emailservice;
+
+import com.tngtech.archunit.junit.AnalyzeClasses;
+import com.tngtech.archunit.junit.CacheMode;
+import it.aboutbits.archunit.toolbox.ArchitectureTestBase;
+import org.jspecify.annotations.NullMarked;
+
+@AnalyzeClasses(
+ packages = ArchitectureTest.PACKAGE,
+ cacheMode = CacheMode.PER_CLASS
+)
+@NullMarked
+class ArchitectureTest extends ArchitectureTestBase {
+ static final String PACKAGE = "it.aboutbits.springboot.emailservice";
+}
diff --git a/src/test/java/it/aboutbits/springboot/emailservice/TestApplication.java b/src/test/java/it/aboutbits/springboot/emailservice/TestApplication.java
index 20454aa..5d36525 100644
--- a/src/test/java/it/aboutbits/springboot/emailservice/TestApplication.java
+++ b/src/test/java/it/aboutbits/springboot/emailservice/TestApplication.java
@@ -1,11 +1,13 @@
package it.aboutbits.springboot.emailservice;
+import org.jspecify.annotations.NullMarked;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SuppressWarnings("checkstyle:HideUtilityClassConstructor")
@SpringBootApplication
@EnableEmailService
+@NullMarked
public class TestApplication {
public static void main(final String[] args) {
SpringApplication.run(TestApplication.class, args);
diff --git a/src/test/java/it/aboutbits/springboot/emailservice/lib/application/ManageEmailTest.java b/src/test/java/it/aboutbits/springboot/emailservice/lib/application/ManageEmailTest.java
index e91b1e6..1c6b9e8 100644
--- a/src/test/java/it/aboutbits/springboot/emailservice/lib/application/ManageEmailTest.java
+++ b/src/test/java/it/aboutbits/springboot/emailservice/lib/application/ManageEmailTest.java
@@ -6,14 +6,15 @@
import it.aboutbits.springboot.emailservice.lib.exception.EmailException;
import it.aboutbits.springboot.emailservice.support.database.WithPostgres;
import jakarta.mail.internet.MimeMessage;
+import org.jspecify.annotations.NullMarked;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
-import org.springframework.boot.test.mock.mockito.MockBean;
-import org.springframework.boot.test.mock.mockito.SpyBean;
import org.springframework.mail.MailSendException;
import org.springframework.mail.javamail.JavaMailSender;
+import org.springframework.test.context.bean.override.mockito.MockitoBean;
+import org.springframework.test.context.bean.override.mockito.MockitoSpyBean;
import java.io.ByteArrayInputStream;
import java.time.OffsetDateTime;
@@ -30,11 +31,12 @@
@SpringBootTest
@WithPostgres
+@NullMarked
class ManageEmailTest {
- @SpyBean
+ @MockitoSpyBean
JavaMailSender javaMailSender;
- @MockBean
+ @MockitoBean
AttachmentDataSource attachmentDataSource;
@Autowired
diff --git a/src/test/java/it/aboutbits/springboot/emailservice/lib/application/QueryEmailTest.java b/src/test/java/it/aboutbits/springboot/emailservice/lib/application/QueryEmailTest.java
index ab7e5e4..ff400c7 100644
--- a/src/test/java/it/aboutbits/springboot/emailservice/lib/application/QueryEmailTest.java
+++ b/src/test/java/it/aboutbits/springboot/emailservice/lib/application/QueryEmailTest.java
@@ -5,12 +5,13 @@
import it.aboutbits.springboot.emailservice.lib.jpa.EmailRepository;
import it.aboutbits.springboot.emailservice.support.database.WithPostgres;
import it.aboutbits.springboot.emailservice.support.database.factory.EmailFactory;
+import org.jspecify.annotations.NullMarked;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
-import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.data.domain.PageRequest;
import org.springframework.mail.javamail.JavaMailSender;
+import org.springframework.test.context.bean.override.mockito.MockitoBean;
import java.util.Set;
@@ -19,8 +20,9 @@
@SpringBootTest
@WithPostgres
+@NullMarked
class QueryEmailTest {
- @MockBean
+ @MockitoBean
JavaMailSender javaMailSender;
@Autowired
diff --git a/src/test/java/it/aboutbits/springboot/emailservice/support/database/PostgresTestcontainer.java b/src/test/java/it/aboutbits/springboot/emailservice/support/database/PostgresTestcontainer.java
index 90bc89e..167c93d 100644
--- a/src/test/java/it/aboutbits/springboot/emailservice/support/database/PostgresTestcontainer.java
+++ b/src/test/java/it/aboutbits/springboot/emailservice/support/database/PostgresTestcontainer.java
@@ -2,6 +2,7 @@
import lombok.Data;
import lombok.extern.log4j.Log4j2;
+import org.jspecify.annotations.NullMarked;
import org.junit.jupiter.api.extension.AfterEachCallback;
import org.junit.jupiter.api.extension.BeforeAllCallback;
import org.junit.jupiter.api.extension.ExtensionContext;
@@ -17,6 +18,7 @@
import java.util.StringJoiner;
@Log4j2
+@NullMarked
public class PostgresTestcontainer implements BeforeAllCallback, AfterEachCallback {
public static final PostgreSQLContainer> POSTGRES_CONTAINER;
private static final Set TABLES_TO_IGNORE = Set.of(
@@ -101,7 +103,7 @@ private void cleanTablesData(List tablesToClean, Connection connectio
}
@Data
- private static class TableData {
+ private static final class TableData {
private final String schema;
private final String name;
diff --git a/src/test/java/it/aboutbits/springboot/emailservice/support/database/factory/EmailFactory.java b/src/test/java/it/aboutbits/springboot/emailservice/support/database/factory/EmailFactory.java
index ef841d8..1c37b19 100644
--- a/src/test/java/it/aboutbits/springboot/emailservice/support/database/factory/EmailFactory.java
+++ b/src/test/java/it/aboutbits/springboot/emailservice/support/database/factory/EmailFactory.java
@@ -1,14 +1,16 @@
package it.aboutbits.springboot.emailservice.support.database.factory;
-import com.github.javafaker.Faker;
import it.aboutbits.springboot.emailservice.lib.EmailState;
import it.aboutbits.springboot.emailservice.lib.model.Email;
+import it.aboutbits.springboot.testing.testdata.FakerExtended;
+import org.jspecify.annotations.NullMarked;
import java.time.OffsetDateTime;
import java.util.List;
+@NullMarked
public final class EmailFactory {
- private static final Faker FAKER = new Faker();
+ private static final FakerExtended FAKER = new FakerExtended();
private EmailFactory() {
}