Spring Boot. Forward on RestController - java

I am implementing a mechanism for replacing short links.
I need to forwarded request to another controller. I found examples how to do it in spring on models, but I don't understand how to do it in RestControllers
Example what i found (use models)
#Controller
public class ShrotLinkForwardController {
#RequestMapping("/s/*")
public String myMethod(HttpServletRequest request) {
return "forward:/difmethod";
}
}
Or maybe I'm looking in the wrong direction and I need to make a filter?
UPD. I don't know the final endpoint, it is calculated in the forwarded method. So, i cant autowired other controller

There are 2 ways to achieve what you want.
1. Call the method on the target controller directly.
Controllers are just normal Spring beans. You can get it via autowire.
#Controller
public class ShrotLinkForwardController {
#Autowired
OtherController otherController;
#RequestMapping("/s/*")
public String myMethod(Model model) {
otherController.doStuff();
return ...;
}
}
2. Trigger the forward by returning a string
To trigger the forward, try returning a String instead of ModelAndView.
This is the approach you mentioned in your question. Note that the syntax should be forward:/forwardURL. The string after forward: is the URL pointing to another controller, not the method name.
#Controller
public class ShrotLinkForwardController {
#RequestMapping("/s/*")
public String myMethod(Model model) {
return "forward:/forwardURL";
}
}

you could inject the target controller and simply call the method
#Controller
public class ShortLinkForwardController {
#Autowired
private RestController target;
#RequestMapping("/s/*")
public String myMethod(HttpServletRequest request) {
return target.myMethod(request);
}
}
Caveat: Path related request properties will still point to "/s/*"
Or use ResponseEntity and set target location...
public ResponseEntity<Void> myMethod(HttpServletRequest request) {
return ResponseEntity.status(302).location(URI.create(...)).build();
}

All answers are about returning String
But I've found another solution
Maybe it will help someone with my problem in case when you need to make forward from one REST endpoint to another REST endpoint.
And it also could be applied to your case.
#RestController
public class CustomerController {
#GetMapping("/forwarding_endpoint")
public void makeForward(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
request.getSession().getServletContext().getRequestDispatcher("/forward_endpoint").forward(request, response);
}
}

UPD. I don't know the final endpoint, it is calculated in the
forwarded method. So, i cant autowired other controller
but I don't understand how to do it in RestControllers
I can see some indications of possible bad design here, so I will try to explain the possible issues and how should be handled according to best practices.
If your requirement is to make a forward to another controller, then this might be an indication of 3 possible issues:
The job to be done by the other controller (which you say you want to forward to) can be extracted into a service method in service layer. Then both controllers can call the same service method, without each controller be aware of the other.
Your need could also be an indicator of the following issue. You need 2 controllers for exactly the same practical reason, so that they provide for same input exactly the same output, but to be available from 2 different URLs. If this is the case then you can use just 1 controller and allow it to be executed for both URLs. See the following code to achieve this:
#RequestMapping({"/s/*", "/s2/*})
public String myMethod(HttpServletRequest request) {
return "some response";
}
You need to expose only 1 URL to the client which will serve everything. Then the approach with forward will also not benefit you, since the client will be able to reach the other forwarded controller directly if he wishes so. In this case you can implement 1 single controller which then according to the needs builds different responses. You can do this in RestController although not suggested by Sonar and other code review tools by marking the method to return ResponseEntity<?>. Example:
#RequestMapping("/s/*")
public ResponseEntity<?> myMethod(HttpServletRequest request) {
if (condition 1) {
return new ResponseEntity<YourObject1>(HttpStatus.OK);
} else if (condition 2) {
return new ResponseEntity<YourObject2>(HttpStatus.OK);
} else {
return new ResponseEntity<YourObject3>(HttpStatus.OK);
}
}
this last choice is not considered best practice with <?> but for this requirement I don't see any other way out.

Related

Spring boot - Combine DTO and form validation

