Skip to content

Commit

Permalink
feat: expose endpoint to search using specification
Browse files Browse the repository at this point in the history
  • Loading branch information
rajadilipkolli committed Oct 28, 2023
1 parent b17ffc4 commit a126788
Show file tree
Hide file tree
Showing 7 changed files with 182 additions and 163 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ public class Initializer implements CommandLineRunner {
public void run(String... args) {
log.info("Running Initializer.....");
List<Actor> actorList =
LongStream.rangeClosed(1, 30)
LongStream.rangeClosed(1, 50)
.mapToObj(
actorId -> {
Actor actor = new Actor();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@
import java.util.ArrayList;
import java.util.List;
import lombok.Getter;
import lombok.Setter;

@Getter
@Setter
public class ActorsFilter {

private Kind kind;
Expand All @@ -14,14 +16,6 @@ public class ActorsFilter {

public ActorsFilter() {}

public void setKind(Kind kind) {
this.kind = kind;
}

public void setField(String field) {
this.field = field;
}

@JsonIgnore
public String getValue() {
return values.get(0);
Expand Down Expand Up @@ -70,11 +64,7 @@ public void setHigh(String high) {
values.set(1, high);
}

public void setValues(List<String> values) {
this.values = values;
}

public static enum Kind {
public enum Kind {
EQ,
LT,
GT,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
public record FindActorsQuery(
int pageNo,
int pageSize,
Integer firstResult,
Integer maxResults,
Long lowest,
Long highest,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
package com.example.keysetpagination.repositories;

import com.blazebit.persistence.spring.data.repository.KeysetAwarePage;
import com.blazebit.persistence.spring.data.repository.KeysetPageable;
import com.example.keysetpagination.entities.Actor;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.repository.ListCrudRepository;

public interface ActorRepository extends JpaRepository<Actor, Long> {
public interface ActorRepository extends ListCrudRepository<Actor, Long> {

KeysetAwarePage<Actor> findAll(Specification<Actor> specification, Pageable pageable);
KeysetAwarePage<Actor> findAll(Specification<Actor> specification, KeysetPageable pageable);

KeysetAwarePage<Actor> findAll(KeysetPageable keysetPageable);
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
import org.springframework.transaction.annotation.Transactional;

@Service
@Transactional
@Transactional(readOnly = true)
@RequiredArgsConstructor
public class ActorService {

Expand All @@ -42,8 +42,46 @@ public PagedResult<ActorResponse> findAll(
ActorsFilter[] actorsFilter, FindActorsQuery findActorsQuery) {
Specification<Actor> specification = getSpecificationForFilter(actorsFilter);
KeysetPageable keysetPageable = createPageable(findActorsQuery);
KeysetAwarePage<Actor> keysetAwarePage =
actorRepository.findAll(specification, keysetPageable);
return getActorResponsePagedResult(actorRepository.findAll(specification, keysetPageable));
}

public PagedResult<ActorResponse> findAll(FindActorsQuery findActorsQuery) {
KeysetPageable keysetPageable = createPageable(findActorsQuery);
return getActorResponsePagedResult(actorRepository.findAll(keysetPageable));
}

public Optional<ActorResponse> findActorById(Long id) {
return actorRepository.findById(id).map(actorMapper::toResponse);
}

@Transactional
public ActorResponse saveActor(ActorRequest actorRequest) {
Actor actor = actorMapper.toEntity(actorRequest);
Actor savedActor = actorRepository.save(actor);
return actorMapper.toResponse(savedActor);
}

@Transactional
public ActorResponse updateActor(Long id, ActorRequest actorRequest) {
Actor actor =
actorRepository.findById(id).orElseThrow(() -> new ActorNotFoundException(id));

// Update the actor object with data from actorRequest
actorMapper.mapActorWithRequest(actor, actorRequest);

// Save the updated actor object
Actor updatedActor = actorRepository.save(actor);

return actorMapper.toResponse(updatedActor);
}

@Transactional
public void deleteActorById(Long id) {
actorRepository.deleteById(id);
}

private PagedResult<ActorResponse> getActorResponsePagedResult(
KeysetAwarePage<Actor> keysetAwarePage) {
List<ActorResponse> actorResponseList =
actorMapper.toResponseList(keysetAwarePage.getContent());
return new PagedResult<>(
Expand Down Expand Up @@ -71,7 +109,7 @@ private KeysetPageable createPageable(FindActorsQuery findActorsQuery) {
: Sort.Order.desc(findActorsQuery.sortBy()));
KeysetPage keysetPage =
new DefaultKeysetPage(
findActorsQuery.firstResult() == null ? 0 : findActorsQuery.firstResult(),
findActorsQuery.pageNo() - 1,
findActorsQuery.maxResults() == null
? findActorsQuery.pageSize()
: findActorsQuery.maxResults(),
Expand All @@ -81,154 +119,111 @@ private KeysetPageable createPageable(FindActorsQuery findActorsQuery) {
keysetPage, PageRequest.of(pageNo, findActorsQuery.pageSize(), sort));
}

public Optional<ActorResponse> findActorById(Long id) {
return actorRepository.findById(id).map(actorMapper::toResponse);
}

public ActorResponse saveActor(ActorRequest actorRequest) {
Actor actor = actorMapper.toEntity(actorRequest);
Actor savedActor = actorRepository.save(actor);
return actorMapper.toResponse(savedActor);
}

public ActorResponse updateActor(Long id, ActorRequest actorRequest) {
Actor actor =
actorRepository.findById(id).orElseThrow(() -> new ActorNotFoundException(id));

// Update the actor object with data from actorRequest
actorMapper.mapActorWithRequest(actor, actorRequest);

// Save the updated actor object
Actor updatedActor = actorRepository.save(actor);

return actorMapper.toResponse(updatedActor);
}

public void deleteActorById(Long id) {
actorRepository.deleteById(id);
}

private Specification<Actor> getSpecificationForFilter(final ActorsFilter[] actorsFilter) {
if (actorsFilter == null || actorsFilter.length == 0) {
return null;
}
return new Specification<Actor>() {
@Override
public Predicate toPredicate(
Root<Actor> root,
CriteriaQuery<?> criteriaQuery,
CriteriaBuilder criteriaBuilder) {
List<Predicate> predicates = new ArrayList<>();
ParserContext parserContext = new ParserContextImpl();
try {
for (ActorsFilter f : actorsFilter) {
SerializableFormat<?> format = FILTER_ATTRIBUTES.get(f.getField());
if (format != null) {
String[] fieldParts = f.getField().split("\\.");
Path<?> path = root.get(fieldParts[0]);
for (int i = 1; i < fieldParts.length; i++) {
path = path.get(fieldParts[i]);
}
switch (f.getKind()) {
case EQ:
predicates.add(
criteriaBuilder.equal(
path,
format.parse(f.getValue(), parserContext)));
break;
case GT:
predicates.add(
criteriaBuilder.greaterThan(
(Expression<Comparable>) path,
(Comparable)
format.parse(
f.getValue(), parserContext)));
break;
case LT:
predicates.add(
criteriaBuilder.lessThan(
(Expression<Comparable>) path,
(Comparable)
format.parse(
f.getValue(), parserContext)));
break;
case GTE:
predicates.add(
criteriaBuilder.greaterThanOrEqualTo(
(Expression<Comparable>) path,
(Comparable)
format.parse(
f.getValue(), parserContext)));
break;
case LTE:
predicates.add(
criteriaBuilder.lessThanOrEqualTo(
(Expression<Comparable>) path,
(Comparable)
format.parse(
f.getValue(), parserContext)));
break;
case IN:
List<String> values = f.getValues();
List<Object> filterValues = new ArrayList<>(values.size());
for (String value : values) {
filterValues.add(format.parse(value, parserContext));
}
predicates.add(path.in(filterValues));
break;
case BETWEEN:
predicates.add(
criteriaBuilder.between(
(Expression<Comparable>) path,
(Comparable)
format.parse(f.getLow(), parserContext),
(Comparable)
format.parse(
f.getHigh(), parserContext)));
break;
case STARTS_WITH:
predicates.add(
criteriaBuilder.like(
(Expression<String>) path,
format.parse(f.getValue(), parserContext)
+ "%"));
break;
case ENDS_WITH:
predicates.add(
criteriaBuilder.like(
(Expression<String>) path,
"%"
+ format.parse(
f.getValue(), parserContext)));
break;
case CONTAINS:
predicates.add(
criteriaBuilder.like(
(Expression<String>) path,
"%"
+ format.parse(
f.getValue(), parserContext)
+ "%"));
break;
default:
throw new UnsupportedOperationException(
"Unsupported kind: " + f.getKind());
}
return (root, criteriaQuery, criteriaBuilder) -> {
List<Predicate> predicates = new ArrayList<>();
ParserContext parserContext = new ParserContextImpl();
try {
for (ActorsFilter f : actorsFilter) {
SerializableFormat<?> format = FILTER_ATTRIBUTES.get(f.getField());
if (format != null) {
String[] fieldParts = f.getField().split("\\.");
Path<?> path = root.get(fieldParts[0]);
for (int i = 1; i < fieldParts.length; i++) {
path = path.get(fieldParts[i]);
}
switch (f.getKind()) {
case EQ:
predicates.add(
criteriaBuilder.equal(
path, format.parse(f.getValue(), parserContext)));
break;
case GT:
predicates.add(
criteriaBuilder.greaterThan(
(Expression<Comparable>) path,
(Comparable)
format.parse(f.getValue(), parserContext)));
break;
case LT:
predicates.add(
criteriaBuilder.lessThan(
(Expression<Comparable>) path,
(Comparable)
format.parse(f.getValue(), parserContext)));
break;
case GTE:
predicates.add(
criteriaBuilder.greaterThanOrEqualTo(
(Expression<Comparable>) path,
(Comparable)
format.parse(f.getValue(), parserContext)));
break;
case LTE:
predicates.add(
criteriaBuilder.lessThanOrEqualTo(
(Expression<Comparable>) path,
(Comparable)
format.parse(f.getValue(), parserContext)));
break;
case IN:
List<String> values = f.getValues();
List<Object> filterValues = new ArrayList<>(values.size());
for (String value : values) {
filterValues.add(format.parse(value, parserContext));
}
predicates.add(path.in(filterValues));
break;
case BETWEEN:
predicates.add(
criteriaBuilder.between(
(Expression<Comparable>) path,
(Comparable)
format.parse(f.getLow(), parserContext),
(Comparable)
format.parse(f.getHigh(), parserContext)));
break;
case STARTS_WITH:
predicates.add(
criteriaBuilder.like(
(Expression<String>) path,
format.parse(f.getValue(), parserContext) + "%"));
break;
case ENDS_WITH:
predicates.add(
criteriaBuilder.like(
(Expression<String>) path,
"%" + format.parse(f.getValue(), parserContext)));
break;
case CONTAINS:
predicates.add(
criteriaBuilder.like(
(Expression<String>) path,
"%"
+ format.parse(f.getValue(), parserContext)
+ "%"));
break;
default:
throw new UnsupportedOperationException(
"Unsupported kind: " + f.getKind());
}
}
} catch (ParseException ex) {
throw new RuntimeException(ex);
}
return criteriaBuilder.and(predicates.toArray(new Predicate[predicates.size()]));
} catch (ParseException ex) {
throw new RuntimeException(ex);
}
return criteriaBuilder.and(predicates.toArray(new Predicate[0]));
};
}

private static class ParserContextImpl implements ParserContext {
private final Map<String, Object> contextMap;

private ParserContextImpl() {
this.contextMap = new HashMap();
this.contextMap = new HashMap<>();
}

public Object getAttribute(String name) {
Expand Down
Loading

0 comments on commit a126788

Please sign in to comment.