How to prevent concurrent call in Spring MVC - java

Deal all, i am facing one problem in spring mvc. From one of our external system say payment gateway will call us [/getRespFromExternal] after their transaction completed, unfortunately they are calling us more than one time at time.
In the method getRespFromExternal, we are calling internal webservice to process payment acknowledgement, before WS call completed itself i get another duplicate call from external system. So i get payInd is null in payResponse method in controller. after first call [WS call] finished then /payResponse from controller is not even calling . Sorry for my english. Please advice me.
Service Method called by External System:
#RequestMapping(value="/getRespFromExternal",method=RequestMethod.POST)
public String getRespFromExternal(HttpServletRequest httpRequest,HttpServletResponse httpResponse,HttpSession session){
if (httpRequest != null && session.getAttribute("extRespId") == null) {
session.setAttribute("extRespId", httpRequest.getParameter("tranID"));
// internal web service call
callInternalWS(); // to process payment
before processing above WS method i get another call from external[/getRespFromExternal] for second time;
session.setAttribute("payInd", "Y");
return "redirect:/payResponse/";
}
return "redirect:/payResponse/";
}
Controller Method :
#RequestMapping(value = "/payResponse/", method = RequestMethod.GET)
public ModelAndView payResponse(HttpServletRequest request, HttpSession session) {
String payInd = (String) session.getAttribute("payInd");
System.out.println("payInd --> "+payInd);
if (payInd != null && payInd.trim().equalsIgnoreCase("Y") ) {
}
}

If you are getting multiple calls to your service with the same transaction if, that should be treated as an error.
I assume that you are using a transactional database to record the payments.
If so, then you could make the transaction id a unique key in the database. If the SQL insert or update to record the transaction fails due to a duplicate key, redirect to a different URL to report the error.

Related

Service method arguments, object identifiers vs object references

