Spring Cacheable unless condition doesn't work - java

I have developed a REST end-point in Springboot that takes a String ID and responds with a ModelAndView. This end-point is marked with #Cacheable annotation. Now there are two things that can happen at the given end-point.
Case 1: The request ID exists in the DB and yields a URL to which redirection needs to happen. In this case, response should be cached so that upon consecutive requests of the same ID, the result can be served from Cache
Case 2: The requested ID doesn't exist in the DB and thus redirection should happen to a specific URL and no caching should be done in this scenario.
So below is my method
#GetMapping("{id}")
#Cacheable(value = "url-single", key = "#id", unless = "#result.view!=\"redirect:/notfound\"")
public ModelAndView redirect(#PathVariable("id") String id, ServletRequest servletRequest,
ServletResponse servletResponse) {
HttpServletRequest request = HttpServletRequest.class.cast(servletRequest);
LOG.info("Redirection request from: {} for Short URL Key: {}", request.getRemoteAddr(), id);
try {
Optional<String> originalUrlOptional = urlManagerService.retrieveOriginalUrl(id);
if (originalUrlOptional.isPresent() && !StringUtils.isEmpty(originalUrlOptional.get())) {
LOG.info("Found Original URL: {} for Short URL Key: {}", originalUrlOptional.get(), id);
return new ModelAndView("redirect:https://" + originalUrlOptional.get());
}
} catch (NoSuchElementException e) {
LOG.error("Error while redirecting: {}", e.getMessage(), e);
}
return new ModelAndView("redirect:/notfound");
}
If I understand it correctly from here, the keyword unless in #Cacheable applies to the return type and in order to access any particular member variable of the return type object, we have to refer to it as #result.attributeName <comparison> <value>.
So why isn't anything being stored in my Redis cache? If I remove the unless condition, everything gets stored. Is the condition not correct?

I have been looking at your unless statement:
unless = "#result.view!=\"redirect:/notfound\""
so this means that it WONT be cached if the result.view IS NOT redirect:/notfound.
My assumption is that you want to cache it, except when redirect:/notfound.
There is a "double negative" here.
So you probably should use:
unless = "#result.view==\"redirect:/notfound\""
(so use '==' instead of '!=')
Let me know if this works!

I found where I was going wrong after playing with the unless conditional keyword for awhile. The #result inside the double quotes (") of the unless keyword is literally the object to returned. What that means is that, you can call whatever method can be called on that object.
The statement "#result.view==\"redirect:/notfound\"" was failing because the object didn't expose any member variable called view. Using the expression unless = "#result.getViewName().equals(\"redirect:/notfound\")" actually did the trick because there was a method getViewName() on the object that returned String and calling equals() on it did the actual comparison.
I guess I got stuck here because I wasn't not familiar with Spring Expression Language or SpEL

Related

How to check if Request object contains a given parameter

I have a REST endpoint /v1/abc.
It is a POST method.
In the request body i send an object Test which looks as below.
Class Test {
Boolean flag = null;
String name = null;
}
I invoke the endpoint using swagger.
In the request body, i set below.
{
"name" : "hello"
}
If you notice, I am not sending flag at all.
In my API, i want to test if the request object has flag or not. If not present, i throw an exception.
Can you tell me how to achieve this?
I assume that you have a simple POST method like this one:
#PostMapping(path = "/v1/abc")
public void addAbc(#RequestBody Test test) {
//code
}
So you could just check test.getFlag() != null and thereafter raise an exception or not.
As an alternative you could add #Valid to your method parameter to enable JSR 303 bean validation and make use of #NotNull on your flag field.
Little remark: the keyword class in your example has to be written in lowercase.

Handle Resource not found in Rest API

