Custom Logic with Injected Values when using Spring Data Rest - java

I want to convert an existing web service to take advantage of spring-data-rest.
How can I implement custom logic with injected values (specifically an OAuth2 Principal) on top of spring data rest to keep existing functionality ?
For example, say I want to override the GET method for /person/1 to contact an auditing web service before it goes on to return the data of person 1.
Right now, before using spring-data-rest, I would have:
#RequestMapping(value = "/person/{id}", method = RequestMethod.GET)
public void getPerson(#RequestBody ...., Principal principal)
{
/* Let Big Brother know that principal.getName() is doing this */
return thePerson;
}
How would I do something like this with Spring Data Rest generated endpoints?

Thanks for the suggestions. I found out that what works best (for me) in this case is what Dave Syer suggested and to just go with AspectJ.
In the end this will allow you to add custom logging logic, or anything else really, to the methods of a Spring Data JPA Repository:
#Component
#Aspect
public class MyRepositoryAuditor {
// pointcut on all methods with any arguments inside MyRepository
#Pointcut("execution(public * example.jpa.repositories.MyRepository.*(..) )")
public void publicRepositoryMethod() {
}
// if they return normally
#AfterReturning("publicRepositoryMethod()")
public void publicRepositoryMethod(JoinPoint jp) throws Throwable {
String methodName = jp.getSignature().getName();
.... perform logging ....
}
}

Related

Spring MVC Controller implementation advice

Currently I have a domainObjectPersistenceService which calls DAO functions on the Domain Object, and I have a separate domainObjectDomainService which processes Business Logic. For example, userPersistenceService and userDomainService.
I am unsure whether to handle the initial call to the domainObjectPersistenceService from the Controller directly, or call it from inside the domainObjectDomainService.
What is the preferred way?
#Controller
public class Controller {
public controllerMethod(int fileId) {
domainObject = domainObjectPersistenceService.getFile(fileId);
data = domainObjectDomainService.processFile(domainObject);
// convert data into DTO
return dataDTO;
}
}
or
#Controller
public class Controller {
public controllerMethod(int fileId) {
// domainObjectDomainService handles persistence layer calls.
data = domainObjectDomainService.processFile(fileId);
// convert data into DTO
return dataDTO;
}
}
Many thanks!
See Domain Driven Design: Domain Service, Application Service
In your case Controller is Application Service. It is an orchestrator, that retrieves objects from the repository and pass to the Domain Service.
First way is correct.

How to test with Postman a controller's method that has one or more objects parameters (and not simple params) in Spring?

I am a newbie in Spring development. I need to create a simple application, a controller that has a method that takes as parameter an object of a custom designed entity class into the project. The prototype looks like this:
#RestController
public class JobsController {
#PostMapping("/search")
public ResponseEntity<?> search() {
log.info("JobsController -> search method");
//JobSearchEntity jobSearchEntity = modelMapper.map(jobSearch, JobSearchEntity.class);
List<JobEntity> jobs = jobService.searchJobs();
//log.info(String.format("Job found: %s ", jobSearch));
return ResponseEntity.ok(jobs);
}
}
Can someone who is more advanced into this staff with Postman testing tell me how to do that , how to test a controller method which takes parameters?
You can use postman to submit parameters in JSON format after adding # requestbody annotation on the method, or submit parameters directly in form without annotation
You can use this example. Is very simple exemple.
#RestController
#RequestMapping("/root")
public class RootController {
private final RootService service;
public RootController(final RootService service) {
this.service = service;
}
#PostMapping("/exemple")
public void createRoot(#RequestBody final RootDto dto) {
service.createRoot(dto);
}
}
Then you can send request to POST host/root/exemple with your JSON.
More exampls you can find here: https://www.baeldung.com/spring-request-response-body
It seems you are missing an honest search on google about the subject.
You can make use of #RequestBody annotation to accept method arguments.
Check these page for examples --
#RequestBody and #ResponseBody annotations in Spring
https://stackabuse.com/get-http-post-body-in-spring/
https://www.twilio.com/blog/create-rest-apis-java-spring-boot
These set of playlist on youtube are very good starter course for SpringBoot -
https://www.youtube.com/c/JavaBrainsChannel/playlists
Postman Tutorial--
https://www.youtube.com/watch?v=VywxIQ2ZXw4
To get data from api is preferred to use GET method :
#RestController
public class JobsController {
#GetMapping("/search")
public ResponseEntity<?> search(#RequestParam("id") String id,#RequestParam("desc") String desc) {
log.info("JobsController -> search method");
//JobSearchEntity jobSearchEntity = modelMapper.map(jobSearch, JobSearchEntity.class);
List<JobEntity> jobs = jobService.searchJobs();
//log.info(String.format("Job found: %s ", jobSearch));
return ResponseEntity.ok(jobs);
}
}
you call this api with post man this way :
#PostMapping used usually to save new data (example : create job )
Take look on rest resource naming guide

Spring IOC DI with runtime parameters

I'm relativity new to IOC and DI, so I'm guessing that I am missing some high-level design principle here, but I cannot figure out how to get my architecture working.
I have a REST API endpoint that accepts two pieces of POST data: customer ID, and Type ID. The rest api then needs to return a set of data for that specific customer/type combo.
Here is a crude picture of what I am doing:
The controller is taking the entity IDs passed in via post data, and via a JPA repository getting the appropriate Entities for them.
I then construct a data generator object (that takes the entities as constructor parameters), and use that to handle all of the data gathering for the API.
The Problem: because the Data Generator takes the two dynamic constructor parameters, it cannot be DI'ed into the Controller, but instead must be made with new. Inside of the Data Generator, however, I need access to JPA repositories. The only way to get access to these repositories is via DI. I cannot DI however, as the object was new'ed not DI'ed by the IOC container.
Is there a way to architect this so that I don't have this problem? Am I breaking some rule regarding IOC? Do I have wrong assumptions somewhere? Any advice is appreciated.
Thanks!
Edit: Pseudo code for Data Generator
public class DataGenerator {
private Customer customer;
private Type type
public DataGenerator(Customer customer, Type type) {
this.cusomter = customer;
this.type = type;
}
public generateData() {
if(customer == x && type == y) {
//JPA REPOSITORY QUERY
} else {
//DIFFERENT JPA REPOSITORY QUERY
}
}
}
I think you may have gotten confused somewhere along the line. You should have a Service that hits your repositories, and provide the information to the controller. One crude setup would be something like this.
#Controller
public MyController {
#AutoWired
private DataService dataService;
#RequestMapping(value = "/", method = RequestMethod.GET)
private DataGenerator readBookmark(#PathVariable Long customerId, #PathVariable Integer typeId) {
return dataService.getData(customerId, typeId);
}
}
#Service
public class DataService {
#AutoWired
private JPARepository repository;
public DataGenerator getData(long customerId, int typeId) {
Type typeDetails = repository.getType(typeId);
Customer customerDetails = repository.getCustomer(customerId);
return new DataGenerator(customerDetails, typeDetails);
}
}

Spring MVC: bind request attribute to controller method parameter

In Spring MVC, it is easy to bind request parameter to method paramaters handling the request. I just use #RequestParameter("name"). But can I do the same with request attribute? Currently, when I want to access request attribute, I have to do following:
MyClass obj = (MyClass) request.getAttribute("attr_name");
But I really would like to use something like this instead:
#RequestAttribute("attr_name") MyClass obj
Unfortunately, it doesn't work this way. Can I somehow extend Spring functionality and add my own "binders"?
EDIT (what I'm trying to achieve): I store currently logged user inside request attribute. So whenever I want to access currently logged user (which is pretty much inside every method), I have to write this extra line user = (User) request.getAttribute("user");. I would like to make it as short as possible, preferably inject it as a method parameter. Or if you know another way how to pass something across interceptors and controllers, I would be happy to hear it.
Well, I finally understood a little bit how models work and what is #ModelAttribute for. Here is my solution.
#Controller
class MyController
{
#ModelAttribute("user")
public User getUser(HttpServletRequest request)
{
return (User) request.getAttribute("user");
}
#RequestMapping(value = "someurl", method = RequestMethod.GET)
public String HandleSomeUrl(#ModelAttribute("user") User user)
{
// ... do some stuff
}
}
The getUser() method marked with #ModelAttribute annotation will automatically populate all User user parameters marked with #ModelAttribute. So when the HandleSomeUrl method is called, the call looks something like MyController.HandleSomeUrl(MyController.getUser(request)). At least this is how I imagine it. Cool thing is that user is also accessible from the JSP view without any further effort.
This solves exactly my problem however I do have further questions. Is there a common place where I can put those #ModelAttribute methods so they were common for all my controllers? Can I somehow add model attribute from the inside of the preHandle() method of an Interceptor?
Use (as of Spring 4.3) #RequestAttribute:
#RequestMapping(value = "someurl", method = RequestMethod.GET)
public String handleSomeUrl(#RequestAttribute User user) {
// ... do some stuff
}
or if the request attribute name does not match the method parameter name:
#RequestMapping(value = "someurl", method = RequestMethod.GET)
public String handleSomeUrl(#RequestAttribute(name="userAttributeName") User user) {
// ... do some stuff
}
I think what you are looking for is:
#ModelAttribute("attr_name") MyClass obj
You can use that in the parameters for a method in your controller.
Here is a link a to question with details on it What is #ModelAttribute in Spring MVC?
That question links to the Spring Documentation with some examples of using it too. You can see that here
Update
I'm not sure how you are setting up your pages, but you can add the user as a Model Attribute a couple different ways. I setup a simple example below here.
#RequestMapping(value = "/account", method = RequestMethod.GET)
public ModelAndView displayAccountPage() {
User user = new User(); //most likely you've done some kind of login step this is just for simplicity
return new ModelAndView("account", "user", user); //return view, model attribute name, model attribute
}
Then when the user submits a request, Spring will bind the user attribute to the User object in the method parameters.
#RequestMapping(value = "/account/delivery", method = RequestMethod.POST)
public ModelAndView updateDeliverySchedule(#ModelAttribute("user") User user) {
user = accountService.updateDeliverySchedule(user); //do something with the user
return new ModelAndView("account", "user", user);
}
Not the most elegant, but works at least...
#Controller
public class YourController {
#RequestMapping("/xyz")
public ModelAndView handle(
#Value("#{request.getAttribute('key')}") SomeClass obj) {
...
return new ModelAndView(...);
}
}
Source : http://blog.crisp.se/tag/requestattribute
From spring 3.2 it can be done even nicer by using Springs ControllerAdvice annotation.
This then would allow you to have an advice which adds the #ModelAttributes in a separate class, which is then applied to all your controllers.
For completeness, it is also possible to actually make the #RequestAttribute("attr-name") as is.
(below modified from this article to suit our demands)
First, we have to define the annotation:
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.PARAMETER)
public #interface RequestAttribute {
String value();
}
Then we need a [WebArgumentResolver] to handle what needs to be done when the attribute is being bound
public class RequestAttributeWebArgumentResolver implements WebArgumentResolver {
public Object resolveArgument(MethodParameter methodParameter, NativeWebRequest nativeWebRequest) throws Exception {
// Get the annotation
RequestAttribute requestAttributeAnnotation = methodParameter.getParameterAnnotation(RequestAttribute.class);
if(requestAttributeAnnotation != null) {
HttpServletRequest request = (HttpServletRequest) nativeWebRequest.getNativeRequest();
return request.getAttribute(requestAttributeAnnotation.value);
}
return UNRESOLVED;
}
}
Now all we need is to add this customresolver to the config to resolve it:
<bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter">
<property name="customArgumentResolver">
<bean class="com.sergialmar.customresolver.web.support.CustomWebArgumentResolver"/>
</property>
</bean>
And we're done!
Yes, you can add your own 'binders' to the request attribute - see spring-mvc-3-showcase, or use #Peter Szanto's solution.
Alternatively, bind it as a ModelAttribute, as recommended in other answers.
As it's the logged-in user that you want to pass into your controller, you may want to consider Spring Security. Then you can just have the Principle injected into your method:
#RequestMapping("/xyz")
public String index(Principal principle) {
return "Hello, " + principle.getName() + "!";
}
In Spring WebMVC 4.x, it prefer implements HandlerMethodArgumentResolver
#Override
public boolean supportsParameter(MethodParameter parameter) {
return parameter.getParameterAnnotation(RequestAttribute.class) != null;
}
#Override
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
return webRequest.getAttribute(parameter.getParameterAnnotation(RequestAttribute.class).value(), NativeWebRequest.SCOPE_REQUEST);
}
}
Then register it in RequestMappingHandlerAdapter

