Spring MVC something like OptimisticLocking in the controller - java

I have a Spring controller and two methods that one prepare order data for user to edit, the other merage the latest order information change come back from the form (POST).
Now I want to make some procedure working like OptiMistic Locking without add version column in database. Because client take sometime to edit the order before session expires, I have to make the post method to be sure the order is still on the same status when client first open the order edit page
So I add a orderStatus attribute to carry the original status before order edit page is loaded and compare with the latest status from DB when save the order edit, like this:
#RequestMapping("/order")
#Controller
public class OrderController{
private String orderStatus;
#RequestMapping(value = "/{orderId}/edit", method = RequestMethod.GET)
public String prepareOrderEditForm(....){
....
// remember the original status
orderStatus = Order.getLatestStatus(orderId);
}
#RequestMapping(value = "/processEdit", method = RequestMethod.POST)
public String prepareOrderEditForm(....){
....
String currentOrderStatus = Order.getLatestStatus(orderId);
// compare most recent status with the original status
if(currentOrderStatus.equals(orderStatus) == false){
....//something is wrong, someone may be already edit the order
return "failed";
}else{
orderService.merge(order);
orderStatus = order.getNewStatus();
}
return "updated";
}
}
I understand this design is not as robust as Optimistic locking by database it may ignore the time when DML executed, also the orderStatus here should use getter/setter as java bean. So please exclude above concern but put the scope only within this controller
My question is, as Spring MVC is working in single thread like JSP Servlet, is there any problem while multiple user log on to the spring web application and edit the same order so orderStatus in this controller class somehow intertwined by multiple user(concurrency issue)?

Neither JSP nor Spring MVC are single threaded.
You should always try not to change instance/static variables in your controller methods.
Edit:
I should have mentioned that session thread safety will only work when you set Spring MVC abstract controller's synchronizeOnSession to true (by default it is false).
In your case you can:
1) set synchronizeOnSession to true in your spring application context where you define your controller.
2) Extend from Spring's AbstractController and override handleRequestInternal method.
3) in handleRequestInternal call request.getSession().setAttribute("orderStatus")

Related

How to pass data between two endpoints in MVC Spring Controlller