I am developing a Rest API in spring boot. Which of the following is the best way to handle when an instance of resource not found ?
#GetMapping(value="/book/{id}")
public ResponseEntity<Book> getBook(#PathVariable String id){
Book book = bookService.getBook();
// Which is best Approach for resource instance not found ?
if(book == null) {
// This one
return new ResponseEntity<>(book, HttpStatus.NO_CONTENT);
//OR
return new ResponseEntity<>(book, HttpStatus.NOT_FOUND);
//OR
throw new DataNotFoundException("Book with id " + id + " Does not exist");
}
return new ResponseEntity<>(book , HttpStatus.OK);
}
I am clear about that when a collection of resource not found in Db then to pass an empty collection instead of null but I am not clear what to do with an instance of resource.
I have also read on StackOverflow that HttpStatus.NOT_FOUND should be used when a Resource under the criteria cannot exist instead of do not exist in the Db.
What is best approach to handle this ?
When working with Spring MVC, you usually have two choices when returning your result, either you work with plain objects, or you work with the ResponseEntity class. Neither of those is better than the other. Additionally, you can decide whether or not you separate your error handling using exceptions or not.
Given that, your third scenario by throwing an exception is essentially the same as one of your first two options. By default, throwing an exception will result into a 500 Internal Server Error, but it can be changed by using the #ResponseStatus annotation, for example:
#ResponseStatus(HttpStatus.NOT_FOUND) // Or #ResponseStatus(HttpStatus.NO_CONTENT)
public class DataNotFoundException extends RuntimeException {
}
Alternatively, you can also define an exception handler. Again, this can be done by either working with plain objects or ResponseEntity, for example:
#ResponseStatus(HttpStatus.NOT_FOUND) // Or #ResponseStatus(HttpStatus.NO_CONTENT)
#ExceptionHandler(DataNotFoundException.class)
public Book handleNotFound(DataNotFoundException ex) {
return null;
}
Or:
#ExceptionHandler(DataNotFoundException.class)
public ResponseEntity<Book> handleNotFound(DataNotFoundException ex) {
return new ResponseEntity<>(null, HttpStatus.NOT_FOUND); // Or HttpStatus.NO_CONTENT
}
Again, neither is better than the other and what you choose is mostly based upon personal preference. However, you should probably use one consistently.
Now, that means that there are still two choices left, either by choosing HttpStatus.NOT_FOUND (404) or HttpStatus.NO_CONTENT (204). While you can technically use either status, they have a different meaning:
204 = The request was succesful, but there's nothing.
404 = The request was not succesful, the resource does not exist
Now, if you request /book/123 and there's no book with ID 123, it could be seen as a resource that doesn't exist, and thus, HttpStatus.NOT_FOUND makes most sense.
First of all I think that you mean #PathVariable and not #RequestParam for your method parameter (see difference between PathVariable and RequestParam here ).
Secondly, it will be ambiguous for the client that receives the 404 not found response as this means that :
The server has not found anything matching the requested address (URI)
( not found ). This means the URL you have typed is wrong or obsolete
and does not match any document existing on the server (you may try to
gradualy remove the URL components from the right to the left to
eventualy retrieve an existing path).
Knowing that your return type is a ResponsEntity, it will be more appropriate to have this :
#GetMapping(value="/book/{id}")
public ResponseEntity getBook(#PathVariable String id){
Optional<Book> book = bookService.getBook();
if(book.isPresent()) {
return ResponseEntity.status(HttpStatus.OK).body(book.get());
}
return ResponseEntity.status(HttpStatus.NO_CONTENT).build();
}
If your endpoint gets book by id and the book does not exists, return 400. Do not return 404. 404 is protocol error: it should be reserved for wrong URL. Now URL is correct but id is wrong. Id almost often is not guessed, but returned by previous query. It cannot disappear suddenly: if id is wrong, the request is wrong.
If your endpoint gets book by title and the book does not exists, return 204. That is absolutely normal that book does not exists in such case and client should be prepared to handle 204.
Someone could argue that difference between 400 and 204 is fuzzy and better always return 204. Indeed, difference may be fuzzy, but from monitoring perspective I would like to know when everything is ok (no book found by title) and when something smells (no book found by id).
I know that my answer does not comply REST directives (or maybe does not comply). I don't care it too much. I simply think that 404 should be reserved for application server and should not be used by application. Reason is already explained in other answer here.
Summary:
404: wrong URL
400: wrong id
204: not found and that is OK
just return 404 HttpStatus to client ,do not waste time on it.No one will request id that not exist in db normally. usually client request like model/{id} come from
against your Collection [model1,model2,.....]
Whenever a resource cannot be found, you should indicate that to the client, most commonly using the HTTP Status Code 404 Not Found, as you already mentioned.
For collections, simply return an empty array in the response body (alongside with response code 200 OK, this is my opinion tough), do not return 404 Not Found since the resource actually exists.
Please note that 202 No Content is a bad choice here, since the server has not successfully fulfilled the request. Instead, use this return code, for example, for a successful PUT request (you have changed internal data but return no content in the response body).
In most APIs you will encounter additional information in the response body:
{"messages":{"error":[{"code":404,"message":"Resource not found."}]}}
You will find list of all errors and their response codes with informative descriptions. One thing is important tough: Stick to one format, otherwise it will be a pain for clients. Most APIs also only use about 6-8 HTTP response codes.
Also, Spring has a number of utilities to help you out:
#ResponseStatus(value=HttpStatus.NOT_FOUND, reason="No such Order")
public class OrderNotFoundException extends RuntimeException {
// ...
}
Or, the following annotation to create a custom response format:
#ExceptionHandler({ YourException.class })

