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.
Related
I have an application that uses Spring Boot 2.1.8 with an Angular front end. I have defined my repositories using #RepositoryRestResource. The Spring Boot app basically provides a REST API for the Angular piece. The application defines several business entities and the REST respo provides CRUD functionality for each entity. Here is a typical repo interface for one of those entities:
// REST resouce /api/privgroups
#RepositoryRestResource(collectionResourceRel = "privgroups", path = "privgroups")
public interface PrivGroupRepository extends CrudRepository<PrivGroup, Long>
{
List<PrivGroup> findAll();
}
GETing and single entity, POSTing (creating an entity) and PUTing (updating an entity) are all working fine, but I would like to return a custom header when updating (HTTP PUT) an entity. The header would be consumed by the Angular side to display a custom toastr message specific to that entity. Since the repositories also implement the REST interface I am unsure how to add a specific header that would change based on the target entity.
I have developed applications that include a REST controller that calls against a service which in turn calls against a repository. In this case, I have more control and can easily return custom headers like so:
#PutMapping("/{id}")
public ResponseEntity<MyEntity> updateMyEntity(#PathVariable("id") Long id, #RequestBody MyEntity myEntity)
{
MyEntity updatedEntity = this.MyEntityService.updateMyEntity(MyEntity);
return ResponseEntity.ok()
.header("X-Save", "MyEntity")
.body(updatedEntity);
}
Is there a newer "built-in" technique I can use to achieve this? I know I can add headers using a filter and I've read a couple posts on the subject. I think it would be difficult to identify the entity being updated and I'm unsure this is the best approach.
NOTE that this post:
Rest API - how add custom headers?
is really old.
The spring data rest docs https://docs.spring.io/spring-data/rest/docs/current/reference/html/
don't have anything specific on the subject.
I have used Spring Data Rest recently and I didn't find any "built-in" technique for that. However you can achieve that by implementing the ResponseBodyAdvice interface in a class annotated with #ControllerAdvice. This is how I got it:
#ControllerAdvice
public class PutMyEntityBodyAdvice implements ResponseBodyAdvice<Object> {
#Override
public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
// Write the condition to check if beforyBodyWrite should be called
return returnType.getParameterType().equals(ResponseEntity.class);
}
#Override
public Object beforeBodyWrite(Object object, MethodParameter returnType, MediaType selectedContentType,
Class<? extends HttpMessageConverter<?>> selectedConverterType,
ServerHttpRequest request, ServerHttpResponse response) {
// Check if it is a PUT request for MyEntity object
if (HttpMethod.PUT.equals(request.getMethod()) && object instanceof PersistentEntityResource) {
PersistentEntityResource persistentEntityResource = (PersistentEntityResource) object;
if (persistentEntityResource.getContent().getClass().equals(MyEntity.class)) {
// Add custom header or manipulate response object
response.getHeaders().add("X-Save", "MyEntity");
}
}
return object;
}
}
The answer by #pepevalbe looks promising.
An alternative might be - as you suggested - using a standard Servlet Filter or Spring HandlerInterceptor.
To address the issue of getting a reference to the modified entity in that case you could register a Spring Data Rest event listener that would simply store a reference to the modified entity in ThreadLocal storage from where it could be retrieved in the Filter or HandlerInterceptor.
https://www.baeldung.com/java-threadlocal
https://docs.spring.io/spring-data/rest/docs/current/reference/html/#events.application-listener
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.
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.
I am trying to do a simple android application that communicates with a Spring server.
I'd like to use Sessions to store data of each logged in User.
My App exchange Json objects with the server and the Request Mapping is like this:
#Controller
public class LoginController {
#Autowired
private IUserDao userDao;
#RequestMapping( value = "/loginJson",method = RequestMethod.POST)
public #ResponseBody loginResponse login(#RequestBody loginModel login) {
loginResponse response=userDao.checkCredentials(login.getUsername(),login.getPassword());
System.out.println("Result="+response.isSuccess());
System.out.println("Received:"+login.getUsername()+" "+login.getPassword());
return response;
}
}
The controller is working fine, but I can't figure out how to store a sessione variable. I found many documents explaining Spring Sessions, but each of them different from the other.
Someone can suggest me some simple way to do this or some kind of good tutorial?
Not sure what you mean by saying Spring session, but you can declare additional HttpSession parameter in your method, and then do whatever you like inside of the method. Is this what you wanted to find out?
I have developed a Spring MVC - Hibernate application as told here.
Now I am trying to modify this code to create a REST application as told here.
I have added Jackson library to the classpath and added #XmlRootElement.
#XmlRootElement(name = "persons")
public class Person implements Serializable {
But if I do a application/json request then I still get the html code back.
What I am doing wrong / forgot to do?
My controller:
#RequestMapping(value = "/persons", method = RequestMethod.GET)
#ResponseBody
public String getPersons(Model model) {
logger.info("Received request to show all persons");
// Retrieve all persons by delegating the call to PersonService
List<Person> persons = personService.getAll();
model.addAttribute("persons", persons);
return "personspage";
}
Changed the Controller, but get an error:
t
ype Status report
message /Buddies/WEB-INF/jsp/main/persons/1.jsp
description The requested resource (/Buddies/WEB-INF/jsp/main/persons/1.jsp) is not available.
Your controller should look like this:
#RequestMapping(value = "/persons/{id}", method = RequestMethod.GET)
#ResponseBody
public Person getPerson(#PathVariable int id) {
Person person = personService.getPersonById(id);
return person;
}
If you want to return a list of Person objects, you need an extra wrapper object, see: Using JAXB to unmarshal/marshal a List<String>.
You are probably missing AnnotationMethodHandlerAdapter and messageConverter in your spring configuration.