I understand that it is probably better to pass objects into a service method, but is this still the case if the caller would first have to look up the object before calling the service? And if so, why?
Example
Let's say I have a RoleService, that adds a role to the given user. And let's say the RoleService is called via a web controller or possibly a REST API. The web controller takes the userId and roleId as input from the web request.
Would I be better off using this service method?
public void addRoleToUser(long userId, long roleId) {
User user = userRepository.find(userId);
Role role = userRepository.find(roleId);
user.addRole(role);
}
Or this one? The web controller would obviously need to retrieve both objects before calling the service in this case.
public void addRoleToUser(User user, Role role) {
user.addRole(role);
userRepository.save(user);
}
Whether called via a web controller or a REST API, the incoming request would only be giving the 2 ID's, so you have to do the find() calls somewhere.
You certainly cannot trust the caller to have up-to-date information about the two objects, and it's a waste to transmit the full objects if you're only going to use the ID's anyway.
It is common to have the service API also be the database transaction boundary (service class or method annotated with #Transactional), so it is best to have the service method do the find() and addRole() calls, so they all execute in a single database transaction.

Spring : call REST API after receiving response from another REST API

I have a controller that calls a webservice to start a batch job, when the result is returned, it should call another REST API based on this result. Then it should wait for the new result, and return this second result to user:
#RestController
public class LaunchController {
#PostMapping(path = "/launch", consumes = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<LaunchResult> launch(#Valid #RequestBody LaunchParams params) {
// in launch() I call the first REST API
LaunchResult result = myService.launch(params);
// here I need to call another REST API
AnotherResult result2 = callAnotherWebAPIBasedOnThisResult(result);
return ResponseEntity.ok(result2);
}
Now I want to know that is it good practice to do it like this (synchronously) and all in one controller ? Exist other way of doing this ?
Your controller is perfectly fine as it does not have any application logic inside and it actually calls the service methods. But It lacks the exception handling. You have catch with proper exceptions with try catch block or throws keyword.
The decision to convert the endpoint to an asychronous one depends on a few factors :
Is the batch job going to take time to be executed.
Can this process be converted to an asynchronous one.
Does the use case expect the user to wait until the action is completed.
If the your answer is yes, it's better to convert the endpoint to an ayschronous one and update the user with the details later after all processes including the batch processes are completed . It's always better NOT to keep the user waiting for a response. Non-blocking requests makes sense when you are dealing with a lot of data and processing needed for this data. Also, by making this request asynchronous you will have better control over the processing stages and provide the user with better statistics incase any of the processing stage resulted in failure. For instance the batch job could fail or the second rest api call could result in an error.

Spring session scope bean reset between requests when accessing through zuul

The spring app has a session bean, which contains certain data. That data is loaded from DB at init request to the controller, which is always a first request client calls at start up. That data is used for other requests by same user. Now, everything works fine on its own. However after trying to integrate the app into the system using zuul (which as far as I understand in this context simply redirects request from one url into another), it broke. Whenever a method is called after the init, the session bean's data is null.
Here is a snippet from service class:
#Autowired
TaskCache cache;
#Override
public void initUserSession() {
List<Task> data = loadTasks();
cache.setTasks(data);
LinearFilterStack<Task> fs = createFilterStack(data);
cache.setFilterStack(fs);
System.out.println(cache.hashCode()); //hashcode stays same
System.out.println(cache.getFilterStack() == null) //false
}
#Override
public List<Task> getTasks(Sort sort) {
System.out.println(cache.hashCode()); //hashcode stays same
System.out.println(cache.getFilterStack() == null) //true
LinearFilterStack<Task> fs = cache.getFilterStack();
List<Task> tasks = fs.filter(cache.getTasks()); //Obviously NPE
sortTasks(tasks, sort);
return tasks;
}
#Component
#Scope(value=WebApplicationContext.SCOPE_SESSION, proxyMode=ScopedProxyMode.TARGET_CLASS)
public class TaskCache { ... }
And again, this only happens through zuul. I.e. if I use localhost:30022/rest/... it works, if I use localhost:8080/app/tasks/rest/... (which zuul redirects to localhost:30022/rest/...) I get NPE, because the cache bean loses its data after init request.
That could be caused by default behavior of Zuul that prevents passsing of cookie related headers.
The following is that default configuration of Zuul and it doesn't allow pass below headers to your downstream API servers.
zuul.sensitiveHeaders= Authorization,Cookie,Set-Cookie
So please try to define below properties. It will allow all your cookie related header to be passed to your API servers.
zuul.sensitiveHeaders= Authorization
You can find more details in section "Cookies and Sensitive Headers" of this document

How to access web layer(Controllers) from the DAO Layer?

I had to cancel the running queries. In my DAO I set the Entity.session in the ServletContext when I start the query and remove it when the query is finished. Then check in the Controller if the session is present in the ServletContext, if it is present then I cancel the query by calling the session.cancelQuery() from the session object in the ServletContext.
This was working fine in my dev environment, but in my pre-prod testing the entire code does not run inside the tomcat container. The web part runs in tomcat whereas the data layer runs a java application. hence I could not find the ServletContext in the DAO class and it gave me a ClassNotFound Exception
So I decoupled the web layer in the DAO. Now I set the hibernate.session in the controller itself when it calls the DAO calculate(). But this created a problem, now the session exists even if there are no calculations going on and in actual there are some post or precalculations. And my mechanism to cancel the query doesn't work.
So what I need is a way to access the Controller from the DAO to set the session. I could have used a static method in the Controller and then set the session from it but I think this is again not a good practice.
DAO initial Code:
public calculate(){
Session session = mEntityManager.unwrap(Session.class);
//pre possing
if(session != null)
{
mContext.setAttribute(""+view.getId(), session);
}
List<Object[]> results = query.getResultList();
mContext.removeAttribute(""+view.getId());
//post processing
}
Decoupled DAO code:
The method getSession() is called from the controller before the calculate method is called in the controller. And then when the user requests a cancel from the UI the cancel method is called in the controller.
public Session getSession()
{
Session session = mEntityManager.unwrap(Session.class);
return session;
}
public calculate(){
//pre possing
List<Object[]> results = query.getResultList();
//post processing
}
Controller:
#RequestMapping
public WebServiceResponse cancel(HttpServletRequest request)
{
if(mContext.getAttribute(id) != null)
((Session)mContext.getAttribute(id)).cancelQuery();
}
It seems you have convoluted control flow because you are not properly separating detection of a problem from handling the problem: you detect a problem in the data layer but need to handle it in the presentation layer.
Consider using exceptions. Have the data layer throw an exception when it detects a problem. The presentation layer can handle problems in the data layer by catching exceptions.

MVC interceptor vs Spring security filter vs something else...?

I'm using Spring-MVC with Spring Security for my web application. It includes user registration pages and private user panel. I have it set up currently with the following URL patterns:
whatever/myapp/login user log in
whatever/myapp/register?step=1 start registration
whatever/myapp/account/** private area views (pages)
whatever/myapp/pending view shown while post-registration processes complete
whatever/myapp/blocked account blocked view
whatever/myapp/register/retry if registration failed, allow retry
Essentially, these URLs below should require user authentication, i.e. require log-in:
whatever/myapp/account/** (private area pages)
whatever/myapp/pending (this page has a timer set to redirect to /account/home)
whatever/myapp/register/retry
This is quite straightforward to achieve using Spring security. However, regardless of user authentication through Spring security, private area pages should be accessible or not, depending on user's current account status (stored in my DB).
More specifically: if a user tries to access anything in the private area (/account/**), he should be shown the appropriate view (redirected to appropriate page), according to the status. I have these statuses defined:
suspended - relates to pending view
enabled - allow full access
disabled - not relevant here
retry_allowed- relates to retry view
blocked - relates to account-blocked view
Currently, I have a MVC interceptor setup to /account/**, that checks user status, and redirects to appropriate pages, but somehow I get the sense that this is not really the ideal or appropriate solution here, since I'm facing strange behavior, like multiple controller invocation... and also I'm not quite certain when to return true / false within preHandle() method. Here's the code snippet from the interceptor:
#Override
public boolean preHandle(
HttpServletRequest request,
HttpServletResponse response,
Object arg2)
throws Exception {
IPanelUser pUser = (IPanelUser) SecurityContextHolder.getContext()
.getAuthentication().getPrincipal();
// check principal first and then load from DB
// "suspended" is initial status upon registration
if(pUser.getCustomer().getStatus() == CustomerStatus.Suspended.getCode()) {
// if suspended, load from DB and update status
Customer customer = this.customerService.getUserByUsername(pUser.getUsername());
if(customer != null)
pUser.getCustomer().setStatus(customer.getStatus());
// still suspended? redirect to pending
if(pUser.getCustomer().getStatus() == CustomerStatus.Suspended.getCode()) {
response.sendRedirect("../pending");
return false;
}
}
if(pUser.getCustomer().getStatus() == CustomerStatus.Blocked.getCode()) {
// redirect to blocked page
response.sendRedirect("../blocked");
SecurityContextHolder.clearContext();
return false;
}
if(pUser.getCustomer().getStatus() == CustomerStatus.AllowRetry.getCode()) {
// redirect to CC submission page
response.sendRedirect("../register/retry");
return false;
}
if(pUser.getCustomer().getStatus() == CustomerStatus.Enabled.getCode() ||
pUser.getCustomer().getStatus() == CustomerStatus.Disabled.getCode()) {
// do nothing
}
return true;
}
.
Is this a valid approach ? Any alternative suggestions ?
All options are valid, it depends on the level of abstraction you want.
In a Filter, you only have access to HttpServletRequest and HttpServletResponse objects, so you are very much coupled with the Servlet API. You also don't (directly) have access to all the great Spring functionality like returning a view to be rendered or a ResponseEntity.
In a HandlerInterceptor, it's again more of the same. You can do your redirection or request handling directly in the preHandle() where you don't have access to the ModelAndView or set a flag which you check in postHandle(). You would have access to the ModelAndView but not to some other Spring MVC functionality.
Spring Security is a good alternative, but I find it has a lot of configuration that I don't like too much.
One final alternative, that I like the most, is to use AOP (you can do this with Spring Security or Shiro as well). You create an annotation like #Private and you annotate your #Controller handler methods. You use AOP to advise these methods. The advice basically checks some session or request attribute for a flag (authorized or not). If you are allowed, you continue executing the handler method, if not, you throw an UnauthorizedException (or similar). You then also declare an #ExceptionHandler for that exception where you have pretty much complete control over how the response is generated: a ModelAndView (and related), a ResponseEntity, annotate the handler with #ResponseBody, write the response directly, etc. I feel like you have much more control, if you want it.

Categories

Resources