Jersey reuse parameter validation for subresources

I have to following endpoint structure in Jersey:
/objects/
/objects/:id
/objects/:id/:field
/objects/:id/:field/:subfield
The IDs I'm using have a very specific format, so I first check if the format is valid before making a request to the database.
Right now I have to put this code in each of the POST, PUT, GET, DELETE functions for each of the functions that has :id as a parameter. So this just means an early return.
if (!isIdValid(id)){
return Response.status(Response.StatusType.BAD_REQUEST)
.entity("The ID you've provided is invalid")
.build();
}
(In reality the error entity is an object containing more information about the error)
And then for each function using the :field or :subfield parameters the code is similar. This checking and error-handling behavior has to be copied every time. And when I start copy-pasting stuff, I start thinking: there should be a better way?
I would like to place the :id checking code at the the /objects/:id level, and then all further nested levels are assumed have a valid ID. The same for the other parameters further nesting down.
I've been looking into using subresource locators, but then you create a function returning a new instance of the subresource. I can't put a conditional return of a Response-object at that level for if the validation fails.
#Path("{id}")
function Class<ObjectFieldResource> getObjectById(#PathParam("id") String id){
return ObjectFieldResource.class;
}
I could start throwing exceptions, but I would rather avoid that, since I don't really consider invalid input to be an exception.
How would such a structure best be implemented? I've looked at bean validation but that doesn't seem to allow me to define validation for my specific format + custom error responses.
Am I missing something in the way subresources should be implemented?
Solution 1
If you can use regexp checks instead of your isIdValid method it's possible to define your resources like this
#POST
#Path("objects/{id:\\d+}")
public Response doSmth(#PathParam("id") String id) {
...
}
In a case of invalid id format caller will have 'Not Found' response status without even reaching your doSmth method.
Obviously, you can use String constants for all equal path values.
final static String ID_RES = "objects/{id:\\d+}";
#POST
#Path(ID_RES)
public Response postSmth(#PathParam("id") String id) {
...
}
...
#GET
#Path(ID_RES)
public Object getSmth(#PathParam("id") String id) {
...
}
The can also read full description of Path#value parameter
Solution 2
Create and register at your REST server javax.ws.rs.container.ContainerRequestFilter implementation with filter method having needed URI checks.
The single filter parameter has ContainerRequestContext type from witch you can call getUriInfo for getting URI and method abortWith(Response response) which can be used for aborting caller request if your resource ids validation was failed.
See Chapter 10. Filters and Interceptors chapter of Jersey Manual.