Spring MVC 3.0: How to validate path variable that is global to all request mappings efficiently?

I'm trying to get my feet wet with Spring MVC 3.0, and while I can get it to work, I can't seem to handle this particular scenario efficiently.
I have a controller with that handles "/{studyName}/module" prefix, and it looks something like this:-
#Controller
#RequestMapping(value = "/{studyName}/module")
public class ModuleController {
#RequestMapping(...)
public ModelAndView getA(#PathVariable String studyName, ...) {
if (!validStudy(studyName)) { return bad request; }
...
}
#RequestMapping(...)
public ModelAndView getB(#PathVariable String studyName, ...) {
if (!validStudy(studyName)) { return bad request; }
...
}
#RequestMapping(...)
public ModelAndView getC(#PathVariable String studyName, ...) {
if (!validStudy(studyName)) { return bad request; }
...
}
#RequestMapping(...)
public ModelAndView getD(#PathVariable String studyName, ...) {
if (!validStudy(studyName)) { return bad request; }
...
}
}
The problem with this code is, I have the studyName validation scattered all over the methods and possibly in other Controllers' methods too. Is there a way I can perform validation on studyName path variable all in one spot without using something like AOP? How do you handle validation like this?
Thanks.
Right now, it's a little tricky to make this happen automatically, but it is possible. You should use a Bean validation (JSR-303) provider that implements appendix C. Currently that's Apache BeanValidation or Hibernate Validator 4.2 (which is in beta).
Add your chosen bean validation implementation to the classpath. This will be the implementation of JSR-303 that Spring MVC uses.
Second, annotate the method parameter with #Valid and any constraint annotations, like #NonNull.
This will look something like:
public ModelAndView getB(#Valid #NonNull #PathVariable String studyName, ...) {
That should work. You'd then need to check your Spring errors for any problems.
Alternatively, if you don't make use of any other Spring parameters, you can register a validator with an InitBinder like so:
#InitBinder
public void initBinder(WebDataBinder binder) {
binder.setValidator(new StudyNameValidator());
}
Create a class StudyName then have a WebArgumentResolver registered for StudyName and have your validation take place there.
public ModelAndView getA(#PathVariable StudyName studyName){
...
}
public class StudyNameResolver implements WebArgumentResolver{
//have resolveArgument method do validation if resolved to a StudyName
}
I am starting to use spring 3 and I do like your solution of validating in this way:
public ModelAndView getB(#Valid #NonNull #PathVariable String studyName, ...) {
However, once the pathvariable is invalid (in this case studyName = null) how do you catch and display that error?
I have tried to use binding result but it just doesn't work. In addition, do you know how to display the error on the jsp?
Thanks
Create a simple validation class:
public class StudyValidator {
public boolean validateStudy(String studyName) {
//your validate logic here
}
}
then inject it into the ModuleController:
class ModuleController {
private StudyValidator sv = new StudyValidator(); //use spring injection to populate.
boolean validStudy(String studyName) {
return sv.validateStudy(studyName);
}
}
Simples.
Hmmm, not sure if it would work, but you might be able to the #Valid annotation as briefly mentioned in this link on validators.
Good Luck!

Categories