I am quiet new in MVC and Spring and I want to learn best practices and solution so I decided to ask what is the best practice or how you would solve my problem.
I got controller with scope session (To store data in global variable not overrided when another user send request). I got global variable as I mentioned and two endpoints. First endpoint POST - here I send form and call another REST service to fetch data - call depends on data from form. Second endpoint GET - here i send which page to return. This endpoint is used for pagination.
Where is the problem? I have to store data in global variable because when I fetch data in POST endpoint I do not have access to it in GET endpoint. I do not like this solution. Do you have any ideas how to solve it better?
#Controller
#SessionScope
public class someController {
Global variable
#PostMapping(value = "/endpoint")
public String endpoint1(Form form){
//here I fetch data from another REST service depends on form data and save it to global variable
}
#GetMapping(value = "/anotherenpoint")
public String endpoint1(int page){
//here I get data from global variable and return to view as Page object
}
Your #Controller shouldn't be session scoped, instead make your variable session scoped. (you can also annotate it with #Autowired, so your controller automatically has the right variable for the current user)

Spring Boot authorisation in services

I have a Spring Boot application which uses Spring Security to Authenticate and Authorise requests using a JWT. However, some requests should only be able to be executed by a particular user. For example:
GET /users/{id}/orders should only return the list of orders if {id} is the current user or the current user is ADMIN
PUT /orders/{id} should only edit the order if the its payer is the current user
PUT /representation-requests/{id}/accept should only work if the current user is the target of the representation request
Because of the usage of JWTs, the way I get the current user's ID is by
String userId = ((DecodedJWT) SecurityContextHolder.getContext().getAuthentication().getDetails()).getSubject();
I've implemented this in the various methods of the services responsible for handling each API call. My question is if there is a more general way to do this using Spring Boot and Spring Security? Or is this not a standard use case?
I have looked at #PreAuthorize annotation in controllers, but it does not suite my needs as the URL parameters are not enough to check the nested entities. #PostAuthorize in controllers seems closer, but because of the JWT I couldn't get it to work (and it also seems a bit clunky to do long code in annotations, not sure it is better than doing it in the service itself). I appreciate a pointer in the right direction.
You'll have to play around with it a bit (I can't give you 100% specifics for JWT), but you basically could use SpEL to achieve what you want. (I'm unsure why you think #PostAuthorize could be a better fit, though)
Either like so, for simple checks (# denotes a method parameter)
#PreAuthorize("principal?.id == #id")
public List<Order> ordersForUsers(Integer id) {
// do someting
}
Or like so, delegating to a custom bean (# denotes a bean name, hash the method param, 'admin' the role you want, or any other parameter, really)
#PreAuthorize("#yourBean.hasPermission(principal, #id, 'admin')")
public List<Order> ordersForUsers(Integer id) {
// do someting
}

Does #Cacheable annotated methods execute when the actual data is modified?

I am building a RESTful web service that can be consumed by a browser or another web service.
I am willing to reduce the bandwidth through caching, however i want the method to be executed and send the actual data only if it's different than the last modified cache.
From my understanding of the #cacheable annotation, the method is only executed once and the output is cached until the cache expires .
Also #CachePut executes everytime and updates the cache but does it send the cache again even if it's not updated?
summary is: i need the client to be able to send the last modified date of it's cache and only get a new data if it has been modified.
Also how does Spring handle the client side caching and if-modified-since headers? does i need to save the last modified time or it is automatically handled ?
No, you need to do it by yourself.
You need to annotate your "fetch" method with #Cacheable(docs) and then, annotate "update" method with #CacheEvict (docs) in order to "drop" your cache. So when you would fetch your data next time after its modification, it will be fresh.
Alternatively, you can create another method with #CacheEvict and manually call it from "update" method.
The cache related annotations (#Cacheable, #CacheEvict etc) will only deal with the cache being maintained by application. Any http response header like last-modified etc has to be managed seperately. Spring MVC provides a handy way to deal with it (docs).
The logic to calculate the last modified time has to be obviously application specific.
An example of its usage would be
MyController {
#Autowire
CacheService cacheService;
#RequestMapping(value = "/testCache", method = RequestMethod.GET)
public String myControllerMethod(WebRequest webRequest, Model model, HttpServletResponse response) {
long lastModified = // calculate as per your logic and add headers to response
if (request.checkNotModified(lastModified)) {
// stop processing
return null;
} else {
return cacheService.getData(model);
}
}
#Component
public class CacheService{
#Cacheable(value = "users", key = "#id")
public String getData(Model model) {
//populate Model
return "dataview";
}

SessionAttributes when open new browser tabs

I have an Spring-mvc application and in each controller I add a form to SessionAttributes to preserve properties when save, delete or do another get request. Main problem becomes when I try to open some link in another browser tab and try to submit the first one. I tried this solution but when I do a redirect (in controller I only have 1 return for view and the other methods do a redirect) it creates a new conversation and can't find previous one.
I have another question about this triying to use spring-session, question It's here but I don't know if this will work too.
Did you look into Spring's RedirectAttributes? I haven't used it myself but it sounds like it should do what you would like. RedirectAttributes is typically used for GET/redirect/POST patterns and at least one user seems to think passing session attributes this way is bad practice, however they go on to mention there doesn't seem to be a better solution. Anyway, the example shown in the documentation:
#RequestMapping(value = "/accounts", method = RequestMethod.POST)
public String handle(Account account, BindingResult result, RedirectAttributes redirectAttrs) {
if (result.hasErrors()) {
return "accounts/new";
}
// Save account ...
redirectAttrs.addAttribute("id", account.getId()).addFlashAttribute("message", "Account created!");
return "redirect:/accounts/{id}";
}
would add the "message" attribute to a RedirectModel, and if your controller redirects, then whatever method handles the redirect can access that data like so:
#RequestMapping(value = "/accounts", method = RequestMethod.POST)
public String handleRedirect(Model model) {
String message = (String) model.asMap().get("message");
return new ModelAndView();
}
So adding session attributes should be possible in the same way. Another reference here.
EDIT
I was looking through the Spring documentation and they also mention this annotation #SessionAttributes. From the documentation:
The type-level #SessionAttributes annotation declares session attributes used by a specific handler. This will typically list the names of model attributes or types of model attributes which should be transparently stored in the session or some conversational storage, serving as form-backing beans between subsequent requests.
Could this be what you need?
And also a link to documentation on flash attributes.
This is the solution we have come up with, nothing to do with Spring:
On each html form of your application you will have to include a hidden field. Let's name this field CSRF_TOKEN. This field should have a randomly generated value. This value is placed both in the session and the hidden field. The name of the session attribute is SESSION_CSRF_TOKEN
When the form is submitted to the server, you check whether the value in the session (SESSION_CSRF_TOKEN) equals the value sent in the HTTP request parameter CSRF_TOKEN. If not, you show some kind of error message and you stop processing. If they are equal, proceed.
If the user opens a new tab or duplicates a tab, the server will re-render the page and a new CSRF_TOKEN will be generated. So the user will only be able to submit the form from the newly opened tab , and not from the original.
This solution offers an additional bonus: It protects from CSRF attacks.

Fill a session bean with data before other methods execute in Controller

I'm creating a Spring application and in it I have a side bar which can be use to access modules of the application.
The side bar remains visible through out all views of the application. I create the view using Apache Tiles.
Since I don't want to hard code the menu items in the JSP, I want to populate required data(Link name, URL pattern) from database.
I want to do this only once so my plan is to create a session bean to put all these menu details in to it.
I create a Class called "MenuDAO" and I autowire it to the controller. Then I create a method to return that "MenuDAO" object and I mark it as a "ModelAttribute" in my controller.
#Autowired
private MenuDAO menuDAO;
#ModelAttribute("userDetails")
public UserDetail getUserDetail(){
return this.userDetails;
}
Now I am wandering where can I fill this DAO with data. I want to do it once per session(like when user logged in).
I cannot do it in the method with request mapping because once it get called Model attributes are already injected so only when the page loads next time the new data will be available.
Again I thought of doing it inside a method annotated with #PostConstruct. But then I need to do it in every Controller.
So I want to know where I can do this correctly so that I can populate this bean once per user session.
Check this question it is quite similar :
Dynamic content in all page in Spring MVC
You will have to populate your modelAndView in a postHandle method of an interceptor.
The interceptors are called for every request so it is your responsability to not call your dao for every request.
I would suggest you to store your menu in a cache instead of putting it in the session (using ehcache which is easy to set up in spring) .
I solve it as follows. I don't feel that this is the most convenient way. But it works.
I autowire session bean and mark required attribute as model attrbutes.
#Autowired
private MenuDAO menuDAO;
#ModelAttribute("menuDetails")
public List<ElabModuleWebProperties> getMenuDetails(){
return this.menuDAO.getMenuList();
}
After authenticate I redirect to url patter "home" by Spring Security. In that method I check whether the session bean has initialized and if not I do it there.
#RequestMapping(value = "home", method = RequestMethod.GET)
public String showHome(ModelMap model, Principal principal){
logger.debug("User accessing home");
if(!menuDAO.isMenuInitiationDone()){
logger.debug("menuDAO is empty, populating data for the object");
menuDAO.setMenuList(loginService.loadUserDetailsByUsername(SecurityContextHolder.getContext().getAuthentication().getName()));
menuDAO.setMenuInitiationDone(true);
}
model.put("menuDetails", menuDAO.getMenuList());
return "welcome";
}
In other controllers I can just autowire the bean and use it. No problem.

Categories