Should I put my business invariant check in controller

I am working with Spring MVC. I need to check duplicated premises name before saving premises.
In Controller
public void savePremises() {
// Check duplicate
Set<String> premiseNames = new HashSet<String>();
//premises is global variable which contain the list of premises to save
for (Premise premise : premises) {
String premiseName = premise.getPremiseName();
if (premiseNames.contains(premiseName)) {
Clients.showNotification("Duplicated primise name "
+ premiseName);
return;
} else {
premiseNames.add(premiseName);
}
}
..............
}
Because I will use this checking code for premises in another controller, so I want to put it in service class to reuse later.
Then I need to return 2 value from service function:
+ A boolean value to indicate the result of checking.
+ A string value for duplicated premise name.
I have a solution that returning null value for premise name to indicate no duplicated premise name. Is it bad practice to do this?
In Controller
public void savePremises(){
String premiseName =premiseService.isDuplicatedPremiseName(premises);
if( premiseName!=null) {
Clients.showNotification("Duplicated primise name "
+ premiseName);
return;
}
}
In Service
public String isDuplicatedPremiseName(List<Premise> premises) {
Set<String> premiseNames = new HashSet<String>();
for (Premise premise : premises) {
String premiseName = premise.getPremiseName();
if (premiseNames.contains(premiseName)) {
return premiseName;
} else {
premiseNames.add(premiseName);
}
}
return null;
}
Ideally, Spring MVC controllers should be a very thin adapter from service logic to HTTP (either HTML or a JSON/XML REST API). Besides basic DTO validation, business logic should be contained in your service classes. This makes the business logic easier to test and more reusable (e.g., between controllers that present both HTML and JSON).
In your case, unless the users are actively expecting to search for duplicates (perhaps in a search for public records), it would be better not to explicitly call the checker at all. The business logic should be responsible for ensuring that invariants are preserved and throw an exception if not. You can either catch the exception in the controller (usually the case for an HTML controller) or annotate it with Spring HTTP annotations and allow it to propagate (often the case for a JSON controller).
I have a solution that returning null value for premise name to indicate no duplicated premise name. Is it bad practice to do this?
Yes, returning null is definitely bad practice.
Instead, you can use Optional<T> class that contains value or contains indication that value is not available.
public Optional<String> GetFirstDuplicate(List<Premise> premises)
I need to check duplicated premises name before saving premises.
If your business logic doesn't allow duplicates in premise names, you should definitely check for duplication in controller before passing these names into business layer.
Exceptions in business layer are used to indicate coder's mistakes, not user's mistakes. Occasionally entering the same name twice is user's mistake.

How to avoid concurrent access of controller method with the same session in java spring?

