Add search publications use case but it's bugged.

This commit is contained in:
Florian THIERRY
2024-03-15 14:56:34 +01:00
parent 50b305c3cd
commit 6e2b86153e
21 changed files with 589 additions and 7 deletions

View File

@@ -37,5 +37,24 @@
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-params</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>

View File

@@ -1,9 +1,11 @@
package org.codiki.infrastructure.publication;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
import org.codiki.domain.publication.model.Publication;
import org.codiki.domain.publication.model.search.PublicationSearchCriterion;
import org.codiki.domain.publication.port.PublicationPort;
import org.codiki.infrastructure.publication.model.PublicationEntity;
import org.codiki.infrastructure.publication.repository.PublicationRepository;
@@ -12,9 +14,14 @@ import org.springframework.stereotype.Component;
@Component
public class PublicationJpaAdapter implements PublicationPort {
private final PublicationRepository repository;
private final PublicationSearchCriteriaJpaAdapter publicationSearchCriteriaJpaAdapter;
public PublicationJpaAdapter(PublicationRepository repository) {
public PublicationJpaAdapter(
PublicationRepository repository,
PublicationSearchCriteriaJpaAdapter publicationSearchCriteriaJpaAdapter
) {
this.repository = repository;
this.publicationSearchCriteriaJpaAdapter = publicationSearchCriteriaJpaAdapter;
}
@Override
@@ -33,4 +40,13 @@ public class PublicationJpaAdapter implements PublicationPort {
public void delete(Publication publication) {
repository.deleteById(publication.id());
}
@Override
public List<Publication> search(List<PublicationSearchCriterion> criteria) {
List<PublicationSearchCriterion> adaptedCriteria = publicationSearchCriteriaJpaAdapter.adaptCriteriaForJpa(criteria);
return repository.search(adaptedCriteria)
.stream()
.map(PublicationEntity::toDomain)
.toList();
}
}

View File

@@ -0,0 +1,35 @@
package org.codiki.infrastructure.publication;
import java.util.LinkedList;
import java.util.List;
import org.apache.commons.lang3.StringUtils;
import org.codiki.domain.publication.model.search.PublicationSearchCriterion;
import org.springframework.stereotype.Component;
@Component
public class PublicationSearchCriteriaJpaAdapter {
public List<PublicationSearchCriterion> adaptCriteriaForJpa(List<PublicationSearchCriterion> initialCriteria) {
List<PublicationSearchCriterion> result = new LinkedList<>();
for (PublicationSearchCriterion criterion : initialCriteria) {
boolean criterionAdaptationOccurred = false;
if (criterion.value() instanceof String criterionValue) {
String unaccentedCriterionValue = StringUtils.stripAccents(criterionValue);
result.add(new PublicationSearchCriterion(
criterion.searchField(),
criterion.searchType(),
unaccentedCriterionValue
));
criterionAdaptationOccurred = true;
}
if (!criterionAdaptationOccurred) {
result.add(criterion);
}
}
return result;
}
}

View File

@@ -23,7 +23,7 @@ public class AuthorEntity {
@Id
private UUID id;
@Column(nullable = false)
private String name;
private String pseudo;
// private String illustrationId;
public AuthorEntity(Author author) {
@@ -35,6 +35,6 @@ public class AuthorEntity {
}
public Author toDomain() {
return new Author(id, name, "image");
return new Author(id, pseudo, "image");
}
}

View File

@@ -0,0 +1,10 @@
package org.codiki.infrastructure.publication.repository;
import java.util.List;
import org.codiki.domain.publication.model.search.PublicationSearchCriterion;
import org.codiki.infrastructure.publication.model.PublicationEntity;
public interface CustomPublicationRepository {
List<PublicationEntity> search(List<PublicationSearchCriterion> criteria);
}

View File

@@ -0,0 +1,41 @@
package org.codiki.infrastructure.publication.repository;
import java.util.List;
import org.codiki.domain.publication.model.search.PublicationSearchCriterion;
import org.codiki.infrastructure.publication.model.PublicationEntity;
import org.springframework.stereotype.Repository;
import jakarta.persistence.EntityManager;
import jakarta.persistence.criteria.CriteriaBuilder;
import jakarta.persistence.criteria.CriteriaQuery;
import jakarta.persistence.criteria.Predicate;
import jakarta.persistence.criteria.Root;
@Repository
public class CustomPublicationRepositoryImpl implements CustomPublicationRepository {
private final EntityManager entityManager;
private final PublicationPredicateMapper publicationPredicateMapper;
public CustomPublicationRepositoryImpl(EntityManager entityManager, PublicationPredicateMapper publicationPredicateMapper) {
this.entityManager = entityManager;
this.publicationPredicateMapper = publicationPredicateMapper;
}
@Override
public List<PublicationEntity> search(final List<PublicationSearchCriterion> criteria) {
CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();
CriteriaQuery<PublicationEntity> query = criteriaBuilder.createQuery(PublicationEntity.class);
Root<PublicationEntity> fromPublication = query.from(PublicationEntity.class);
Predicate predicate = publicationPredicateMapper.map(criteria, criteriaBuilder, fromPublication);
CriteriaQuery<PublicationEntity> criteriaQuery = query.select(fromPublication)
.distinct(true)
.where(predicate);
return entityManager.createQuery(criteriaQuery)
.getResultList();
}
}

View File

@@ -0,0 +1,79 @@
package org.codiki.infrastructure.publication.repository;
import java.util.List;
import static org.codiki.domain.publication.model.search.PublicationSearchField.AUTHOR_PSEUDO;
import org.codiki.domain.publication.model.search.PublicationSearchCriterion;
import org.codiki.domain.publication.model.search.PublicationSearchField;
import org.codiki.infrastructure.publication.model.PublicationEntity;
import org.springframework.stereotype.Component;
import static jakarta.persistence.criteria.JoinType.LEFT;
import jakarta.persistence.criteria.CriteriaBuilder;
import jakarta.persistence.criteria.From;
import jakarta.persistence.criteria.Predicate;
import jakarta.persistence.criteria.Root;
@Component
public class PublicationPredicateMapper {
public Predicate map(
List<PublicationSearchCriterion> criteria,
CriteriaBuilder criteriaBuilder,
Root<PublicationEntity> fromPublication
) {
List<Predicate> criteriaPredicates = criteria.stream()
.map(criterion -> map(criterion, criteriaBuilder, fromPublication))
.toList();
return criteriaBuilder.or(criteriaPredicates.toArray(new Predicate[]{}));
}
private Predicate map(
PublicationSearchCriterion criterion,
CriteriaBuilder criteriaBuilder,
Root<PublicationEntity> fromPublication
) {
return switch (criterion.searchType()) {
case EQUALS -> mapEqualsPredicate(criteriaBuilder, fromPublication, criterion.searchField(), criterion.value());
case CONTAINS -> mapContainsPredicate(criteriaBuilder, fromPublication, criterion.searchField(), criterion.value());
default -> null;
};
}
private Predicate mapEqualsPredicate(
CriteriaBuilder criteriaBuilder,
Root<PublicationEntity> fromPublication,
PublicationSearchField searchField,
Object value
) {
From<?, ?> from = fromPublication;
String attributeName = searchField.name().toLowerCase();
if (searchField == AUTHOR_PSEUDO) {
from = fromPublication.join("author", LEFT);
attributeName = "pseudo";
}
return criteriaBuilder.equal(
from.get(attributeName),
value
);
}
private Predicate mapContainsPredicate(
CriteriaBuilder criteriaBuilder,
Root<PublicationEntity> fromPublication,
PublicationSearchField searchField,
Object value
) {
From<?, ?> from = fromPublication;
String attributeName = searchField.name().toLowerCase();
if (searchField == AUTHOR_PSEUDO) {
from = fromPublication.join("author", LEFT);
attributeName = "pseudo";
}
return criteriaBuilder.like(
from.get(attributeName),
String.format("%%%s%%", value)
);
}
}

View File

@@ -8,7 +8,7 @@ import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
public interface PublicationRepository extends JpaRepository<PublicationEntity, UUID> {
public interface PublicationRepository extends JpaRepository<PublicationEntity, UUID>, CustomPublicationRepository {
@Query("""
SELECT p
FROM PublicationEntity p

View File

@@ -0,0 +1,40 @@
package org.codiki.infrastructure.publication;
import java.util.List;
import static org.assertj.core.api.Assertions.assertThat;
import static org.codiki.domain.publication.model.search.ComparisonType.CONTAINS;
import static org.codiki.domain.publication.model.search.PublicationSearchField.KEY;
import org.codiki.domain.publication.model.search.PublicationSearchCriterion;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
class PublicationSearchCriteriaJpaAdapterTest {
private PublicationSearchCriteriaJpaAdapter adapter;
@BeforeEach
void setUp() {
adapter = new PublicationSearchCriteriaJpaAdapter();
}
@Nested
public class AdaptCriteriaForJpa {
@Test
void should_adapt_criteria_for_jpa() {
// given
List<PublicationSearchCriterion> initialCriteria = List.of(
new PublicationSearchCriterion(KEY, CONTAINS, "critère")
);
// when
List<PublicationSearchCriterion> result = adapter.adaptCriteriaForJpa(initialCriteria);
// then
List<PublicationSearchCriterion> expectedResult = List.of(
new PublicationSearchCriterion(KEY, CONTAINS, "critere")
);
assertThat(result).isEqualTo(expectedResult);
}
}
}