How to add custom search link to Spring Data Rest - spring

I am trying to create a custom search for my users repository. I have a custom restcontroller for it
#BasePathAwareController
#RequestMapping("/users")
#MultipartConfig(fileSizeThreshold = 20971520)
public class UserController implements ResourceProcessor<Resource<User>>,{
#Autowired
UserRepository userReposiotry;
#Autowired
private EntityLinks entityLinks;
#RequestMapping(value = "/search/getAvatar", method = RequestMethod.GET, produces = "image/jpg")
public ResponseEntity<InputStreamResource> downloadImage(#RequestParam("username") String username)
throws IOException {
ClassPathResource file = new ClassPathResource("uploads/" + username+ "/avatar.jpg");
return ResponseEntity
.ok()
.contentLength(file.contentLength())
.contentType(
MediaType.parseMediaType("application/octet-stream"))
.body(new InputStreamResource(file.getInputStream()));
}
#Override
public Resource<User> process(Resource<User> resource) {
LinkBuilder lb = entityLinks.linkFor(User.class);
resource.add(new Link(lb.toString()));
**// How can I add the search/getAvatar to the user search resource?**
return resource;
}
}
The first issue is that I get a 404 when trying to call /users/search/getAvatar?username=Tailor
The second is that how can I add this to the users search links?
Thank you

To add a search link, you need to extend RepositorySearchesResource as illustrated here:
Spring Data Rest Add custom endpoint to specific reposiotry
Spring Data Rest - Add link to search endpoint
As pointed out in the comments, be sure to check the domain type so as to add search link only for relevant repository.

Related

Spring DATA REST - How to convert entities to resources in custom controller using default spring implementation

I have created a custom controller which needs to convert entities to resources. I have annotated my repositories with #RepositoryRestResource annotation. I want to know if there is a way I can invoke the default functionality of spring Data REST from my custom controller which serializes the entities to resources with links to other entities embedded in them.
I don't want to return entities from my handler method but Resources.
Thanks.
Very simple, using objects Resource or Resources. For example - in this controller we add custom method which return list of all user roles which are enums:
#RepositoryRestController
#RequestMapping("/users/roles")
public class RoleController {
#GetMapping
public ResponseEntity<?> getAllRoles() {
List<Resource<User.Role>> content = new ArrayList<>();
content.addAll(Arrays.asList(
new Resource<>(User.Role.ROLE1),
new Resource<>(User.Role.ROLE2)));
return ResponseEntity.ok(new Resources<>(content));
}
}
To add links to resource you have to use object RepositoryEntityLinks, for example:
#RequiredArgsConstructor
#RepositoryRestController
#RequestMapping("/products")
public class ProductController {
#NonNull private final ProductRepo repo;
#NonNull private final RepositoryEntityLinks links;
#GetMapping("/{id}/dto")
public ResponseEntity<?> getDto(#PathVariable("id") Integer productId) {
ProductProjection dto = repo.getDto(productId);
return ResponseEntity.ok(toResource(dto));
}
private ResourceSupport toResource(ProductProjection projection) {
ProductDto dto = new ProductDto(projection.getProduct(), projection.getName());
Link productLink = links.linkForSingleResource(projection.getProduct()).withRel("product");
Link selfLink = links.linkForSingleResource(projection.getProduct()).slash("/dto").withSelfRel();
return new Resource<>(dto, productLink, selfLink);
}
}
For more example see my 'how-to' and sample project.

Spring how to resolve entity uri in #RepositoryRestController

I'm using Spring boot 1.5.3, Spring Data REST, Spring HATEOAS. Spring Data REST is amazing and does a perfect job, but sometimes it's needed custom business logic and therfore I need to create a custom controller.
I'm going to use a #RepositoryRestController to benefit of Spring Data REST functionality (http://docs.spring.io/spring-data/rest/docs/current/reference/html/#customizing-sdr.overriding-sdr-response-handlers).
Because Spring Data REST use HATEOAS by default, I'm using that. I need a controller like this:
#RepositoryRestController
#RequestMapping(path = "/api/v1/workSessions")
public class WorkSessionController {
#Autowired
private EntityLinks entityLinks;
#Autowired
private WorkSessionRepository workSessionRepository;
#Autowired
private UserRepository userRepository;
#PreAuthorize("isAuthenticated()")
#RequestMapping(method = RequestMethod.POST, path = "/start")
public ResponseEntity<?> start(#RequestBody(required = true) CheckPoint checkPoint) {
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
if (checkPoint == null) {
throw new RuntimeException("Checkpoint cannot be empty");
}
if (workSessionRepository.findByAgentUsernameAndEndDateIsNull(auth.getName()).size() > 0) {
// TODO return exception
throw new RuntimeException("Exist a open work session for the user {0}");
}
// ...otherwise it's opened a new work session
WorkSession workSession = new WorkSession();
workSession.setAgent(userRepository.findByUsername(auth.getName()));
workSession.setCheckPoint(checkPoint);
workSession = workSessionRepository.save(workSession);
Resource<WorkSession> resource = new Resource<>(workSession);
resource.add(entityLinks.linkFor(WorkSession.class).slash(workSession.getId()).withSelfRel());
return ResponseEntity.ok(resource);
}
}
Because the parameter CheckPoint must be an existant resource, I want the client send the link of the resourse (like you can do in Spring Data REST POST methods).
Unfortunately when I try to do that, server side I receive an empty CheckPoint object.
I already read Resolving entity URI in custom controller (Spring HATEOAS) and converting URI to entity with custom controller in spring data rest? and expecially Accepting a Spring Data REST URI in custom controller.
I'm wondering if there is a best practice to do this avoiding to expose id to the client, followind so the HATEOAS principles.
Try to change you controller like this:
#RepositoryRestController
#RequestMapping(path = "/checkPoints")
public class CheckPointController {
//...
#Transactional
#PostMapping("/{id}/start")
public ResponseEntity<?> startWorkSession(#PathVariable("id") CheckPoint checkPoint) {
//...
}
}
That will mean: "For CheckPoint with given ID start new WorkSession".
Your POST request will be like: localhost:8080/api/v1/checkPoints/1/start.

discussion about #RequestParam and #RequestBody

I have a class:
public class user{
private String id;
private MultiPartFile file;
**Getters And Setters**
}
And in the Controller:
#PostMapping(value="/upload)
public void upload(User user){
}
In the front end I post data with form-data.I can get the user object.
But when I add #RequestBody and #RequestParam,it can't works.
in my opinion,#RequestParam is used to binding parameter to simple class . when I use #RequestBody ,spring will find HttpMessageConverter to convert http request body to class.But I'm not sure about that.Does anyone can explain to me?
So, I believe we are talking about org.springframework.web.multipart.MultipartFile, which is to be used together with #RequestParam variable. The mechanism is somewhat special in this case.
I had a similar problem, and what I ended up using was org.springframework.web.multipart.commons.CommonsMultipartResolver. From frontend I've constructed multipart request with two parts, in your scenario it could be user (containing just JSON data) and file (containing the file itself), e.g.:
#PostMapping(value="/upload")
public void upload(#RequestParam("user") User user, #RequestParam("file") MultipartFile file){
...
}
But then, you need to configure custom serialization of the User part, which can be done using org.springframework.web.multipart.commons.CommonsMultipartResolver. You can configure it using bean config like this:
#Configuration
public class MappingConfig {
#Order(Integer.MIN_VALUE)
#Bean(name = "multipartResolver")
public CommonsMultipartResolver multipartResolver() {
return new CommonsMultipartResolver();
}
#Bean
public Converter<String, User> stringToUser() {
return new Converter<String, User>() {
#Override
public User convert(String jsonString) {
return new Gson().fromJson(jsonString, User.class);
}
};
}
...
}
Also, as you can see I am using Gson manually, I couldn't find a better way how to do it. Also, it doesn't play with Java 8 lambdas, so it cannot be shortened (because of explicit types are needed for it to work).
I hope that this will at least points you to a right path.

how to configure spring-data-rest search method path with #PathVariable

I want to customize my spring-data-rest search method path by passing parameter as a path variable like follows
http://localhost:8080/orders/search/customers/{customerId}
findByCustomer(#PathVariable("customerId") Integer customer);
The search resource listh the links as follows
http://localhost:8080/orders/search/customers/%7BcustomerId%7D
How to expose search url with path params?
You can use custom handler similar to this:
#RepositoryRestController
public class OrderController {
#Autowired
OrderRepository orderRepository;
#GetMapping("/orders/search/customers/{id}")
public #ResponseBody ResponseEntity<?> getByCustomers(#PathVariable Integer customer) {
Order order = orderRepository.findOne(id);
if(order == null) return ResponseEntity.status(HttpStatus.NOT_FOUND).body(null);
Resource<Order> resource = new Resource<Order>(order);
return ResponseEntity.ok(resource);
}
}
More about this can be found here.
Use HttpServletRequest to get the request url:
findByCustomer(#PathVariable("customerId") Integer customer, HttpServletRequest request){
String request = request.getRequestURL().toString(); // StringBuffer, so use append if you want to...
[...]
}
also you can use request.getQueryString() to get the query part after ?.

Spring Data Rest - Add link to search endpoint

In our Spring-Data-Rest Project we have a custom (fuzzy) search on a /buergers/search/findBuergerFuzzy?searchString="..." endpoint.
Is it possible to add a link for it on the /buergers/search endpoint (Without overriding the automatically exposed Repository findBy Methods)?
The Controller exposing the search:
#BasePathAwareController
#RequestMapping("/buergers/search/")
public class BuergerSearchController {
#Autowired
QueryService service;
#RequestMapping(value = "/findBuergerFuzzy", method = RequestMethod.GET)
public
#ResponseBody
ResponseEntity<?> findBuergerFuzzy(PersistentEntityResourceAssembler assembler, #Param("searchString") String searchString) {
if (searchString.length() < 3)
throw new IllegalArgumentException("Search String must be at least 3 chars long.");
List<Buerger> list = service.query(searchString, Buerger.class, new String[]{"vorname", "nachname", "geburtsdatum", "augenfarbe"});
final List<PersistentEntityResource> collect = list.stream().map(assembler::toResource).collect(Collectors.toList());
return new ResponseEntity<Object>(new Resources<>(collect), HttpStatus.OK);
}
}
UPDATE: This is an outdated workaround answer. Upgrade to Spring HATEOAS 1.0.
Old Workaround:
Digging the spring-data-rest source i found the RepositorySearchesResource which seems to solve the problem.
#Component
public class SearchResourcesProcessor implements ResourceProcessor<RepositorySearchesResource> {
#Override
public RepositorySearchesResource process(RepositorySearchesResource repositorySearchesResource) {
final String search = repositorySearchesResource.getId().getHref();
final Link findFullTextFuzzy = new Link(search + "/findFullTextFuzzy{?q}").withRel("findFullTextFuzzy");
repositorySearchesResource.add(findFullTextFuzzy);
return repositorySearchesResource;
}
}
Because we generate this code by templates, this is sufficient and fullfills our needs. Make sure to check the comments for the right and safe way.
Version
migrate-to-1.0.changes
ResourceSupport is now RepresentationModel
Resource is now EntityModel
Resources is now CollectionModel
PagedResources is now PagedModel
Code
The code for new version:
import org.springframework.data.rest.webmvc.RepositorySearchesResource;
import org.springframework.hateoas.Link;
import org.springframework.hateoas.server.RepresentationModelProcessor;
import org.springframework.stereotype.Component;
#Component
public class RepositorySearchesProcessor implements RepresentationModelProcessor<RepositorySearchesResource> {
#Override
public RepositorySearchesResource process(RepositorySearchesResource model) {
System.out.println(model.getDomainType());
model.add(Link.of(model.getRequiredLink("self").getHref() + "/findFullTextFuzzy{?q}").withRel("findFullTextFuzzy"));
return model;
}
}
How
About how to find what resource or model you use, after setting breakpoints in each method of RepresentationModel, you maybe find something useful :

Resources