I am currently developing an API where I'm using DTO for the first time. So far I've used Spring's form validation with javax.validation.
So my question is if there is a way to combine both DTO and "form" validation. Let me explain myself: lets say I have a service to log in and another to register. In the service to register we have: name, password and email, the 3 of them must be filled. As for the login service, only the email and password must be filled. So we'd have something like:
private String name;
private String password;
private String email;
Until now, what I did was to create a POJO per request (forms) and then use annotations such as #NotNull but now with DTO in the project I'm in now they just have the same DTO and business object with the same properties and no constraints.
How could I do what I was usually doing? Checking the fields that must be not null in the controller looks a little dirty to me and I can't just put something like #NotNull in the UserDTO because then in the two examples I said I'd have to send also the name when logging in although it's not needed for that service.
So, how could I combine these 2 things? Is this something not possible or there's a better approach?
Thanks.
I assume you are using two separate controllers for login and register requests.
And if it is the case, then you can make good use of org.springframework.validation.Validator interface:
#Component("registrationValidator")
public class RegistrationValidatorImpl implements Validator {
#Override
public boolean supports(final Class<?> aClass) {
}
#Override
public void validate(final Object o, final Errors errors) {
}
}
Create RegistrationValidatorImpl and LoginValidatorIml and #Autowire it in your controllers.
The usage of validator is simple:
invokeValidator(registrationValidator, someDTO, errors);
if (errors.hasErrors()) {
return new ResponseEntity(HttpStatus.BAD_REQUEST); //or whatever logic here
}
The controller method signature should be similar to this:
#RequestMapping(value = "/register", method = RequestMethod.POST)
public ResponseEntity register(#RequestBody final SomeDTO someDTO, final HttpServletRequest request, final Errors errors) {}
I case of one controller, I assume you have different methods mapped to login and register requests. You can #Autowire both validators in controller and use each in separate methods.
Using groups for validation with javax.validation did the work. I followed the answer in this question (as Andrew suggested), then I just had to put every field I wanted to have different rules in different groups.

Spring Boot HTTP status without throwing exceptions

I want to return HTTP status other than 200 without using the #ExceptionHandler annotation.
The reason for this is that not every call to my application, which results in a status which is not OK should throw an exception, at least not in my opinion.
As an example, if a user is trying to log into the system, but provides an inaccurate password, I see no reason to throw an exception over this just in order to be able to return a 401 status. Instead, I would like to be able to return the status from within a "regular" method.
The reason behind this is that throwing unnecessary exceptions both clutters my log files, and "uses" my log aggregator (Rollbar/Sentry) monthly allowance.
I've tried annotating methods with #ResponseStatus and #ResponseBody, but it didn't work.
I looked around for blog posts or other articles on the issue but couldn't find anything.
Any idea if this is possible?
You can use RessponseEntity. Replace the /*STATUS CODE*/ with whatever you want:
#GetMapping("/url")
#ResponseBody
public ResponseEntity<?> handlerMethod(/*parameters*/) {
// ...
return ResponseEntity.status(/*STATUS CODE*/).body(...);
}
Have a look at the following example
#RestController
public class HelloWorldController {
#GetMapping("/hello")
public ResponseEntity<String> sayHello() {
return ResponseEntity.status(HttpStatus.CREATED).body("Hello World!");
}
}

Spring Rest Web Request Scope

I'm in the process of writing a Spring REST type interface to a database that will retrieve user specific results for various resources.
To hold the user I have a spring #Component annotated bean called CurrentUser as a temporary measure.
#Component
public class CurrentUser {
#Autowired
private UserDAO userDAO;
private String userId;
private String email;
private String notes;
public String getUserId() {
return userId;
}
public void setUserId(String userId) throws ApiException {
User user = userDAO.getUser(userId) // Database call to
if (!user.isValid()) {
throw ApiException(...) // The exception would crash back to the user as a error in the response
}
// Valud user so set these aspects.
this.userId = user.userId;
this.email = user.email;
}
}
This object is initialised in a Spring Interceptor on each call to any method in the API using the following interceptor.
public class AuthenticationInterceptor extends HandlerInterceptorAdapter {
#Autowired
private CurrentUser user;
#Autowired
private RequestParameters requestParameters;
#Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws ApiException {
user.setUserId(StringUtils.defaultString(request.getParameter("userId"), "defaultUser"));
return true;
}
}
This is only a place-holder to identify the users until proper authentication can be added.
I'm relatively new to Spring, and the reason for this post
is to increase my understanding of the thread safety aspects of Spring in situations like this
I've recently discovered that Spring is not automatically thread safe, I may need to give more consideration to the scopes.
What I want to understand is the following:
For the above setup, is there any danger that 1000s of simultaneous requests, would potentially interfere and overwrite each other?
e.g. A request for one user would potentially be overwritten with a different user from a separate http request, causing the requestor to receive the wrong data.
What would be the best approach to solving this. (Even though it will be replaced, I have other objects instantiated in similar ways)
Options I'm looking at (if this is an issue), is setting prototype scope, or attaching to the request / session directly rather than allowing them their own autowired object.
Any information anyone could give me would be much appreciated, as I'm a fan of getting it right(er) to start with, than dealing with bad assumptions later on.
Answer 1: Yes, and you don't need 1000 requests to get into trouble. 2 requests in parallel are enough.
Answer 2:
The main problem here is one of scoping:
Default scope of Spring managed beans is Singleton. That means that only one instance of your CurrentUser exists per Application.
That is obviously not what you want. (Since you have a severe security issue here, with only one CurrentUser instance per application).
Simple Answer:
I would probably use Spring Security ;-)
Even Simpler Answer:
If that is not an option:
Use a Filter instead of a HandlerInterceptor (more direct control over clean up)
Create a Thread Local to store the user (and use a finally in the Filter to clean it up) and set it in the Filter
Create a request scoped Service (use #ScopedProxy, to be able to wire it into Singletons), that accesses the ThreadLocal as a UserService (you will need an interface to make it work easily)
Autowire this UserService where you need it
Since by specification each request in a Servlet environment is bound to a thread, and thread locals are inherently thread-safe, you are completely thread safe and it will scale well. The overhead of the scoped proxy is minimal.
(This is only one option, other options could make use of the request scope explicitly or use aspects in a slightly more elegant manner. However, it is a rather simple approach and gets the job done. For more complex needs I would seriously recommend looking into Spring Security).
You ca use parameter resolver feature of spring mvc without making it a bean.
to do that implement the interface HandlerMethodArgumentResolver and register that into the container. And then your handler method can have a argument of type current user
import org.springframework.core.MethodParameter;
import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.ModelAndViewContainer;
public class CurrentUserArgumentResolver implements HandlerMethodArgumentResolver{
#Override
public boolean supportsParameter(MethodParameter parameter) {
if (parameter.getParameterType().equals(CurrentUser.class)) {
return true;
}
return false;
}
#Override
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest,
WebDataBinderFactory binderFactory) throws Exception {
if (supportsParameter(parameter)) {
String userId = (String) webRequest.getAttribute("userId", NativeWebRequest.SCOPE_REQUEST);
return new CurrentUser(userId);
}
return null;
}
public class CurrentUser{
public CurrentUser(String userId) {
// TODO Auto-generated constructor stub
}
}
}
After this you can have handler method of stype
#RequestMapping
public String handler(CurrentUser user){
....
}

