Skip to content

Commit

Permalink
James 1409 - Change JPARecipientRewriteTable to store separate record…
Browse files Browse the repository at this point in the history
… per target address (#2444)
  • Loading branch information
amichair authored Oct 14, 2024
1 parent ab68ea4 commit 5408d21
Show file tree
Hide file tree
Showing 15 changed files with 363 additions and 259 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
import org.apache.james.user.api.UsersRepository;
import org.apache.james.webadmin.Routes;
import org.apache.james.webadmin.dto.DTOModuleInjections;
import org.apache.james.webadmin.dto.MappingSourceModule;
import org.apache.james.webadmin.dto.MappingsModule;
import org.apache.james.webadmin.mdc.RequestLogger;
import org.apache.james.webadmin.routes.AddressMappingRoutes;
import org.apache.james.webadmin.routes.AliasRoutes;
Expand Down Expand Up @@ -75,7 +75,7 @@ protected void configure() {
routesMultibinder.addBinding().to(DeleteUserDataRoutes.class);

Multibinder<JsonTransformerModule> jsonTransformerModuleMultibinder = Multibinder.newSetBinder(binder(), JsonTransformerModule.class);
jsonTransformerModuleMultibinder.addBinding().to(MappingSourceModule.class);
jsonTransformerModuleMultibinder.addBinding().to(MappingsModule.class);

Multibinder.newSetBinder(binder(), RequestLogger.class).addBinding().to(UserCreationRequestLogger.class);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,12 @@ public void listSourcesShouldReturnWhenMultipleSourceMapping() {

}

@Test
@Disabled("XMLRecipientRewriteTable is read only")
public void listSourcesShouldReturnWhenSourceHasMultipleMappings() {

}

@Test
@Disabled("XMLRecipientRewriteTable is read only")
public void listSourcesShouldReturnWhenHasForwardMapping() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,10 @@
import static org.apache.james.rrt.jpa.model.JPARecipientRewrite.SELECT_ALL_MAPPINGS_QUERY;
import static org.apache.james.rrt.jpa.model.JPARecipientRewrite.SELECT_SOURCES_BY_MAPPING_QUERY;
import static org.apache.james.rrt.jpa.model.JPARecipientRewrite.SELECT_USER_DOMAIN_MAPPING_QUERY;
import static org.apache.james.rrt.jpa.model.JPARecipientRewrite.UPDATE_MAPPING_QUERY;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import jakarta.inject.Inject;
Expand Down Expand Up @@ -73,12 +72,44 @@ public void setEntityManagerFactory(EntityManagerFactory entityManagerFactory) {

@Override
public void addMapping(MappingSource source, Mapping mapping) throws RecipientRewriteTableException {
Mappings map = getStoredMappings(source);
if (!map.isEmpty()) {
Mappings updatedMappings = MappingsImpl.from(map).add(mapping).build();
doUpdateMapping(source, updatedMappings.serialize());
} else {
doAddMapping(source, mapping.asString());
EntityManager entityManager = entityManagerFactory.createEntityManager();
final EntityTransaction transaction = entityManager.getTransaction();
try {
JPARecipientRewrite jpaRecipientRewrite = new JPARecipientRewrite(source.getFixedUser(), Domain.of(source.getFixedDomain()), mapping.asString());
transaction.begin();
entityManager.merge(jpaRecipientRewrite);
transaction.commit();
} catch (PersistenceException e) {
LOGGER.debug("Failed to save virtual user", e);
if (transaction.isActive()) {
transaction.rollback();
}
throw new RecipientRewriteTableException("Unable to add mapping", e);
} finally {
EntityManagerUtils.safelyClose(entityManager);
}
}

@Override
public void removeMapping(MappingSource source, Mapping mapping) throws RecipientRewriteTableException {
EntityManager entityManager = entityManagerFactory.createEntityManager();
final EntityTransaction transaction = entityManager.getTransaction();
try {
transaction.begin();
entityManager.createNamedQuery(DELETE_MAPPING_QUERY)
.setParameter("user", source.getFixedUser())
.setParameter("domain", source.getFixedDomain())
.setParameter("targetAddress", mapping.asString())
.executeUpdate();
transaction.commit();
} catch (PersistenceException e) {
LOGGER.debug("Failed to remove mapping", e);
if (transaction.isActive()) {
transaction.rollback();
}
throw new RecipientRewriteTableException("Unable to remove mapping", e);
} finally {
EntityManagerUtils.safelyClose(entityManager);
}
}

Expand All @@ -96,18 +127,17 @@ protected Mappings mapAddress(String user, Domain domain) throws RecipientRewrit
}

@Override
@SuppressWarnings("unchecked")
public Mappings getStoredMappings(MappingSource source) throws RecipientRewriteTableException {
EntityManager entityManager = entityManagerFactory.createEntityManager();
try {
@SuppressWarnings("unchecked")
List<JPARecipientRewrite> virtualUsers = entityManager.createNamedQuery(SELECT_USER_DOMAIN_MAPPING_QUERY)
List<Mapping> mappings = (List<Mapping>) entityManager.createNamedQuery(SELECT_USER_DOMAIN_MAPPING_QUERY)
.setParameter("user", source.getFixedUser())
.setParameter("domain", source.getFixedDomain())
.getResultList();
if (virtualUsers.size() > 0) {
return MappingsImpl.fromRawString(virtualUsers.get(0).getTargetAddress());
}
return MappingsImpl.empty();
.getResultStream()
.map(r -> Mapping.of((((JPARecipientRewrite)r).getTargetAddress())))
.collect(Collectors.toList());
return MappingsImpl.fromMappings(mappings.stream());
} catch (PersistenceException e) {
LOGGER.debug("Failed to get user domain mappings", e);
throw new RecipientRewriteTableException("Error while retrieve mappings", e);
Expand All @@ -117,16 +147,16 @@ public Mappings getStoredMappings(MappingSource source) throws RecipientRewriteT
}

@Override
@SuppressWarnings("unchecked")
public Map<MappingSource, Mappings> getAllMappings() throws RecipientRewriteTableException {
EntityManager entityManager = entityManagerFactory.createEntityManager();
Map<MappingSource, Mappings> mapping = new HashMap<>();
try {
@SuppressWarnings("unchecked")
List<JPARecipientRewrite> virtualUsers = entityManager.createNamedQuery(SELECT_ALL_MAPPINGS_QUERY).getResultList();
for (JPARecipientRewrite virtualUser : virtualUsers) {
mapping.put(MappingSource.fromUser(virtualUser.getUser(), virtualUser.getDomain()), MappingsImpl.fromRawString(virtualUser.getTargetAddress()));
}
return mapping;
return (Map<MappingSource, Mappings>) entityManager.createNamedQuery(SELECT_ALL_MAPPINGS_QUERY)
.getResultStream()
.collect(Collectors.toMap(
r -> MappingSource.fromUser(((JPARecipientRewrite)r).getUser(), ((JPARecipientRewrite)r).getDomain()),
r -> MappingsImpl.fromRawString(((JPARecipientRewrite)r).getTargetAddress()),
(m1, m2) -> MappingsImpl.from(m1).addAll(m2).build()));
} catch (PersistenceException e) {
LOGGER.debug("Failed to get all mappings", e);
throw new RecipientRewriteTableException("Error while retrieve mappings", e);
Expand All @@ -144,9 +174,8 @@ public Stream<MappingSource> listSources(Mapping mapping) throws RecipientRewrit
try {
return entityManager.createNamedQuery(SELECT_SOURCES_BY_MAPPING_QUERY, JPARecipientRewrite.class)
.setParameter("targetAddress", mapping.asString())
.getResultList()
.stream()
.map(user -> MappingSource.fromUser(user.getUser(), user.getDomain()));
.getResultStream()
.map(r -> MappingSource.fromUser(r.getUser(), r.getDomain()));
} catch (PersistenceException e) {
String error = "Unable to list sources by mapping";
LOGGER.debug(error, e);
Expand All @@ -155,97 +184,4 @@ public Stream<MappingSource> listSources(Mapping mapping) throws RecipientRewrit
EntityManagerUtils.safelyClose(entityManager);
}
}

@Override
public void removeMapping(MappingSource source, Mapping mapping) throws RecipientRewriteTableException {
Mappings map = getStoredMappings(source);
if (map.size() > 1) {
Mappings updatedMappings = map.remove(mapping);
doUpdateMapping(source, updatedMappings.serialize());
} else {
doRemoveMapping(source, mapping.asString());
}
}

/**
* Update the mapping for the given user and domain
*
* @return true if update was successfully
*/
private boolean doUpdateMapping(MappingSource source, String mapping) throws RecipientRewriteTableException {
EntityManager entityManager = entityManagerFactory.createEntityManager();
final EntityTransaction transaction = entityManager.getTransaction();
try {
transaction.begin();
int updated = entityManager
.createNamedQuery(UPDATE_MAPPING_QUERY)
.setParameter("targetAddress", mapping)
.setParameter("user", source.getFixedUser())
.setParameter("domain", source.getFixedDomain())
.executeUpdate();
transaction.commit();
if (updated > 0) {
return true;
}
} catch (PersistenceException e) {
LOGGER.debug("Failed to update mapping", e);
if (transaction.isActive()) {
transaction.rollback();
}
throw new RecipientRewriteTableException("Unable to update mapping", e);
} finally {
EntityManagerUtils.safelyClose(entityManager);
}
return false;
}

/**
* Remove a mapping for the given user and domain
*/
private void doRemoveMapping(MappingSource source, String mapping) throws RecipientRewriteTableException {
EntityManager entityManager = entityManagerFactory.createEntityManager();
final EntityTransaction transaction = entityManager.getTransaction();
try {
transaction.begin();
entityManager.createNamedQuery(DELETE_MAPPING_QUERY)
.setParameter("user", source.getFixedUser())
.setParameter("domain", source.getFixedDomain())
.setParameter("targetAddress", mapping)
.executeUpdate();
transaction.commit();

} catch (PersistenceException e) {
LOGGER.debug("Failed to remove mapping", e);
if (transaction.isActive()) {
transaction.rollback();
}
throw new RecipientRewriteTableException("Unable to remove mapping", e);

} finally {
EntityManagerUtils.safelyClose(entityManager);
}
}

/**
* Add mapping for given user and domain
*/
private void doAddMapping(MappingSource source, String mapping) throws RecipientRewriteTableException {
EntityManager entityManager = entityManagerFactory.createEntityManager();
final EntityTransaction transaction = entityManager.getTransaction();
try {
transaction.begin();
JPARecipientRewrite jpaRecipientRewrite = new JPARecipientRewrite(source.getFixedUser(), Domain.of(source.getFixedDomain()), mapping);
entityManager.persist(jpaRecipientRewrite);
transaction.commit();
} catch (PersistenceException e) {
LOGGER.debug("Failed to save virtual user", e);
if (transaction.isActive()) {
transaction.rollback();
}
throw new RecipientRewriteTableException("Unable to add mapping", e);
} finally {
EntityManagerUtils.safelyClose(entityManager);
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,14 @@
import static org.apache.james.rrt.jpa.model.JPARecipientRewrite.SELECT_ALL_MAPPINGS_QUERY;
import static org.apache.james.rrt.jpa.model.JPARecipientRewrite.SELECT_SOURCES_BY_MAPPING_QUERY;
import static org.apache.james.rrt.jpa.model.JPARecipientRewrite.SELECT_USER_DOMAIN_MAPPING_QUERY;
import static org.apache.james.rrt.jpa.model.JPARecipientRewrite.UPDATE_MAPPING_QUERY;

import java.io.Serializable;

import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.IdClass;
import jakarta.persistence.Index;
import jakarta.persistence.NamedQuery;
import jakarta.persistence.Table;

Expand All @@ -42,36 +42,40 @@
* persistence.
*/
@Entity(name = "JamesRecipientRewrite")
@Table(name = JPARecipientRewrite.JAMES_RECIPIENT_REWRITE)
@Table(name = JPARecipientRewrite.JAMES_RECIPIENT_REWRITE, indexes = {
// note: the generated primary key index also includes these 3 fields, but in unuseful alphabetic order
@Index(name = "USER_NAME_DOMAIN_NAME_TARGET_ADDRESS_INDEX", columnList = "USER_NAME,DOMAIN_NAME,TARGET_ADDRESS"),
@Index(name = "TARGET_ADDRESS_INDEX", columnList = "TARGET_ADDRESS")
})
@NamedQuery(name = SELECT_USER_DOMAIN_MAPPING_QUERY, query = "SELECT rrt FROM JamesRecipientRewrite rrt WHERE rrt.user=:user AND rrt.domain=:domain")
@NamedQuery(name = SELECT_ALL_MAPPINGS_QUERY, query = "SELECT rrt FROM JamesRecipientRewrite rrt")
@NamedQuery(name = DELETE_MAPPING_QUERY, query = "DELETE FROM JamesRecipientRewrite rrt WHERE rrt.user=:user AND rrt.domain=:domain AND rrt.targetAddress=:targetAddress")
@NamedQuery(name = UPDATE_MAPPING_QUERY, query = "UPDATE JamesRecipientRewrite rrt SET rrt.targetAddress=:targetAddress WHERE rrt.user=:user AND rrt.domain=:domain")
@NamedQuery(name = SELECT_SOURCES_BY_MAPPING_QUERY, query = "SELECT rrt FROM JamesRecipientRewrite rrt WHERE rrt.targetAddress=:targetAddress")
@IdClass(JPARecipientRewrite.RecipientRewriteTableId.class)
public class JPARecipientRewrite {
public static final String SELECT_USER_DOMAIN_MAPPING_QUERY = "selectUserDomainMapping";
public static final String SELECT_ALL_MAPPINGS_QUERY = "selectAllMappings";
public static final String DELETE_MAPPING_QUERY = "deleteMapping";
public static final String UPDATE_MAPPING_QUERY = "updateMapping";
public static final String SELECT_SOURCES_BY_MAPPING_QUERY = "selectSourcesByMapping";

public static final String JAMES_RECIPIENT_REWRITE = "JAMES_RECIPIENT_REWRITE";

public static class RecipientRewriteTableId implements Serializable {

private static final long serialVersionUID = 1L;
private static final long serialVersionUID = 2L;

private String user;

private String domain;

private String targetAddress;

public RecipientRewriteTableId() {
}

@Override
public int hashCode() {
return Objects.hashCode(user, domain);
return Objects.hashCode(user, domain, targetAddress);
}

@Override
Expand All @@ -83,7 +87,9 @@ public boolean equals(Object obj) {
return false;
}
final RecipientRewriteTableId other = (RecipientRewriteTableId) obj;
return Objects.equal(this.user, other.user) && Objects.equal(this.domain, other.domain);
return Objects.equal(this.user, other.user)
&& Objects.equal(this.domain, other.domain)
&& Objects.equal(this.targetAddress, other.targetAddress);
}
}

Expand All @@ -106,6 +112,7 @@ public boolean equals(Object obj) {
* The target address. column name is chosen to be compatible with the
* JDBCRecipientRewriteTableList.
*/
@Id
@Column(name = "TARGET_ADDRESS", nullable = false, length = 100)
private String targetAddress = "";

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -411,6 +411,19 @@ default void listSourcesShouldReturnWhenMultipleSourceMapping() throws Exception
assertThat(virtualUserTable().listSources(mapping)).contains(source, source2);
}

@Test
default void listSourcesShouldReturnWhenSourceHasMultipleMappings() throws Exception {
MappingSource source = MappingSource.fromUser(USER, Domain.of("james"));
Mapping mapping = Mapping.group(ADDRESS);
Mapping mapping2 = Mapping.group(ADDRESS_2);

virtualUserTable().addMapping(source, mapping);
virtualUserTable().addMapping(source, mapping2);

assertThat(virtualUserTable().listSources(mapping)).contains(source);
assertThat(virtualUserTable().listSources(mapping2)).contains(source);
}

@Test
default void listSourcesShouldReturnWhenHasForwardMapping() throws Exception {
Mapping mapping = Mapping.forward("forward");
Expand Down
Loading

0 comments on commit 5408d21

Please sign in to comment.