Spring Data REST custom query integration - spring

I want to create a REST link for an Employee entity that will basically be a findByAllFields query. Of course this should be combined with Page and Sort. In order to do that I have implemented the following code:
#Entity
public class Employee extends Persistable<Long> {
#Column
private String firstName;
#Column
private String lastName;
#Column
private String age;
#Column
#Temporal(TemporalType.TIMESTAMP)
private Date hiringDate;
}
So I would like to have lets say a query where I can do:
http://localhost:8080/myApp/employees/search/all?firstName=me&lastName=self&ageFrom=20&ageTo=30&hiringDateFrom=12234433235
So I have the following Repository
#RepositoryRestResource(collectionResourceRel="employees", path="employees")
public interface EmployeeRepository extends PagingAndSortingRepository<Employee, Long>,
JpaSpecificationExecutor<Employee> {
}
Ok so now I need a RestController
#RepositoryRestController
public class EmployeeSearchController {
#Autowired
private EmployeeRepository employeRepository;
#RequestMapping(value = "/employees/search/all/search/all", method = RequestMethod.GET)
public Page<Employee> getEmployees(EmployeeCriteria filterCriteria, Pageable pageable) {
//EmployeeSpecification uses CriteriaAPI to form dynamic query with the fields from filterCriteria
Specification<Employee> specification = new EmployeeSpecification(filterCriteria);
return employeeRepository.findAll(specification, pageable);
}
Ok, obviously this does its job but it is not integrated with HATEOAS.
I have attempted to assemble a resource changing the controller to this:
public PagedResources<Resource<Employee>> getEmployees(
PagedResourcesAssembler<Employee> assembler,
EmployeeCriteria filterCriteria, Pageable pageable) {
//EmployeeSpecification uses CriteriaAPI to form dynamic query with the fields from filterCriteria
Specification<Employee> specification = new EmployeeSpecification(filterCriteria);
Page<Employee> employees = employeeRepository.findAll(specification, pageable);
return assembler.toResource(employees);
}
Obviously I'm missing something from the above since it doesnt work and I'm getting the following Exception:
Could not instantiate bean class [org.springframework.data.web.PagedResourcesAssembler]: No default constructor found;
Ok so to make the question clear I am trying to integrate the above resource into the rest of the HATEOAS architecture. I'm not entirely sure if this is the correct approach so any other suggestions are welcome.
EDIT:
Here you can see a similar implementation. Please take a look at the configuration, you will see that all but one of the "Person" controllers are working.
https://github.com/cgeo7/spring-rest-example

Try autowring PagedResourcesAssembler as a class member and change method signature something like below
#RepositoryRestController
public class EmployeeSearchController {
#Autowired
private EmployeeRepository employeRepository;
#Autowired
private PagedResourcesAssembler<Employee> pagedAssembler;
#RequestMapping(value = "/employees/search/all/search/all", method = RequestMethod.GET)
public ResponseEntity<Resources<Resource<Employee>>> getEmployees(EmployeeCriteria filterCriteria, Pageable pageable) {
//EmployeeSpecification uses CriteriaAPI to form dynamic query with the fields from filterCriteria
Specification<Employee> specification = new EmployeeSpecification(filterCriteria);
Page<Employee> employees = employeeRepository.findAll(specification, pageable);
return assembler.toResource(employees);
}
}
This works perfectly with Spring Data Rest 2.1.4.RELEASE

The code by #Stackee007 works but the resource won't include self links. In order to do that, a little more is required.
#Autowired
PagedResourcesAssembler<Appointment> pagedResourcesAssembler;
#RequestMapping(value = "/findTodaysSchedule")
public HttpEntity<PagedResources<Resource<Appointment>>> getTodaysSchedule(
PersistentEntityResourceAssembler entityAssembler, Pageable pageable) {
Page<Appointment> todaysSchedule = apptRepo.findByStartTimeBetween(beginningOfDay, endOfDay, pageable);
#SuppressWarnings({ "unchecked", "rawtypes" })
PagedResources<Resource<Appointment>> resource = pagedResourcesAssembler.toResource(todaysSchedule,
(ResourceAssembler) entityAssembler);
return new ResponseEntity<>(resource, HttpStatus.OK);
}

Spring HATEOAS has changed the name of Resource, PagedResources and some other classes. See here. Below is a working version in 2020.
#RepositoryRestController
public class EmployeeSearchController {
#Autowired
private EmployeeRepository employeRepository;
#Autowired
private PagedResourcesAssembler<Employee> pagedAssembler;
#RequestMapping(value = "/employees/search/all", method = RequestMethod.GET)
public ResponseEntity<PagedModel<EntityModel<Employee>>> getEmployees(PersistentEntityResourceAssembler entityAssembler,,
EmployeeCriteria filterCriteria,
Pageable pageable) {
Specification<Employee> specification = new EmployeeSpecification(filterCriteria);
Page<Employee> employees = employeeRepository.findAll(specification, pageable);
return ResponseEntity.ok(pagedAssembler.toModel(plants, (RepresentationModelAssembler) entityAssembler));
}
}

Related

#PreAuthorize how do i add a parameter into the expression

Maybe somebody has an idea
I have an abstract controller providing me the endpoints i need.
In the #PreAuthorized i would execute the check if the user has the required roles.
Problem is that i have only one function and i want to check which endpoint is currently evaluated.
This is the code:
public abstract class CoreController<T> {
private final JpaRepository repository;
private final CoreService service;
public String endpoint;
private String view;
public CoreController(CoreService service, JpaRepository repository, String endpoint, String view) {
this.service=service;
this.repository = repository;
this.endpoint=endpoint;
this.view=view;
}
#PreAuthorize("#checkAccess.isAllowedToGet(#endpoint)")
#RequestMapping(method = RequestMethod.GET, value = "/get")
public ResponseEntity<CrudPage<Map<String, Object>>> get(CoreCriteria criteria) {
criteria.setView(view);
CrudPage<Map<String, Object>> data = service.getPage(criteria);
return ResponseEntity.ok(data);
}
The problem is the endpoint is always null when the function is called.
How should i change the expression to make this work.

Spring boot + DocumentDB custom Query for searching documents

I have Spring Boot + DocumentDB application in which we need to implement a search API, The Search fields are part of nested Json are as below:
{
"id":"asdf123a",
"name":"XYZ",
add{
"city":"ABC"
"pin":123456,
}
}
I need to search with name="XYZ" and city="ABC", I'm trying with below code but somehow not able to retrieve the record.
But I'm able to retrieve with just by Name or ID, but not with name and city or just city which is part of nested JSON.
Employee{
private String id;
private String name;
private Address add
/*Getter and Setters {} */
}
Address{
private String city;
private Long pin;
/*Getter and Setters {} */
}
public class EmployeeController {
EmployeeRepository repository;
#Autowire
EmployeeController (EmployeeRepository repository){
this.repository = repository;
}
#GetMapping(value = "/search", produces = APPLICATION_JSON_VALUE_WITH_UTF8)
public ResponseEntity<?> Search (#RequestParam ("name")String name,
#RequestParam("city") String city){
return new ResponseEntity <> (repository
.findByNameLikeAndAddressCityLike(
name, city),
HttpStatus.OK
);
}
}
#Repository
public interface EmployeeRepository extends DocumentDbRepository<Employee,
String> {
Optional<Employee>findByNameLike(String name); // Perfectly working
Optional<Employee>findByAddressCityLike(String city); // Not working
Optional<Employee>findByNameLikeAndAddressCityLike(String name, String
city); // Not Working
}
Also Just like Spring JPA we use #Query to fire custom/ native query are there any DocumentDB Annotation present if so please guide me with example or Docuemnt. Looking for help

Filtering with Spring Data Projection

I've created a class based Projection for my Spring Data Repository. That works great. Then I tried to annotate the constructor with #QueryProjection from QueryDSL, hoping to get a REST Endpoint with paging, sorting and filtering.
The code looks like this, but there are way more fields and details omitted for brevity:
Entity:
#Data
#Entity
public class Entity extends BaseEntity {
private String fieldA, fieldB;
private AnotherEntity ae;
}
DTO:
#Getter
#FieldDefaults(makeFinal = true, level = AccessLevel.PRIVATE)
public class EntityDto {
private final String fieldA;
private final String anotherEntityFieldC;
#QueryProjection
public EntityDto(final String fieldA, final String anotherEntityFieldC) {
this.fieldA = fieldA;
this.anotherEntityFieldC = anotherEntityFieldC;
}
}
Repository:
public EntityRepo extends JpaRepository<Entity, Long>, QuerydslBinderCustomizer<EntityPath<Entity>>, QuerydslPredicateExecutor<Entity> {
Page<EntityDto> findPageProjectedBy(Predicate predicate, Pageable pageable);
}
Endpoint:
#RestController
#RequestMapping(EntityEndpoint.ROOT)
#RequiredArgsConstructor(onConstructor = #__({#Autowired}))
public EntityEndpoint {
private final EntityRepo er;
#GetMapping
public ResponseEntity<PagedResources<Resource<EntityDto>>> getAllEntities(
Pageable pageable,
#QuerydslPredicate(root = EntityDto#.class) Predicate predicate,
PagedResourcesAssembler<EntityDto> assembler) {
Page<EntityDto> page = er.findPageProjectedBy(predicate, pageable);
return new ResponseEntity<>(assembler.toResource(page), HttpStatus.OK);
}
}
I get the exception:
java.lang.IllegalStateException: Did not find a static field of the same type in class at.dataphone.logis4.model.dto.QBuchungDto!
Stacktrace as gist
And that's the URL:
curl -X GET --header 'Accept: application/json' 'http://localhost:8080/Entity'
Well, it seems like you can only query with the actual Entity.
I think of it as the part in the 'WHERE'-clause, which can only have columns actually in the selected Entities, while the DTO projection happens in the 'SELECT'-clause.
The #QueryProjection-Annotation seems to serve only for the QueryDsl code generator to mark class which then can be used to create projections in a select-clause

Get all documents from an index using spring-data-elasticsearch

I am trying to connect to my external ElasticSearch server with Spring Boot.
If I do a curl from command line, I get expected results.
curl "http://ipAddr:9200/indexName/TYPE/_search?pretty=true"
But getting this error when I try to access it via Spring Boot.
<html><body><h1>Whitelabel Error Page</h1><p>This application has no explicit mapping for /error, so you are seeing this as a fallback.</p><div id='created'>Mon Sep 11 12:39:15 IST 2017</div><div>There was an unexpected error (type=Internal Server Error, status=500).</div><div>Could not write JSON: (was java.lang.NullPointerException); nested exception is com.fasterxml.jackson.databind.JsonMappingException: (was java.lang.NullPointerException) (through reference chain: java.util.ArrayList[0]->org.springframework.data.elasticsearch.core.aggregation.impl.AggregatedPageImpl["facets"])</div></body></html>
Not sure why a NullPointerException and what is aggregartion.impl
Here is my Spring Application:
Controller:
#RestController
public class PojoController {
#Autowired
PojoService pojoService;
#RequestMapping(value = "/", method=RequestMethod.GET)
public #ResponseBody String index() {
return new String("Welcome:)");
}
#RequestMapping(value = "/all", method = RequestMethod.GET,
produces = { MediaType.APPLICATION_JSON_VALUE })
#ResponseBody List<POJO> findAll() {
try {
List<POJO> pojoObj = pojoService.findAll();
return pojoObj;
} catch (Exception exp) {
exp.printStackTrace();
return null;
}
}
}
Repository:
#Repository
public interface PojoRepository extends ElasticsearchRepository<POJO, Integer> {
List<POJO> findAll();
}
Service:
#Service
public class POJOServiceImpl implements POJOService{
private POJORepository pojoRepository;
private ElasticsearchTemplate elasticsearchTemplate;
#Autowired
public void setPojoRepository(PojoRepository pojoRepository) {
this.pojoRepository = pojoRepository;
}
public POJO findOne(String id) {
return pojoRepository.findOne(id);
}
public List<POJO> findAll() {
return (List<POJO>) pojoRepository.findAll();
}
}
POJO class:
#Document(indexName = "INDEX", type = "TYPE")
public class POJO {
#Id
private Integer id;
private String name;
public POJO(){
// empty
}
public POJO(Integerid, String name) {
super();
this.id = id;
this.name = name;
}
// getters and setters
}
I should be able to query all the documents in the index. Later on, I will try and use filters etc.
Any help is appreciated. Thanks :)
It looks like Jackson has a problem with handling your POJO (probably related to this issue: DATAES-274) - the problematic part is casting in repository from Iterable collection to List.
Update
In case of repositories, spring-data-elasticsearch behaves a bit different than you would expect. Taking your example:
#Repository
public interface PojoRepository extends ElasticsearchRepository<POJO, Integer> {
List<POJO> findAll();
}
and after calling in your rest controller:
List<POJO> pojoObj = pojoService.findAll();
in debugger you will see something like this:
You would expect that pojoObj list contains objects of POJO class.
And here comes the surprise - pojoObj ArrayList contains one object of AggregatedPageImpl type and its content field is the right list that contains your POJO objects.
This is the reason why you get:
Could not write JSON: ... java.util.ArrayList[0]->org.springframework.data.elasticsearch.core.aggregation.impl.AggregatedPageImpl[\"facets\"])
As I wrote before, Jackson cannot handle this while serializing POJO objects.
Solution 1
Let repositories return Iterable collection (by default).
#Repository
public interface PojoRepository extends ElasticsearchRepository<POJO, Integer> {
}
Move the conversion part to the service but use some utility method (here with Guava) in order to have it like this:
import com.google.common.collect.Lists;
public List<POJO> findAll() {
return Lists.newArrayList(pojoRepository.findAll());
}
Solution 2
Use Page in repository (here simplified version without parameters):
#Repository
public interface PojoRepository extends ElasticsearchRepository<POJO, Integer> {
Page<TestDto> findAll();
}
If you still want to operate on list - get content from page in service:
public List<POJO> findAll() {
return testDtoRepository.findAll().getContent();
}

Implementing/Overriding MongoRepository Keep HATEOAS Formatting

I have a simple MongoRepository I would like to modify to return the generated ObjectId on post(save()).
public interface EmployeeRepository extends MongoRepository<Employee, String>
{
public void delete(Employee employee);
public Employee save(Employee employee);
public Employee findOne(String id);
public List<Employee> findAll();
public Employee findByName(String principal);
}
I have explored ways to generate the id client side and pass it in the post BUT I really want Spring to handle this.
I've tried intercepting with a controller and returning the object in the ResponseBody, like so:
#RequestMapping(value=URI_EMPLOYEES, method=RequestMethod.POST)
public #ResponseBody Employee addEmployee(#RequestBody Employee employee) {
return repo.save(employee);
}
Problem with this is it forces me to re-work all the HATEOAS related logic Spring handles for me. Which is a MAJOR pain. (Unless I'm missing something.)
What's the most effective way of doing this without having to replace all of the methods?
Was using #Controller instead of #RepositoryRestController which was causing things to act up.
We can now easily override the POST method on this resource to return whatever we want while keeping spring-data-rest's implementation of the EmployeeRepository intact.
#RepositoryRestController
public class EmployeeController {
private final static String URI_EMPLOYEES = "/employees";
#Autowired private EmployeeRepository repo;
#RequestMapping(value=URI_EMPLOYEES, method=RequestMethod.POST)
public #ResponseBody HttpEntity<Employee> addVideo(#RequestBody Employee employee) {
return new ResponseEntity<Employee>(repo.save(employee), HttpStatus.OK);
}
}
Have a look at the interface:
public Employee save(Employee employee)
You can get the saved entity by simply doing
Employee saved = repository.save(employee);
return saved.getId();
The being said, you surely can generate the Id and set it via setId(). But once an ID is saved, it is immutable. Changing the Id and saving that entity would result in a new document saved in MongoDB.

Resources