Best Practice of redirecting in Spring Controller

Here I have to controller methods for example,
(Case 1) One way is
#Controller
#requestMapping("main")
Class ControllerClass{
#RequestMapping("first", method = RequestMethod.POST)
public String post(Model model){
//processing
return "redirect:second";
}
#RequestMapping("second", method = RequestMethod.GET)
public String post(Model model){
//processing
return "myview";
}
}
And (Case 2) another way is
#Controller
#requestMapping("main")
Class ControllerClass{
#RequestMapping("first", method = RequestMethod.POST)
public String post(Model model){
//processing
return "redirect:/main/second";
}
#RequestMapping("second", method = RequestMethod.GET)
public String post(Model model){
//processing
return "myview";
}
}
Both ways work correctly, but I want to know which one is better to avoid future problems like one I faced recently:
When I forwarded the request to /main/first from another controller, I got a 404 error in code which is using Case 1.
As per Spring Documentation:
The redirect: prefix
While the use of RedirectView works fine, if the controller itself creates the RedirectView, there is no avoiding the fact that the controller is aware that a redirection is happening. This is really suboptimal and couples things too tightly. The controller should not really care about how the response gets handled. In general it should operate only in terms of view names that have been injected into it.
The special redirect: prefix allows you to accomplish this. If a view name is returned that has the prefix redirect:, the UrlBasedViewResolver (and all subclasses) will recognize this as a special indication that a redirect is needed. The rest of the view name will be treated as the redirect URL.
The net effect is the same as if the controller had returned a RedirectView, but now the controller itself can simply operate in terms of logical view names. A logical view name such as redirect:/myapp/some/resource will redirect relative to the current Servlet context, while a name such as redirect:http://myhost.com/some/arbitrary/path will redirect to an absolute URL.
Most Real time enterprise project prefers to use case 2 in all controllers they use,so that the inter calling between different controller is fine.

