You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@james.apache.org by bt...@apache.org on 2022/09/16 01:10:17 UTC
[james-project] branch master updated: JAMES-2656 - Add initial JPAMailRepository implementation (#1176)
This is an automated email from the ASF dual-hosted git repository.
btellier 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 066e2c98e7 JAMES-2656 - Add initial JPAMailRepository implementation (#1176)
066e2c98e7 is described below
commit 066e2c98e71e24a6ae4fc7d621b231e73a04c83b
Author: amichair <am...@amichais.net>
AuthorDate: Fri Sep 16 04:10:12 2022 +0300
JAMES-2656 - Add initial JPAMailRepository implementation (#1176)
---
.../src/main/resources/META-INF/persistence.xml | 3 +-
.../src/main/resources/META-INF/persistence.xml | 3 +-
.../java/org/apache/james/JPAJamesServerTest.java | 5 +-
.../src/main/resources/META-INF/persistence.xml | 3 +-
.../modules/data/JPAMailRepositoryModule.java | 6 +-
.../mailrepository/file/FileMailRepository.java | 13 -
.../mailrepository/jdbc/JDBCMailRepository.java | 101 ++----
server/data/data-jpa/pom.xml | 15 +-
.../mailrepository/jpa/JPAMailRepository.java | 379 +++++++++++++++++++++
.../jpa/JPAMailRepositoryUrlStore.java | 1 +
.../mailrepository/jpa/MimeMessageJPASource.java} | 45 +--
.../james/mailrepository/jpa/model/JPAMail.java | 246 +++++++++++++
.../mailrepository/jpa/{ => model}/JPAUrl.java | 2 +-
.../src/main/resources/META-INF/persistence.xml | 3 +-
.../james/jpa/healthcheck/JPAHealthCheckTest.java | 2 +-
.../mailrepository/jpa/JPAMailRepositoryTest.java | 70 ++++
.../jpa/JPAMailRepositoryUrlStoreExtension.java | 1 +
.../mailrepository/MailRepositoryContract.java | 2 +
18 files changed, 777 insertions(+), 123 deletions(-)
diff --git a/server/apps/jpa-app/src/main/resources/META-INF/persistence.xml b/server/apps/jpa-app/src/main/resources/META-INF/persistence.xml
index e847af4a4b..3c26a90ca2 100644
--- a/server/apps/jpa-app/src/main/resources/META-INF/persistence.xml
+++ b/server/apps/jpa-app/src/main/resources/META-INF/persistence.xml
@@ -32,7 +32,8 @@
<class>org.apache.james.mailbox.jpa.user.model.JPASubscription</class>
<class>org.apache.james.domainlist.jpa.model.JPADomain</class>
- <class>org.apache.james.mailrepository.jpa.JPAUrl</class>
+ <class>org.apache.james.mailrepository.jpa.model.JPAUrl</class>
+ <class>org.apache.james.mailrepository.jpa.model.JPAMail</class>
<class>org.apache.james.user.jpa.model.JPAUser</class>
<class>org.apache.james.rrt.jpa.model.JPARecipientRewrite</class>
<class>org.apache.james.sieve.jpa.model.JPASieveQuota</class>
diff --git a/server/apps/jpa-smtp-app/src/main/resources/META-INF/persistence.xml b/server/apps/jpa-smtp-app/src/main/resources/META-INF/persistence.xml
index 1dd8538d5b..07931a5f31 100644
--- a/server/apps/jpa-smtp-app/src/main/resources/META-INF/persistence.xml
+++ b/server/apps/jpa-smtp-app/src/main/resources/META-INF/persistence.xml
@@ -25,7 +25,8 @@
<persistence-unit name="Global" transaction-type="RESOURCE_LOCAL">
<class>org.apache.james.domainlist.jpa.model.JPADomain</class>
- <class>org.apache.james.mailrepository.jpa.JPAUrl</class>
+ <class>org.apache.james.mailrepository.jpa.model.JPAUrl</class>
+ <class>org.apache.james.mailrepository.jpa.model.JPAMail</class>
<class>org.apache.james.user.jpa.model.JPAUser</class>
<class>org.apache.james.rrt.jpa.model.JPARecipientRewrite</class>
<properties>
diff --git a/server/apps/jpa-smtp-app/src/test/java/org/apache/james/JPAJamesServerTest.java b/server/apps/jpa-smtp-app/src/test/java/org/apache/james/JPAJamesServerTest.java
index cdbeaae97d..e580d5e1cf 100644
--- a/server/apps/jpa-smtp-app/src/test/java/org/apache/james/JPAJamesServerTest.java
+++ b/server/apps/jpa-smtp-app/src/test/java/org/apache/james/JPAJamesServerTest.java
@@ -32,7 +32,8 @@ import javax.persistence.EntityManagerFactory;
import org.apache.james.backends.jpa.JpaTestCluster;
import org.apache.james.domainlist.jpa.model.JPADomain;
-import org.apache.james.mailrepository.jpa.JPAUrl;
+import org.apache.james.mailrepository.jpa.model.JPAUrl;
+import org.apache.james.mailrepository.jpa.model.JPAMail;
import org.apache.james.modules.protocols.SmtpGuiceProbe;
import org.apache.james.rrt.jpa.model.JPARecipientRewrite;
import org.apache.james.user.jpa.model.JPAUser;
@@ -66,7 +67,7 @@ public class JPAJamesServerTest {
.overrideWith(
new TestJPAConfigurationModule(),
(binder) -> binder.bind(EntityManagerFactory.class)
- .toInstance(JpaTestCluster.create(JPAUser.class, JPADomain.class, JPARecipientRewrite.class, JPAUrl.class)
+ .toInstance(JpaTestCluster.create(JPAUser.class, JPADomain.class, JPARecipientRewrite.class, JPAUrl.class, JPAMail.class)
.getEntityManagerFactory()));
}
diff --git a/server/apps/spring-app/src/main/resources/META-INF/persistence.xml b/server/apps/spring-app/src/main/resources/META-INF/persistence.xml
index 405b5b519b..69659a9eae 100644
--- a/server/apps/spring-app/src/main/resources/META-INF/persistence.xml
+++ b/server/apps/spring-app/src/main/resources/META-INF/persistence.xml
@@ -33,7 +33,8 @@
<class>org.apache.james.mailbox.jpa.mail.model.JPAMailboxAnnotation</class>
<class>org.apache.james.mailbox.jpa.user.model.JPASubscription</class>
<class>org.apache.james.domainlist.jpa.model.JPADomain</class>
- <class>org.apache.james.mailrepository.jpa.JPAUrl</class>
+ <class>org.apache.james.mailrepository.jpa.model.JPAUrl</class>
+ <class>org.apache.james.mailrepository.jpa.model.JPAMail</class>
<class>org.apache.james.user.jpa.model.JPAUser</class>
<class>org.apache.james.rrt.jpa.model.JPARecipientRewrite</class>
<class>org.apache.james.sieve.jpa.model.JPASieveQuota</class>
diff --git a/server/container/guice/jpa-common/src/main/java/org/apache/james/modules/data/JPAMailRepositoryModule.java b/server/container/guice/jpa-common/src/main/java/org/apache/james/modules/data/JPAMailRepositoryModule.java
index f17764fb9e..99ac66dce7 100644
--- a/server/container/guice/jpa-common/src/main/java/org/apache/james/modules/data/JPAMailRepositoryModule.java
+++ b/server/container/guice/jpa-common/src/main/java/org/apache/james/modules/data/JPAMailRepositoryModule.java
@@ -22,7 +22,7 @@ package org.apache.james.modules.data;
import org.apache.commons.configuration2.BaseHierarchicalConfiguration;
import org.apache.james.mailrepository.api.MailRepositoryUrlStore;
import org.apache.james.mailrepository.api.Protocol;
-import org.apache.james.mailrepository.file.FileMailRepository;
+import org.apache.james.mailrepository.jpa.JPAMailRepository;
import org.apache.james.mailrepository.jpa.JPAMailRepositoryUrlStore;
import org.apache.james.mailrepository.memory.MailRepositoryStoreConfiguration;
@@ -40,8 +40,8 @@ public class JPAMailRepositoryModule extends AbstractModule {
bind(MailRepositoryStoreConfiguration.Item.class)
.toProvider(() -> new MailRepositoryStoreConfiguration.Item(
- ImmutableList.of(new Protocol("file")),
- FileMailRepository.class.getName(),
+ ImmutableList.of(new Protocol("jpa")),
+ JPAMailRepository.class.getName(),
new BaseHierarchicalConfiguration()));
}
}
diff --git a/server/data/data-file/src/main/java/org/apache/james/mailrepository/file/FileMailRepository.java b/server/data/data-file/src/main/java/org/apache/james/mailrepository/file/FileMailRepository.java
index a76ecb8ea0..78f9bf13a3 100644
--- a/server/data/data-file/src/main/java/org/apache/james/mailrepository/file/FileMailRepository.java
+++ b/server/data/data-file/src/main/java/org/apache/james/mailrepository/file/FileMailRepository.java
@@ -54,20 +54,7 @@ import com.github.fge.lambdas.Throwing;
import com.google.common.collect.Iterators;
/**
- * <p>
* Implementation of a MailRepository on a FileSystem.
- * </p>
- * <p>
- * Requires a configuration element in the .conf.xml file of the form:
- * <p/>
- * <pre>
- * <repository destinationURL="file://path-to-root-dir-for-repository"
- * type="MAIL"
- * model="SYNCHRONOUS"/>
- * </pre>
- * <p/>
- * Requires a logger called MailRepository.
- * </p>
*/
public class FileMailRepository implements MailRepository, Configurable, Initializable {
private static final Logger LOGGER = LoggerFactory.getLogger(FileMailRepository.class);
diff --git a/server/data/data-jdbc/src/main/java/org/apache/james/mailrepository/jdbc/JDBCMailRepository.java b/server/data/data-jdbc/src/main/java/org/apache/james/mailrepository/jdbc/JDBCMailRepository.java
index 4dd12ff1f8..8c7c017124 100644
--- a/server/data/data-jdbc/src/main/java/org/apache/james/mailrepository/jdbc/JDBCMailRepository.java
+++ b/server/data/data-jdbc/src/main/java/org/apache/james/mailrepository/jdbc/JDBCMailRepository.java
@@ -40,6 +40,7 @@ import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.StringTokenizer;
+import java.util.stream.Collectors;
import javax.annotation.PostConstruct;
import javax.inject.Inject;
@@ -57,6 +58,7 @@ import org.apache.james.lifecycle.api.Configurable;
import org.apache.james.mailrepository.api.Initializable;
import org.apache.james.mailrepository.api.MailKey;
import org.apache.james.mailrepository.api.MailRepository;
+import org.apache.james.mailrepository.api.MailRepositoryUrl;
import org.apache.james.repository.file.FilePersistentStreamRepository;
import org.apache.james.server.core.MailImpl;
import org.apache.james.server.core.MimeMessageWrapper;
@@ -74,27 +76,6 @@ import com.google.common.collect.ImmutableMap;
/**
* Implementation of a MailRepository on a database.
- *
- * <p>
- * Requires a configuration element in the .conf.xml file of the form:
- *
- * <pre>
- * <repository destinationURL="db://<datasource>/<table_name>/<repository_name>"
- * type="MAIL"
- * model="SYNCHRONOUS"/>
- * </repository>
- * </pre>
- *
- * </p>
- * <p>
- * destinationURL specifies..(Serge??) <br>
- * Type can be SPOOL or MAIL <br>
- * Model is currently not used and may be dropped
- * </p>
- *
- * <p>
- * Requires a logger called MailRepository.
- * </p>
*/
public class JDBCMailRepository implements MailRepository, Configurable, Initializable {
private static final Logger LOGGER = LoggerFactory.getLogger(JDBCMailRepository.class);
@@ -165,55 +146,27 @@ public class JDBCMailRepository implements MailRepository, Configurable, Initial
public void configure(HierarchicalConfiguration<ImmutableNode> configuration) throws ConfigurationException {
LOGGER.debug("{}.configure()", getClass().getName());
destination = configuration.getString("[@destinationURL]");
-
- // normalize the destination, to simplify processing.
- if (!destination.endsWith("/")) {
- destination += "/";
- }
- // Parse the DestinationURL for the name of the datasource,
- // the table to use, and the (optional) repository Key.
- // Split on "/", starting after "db://"
- List<String> urlParams = new ArrayList<>();
- int start = 5;
- if (destination.startsWith("dbfile")) {
- // this is dbfile:// instead of db://
- start += 4;
+ MailRepositoryUrl url = MailRepositoryUrl.from(destination); // also validates url
+ // parse the destinationURL into the name of the datasource,
+ // the table to use, and the (optional) repository key
+ String[] parts = url.getPath().asString().split("/", 3);
+ if (parts.length == 0) {
+ throw new ConfigurationException(
+ "Malformed destinationURL - Must be of the format 'db://<data-source>[/<table>[/<repositoryName>]]'. Was passed " + destination);
}
- int end = destination.indexOf('/', start);
- while (end > -1) {
- urlParams.add(destination.substring(start, end));
- start = end + 1;
- end = destination.indexOf('/', start);
- }
-
- // Build SqlParameters and get datasource name from URL parameters
- if (urlParams.size() == 0) {
- String exceptionBuffer = "Malformed destinationURL - Must be of the format '" + "db://<data-source>[/<table>[/<repositoryName>]]'. Was passed " + configuration.getString("[@destinationURL]");
- throw new ConfigurationException(exceptionBuffer);
+ datasourceName = parts[0];
+ if (parts.length > 1) {
+ tableName = parts[1];
}
- if (urlParams.size() >= 1) {
- datasourceName = urlParams.get(0);
- }
- if (urlParams.size() >= 2) {
- tableName = urlParams.get(1);
- }
- if (urlParams.size() >= 3) {
- repositoryName = "";
- for (int i = 2; i < urlParams.size(); i++) {
- if (i >= 3) {
- repositoryName += '/';
- }
- repositoryName += urlParams.get(i);
- }
+ if (parts.length > 2) {
+ repositoryName = parts[2];
}
- LOGGER.debug("Parsed URL: table = '{}', repositoryName = '{}'", tableName, repositoryName);
+ LOGGER.debug("Parsed URL: datasource = '{}', table = '{}', repositoryName = '{}'", datasource, tableName, repositoryName);
inMemorySizeLimit = configuration.getInt("inMemorySizeLimit", 409600000);
-
filestore = configuration.getString("filestore", null);
sqlFileName = configuration.getString("sqlFile");
-
}
/**
@@ -427,14 +380,10 @@ public class JDBCMailRepository implements MailRepository, Configurable, Initial
} else {
insertMessage.setString(5, mc.getMaybeSender().get().toString());
}
- StringBuilder recipients = new StringBuilder();
- for (Iterator<MailAddress> i = mc.getRecipients().iterator(); i.hasNext();) {
- recipients.append(i.next().toString());
- if (i.hasNext()) {
- recipients.append("\r\n");
- }
- }
- insertMessage.setString(6, recipients.toString());
+ String recipients = mc.getRecipients().stream()
+ .map(MailAddress::toString)
+ .collect(Collectors.joining("\r\n"));
+ insertMessage.setString(6, recipients);
insertMessage.setString(7, mc.getRemoteHost());
insertMessage.setString(8, mc.getRemoteAddr());
if (mc.getPerRecipientSpecificHeaders().getHeadersByRecipient().isEmpty()) {
@@ -515,14 +464,10 @@ public class JDBCMailRepository implements MailRepository, Configurable, Initial
} else {
updateMessage.setString(3, mc.getMaybeSender().get().toString());
}
- StringBuilder recipients = new StringBuilder();
- for (Iterator<MailAddress> i = mc.getRecipients().iterator(); i.hasNext();) {
- recipients.append(i.next().toString());
- if (i.hasNext()) {
- recipients.append("\r\n");
- }
- }
- updateMessage.setString(4, recipients.toString());
+ String recipients = mc.getRecipients().stream()
+ .map(MailAddress::toString)
+ .collect(Collectors.joining("\r\n"));
+ updateMessage.setString(4, recipients);
updateMessage.setString(5, mc.getRemoteHost());
updateMessage.setString(6, mc.getRemoteAddr());
updateMessage.setTimestamp(7, new java.sql.Timestamp(mc.getLastUpdated().getTime()));
diff --git a/server/data/data-jpa/pom.xml b/server/data/data-jpa/pom.xml
index 7a4e913b1a..61bd23ca00 100644
--- a/server/data/data-jpa/pom.xml
+++ b/server/data/data-jpa/pom.xml
@@ -43,6 +43,10 @@
<type>test-jar</type>
<scope>test</scope>
</dependency>
+ <dependency>
+ <groupId>${james.groupId}</groupId>
+ <artifactId>james-server-core</artifactId>
+ </dependency>
<dependency>
<groupId>${james.groupId}</groupId>
<artifactId>james-server-data-api</artifactId>
@@ -86,6 +90,11 @@
<type>test-jar</type>
<scope>test</scope>
</dependency>
+ <dependency>
+ <groupId>${james.groupId}</groupId>
+ <artifactId>james-server-testing</artifactId>
+ <scope>test</scope>
+ </dependency>
<dependency>
<groupId>${james.groupId}</groupId>
<artifactId>testing-base</artifactId>
@@ -151,7 +160,8 @@
org/apache/james/user/jpa/model/JPAUser.class,
org/apache/james/rrt/jpa/model/JPARecipientRewrite.class,
org/apache/james/domainlist/jpa/model/JPADomain.class,
- org/apache/james/mailrepository/jpa/JPAUrl.class</includes>
+ org/apache/james/mailrepository/jpa/model/JPAUrl.class,
+ org/apache/james/mailrepository/jpa/model/JPAMail.class</includes>
<addDefaultConstructor>true</addDefaultConstructor>
<enforcePropertyRestrictions>true</enforcePropertyRestrictions>
<toolProperties>
@@ -166,7 +176,8 @@
org.apache.james.user.jpa.model.JPAUser;
org.apache.james.rrt.jpa.model.JPARecipientRewrite;
org.apache.james.domainlist.jpa.model.JPADomain;
- org.apache.james.mailrepository.jpa.JPAUrl)</value>
+ org.apache.james.mailrepository.jpa.model.JPAUrl;
+ org.apache.james.mailrepository.jpa.model.JPAMail)</value>
</property>
</toolProperties>
</configuration>
diff --git a/server/data/data-jpa/src/main/java/org/apache/james/mailrepository/jpa/JPAMailRepository.java b/server/data/data-jpa/src/main/java/org/apache/james/mailrepository/jpa/JPAMailRepository.java
new file mode 100644
index 0000000000..702b661e6f
--- /dev/null
+++ b/server/data/data-jpa/src/main/java/org/apache/james/mailrepository/jpa/JPAMailRepository.java
@@ -0,0 +1,379 @@
+/****************************************************************
+ * 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.mailrepository.jpa;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.sql.Timestamp;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.StringTokenizer;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import javax.annotation.PostConstruct;
+import javax.inject.Inject;
+import javax.mail.MessagingException;
+import javax.mail.internet.AddressException;
+import javax.persistence.EntityManager;
+import javax.persistence.EntityManagerFactory;
+import javax.persistence.EntityTransaction;
+import javax.persistence.NoResultException;
+
+import org.apache.commons.configuration2.HierarchicalConfiguration;
+import org.apache.commons.configuration2.ex.ConfigurationException;
+import org.apache.commons.configuration2.tree.ImmutableNode;
+import org.apache.james.backends.jpa.EntityManagerUtils;
+import org.apache.james.core.MailAddress;
+import org.apache.james.lifecycle.api.Configurable;
+import org.apache.james.mailrepository.api.Initializable;
+import org.apache.james.mailrepository.api.MailKey;
+import org.apache.james.mailrepository.api.MailRepository;
+import org.apache.james.mailrepository.api.MailRepositoryUrl;
+import org.apache.james.mailrepository.jpa.model.JPAMail;
+import org.apache.james.server.core.MailImpl;
+import org.apache.james.server.core.MimeMessageWrapper;
+import org.apache.james.util.streams.Iterators;
+import org.apache.mailet.Attribute;
+import org.apache.mailet.AttributeName;
+import org.apache.mailet.AttributeValue;
+import org.apache.mailet.Mail;
+import org.apache.mailet.PerRecipientHeaders;
+import org.apache.mailet.PerRecipientHeaders.Header;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.node.JsonNodeFactory;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import com.google.common.collect.ImmutableList;
+
+/**
+ * Implementation of a MailRepository on a database via JPA.
+ */
+public class JPAMailRepository implements MailRepository, Configurable, Initializable {
+ private static final Logger LOGGER = LoggerFactory.getLogger(JPAMailRepository.class);
+ private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
+
+ private String repositoryName;
+
+ private final EntityManagerFactory entityManagerFactory;
+
+ @Inject
+ public JPAMailRepository(EntityManagerFactory entityManagerFactory) {
+ this.entityManagerFactory = entityManagerFactory;
+ }
+
+ public String getRepositoryName() {
+ return repositoryName;
+ }
+
+ // note: caller must close the returned EntityManager when done using it
+ protected EntityManager entityManager() {
+ return entityManagerFactory.createEntityManager();
+ }
+
+ @Override
+ public void configure(HierarchicalConfiguration<ImmutableNode> configuration) throws ConfigurationException {
+ LOGGER.debug("{}.configure()", getClass().getName());
+ String destination = configuration.getString("[@destinationURL]");
+ MailRepositoryUrl url = MailRepositoryUrl.from(destination); // also validates url and standardizes slashes
+ repositoryName = url.getPath().asString();
+ if (repositoryName.isEmpty()) {
+ throw new ConfigurationException(
+ "Malformed destinationURL - Must be of the format 'jpa://<repositoryName>'. Was passed " + url);
+ }
+ LOGGER.debug("Parsed URL: repositoryName = '{}'", repositoryName);
+ }
+
+ /**
+ * Initialises the JPA repository.
+ *
+ * @throws Exception if an error occurs
+ */
+ @Override
+ @PostConstruct
+ public void init() throws Exception {
+ LOGGER.debug("{}.initialize()", getClass().getName());
+ list();
+ }
+
+ @Override
+ public MailKey store(Mail mail) throws MessagingException {
+ MailKey key = MailKey.forMail(mail);
+ EntityManager entityManager = entityManager();
+ try {
+ JPAMail jpaMail = new JPAMail();
+ jpaMail.setRepositoryName(repositoryName);
+ jpaMail.setMessageName(mail.getName());
+ jpaMail.setMessageState(mail.getState());
+ jpaMail.setErrorMessage(mail.getErrorMessage());
+ if (!mail.getMaybeSender().isNullSender()) {
+ jpaMail.setSender(mail.getMaybeSender().get().toString());
+ }
+ String recipients = mail.getRecipients().stream()
+ .map(MailAddress::toString)
+ .collect(Collectors.joining("\r\n"));
+ jpaMail.setRecipients(recipients);
+ jpaMail.setRemoteHost(mail.getRemoteHost());
+ jpaMail.setRemoteAddr(mail.getRemoteAddr());
+ jpaMail.setPerRecipientHeaders(serializePerRecipientHeaders(mail.getPerRecipientSpecificHeaders()));
+ jpaMail.setLastUpdated(new Timestamp(mail.getLastUpdated().getTime()));
+ jpaMail.setMessageBody(getBody(mail));
+ jpaMail.setMessageAttributes(serializeAttributes(mail.attributes()));
+ EntityTransaction transaction = entityManager.getTransaction();
+ transaction.begin();
+ jpaMail = entityManager.merge(jpaMail);
+ transaction.commit();
+ return key;
+ } catch (MessagingException e) {
+ LOGGER.error("Exception caught while storing mail {}", key, e);
+ throw e;
+ } catch (Exception e) {
+ LOGGER.error("Exception caught while storing mail {}", key, e);
+ throw new MessagingException("Exception caught while storing mail " + key, e);
+ } finally {
+ EntityManagerUtils.safelyClose(entityManager);
+ }
+ }
+
+ private byte[] getBody(Mail mail) throws MessagingException, IOException {
+ ByteArrayOutputStream out = new ByteArrayOutputStream((int)mail.getMessageSize());
+ if (mail instanceof MimeMessageWrapper) {
+ // we need to force the loading of the message from the
+ // stream as we want to override the old message
+ ((MimeMessageWrapper) mail).loadMessage();
+ ((MimeMessageWrapper) mail).writeTo(out, out, null, true);
+ } else {
+ mail.getMessage().writeTo(out);
+ }
+ return out.toByteArray();
+ }
+
+ private String serializeAttributes(Stream<Attribute> attributes) {
+ Map<String, JsonNode> map = attributes.collect(Collectors.toMap(
+ attribute -> attribute.getName().asString(),
+ attribute -> attribute.getValue().toJson()));
+
+ return new ObjectNode(JsonNodeFactory.instance, map).toString();
+ }
+
+ private List<Attribute> deserializeAttributes(String data) {
+ try {
+ JsonNode jsonNode = OBJECT_MAPPER.readTree(data);
+ if (jsonNode instanceof ObjectNode) {
+ ObjectNode objectNode = (ObjectNode) jsonNode;
+
+ return Iterators.toStream(objectNode.fields())
+ .map(entry -> new Attribute(AttributeName.of(entry.getKey()), AttributeValue.fromJson(entry.getValue())))
+ .collect(ImmutableList.toImmutableList());
+ }
+ throw new IllegalArgumentException("JSON object corresponding to mail attibutes must be a JSON object");
+ } catch (JsonProcessingException e) {
+ throw new IllegalArgumentException("Mail attributes is not a valid JSON object", e);
+ }
+ }
+
+ private String serializePerRecipientHeaders(PerRecipientHeaders perRecipientHeaders) {
+ if (perRecipientHeaders == null) {
+ return null;
+ }
+ Map<MailAddress, Collection<Header>> map = perRecipientHeaders.getHeadersByRecipient().asMap();
+ if (map.isEmpty()) {
+ return null;
+ }
+ ObjectNode node = JsonNodeFactory.instance.objectNode();
+ for (Map.Entry<MailAddress, Collection<Header>> entry : map.entrySet()) {
+ String recipient = entry.getKey().asString();
+ ObjectNode headers = node.putObject(recipient);
+ entry.getValue().forEach(header -> headers.put(header.getName(), header.getValue()));
+ }
+ return node.toString();
+ }
+
+ private PerRecipientHeaders deserializePerRecipientHeaders(String data) {
+ if (data == null || data.isEmpty()) {
+ return null;
+ }
+ PerRecipientHeaders perRecipientHeaders = new PerRecipientHeaders();
+ try {
+ JsonNode node = OBJECT_MAPPER.readTree(data);
+ if (node instanceof ObjectNode) {
+ ObjectNode objectNode = (ObjectNode) node;
+ Iterators.toStream(objectNode.fields()).forEach(
+ entry -> addPerRecipientHeaders(perRecipientHeaders, entry.getKey(), entry.getValue()));
+ return perRecipientHeaders;
+ }
+ throw new IllegalArgumentException("JSON object corresponding to recipient headers must be a JSON object");
+ } catch (JsonProcessingException e) {
+ throw new IllegalArgumentException("per recipient headers is not a valid JSON object", e);
+ }
+ }
+
+ private void addPerRecipientHeaders(PerRecipientHeaders perRecipientHeaders, String recipient, JsonNode headers) {
+ try {
+ MailAddress address = new MailAddress(recipient);
+ Iterators.toStream(headers.fields()).forEach(
+ entry -> {
+ String name = entry.getKey();
+ String value = entry.getValue().textValue();
+ Header header = Header.builder().name(name).value(value).build();
+ perRecipientHeaders.addHeaderForRecipient(header, address);
+ });
+ } catch (AddressException ae) {
+ throw new IllegalArgumentException("invalid recipient address", ae);
+ }
+ }
+
+ @Override
+ public Mail retrieve(MailKey key) throws MessagingException {
+ EntityManager entityManager = entityManager();
+ try {
+ JPAMail jpaMail = entityManager.createNamedQuery("findMailMessage", JPAMail.class)
+ .setParameter("repositoryName", repositoryName)
+ .setParameter("messageName", key.asString())
+ .getSingleResult();
+
+ MailImpl.Builder mail = MailImpl.builder().name(key.asString());
+ if (jpaMail.getMessageAttributes() != null) {
+ mail.addAttributes(deserializeAttributes(jpaMail.getMessageAttributes()));
+ }
+ mail.state(jpaMail.getMessageState());
+ mail.errorMessage(jpaMail.getErrorMessage());
+ String sender = jpaMail.getSender();
+ if (sender == null) {
+ mail.sender((MailAddress)null);
+ } else {
+ mail.sender(new MailAddress(sender));
+ }
+ StringTokenizer st = new StringTokenizer(jpaMail.getRecipients(), "\r\n", false);
+ while (st.hasMoreTokens()) {
+ mail.addRecipient(st.nextToken());
+ }
+ mail.remoteHost(jpaMail.getRemoteHost());
+ mail.remoteAddr(jpaMail.getRemoteAddr());
+ PerRecipientHeaders perRecipientHeaders = deserializePerRecipientHeaders(jpaMail.getPerRecipientHeaders());
+ if (perRecipientHeaders != null) {
+ mail.addAllHeadersForRecipients(perRecipientHeaders);
+ }
+ mail.lastUpdated(jpaMail.getLastUpdated());
+
+ MimeMessageJPASource source = new MimeMessageJPASource(this, key.asString(), jpaMail.getMessageBody());
+ MimeMessageWrapper message = new MimeMessageWrapper(source);
+ mail.mimeMessage(message);
+ return mail.build();
+ } catch (NoResultException nre) {
+ LOGGER.debug("Did not find mail {} in repository {}", key, repositoryName);
+ return null;
+ } catch (Exception e) {
+ throw new MessagingException("Exception while retrieving mail: " + e.getMessage(), e);
+ } finally {
+ EntityManagerUtils.safelyClose(entityManager);
+ }
+ }
+
+ @Override
+ public long size() throws MessagingException {
+ EntityManager entityManager = entityManager();
+ try {
+ return entityManager.createNamedQuery("countMailMessages", long.class)
+ .setParameter("repositoryName", repositoryName)
+ .getSingleResult();
+ } catch (Exception me) {
+ throw new MessagingException("Exception while listing messages: " + me.getMessage(), me);
+ } finally {
+ EntityManagerUtils.safelyClose(entityManager);
+ }
+ }
+
+ @Override
+ public Iterator<MailKey> list() throws MessagingException {
+ EntityManager entityManager = entityManager();
+ try {
+ return entityManager.createNamedQuery("listMailMessages", String.class)
+ .setParameter("repositoryName", repositoryName)
+ .getResultStream()
+ .map(MailKey::new)
+ .iterator();
+ } catch (Exception me) {
+ throw new MessagingException("Exception while listing messages: " + me.getMessage(), me);
+ } finally {
+ EntityManagerUtils.safelyClose(entityManager);
+ }
+ }
+
+ @Override
+ public void remove(MailKey key) throws MessagingException {
+ remove(Collections.singleton(key));
+ }
+
+ @Override
+ public void remove(Collection<MailKey> keys) throws MessagingException {
+ Collection<String> messageNames = keys.stream().map(MailKey::asString).collect(Collectors.toList());
+ EntityManager entityManager = entityManager();
+ EntityTransaction transaction = entityManager.getTransaction();
+ transaction.begin();
+ try {
+ entityManager.createNamedQuery("deleteMailMessages")
+ .setParameter("repositoryName", repositoryName)
+ .setParameter("messageNames", messageNames)
+ .executeUpdate();
+ transaction.commit();
+ } catch (Exception e) {
+ throw new MessagingException("Exception while removing message(s): " + e.getMessage(), e);
+ } finally {
+ EntityManagerUtils.safelyClose(entityManager);
+ }
+ }
+
+ @Override
+ public void removeAll() throws MessagingException {
+ EntityManager entityManager = entityManager();
+ EntityTransaction transaction = entityManager.getTransaction();
+ transaction.begin();
+ try {
+ entityManager.createNamedQuery("deleteAllMailMessages")
+ .setParameter("repositoryName", repositoryName)
+ .executeUpdate();
+ transaction.commit();
+ } catch (Exception e) {
+ throw new MessagingException("Exception while removing message(s): " + e.getMessage(), e);
+ } finally {
+ EntityManagerUtils.safelyClose(entityManager);
+ }
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ return obj instanceof JPAMailRepository
+ && Objects.equals(repositoryName, ((JPAMailRepository)obj).repositoryName);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(repositoryName);
+ }
+}
diff --git a/server/data/data-jpa/src/main/java/org/apache/james/mailrepository/jpa/JPAMailRepositoryUrlStore.java b/server/data/data-jpa/src/main/java/org/apache/james/mailrepository/jpa/JPAMailRepositoryUrlStore.java
index ca1b8d29c1..1f448a9eca 100644
--- a/server/data/data-jpa/src/main/java/org/apache/james/mailrepository/jpa/JPAMailRepositoryUrlStore.java
+++ b/server/data/data-jpa/src/main/java/org/apache/james/mailrepository/jpa/JPAMailRepositoryUrlStore.java
@@ -27,6 +27,7 @@ import javax.persistence.EntityManagerFactory;
import org.apache.james.backends.jpa.TransactionRunner;
import org.apache.james.mailrepository.api.MailRepositoryUrl;
import org.apache.james.mailrepository.api.MailRepositoryUrlStore;
+import org.apache.james.mailrepository.jpa.model.JPAUrl;
public class JPAMailRepositoryUrlStore implements MailRepositoryUrlStore {
private final TransactionRunner transactionRunner;
diff --git a/server/container/guice/jpa-common/src/main/java/org/apache/james/modules/data/JPAMailRepositoryModule.java b/server/data/data-jpa/src/main/java/org/apache/james/mailrepository/jpa/MimeMessageJPASource.java
similarity index 52%
copy from server/container/guice/jpa-common/src/main/java/org/apache/james/modules/data/JPAMailRepositoryModule.java
copy to server/data/data-jpa/src/main/java/org/apache/james/mailrepository/jpa/MimeMessageJPASource.java
index f17764fb9e..f5445c279c 100644
--- a/server/container/guice/jpa-common/src/main/java/org/apache/james/modules/data/JPAMailRepositoryModule.java
+++ b/server/data/data-jpa/src/main/java/org/apache/james/mailrepository/jpa/MimeMessageJPASource.java
@@ -17,31 +17,38 @@
* under the License. *
****************************************************************/
-package org.apache.james.modules.data;
+package org.apache.james.mailrepository.jpa;
-import org.apache.commons.configuration2.BaseHierarchicalConfiguration;
-import org.apache.james.mailrepository.api.MailRepositoryUrlStore;
-import org.apache.james.mailrepository.api.Protocol;
-import org.apache.james.mailrepository.file.FileMailRepository;
-import org.apache.james.mailrepository.jpa.JPAMailRepositoryUrlStore;
-import org.apache.james.mailrepository.memory.MailRepositoryStoreConfiguration;
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
-import com.google.common.collect.ImmutableList;
-import com.google.inject.AbstractModule;
-import com.google.inject.Scopes;
+import org.apache.james.server.core.MimeMessageSource;
-public class JPAMailRepositoryModule extends AbstractModule {
+public class MimeMessageJPASource implements MimeMessageSource {
+
+ private final JPAMailRepository jpaMailRepository;
+ private final String key;
+ private final byte[] body;
+
+ public MimeMessageJPASource(JPAMailRepository jpaMailRepository, String key, byte[] body) {
+ this.jpaMailRepository = jpaMailRepository;
+ this.key = key;
+ this.body = body;
+ }
@Override
- protected void configure() {
- bind(JPAMailRepositoryUrlStore.class).in(Scopes.SINGLETON);
+ public String getSourceId() {
+ return jpaMailRepository.getRepositoryName() + "/" + key;
+ }
- bind(MailRepositoryUrlStore.class).to(JPAMailRepositoryUrlStore.class);
+ @Override
+ public InputStream getInputStream() throws IOException {
+ return new ByteArrayInputStream(body);
+ }
- bind(MailRepositoryStoreConfiguration.Item.class)
- .toProvider(() -> new MailRepositoryStoreConfiguration.Item(
- ImmutableList.of(new Protocol("file")),
- FileMailRepository.class.getName(),
- new BaseHierarchicalConfiguration()));
+ @Override
+ public long getMessageSize() throws IOException {
+ return body.length;
}
}
diff --git a/server/data/data-jpa/src/main/java/org/apache/james/mailrepository/jpa/model/JPAMail.java b/server/data/data-jpa/src/main/java/org/apache/james/mailrepository/jpa/model/JPAMail.java
new file mode 100644
index 0000000000..187241dfcb
--- /dev/null
+++ b/server/data/data-jpa/src/main/java/org/apache/james/mailrepository/jpa/model/JPAMail.java
@@ -0,0 +1,246 @@
+/****************************************************************
+ * 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.mailrepository.jpa.model;
+
+import java.io.Serializable;
+import java.sql.Timestamp;
+import java.util.Objects;
+
+import javax.persistence.Basic;
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.FetchType;
+import javax.persistence.Id;
+import javax.persistence.IdClass;
+import javax.persistence.Index;
+import javax.persistence.Lob;
+import javax.persistence.NamedQueries;
+import javax.persistence.NamedQuery;
+import javax.persistence.Table;
+
+@Entity(name = "JamesMailStore")
+@IdClass(JPAMail.JPAMailId.class)
+@Table(name = "JAMES_MAIL_STORE", indexes = {
+ @Index(name = "REPOSITORY_NAME_MESSAGE_NAME_INDEX", columnList = "REPOSITORY_NAME, MESSAGE_NAME")
+})
+@NamedQueries({
+ @NamedQuery(name = "listMailMessages",
+ query = "SELECT mail.messageName FROM JamesMailStore mail WHERE mail.repositoryName = :repositoryName"),
+ @NamedQuery(name = "countMailMessages",
+ query = "SELECT COUNT(mail) FROM JamesMailStore mail WHERE mail.repositoryName = :repositoryName"),
+ @NamedQuery(name = "deleteMailMessages",
+ query = "DELETE FROM JamesMailStore mail WHERE mail.repositoryName = :repositoryName AND mail.messageName IN (:messageNames)"),
+ @NamedQuery(name = "deleteAllMailMessages",
+ query = "DELETE FROM JamesMailStore mail WHERE mail.repositoryName = :repositoryName"),
+ @NamedQuery(name = "findMailMessage",
+ query = "SELECT mail FROM JamesMailStore mail WHERE mail.repositoryName = :repositoryName AND mail.messageName = :messageName")
+})
+public class JPAMail {
+
+ static class JPAMailId implements Serializable {
+ public JPAMailId() {
+ }
+
+ String repositoryName;
+ String messageName;
+
+ public boolean equals(Object obj) {
+ return obj instanceof JPAMailId
+ && Objects.equals(messageName, ((JPAMailId) obj).messageName)
+ && Objects.equals(repositoryName, ((JPAMailId) obj).repositoryName);
+ }
+
+ public int hashCode() {
+ return Objects.hash(messageName, repositoryName);
+ }
+ }
+
+ @Id
+ @Basic(optional = false)
+ @Column(name = "REPOSITORY_NAME", nullable = false, length = 255)
+ private String repositoryName;
+
+ @Id
+ @Basic(optional = false)
+ @Column(name = "MESSAGE_NAME", nullable = false, length = 200)
+ private String messageName;
+
+ @Basic(optional = false)
+ @Column(name = "MESSAGE_STATE", nullable = false, length = 30)
+ private String messageState;
+
+ @Basic(optional = true)
+ @Column(name = "ERROR_MESSAGE", nullable = true, length = 200)
+ private String errorMessage;
+
+ @Basic(optional = true)
+ @Column(name = "SENDER", nullable = true, length = 255)
+ private String sender;
+
+ @Basic(optional = false)
+ @Column(name = "RECIPIENTS", nullable = false)
+ private String recipients; // CRLF delimited
+
+ @Basic(optional = false)
+ @Column(name = "REMOTE_HOST", nullable = false, length = 255)
+ private String remoteHost;
+
+ @Basic(optional = false)
+ @Column(name = "REMOTE_ADDR", nullable = false, length = 20)
+ private String remoteAddr;
+
+ @Basic(optional = false)
+ @Column(name = "LAST_UPDATED", nullable = false)
+ private Timestamp lastUpdated;
+
+ @Basic(optional = true)
+ @Column(name = "PER_RECIPIENT_HEADERS", nullable = true, length = 10485760)
+ @Lob
+ private String perRecipientHeaders;
+
+ @Basic(optional = false, fetch = FetchType.LAZY)
+ @Column(name = "MESSAGE_BODY", nullable = false, length = 1048576000)
+ @Lob
+ private byte[] messageBody; // TODO: support streaming body where possible (see e.g. JPAStreamingMailboxMessage)
+
+ @Basic(optional = true)
+ @Column(name = "MESSAGE_ATTRIBUTES", nullable = true, length = 10485760)
+ @Lob
+ private String messageAttributes;
+
+ public JPAMail() {
+ }
+
+ public String getRepositoryName() {
+ return repositoryName;
+ }
+
+ public void setRepositoryName(String repositoryName) {
+ this.repositoryName = repositoryName;
+ }
+
+ public String getMessageName() {
+ return messageName;
+ }
+
+ public void setMessageName(String messageName) {
+ this.messageName = messageName;
+ }
+
+ public String getMessageState() {
+ return messageState;
+ }
+
+ public void setMessageState(String messageState) {
+ this.messageState = messageState;
+ }
+
+ public String getErrorMessage() {
+ return errorMessage;
+ }
+
+ public void setErrorMessage(String errorMessage) {
+ this.errorMessage = errorMessage;
+ }
+
+ public String getSender() {
+ return sender;
+ }
+
+ public void setSender(String sender) {
+ this.sender = sender;
+ }
+
+ public String getRecipients() {
+ return recipients;
+ }
+
+ public void setRecipients(String recipients) {
+ this.recipients = recipients;
+ }
+
+ public String getRemoteHost() {
+ return remoteHost;
+ }
+
+ public void setRemoteHost(String remoteHost) {
+ this.remoteHost = remoteHost;
+ }
+
+ public String getRemoteAddr() {
+ return remoteAddr;
+ }
+
+ public void setRemoteAddr(String remoteAddr) {
+ this.remoteAddr = remoteAddr;
+ }
+
+ public Timestamp getLastUpdated() {
+ return lastUpdated;
+ }
+
+ public void setLastUpdated(Timestamp lastUpdated) {
+ this.lastUpdated = lastUpdated;
+ }
+
+ public String getPerRecipientHeaders() {
+ return perRecipientHeaders;
+ }
+
+ public void setPerRecipientHeaders(String perRecipientHeaders) {
+ this.perRecipientHeaders = perRecipientHeaders;
+ }
+
+ public byte[] getMessageBody() {
+ return messageBody;
+ }
+
+ public void setMessageBody(byte[] messageBody) {
+ this.messageBody = messageBody;
+ }
+
+ public String getMessageAttributes() {
+ return messageAttributes;
+ }
+
+ public void setMessageAttributes(String messageAttributes) {
+ this.messageAttributes = messageAttributes;
+ }
+
+ @Override
+ public String toString() {
+ return "JPAMail ( "
+ + "repositoryName = " + repositoryName
+ + ", messageName = " + messageName
+ + " )";
+ }
+
+ @Override
+ public final boolean equals(Object obj) {
+ return obj instanceof JPAMail
+ && Objects.equals(this.repositoryName, ((JPAMail)obj).repositoryName)
+ && Objects.equals(this.messageName, ((JPAMail)obj).messageName);
+ }
+
+ @Override
+ public final int hashCode() {
+ return Objects.hash(repositoryName, messageName);
+ }
+}
diff --git a/server/data/data-jpa/src/main/java/org/apache/james/mailrepository/jpa/JPAUrl.java b/server/data/data-jpa/src/main/java/org/apache/james/mailrepository/jpa/model/JPAUrl.java
similarity index 97%
rename from server/data/data-jpa/src/main/java/org/apache/james/mailrepository/jpa/JPAUrl.java
rename to server/data/data-jpa/src/main/java/org/apache/james/mailrepository/jpa/model/JPAUrl.java
index 8de1eb86b1..9f8e74c69c 100644
--- a/server/data/data-jpa/src/main/java/org/apache/james/mailrepository/jpa/JPAUrl.java
+++ b/server/data/data-jpa/src/main/java/org/apache/james/mailrepository/jpa/model/JPAUrl.java
@@ -17,7 +17,7 @@
* under the License. *
****************************************************************/
-package org.apache.james.mailrepository.jpa;
+package org.apache.james.mailrepository.jpa.model;
import javax.persistence.Column;
import javax.persistence.Entity;
diff --git a/server/data/data-jpa/src/main/resources/META-INF/persistence.xml b/server/data/data-jpa/src/main/resources/META-INF/persistence.xml
index 1927b87f6e..6224adb74f 100644
--- a/server/data/data-jpa/src/main/resources/META-INF/persistence.xml
+++ b/server/data/data-jpa/src/main/resources/META-INF/persistence.xml
@@ -29,7 +29,8 @@
<class>org.apache.james.domainlist.jpa.model.JPADomain</class>
<class>org.apache.james.user.jpa.model.JPAUser</class>
<class>org.apache.james.rrt.jpa.model.JPARecipientRewrite</class>
- <class>org.apache.james.mailrepository.jpa.JPAUrl</class>
+ <class>org.apache.james.mailrepository.jpa.model.JPAUrl</class>
+ <class>org.apache.james.mailrepository.jpa.model.JPAMail</class>
<class>org.apache.james.sieve.jpa.model.JPASieveQuota</class>
<class>org.apache.james.sieve.jpa.model.JPASieveScript</class>
<exclude-unlisted-classes>true</exclude-unlisted-classes>
diff --git a/server/data/data-jpa/src/test/java/org/apache/james/jpa/healthcheck/JPAHealthCheckTest.java b/server/data/data-jpa/src/test/java/org/apache/james/jpa/healthcheck/JPAHealthCheckTest.java
index 16f880bfc9..20ed1bbaa2 100644
--- a/server/data/data-jpa/src/test/java/org/apache/james/jpa/healthcheck/JPAHealthCheckTest.java
+++ b/server/data/data-jpa/src/test/java/org/apache/james/jpa/healthcheck/JPAHealthCheckTest.java
@@ -24,7 +24,7 @@ import static org.assertj.core.api.Assertions.fail;
import org.apache.james.backends.jpa.JpaTestCluster;
import org.apache.james.core.healthcheck.Result;
import org.apache.james.core.healthcheck.ResultStatus;
-import org.apache.james.mailrepository.jpa.JPAUrl;
+import org.apache.james.mailrepository.jpa.model.JPAUrl;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
diff --git a/server/data/data-jpa/src/test/java/org/apache/james/mailrepository/jpa/JPAMailRepositoryTest.java b/server/data/data-jpa/src/test/java/org/apache/james/mailrepository/jpa/JPAMailRepositoryTest.java
new file mode 100644
index 0000000000..3c41aa53ab
--- /dev/null
+++ b/server/data/data-jpa/src/test/java/org/apache/james/mailrepository/jpa/JPAMailRepositoryTest.java
@@ -0,0 +1,70 @@
+/****************************************************************
+ * 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.mailrepository.jpa;
+
+import org.apache.commons.configuration2.BaseHierarchicalConfiguration;
+import org.apache.james.backends.jpa.JpaTestCluster;
+import org.apache.james.mailrepository.MailRepositoryContract;
+import org.apache.james.mailrepository.api.MailRepository;
+import org.apache.james.mailrepository.api.MailRepositoryPath;
+import org.apache.james.mailrepository.api.MailRepositoryUrl;
+import org.apache.james.mailrepository.api.Protocol;
+import org.apache.james.mailrepository.jpa.model.JPAMail;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Disabled;
+
+public class JPAMailRepositoryTest implements MailRepositoryContract {
+
+ final JpaTestCluster JPA_TEST_CLUSTER = JpaTestCluster.create(JPAMail.class);
+
+ private JPAMailRepository mailRepository;
+
+ @BeforeEach
+ void setUp() throws Exception {
+ mailRepository = retrieveRepository(MailRepositoryPath.from("testrepo"));
+ }
+
+ @AfterEach
+ void tearDown() {
+ JPA_TEST_CLUSTER.clear("JAMES_MAIL_STORE");
+ }
+
+ @Override
+ public MailRepository retrieveRepository() {
+ return mailRepository;
+ }
+
+ @Override
+ public JPAMailRepository retrieveRepository(MailRepositoryPath url) throws Exception {
+ BaseHierarchicalConfiguration conf = new BaseHierarchicalConfiguration();
+ conf.addProperty("[@destinationURL]", MailRepositoryUrl.fromPathAndProtocol(new Protocol("jpa"), url).asString());
+ JPAMailRepository mailRepository = new JPAMailRepository(JPA_TEST_CLUSTER.getEntityManagerFactory());
+ mailRepository.configure(conf);
+ mailRepository.init();
+ return mailRepository;
+ }
+
+ @Override
+ @Disabled("JAMES-3431 No support for Attribute collection Java serialization yet")
+ public void shouldPreserveDsnParameters() throws Exception {
+ MailRepositoryContract.super.shouldPreserveDsnParameters();
+ }
+}
diff --git a/server/data/data-jpa/src/test/java/org/apache/james/mailrepository/jpa/JPAMailRepositoryUrlStoreExtension.java b/server/data/data-jpa/src/test/java/org/apache/james/mailrepository/jpa/JPAMailRepositoryUrlStoreExtension.java
index 3eabf52c87..c8af2008d1 100644
--- a/server/data/data-jpa/src/test/java/org/apache/james/mailrepository/jpa/JPAMailRepositoryUrlStoreExtension.java
+++ b/server/data/data-jpa/src/test/java/org/apache/james/mailrepository/jpa/JPAMailRepositoryUrlStoreExtension.java
@@ -21,6 +21,7 @@ package org.apache.james.mailrepository.jpa;
import org.apache.james.backends.jpa.JpaTestCluster;
import org.apache.james.mailrepository.api.MailRepositoryUrlStore;
+import org.apache.james.mailrepository.jpa.model.JPAUrl;
import org.junit.jupiter.api.extension.AfterEachCallback;
import org.junit.jupiter.api.extension.ExtensionContext;
import org.junit.jupiter.api.extension.ParameterContext;
diff --git a/server/mailrepository/mailrepository-api/src/test/java/org/apache/james/mailrepository/MailRepositoryContract.java b/server/mailrepository/mailrepository-api/src/test/java/org/apache/james/mailrepository/MailRepositoryContract.java
index cab80c8189..3eb4f4ba94 100644
--- a/server/mailrepository/mailrepository-api/src/test/java/org/apache/james/mailrepository/MailRepositoryContract.java
+++ b/server/mailrepository/mailrepository-api/src/test/java/org/apache/james/mailrepository/MailRepositoryContract.java
@@ -163,6 +163,8 @@ public interface MailRepositoryContract {
.name(MAIL_1.asString())
.sender(MailAddress.nullSender())
.recipient(MailAddressFixture.RECIPIENT1)
+ .lastUpdated(new Date())
+ .state(Mail.DEFAULT)
.mimeMessage(MimeMessageBuilder.mimeMessageBuilder()
.setSubject("test")
.setText("String body")
---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@james.apache.org
For additional commands, e-mail: notifications-help@james.apache.org