Exposing a paginated search link and its arguments - rest

I'm using the latest of Spring REST and HATEOAS trying to expose a link to a search endpoint.
Here is the resource assembler:
#Component
public class AdminResourceAssembler extends ResourceAssemblerSupport<Admin, AdminResource> {
public AdminResourceAssembler() {
super(AdminController.class, AdminResource.class);
}
public AdminResource toResource(Admin admin) {
AdminResource adminResource = createResourceWithId(admin.getId(), admin);
BeanUtils.copyProperties(admin, adminResource);
adminResource.add(linkTo(AdminController.class).slash(admin.getId()).slash("module").withRel("module"));
return adminResource;
}
}
Here is the end point controller:
#RequestMapping(value = UriMappingConstants.PATH_SEPARATOR + UriMappingConstants.SEARCH, method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
#ResponseBody
public ResponseEntity<PagedResources<AdminResource>> search(#RequestParam(value = "searchTerm", required = true) String searchTerm, #PageableDefault Pageable pageable, PagedResourcesAssembler<Admin> pagedResourcesAssembler, UriComponentsBuilder builder) {
HttpHeaders responseHeaders = new HttpHeaders();
Pageable pageRequest = buildPageRequest(pageable.getPageNumber(), pageable.getPageSize());
Page<Admin> searchedAdmins = adminService.search(searchTerm, pageRequest);
responseHeaders.setLocation(builder.path(UriMappingConstants.PATH_SEPARATOR + UriMappingConstants.ADMINS + UriMappingConstants.PATH_SEPARATOR + "search").queryParam("searchTerm", searchTerm).queryParam("page", pageable.getPageNumber()).queryParam("size", pageable.getPageSize()).buildAndExpand(searchTerm).toUri());
PagedResources<AdminResource> adminPagedResources = pagedResourcesAssembler.toResource(searchedAdmins, adminResourceAssembler);
return new ResponseEntity<PagedResources<AdminResource>>(adminPagedResources, responseHeaders, HttpStatus.OK);
}
private Pageable buildPageRequest(int pageIndex, int pageSize) {
Sort sort = new Sort(new Sort.Order(Sort.Direction.ASC, "lastname"), new Sort.Order(Sort.Direction.ASC, "firstname"));
return new PageRequest(pageIndex, pageSize, sort);
}
First, I'm not sure if I should call in the buildPageRequest method and simply pass in the original pageable to the search service.
The problem I have is two fold:
The published link in the response is missing the searchTerm parameter:
{"rel":"self","href":"http://localhost:8080/nitro-project-rest/admins/search{?page,size,sort}
I would expect it to be like:
{"rel":"self","href":"http://localhost:8080/nitro-project-rest/admins/search{?searchTerm,page,size,sort}
But again, as a newbie I don't know for sure.
And the controller always fetches the first page of the 10 items, ignoring the page number and size arguments I give in the request:
curl -H "Accept:application/json" --user joethebouncer:mignet http://localhost:8080/nitro-project-rest/admins/search?searchTerm=irstna&page=3&size=5
I guess I'm not too far from a asolution, but I don't even know exactly what the exposed link should look like.
Any directions would be very nice :-)
EDIT: Added information
The pageable configuration:
#Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
PageableHandlerMethodArgumentResolver resolver = new PageableHandlerMethodArgumentResolver();
argumentResolvers.add(resolver);
super.addArgumentResolvers(argumentResolvers);
}
The service is simply a wrap around the repository:
public Page<Admin> search(String searchTerm, Pageable page) {
return adminRepository.search(searchTerm, page);
}
which is an interface:
#Query("SELECT a FROM Admin a WHERE LOWER(a.firstname) LIKE LOWER(CONCAT('%', :searchTerm, '%')) OR LOWER(a.lastname) LIKE LOWER(CONCAT('%', :searchTerm, '%')) OR LOWER(a.email) LIKE LOWER(CONCAT('%', :searchTerm, '%')) OR LOWER(a.login) LIKE LOWER(CONCAT('%', :searchTerm, '%')) ORDER BY a.lastname ASC, a.firstname ASC") public
Page search(#Param("searchTerm") String searchTerm, Pageable page);
The database is H2 fronted by JPA and the console shows:
One can see that the offset is missing...
select admin0_.id as id1_1_, admin0_.version as version2_1_, admin0_.email as email3_1_, admin0_.firstname as firstnam4_1_, admin0_.lastname as lastname5_1_, admin0_.login as login6_1_, admin0_.password as password7_1_, admin0_.password_salt as password8_1_, admin0_.post_login_url as post_log9_1_ from admin admin0_ where lower(admin0_.firstname) like lower(('%'||'irstn'||'%')) order by admin0_.lastname ASC, admin0_.firstname ASC, admin0_.lastname asc, admin0_.firstname asc limit 10
Kind Regards,
Stephane Eybert

no idea what buildPageRequest is doing...but i would bet you don't need it and it's the source of your paging problems
Regarding the self link. PagedResourceAssembler creates a very simple default one...but you can build a more complicated one and pass it in as a third parameter of the toResource method. So just build your own link in any of the many ways. I'd suggest something like:
linkTo(methodOn(SomeController.class).search(searchTerm, pageable, null, null))
another thing to note that a self link should never be templated so this is a bug in spring-hateoas (really a dependency) see https://jira.spring.io/browse/DATACMNS-515

Stupid me. I could not see curl needed double quotes around the url to allow for multiple parameters. curl -H "Accept:application/json" --user joethebouncer:mignet "localhost:8080/nitro-project-rest/admins/… now works fine.

Related

How pass value with #Param annottaion to a method

I decided move all my jpql queries to another class, for example
public class QueryUtils {
public static final String FIND_All_CLUBS= "select c from Club c order by c.club_name";
}
In repository I have this:
#Query(value = QueryUtils.FIND_All_CLUBS)
Iterable<Club> findAllAndAndOOrderByClub_name();
And it works fine. But I'm stuck with that thing : what if I have params in my jpql, f.e.
#Query(value = "select c from Comment c where c.post.post_id = :id order by c.replyTo.comment_id nulls last")
Iterable<Comment> findAllCommentsOfPost(#Param("id") long id);
I'm tried to write this:
public static String getFIND_ALL_COMMENTS_OF_POST(String id){
return new StringBuilder("select c from Comment c where c.post.post_id = ").append(id).append(" order by c.replyTo.comment_id nulls last").toString();
}
But how pass param from repository to this method, this code(which I tried is not valid):
#Query(QueryUtils.getFIND_ALL_COMMENTS_OF_POST(id))
Iterable<Comment> findAllCommentsOfPost(#Param("id") long id);
Pls, help me!!
Alternative to #Param, try this one
#Query(value = "select c from Comment c where c.post.post_id = ?1 order by c.replyTo.comment_id nulls last")
Iterable<Comment> findAllCommentsOfPost(long id);

How to find top N elements in Spring Data Jpa?

In Spring Data Jpa to get first 10 rows I can do this findTop10By...(). In my case the number or rows is not defined and comes as a parameter.
Is there something like findTopNBy...(int countOfRowsToGet)?
Here is another way without native query. I added Pageable as a parameter to the method in the interface.
findAllBySomeField(..., Pageable pageable)
I call it like this:
findAllBySomeField(..., PageRequest.of(0, limit)) // get first N rows
findAllBySomeField(..., Pageable.unpaged()) // get all rows
I don't know of a way to do exactly what you want, but if you are open to using #Query in your JPA repository class, then a prepared statement is one alternative:
#Query("SELECT * FROM Entity e ORDER BY e.id LIMIT :limit", nativeQuery=true)
Entity getEntitiesByLimit(#Param("limit") int limit);
Did it by using pagination, as described in the first answer. Just adding a more explicit example.
This example will give you the first 50 records ordered by id.
Repository:
#Repository
public interface MyRepository extends JpaRepository<MyEntity, String> {
Page<MyEntity> findAll(Pageable pageable);
}
Service:
#Service
public class MyDataService {
#Autowired
MyRepository myRepository;
private static final int LIMIT = 50;
public Optional<List<MyEntity>> getAllLimited() {
Page<MyEntity> page = myRepository.findAll(PageRequest.of(0, LIMIT, Sort.by(Sort.Order.asc("id"))));
return Optional.of(page.getContent());
}
}
Found the original idea here:
https://itqna.net/questions/16074/spring-data-jpa-does-not-recognize-sql-limit-command
(which will also link to another SO question btw)

Sort array items when using PagedListAdapter?

I'm using the Paging library.
Currently, I want to sort (using SortList) the items by the description in PagedListAdapter but I have not figured out how to do it.
How to sort elements when using a PagedListAdapter?
Thank you.
I faced this problem too, and was surprised that PagedList doesn't seem to have a straightforward way to sort items. Here's how I achieved the effect I needed using DataSource's mapByPage():
/** sorts MyItems by timestamp in descending order */
private fun DataSource.Factory<Long, MyItem>.sortByDescTime(): DataSource.Factory<Long, MyItem> {
return mapByPage {
myItemsList.sortedWith(compareByDescending { item -> item.timeStamp })
}
}
i.e, the input of mapByPage() should be your sorting function (depends on your setup, mine uses the Kotlin extensions & lambda syntax to sort the items in the target list - using Collections.sortedWith())
And then, used my extension function on the data source to sort the fetched items:
fun fetchItems(userId: Long): LiveData<PagedList<MyItem>> {
val itemDataSource = itemAPIService.getItems(userId).sortByDescTime()
val itemPagedList = LivePagedListBuilder(itemDataSource, 10).build()
return itemPagedList
}
you need to use MediatorLiveData here. though below code is in java, it should serve the basic purpose.
In Your ViewModel add both
public LiveData<PagedList<Customer>> customerList;
public MediatorLiveData<PagedList<Customer>> customerListMediator;
then first fetch liveData, that is, customerList in my case
customerList = new LivePagedListBuilder<>(mAppRepository.getCustomers(new SimpleSQLiteQuery(dynamicQuery)),1).build();
after that use the mediatorLiveData:-
customerListMediator.addSource(customerList, new Observer<PagedList<Customer>>() {
#Override
public void onChanged(#Nullable PagedList<Customer> customers) {
customerListMediator.setValue(customers);
}
});
And in your recyclerview, don't use LiveData list, instead use mediatorLiveData, in my case customerListMediator. like below:-
mViewModel.customerListMediator.observe(this, customers -> {
mViewModel.customerListAdapter.submitList(customers);
Toast.makeText(this, "list updated.", Toast.LENGTH_SHORT).show();
});
mapByPage() function works, but has side effects as you would need to "prepend" the next page items in the PagedList to have the desired effect.
Using Room #Dao and SQL sorting capabilities you can do something like this, which seems more performant as it releases sorting operations from the UI components:
#Dao
public interface MyItemDao{
enum SORT_OPT {
LABEL_ASC, LABEL_DESC, PRICE_ASC, PRICE_DESC, RATING_ASC, RATING_DESC
}
#Update
void update(ItemEntity itemEntity);
#Delete
void delete(ItemEntity itemEntity);
#Query("DELETE FROM item_table")
void deleteAll();
#Query("SELECT * FROM item_table ORDER BY price ASC")
#Transaction
DataSource.Factory<Integer, ItemEntity> getProductsOrderByPriceAsc();
#Query("SELECT * FROM item_table ORDER BY price DESC")
#Transaction
DataSource.Factory<Integer, ItemEntity> getProductsOrderByPriceDesc();
#Query("SELECT * FROM item_table ORDER BY label ASC")
#Transaction
DataSource.Factory<Integer, ItemEntity> getProductsOrderByLabelAsc();
#Query("SELECT * FROM item_table ORDER BY label DESC")
#Transaction
DataSource.Factory<Integer, ItemEntity> getProductsOrderByLabelDesc();
#Query("SELECT * FROM product_table ORDER BY totalRating ASC")
#Transaction
DataSource.Factory<Integer, ItemEntity> getProductsOrderByRatingAsc();
#Query("SELECT * FROM product_table ORDER BY totalRating DESC")
#Transaction
DataSource.Factory<Integer, ItemEntity> getProductsOrderByRatingDesc();
default DataSource.Factory<Integer, ItemEntity> getSortedProducts(SORT_OPT sortOptions) {
switch (sortOptions) {
case LABEL_ASC:
return getProductsOrderByLabelAsc();
case LABEL_DESC:
return getProductsOrderByLabelDesc();
case PRICE_ASC:
return getProductsOrderByPriceAsc();
case PRICE_DESC:
return getProductsOrderByPriceDesc();
case RATING_ASC:
return getProductsOrderByRatingAsc();
case RATING_DESC:
return getProductsOrderByRatingDesc();
default:
return null;
}
}
As you can notice the most important method here is:
default DataSource.Factory<Integer, ItemEntity> getSortedProducts(SORT_OPT sortOptions)
You can store you sort value in sharedPreferences and load it from there.

Spring data mongo pagination

I want to implement pagination with Spring Data Mongo. There are many tutorials and docs suggest to use PagingAndSortingRepository, like this:
StoryRepo extends PagingAndSortingRepository<Story, String>{}
And so because PagingAndSortingRepository provides api for query with paging, I can use it like:
Page<Story> story = storyRepo.findAll(pageable);
My question is where actually is this findAll method here implemented? Do I need to write its implementation by myself?
The StoryRepoImpl which implements StoryRepo needs to implement this method?
You do not need to implement the method as when you autowired the Spring object PagingAndSortingRepository, it automatically implements the method for you.
Please note that since you are using Mongodb, you can extend MongoRepository instead.
Then in Spring, enable pagination using this:
#RequestMapping(value="INSERT YOUR LINK", method=RequestMethod.GET)
public List<Profile> getAll(int page) {
Pageable pageable = new PageRequest(page, 5); //get 5 profiles on a page
Page<Profile> page = repo.findAll(pageable);
return Lists.newArrayList(page);
I got it working by writing my own implementations, something like this:
List<Story> stories = null;
Query query = new Query();
query.with(pageable);
stories = getTemplate().find(query, Story.class);
long total = getTemplate().count(query, Story.class);
Page<Story> storyPage = new PageImpl<Story>(stories, pageable, total);
return storyPage;
I'm working with spring data & mongodb, using mongo template to query data.
In Spring Data, you create an interface and add a method using the naming conventions used by Spring data and the framework will generate the implementation of that method.
To implement pagination, i create this method declaration in my repository:
public interface PersonRepository extends MongoRepository<Person, ObjectId> {
Page<Person> findByName(String name, Pageable pageable);
}
Then, in my service i call this method like this:
Page<Person> persons = personRepository.findByName("Alex", PageRequest.of(0, 100));
Here, the page will contain 100 element.
To paginate a query, you can use something like below:
public interface PersonRepository extends MongoRepository<Person, String> {
Page<Person> findByFirstname(String firstname, Pageable pageable);
}
For more details, please refer to the second query in Example 6.6 in https://docs.spring.io/spring-data/mongodb/docs/1.2.0.RELEASE/reference/html/mongo.repositories.html
The method is implemented by a store-specific class. For the Spring Data JPA module, it's SimpleJpaRepository. You usually let a DI container create instances for these repository interfaces. With Spring you'd activate Spring Data repositories by either using #EnableJpaRepository on a JavaConfig class or
<jpa:repositories base-package="com.acme.repositories" />
This will create a proxy instance for the repo, so that you can get it injected into your clients:
class MyClient {
#Inject
public MyClient(PersonRepository repository) {
…
}
}
Query query1 = new Query();
Integer startIndex = page * size;
Integer endIndex = (page * size) + size;
List<dto> totalRecord = mongoOperation.find(query1, dto.class);
query1.limit((endIndex > totalRecord.size() ? totalRecord.size() : endIndex));
List<dto> responseList = mongoOperation.find(query1, dto.class);
int end = (endIndex > (totalRecord.size() - 1) ? totalRecord.size() - 1 : endIndex);
if (totalRecord.size() > 0 && end == 0)
end = 1;
if (totalRecord.size() > 0)
responseList = responseList.subList(startIndex, end);
int totalPages = totalRecord.size() / size + (totalRecord.size() % size == 0 ? 0 : 1);
As other answer says: you don't need to implement the repository interface classes. The generating magic will handle it.
I wrote this answer because of say implementation way of PageRequest deprecated
-the Pageable pageable = new PageRequest(fromIndex, toIndex); one.
If you want to implement a pageable request nowadays, you need to use like following:
PageRequest page = PageRequest.of(pageNum, pageSize);
List<MyEntity> result =myEntityRepository.findAll(page).toList();

Spring MVC - REST Get Method with RequestParam List<String>

If I have a controller interface like this:
#RequestMapping(method = RequestMethod.GET, value = "/{CustomerId}/audit")
public #ResponseBody Long countAudit(
#PathVariable(value = "CustomerId") String customerId,
#RequestParam(value = "Users", required = false) List<String> users)
And I use RestTemplate to make a call, via getForObject, like so:
RestTemplate restTemplate = new RestTemplate();
List<String> users = new ArrayList<String>();
users.add("Bill");
users.add("John");
String customerId = "1234";
Long cnt = restTemplate.getForObject(url, Long.class, customerId, users);
Why does the first item in the List on the controller side, have a [ in front, while the last item has the matching ]?
On the client.. it looks like this: "Bill", "John"
on the controller(server), it looks like this: "[Bill", "John]"
Any ideas, and is there a way around this or to deal with this? Any other suggestions on how to pass a List<> via requestParam in resttemplate? Thanks for any advice..
The server side is not correct parsing the json format. Instead of parsing it, the server gives you the json string (["Bill", "John"]) of the complete message.
I can not tell you why the server is not parsing it correctly, but I hope that hint helps you to find the problem.