Spring MVC Controller: Redirect without parameters being added to my url

I'm trying to redirect without parameters being added to my URL.
#Controller
...
public class SomeController
{
...
#RequestMapping("save/")
public String doSave(...)
{
...
return "redirect:/success/";
}
#RequestMapping("success/")
public String doSuccess(...)
{
...
return "success";
}
After a redirect my url looks always something like this: .../success/?param1=xxx&param2=xxx.
Since I want my URLs to be kind of RESTful and I never need the params after a redirect, I don't want them to be added on a redirect.
Any ideas how to get rid of them?
In Spring 3.1 a preferred way to control this behaviour is to add a RedirectAttributes parameter to your method:
#RequestMapping("save/")
public String doSave(..., RedirectAttributes ra)
{
...
return "redirect:/success/";
}
It disables addition of attributes by default and allows you to control which attributes to add explicitly.
In previous versions of Spring it was more complicated.
In Spring 3.1 use option ignoreDefaultModelOnRedirect to disable automatically adding model attributes to a redirect:
<mvc:annotation-driven ignoreDefaultModelOnRedirect="true" />
Adding RedirectAttributes parameter doesn't work for me (may be because my HandlerInterceptorAdapter adds some stuff to model), but this approach does (thanks to #reallynic's comment):
#RequestMapping("save/")
public View doSave(...)
{
...
RedirectView redirect = new RedirectView("/success/");
redirect.setExposeModelAttributes(false);
return redirect;
}
In Spring 4 there is a way to do this with java config, using annotations. I'm sharing it in case anyone needs it as I needed it.
On the config class that extends WebMvcConfigurerAdapter, you need to add:
#Autowired
private RequestMappingHandlerAdapter requestMappingHandlerAdapter;
#PostConstruct
public void init() {
requestMappingHandlerAdapter.setIgnoreDefaultModelOnRedirect(true);
}
With this, you do not need to use RedirectAttributes, and it is an equivalent in java config to Matroskin's answer.
If you're using Spring 3.1, you can use Flash Scope,
otherwise you can take a look at the method used in the most voted (not accepted) answer here:
Spring MVC Controller redirect using URL parameters instead of in response
EDIT:
Nice article for 3.1 users:
http://www.tikalk.com/java/redirectattributes-new-feature-spring-mvc-31
Workaround for non-3.1 users:
Spring MVC custom scope bean
Try this:
public ModelAndView getRequest(HttpServletRequest req, Locale locale, Model model) {
***model.asMap().clear();*** // This clear parameters in url
final ModelAndView mav = new ModelAndView("redirect:/test");
return mav;
}

Categories