I would like to know how to make sure that some method in a service is accessed only once at a time per session.
I'll illustrate by a small example:
Assume we have a user in a state A (user.state = A). This user sends a HTTP GET request to our java spring controller to get a page, say /hello. Based on his status, he will be sent to either A or B. Before that, we will change his status to B (see code below).
Now, assume again that the call dao.doSomething(); takes a lot of time. If the user sends another GET (by refreshing his browser for instance), he will call the exact same method dao.doSomething(), resulting in 2 calls.
How can you avoid that?
What happens if you sends 2 HTTP GETs at the same time?
How can you have something consistent in your controller/service/model/database?
Note 1: here we don't issue the 2 HTTP GETs from different browser. We just make them at the same time on the same browser (I'm aware of the max concurrent session solution, but this does not solve my problem.).
Note 2: the solution should not block concurrent accesses of the controller for different users.
I've read a bit about transaction on service, but I'm not sure if this is the solution. I've also read a bit on concurrency, but I still don't understand how to use it here.
I would greatly appreciate your help! Thanks!
code example:
#Controller
public class UserController {
#RequestMapping(value='/hello')
public String viewHelloPage() {
// we get the user from a session attribute
if (user.getState() = A) {
user.setStatus(B);
return "pageA";
}
return "pageB";
}
#Service
public class UserService {
Dao dao;
#Override
public void setStatus(User user) {
dao.doSomething();
user.setStatus(B);
}
}
Although I wouldn't recommend it (as it basically blocks all other calls from the same user to). On most HandlerAdapter implementations you can set the property synchronizeOnSession by default this is false allowing for concurrent requests to come from the same client. When you set this property to true requests will be queued for that client.
How to set it depends on your configuration of the HandlerAdapter.
how to make sure that some method in a service is accessed only once
at a time per session.
Try to Lock on session object in your controller before calling service method
If dao.doSomething() is doing work that you only want to happen once, you should use an idempotent method like PUT or DELETE. There's no law forcing you to use the correct method, but worst-case it's a self-documenting way to tell the world about how your API should be used. If that isn't enough for you, most browsers will try to help you out based on the type of request. For instance, the browser will often use caching to avoid multiple GETs.
It seems like what you really want to know is how to enforce idempotency. This is very application-specific. One general approach is to generate and store a pseudo-unique id on the server side for the client to attach to their request. This way, any request with the same id after the first can be safely ignored. Obviously old ids should be evicted intelligently.
As I said, the solution is often application-specific. In your case above, it looks like you're trying to switch between 2 states, and your implementation is a server-side toggle. You can utilize the client to ensure that multiple requests will not be a problem.
#RequestMapping(value="/hello", method=RequestMethod.PUT)
public String test(#RequestParam("state") String state) {
dao.setState(user, state)
switch (state) {
case "A":
return "B";
case "B":
return "A";
default:
return "error";
}
}
If you don't mind to configure and use AOP, then the following might help you
#Aspect
#Component
public class NonConcurrentAspect implements HttpSessionListener{
private Map<HttpSession, Map<Method, Object>> mutexes = new ConcurrentHashMap<HttpSession, Map<Method, Object>>();
#Around(value = "#annotation(org.springframework.web.bind.annotation.RequestMapping)")
public Object handle(ProceedingJoinPoint pjp) throws Throwable {
MethodInvocationProceedingJoinPoint methodPjp = (MethodInvocationProceedingJoinPoint) pjp;
Method method = ((MethodSignature) methodPjp.getSignature()).getMethod();
ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = requestAttributes.getRequest();
HttpSession session = request.getSession(false);
Object mutex = getMutex(session, method);
synchronized (mutex) {
return pjp.proceed();
}
}
private Object getMutex(HttpSession session, Method method) {
Map<Method, Object> sessionMutexes = mutexes.get(session);
Object mutex = new Object();
Object existingMutex = sessionMutexes.putIfAbsent(method, mutex);
return existingMutex == null ? mutex : existingMutex;
}
#Override
public void sessionCreated(HttpSessionEvent se) {
mutexes.put(se.getSession(), new ConcurrentHashMap<Method, Object>());
}
#Override
public void sessionDestroyed(HttpSessionEvent se) {
mutexes.remove(se.getSession());
}
}
It synchronizes on a per-session per-method mutex. One restriction is that the methods so advised should not call each other (which is hardly a case, unless you violate MVC design pattern severely), otherwise you may face deadlocks.
This would handle all the methods tagged with #RequestMapping, but if you want just few methods be guarded against concurrent execution,
then, as one of the possible solutions, you could introduce your own annotation, e.g.
#Target(ElementType.METHOD)
#Retention(RetentionPolicy.RUNTIME)
#Documented
public #interface NonConcurrent {
}
tag the specific methods with this annotation, and replace #RequestMapping in #Around annotation in the above aspect class with your own.
In highly contended environment you may think of more advanced solution than intrinsic locks.
I would, however, advise against using HandlerAdapter's synchronizeOnSession option, not only because it synchronizes all the invocations on the same mutex, but, which is less obvious, the synchronization on publicly available mutex is potentially dangerous.

Categories

Resources