diff --git a/src/main/java/org/codiki/core/entities/business/SearchEntity.java b/src/main/java/org/codiki/core/entities/business/SearchEntity.java new file mode 100755 index 0000000..94dcc8f --- /dev/null +++ b/src/main/java/org/codiki/core/entities/business/SearchEntity.java @@ -0,0 +1,35 @@ +package org.codiki.core.entities.business; + +import org.codiki.core.entities.persistence.Post; + +public class SearchEntity { + + private Post post; + + private int score; + + public SearchEntity(Post post) { + super(); + this.post = post; + } + + public Post getPost() { + return post; + } + + public void setPost(Post post) { + this.post = post; + } + + public int getScore() { + return score; + } + + public void increaseScore(int pScoreToAdd) { + score += pScoreToAdd; + } + + public void decreaseScore(int pScoreToRemove) { + score -= pScoreToRemove; + } +} diff --git a/src/main/java/org/codiki/core/repositories/PostRepository.java b/src/main/java/org/codiki/core/repositories/PostRepository.java index f428d36..42597ed 100755 --- a/src/main/java/org/codiki/core/repositories/PostRepository.java +++ b/src/main/java/org/codiki/core/repositories/PostRepository.java @@ -10,7 +10,7 @@ import org.springframework.data.repository.query.Param; import org.springframework.stereotype.Repository; @Repository -public interface PostRepository extends CrudRepository { +public interface PostRepository extends CrudRepository, PostSearchRepository { @Query("SELECT p FROM Post p WHERE p.key = :postKey") Optional getByKey(@Param("postKey") final String pPostKey); diff --git a/src/main/java/org/codiki/core/repositories/PostSearchRepository.java b/src/main/java/org/codiki/core/repositories/PostSearchRepository.java new file mode 100755 index 0000000..33fbdfd --- /dev/null +++ b/src/main/java/org/codiki/core/repositories/PostSearchRepository.java @@ -0,0 +1,9 @@ +package org.codiki.core.repositories; + +import java.util.List; + +import org.codiki.core.entities.persistence.Post; + +public interface PostSearchRepository { + List search(final String[] pCriterias); +} diff --git a/src/main/java/org/codiki/core/repositories/PostSearchRepositoryImpl.java b/src/main/java/org/codiki/core/repositories/PostSearchRepositoryImpl.java new file mode 100755 index 0000000..2548b2e --- /dev/null +++ b/src/main/java/org/codiki/core/repositories/PostSearchRepositoryImpl.java @@ -0,0 +1,51 @@ +package org.codiki.core.repositories; + +import java.util.LinkedList; +import java.util.List; + +import javax.persistence.EntityManager; +import javax.persistence.PersistenceContext; +import javax.persistence.PersistenceException; +import javax.persistence.Query; + +import org.codiki.core.entities.persistence.Post; +import org.codiki.core.utils.StringUtils; + +public class PostSearchRepositoryImpl implements PostSearchRepository { + @PersistenceContext + private EntityManager em; + + @SuppressWarnings("unchecked") + @Override + public List search(final String[] pCriterias) { + List result = new LinkedList<>(); + + final String queryStr = buildSearchQuery(pCriterias); + try { + final Query query = em.createQuery(queryStr); + for(int i = 1 ; i <= pCriterias.length ; i++) { + query.setParameter(StringUtils.concat("c", i), StringUtils.concat('%', pCriterias[i - 1].toLowerCase(), '%')); + } + result = query.getResultList(); + } catch(final PersistenceException pEx) { + // TODO + } + + return result; + } + + protected String buildSearchQuery(final String[] pCriterias) { + final StringBuilder result = new StringBuilder("SELECT p FROM Post p JOIN FETCH p.category JOIN FETCH p.author "); + + for(int i = 1 ; i <= pCriterias.length ; i++) { + result.append(StringUtils.concat((i == 1) ? "WHERE" : "OR", " LOWER(p.title) LIKE :c", i, + " OR LOWER(p.description) LIKE :c", i, + " OR LOWER(p.text) LIKE :c", i, + " OR LOWER(p.category.name) LIKE :c", i, + " OR LOWER(p.author.name) LIKE :c", i, + " OR LOWER(p.author.email) LIKE :c", i, " ")); + } + + return result.toString(); + } +} diff --git a/src/main/java/org/codiki/posts/PostController.java b/src/main/java/org/codiki/posts/PostController.java index 47b8f2f..3c38ce4 100755 --- a/src/main/java/org/codiki/posts/PostController.java +++ b/src/main/java/org/codiki/posts/PostController.java @@ -54,7 +54,9 @@ public class PostController { public PostDTO getByKey(@PathVariable("postKey") final String pPostKey, final HttpServletResponse response) { final PostDTO result = getByKeyAndSource(pPostKey, response); - result.setText(parserService.parse(result.getText())); + if(result != null) { + result.setText(parserService.parse(result.getText())); + } return result; } @@ -79,10 +81,9 @@ public class PostController { .map(PostDTO::new).collect(Collectors.toList()); } - @PostMapping("/search") - public List search() { - // TODO - return null; + @GetMapping("/search/{searchCriteria}") + public List search(@PathVariable("searchCriteria") final String pSearchCriteria) { + return postService.search(pSearchCriteria); } @GetMapping("/byCategory/{categoryId}") diff --git a/src/main/java/org/codiki/posts/PostService.java b/src/main/java/org/codiki/posts/PostService.java index 350afed..4fd6bcd 100755 --- a/src/main/java/org/codiki/posts/PostService.java +++ b/src/main/java/org/codiki/posts/PostService.java @@ -1,12 +1,18 @@ package org.codiki.posts; import java.io.IOException; +import java.util.Collections; import java.util.Date; +import java.util.LinkedList; +import java.util.List; import java.util.Optional; +import java.util.stream.Collectors; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; +import org.apache.commons.lang3.StringUtils; +import org.codiki.core.entities.business.SearchEntity; import org.codiki.core.entities.dto.PostDTO; import org.codiki.core.entities.persistence.Category; import org.codiki.core.entities.persistence.Post; @@ -19,6 +25,17 @@ import org.springframework.stereotype.Service; @Service public class PostService { + private static final int SCORE_TITLE = 50; + + private static final int SCORE_DESCRIPTION = 5; + + private static final int SCORE_TEXT = 1; + + private static final int SCORE_CATEGORY = 30; + + private static final int SCORE_AUTHOR_NAME = 40; + + private static final int SCORE_AUTHOR_EMAIL = 40; @Autowired private PostRepository postRepository; @@ -96,4 +113,62 @@ public class PostService { pResponse.sendError(HttpServletResponse.SC_NOT_FOUND); } } + + public List search(String pSearchCriteria) { + final String[] criteriasArray = pSearchCriteria.split(" "); + + final List listSearchEntities = new LinkedList<>(); + postRepository.search(criteriasArray).stream().map(SearchEntity::new).forEach(e -> { + calculateScore(e, criteriasArray); + listSearchEntities.add(e); + }); + Collections.sort(listSearchEntities, (e1, e2) -> e1.getScore() - e2.getScore()); + + return listSearchEntities.stream().map(SearchEntity::getPost).map(PostDTO::new).collect(Collectors.toList()); + } + + void calculateScore(final SearchEntity searchPost, final String[] pCriteriasArray) { + for(final String criteria : pCriteriasArray) { + calculateScoreForTitle(searchPost, criteria); + calculateScoreForDescription(searchPost, criteria); + calculateScoreForText(searchPost, criteria); + calculateScoreForCategory(searchPost, criteria); + calculateScoreForAuthorName(searchPost, criteria); + calculateScoreForAuthorEmail(searchPost, criteria); + } + } + + private void calculateScoreForTitle(final SearchEntity pSearchPost, final String pCriteria) { + if(pSearchPost.getPost().getTitle().contains(pCriteria)) { + pSearchPost.increaseScore(SCORE_TITLE); + } + } + + private void calculateScoreForDescription(final SearchEntity pSearchPost, final String pCriteria) { + final int criteriaOccurence = StringUtils.countMatches(pSearchPost.getPost().getDescription(), pCriteria); + pSearchPost.increaseScore(criteriaOccurence * SCORE_DESCRIPTION); + } + + private void calculateScoreForText(final SearchEntity pSearchPost, final String pCriteria) { + final int criteriaOccurence = StringUtils.countMatches(pSearchPost.getPost().getText(), pCriteria); + pSearchPost.increaseScore(criteriaOccurence * SCORE_TEXT); + } + + private void calculateScoreForCategory(final SearchEntity pSearchPost, final String pCriteria) { + if(pSearchPost.getPost().getCategory().getName().contains(pCriteria)) { + pSearchPost.increaseScore(SCORE_CATEGORY); + } + } + + private void calculateScoreForAuthorName(final SearchEntity pSearchPost, final String pCriteria) { + if(pSearchPost.getPost().getAuthor().getName().contains(pCriteria)) { + pSearchPost.increaseScore(SCORE_AUTHOR_NAME); + } + } + + private void calculateScoreForAuthorEmail(final SearchEntity pSearchPost, final String pCriteria) { + if(pSearchPost.getPost().getAuthor().getEmail().contains(pCriteria)) { + pSearchPost.increaseScore(SCORE_AUTHOR_EMAIL); + } + } }