I created a RestController which look like this :
#RestController
public class GreetingController {
#RequestMapping(value = "/greetings", method = RequestMethod.GET)
public Mono<Greeting> greeting(HttpServletRequest request) {
return Mono.just(new Greeting("Hello..." + request.toString()));
}
}
Unfortunately when I try to hit the "greetings" endpoint I get an exception :
java.lang.IllegalStateException: No resolver for argument [0] of type
[org.apache.catalina.servlet4preview.http.HttpServletRequest]
I am using
compile('org.springframework.boot.experimental:spring-boot-starter-web-reactive')
How to fix this ?
Link to full stack-trace. Link to build.gradle
----------EDIT----------
Using the interface. Now getting :
java.lang.IllegalStateException: No resolver for argument [0] of type
[javax.servlet.http.HttpServletRequest] on method (rest is same)
You should never use the Servlet API in a Spring Reactive Web application. This is not supported and this is making your app container-dependent, whereas Spring Web Reactive can work with non-Servlet runtimes such as Netty.
Instead you should use the HTTP API provided by Spring; here's your code sample with a few changes:
import org.springframework.http.server.reactive.ServletServerHttpRequest;
#RestController
public class GreetingController {
#GetMapping("/greetings")
public Mono<Greeting> greeting(ServerHttpRequest request) {
return Mono.just(new Greeting("Hello..." + request.getURI().toString()));
}
}
You can inject either ServerWebExchange or directly ServerHttpRequest / ServerHttpResponse.
I went deep into the call hierarchy and found that there is this class
InvocableHandlerMethod, in package org.springframework.web.reactive.result.method
, which has :
private List<HandlerMethodArgumentResolver> resolvers = new ArrayList<>();
There is a resolveArguments() method in this class, which is called to "resolve the arguments". Unfortunately there is no resolver for
javax.servlet.http.HttpServletRequest in this list.
There is however a ServerWebExchangeArgumentResolver which is able to resolve ServletServerHttpRequest, from this I can extract the HttpServletRequest. Yeaaa....
So the endpoint looks like :
#RequestMapping(value = "/greetings", method = RequestMethod.GET)
public Mono<Greeting> greeting(ServletServerHttpRequest servletServerHttpRequest) {
HttpServletRequest httpServletRequest = servletServerHttpRequest.getServletRequest();
.
.
.
}
It is important that the ServletServerHttpRequest be from the package org.springframework.http.server.reactive
Related
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
I'm using OpenApi for Spring Boot application and I have authorization logic with JWT. The authorization request at /api/v1/login is intercepted and JSON is returned with the user token:
{
"Bearer": "token for user"
}
Security implementation responsible for capturing logins:
#Component
#RequiredArgsConstructor
class RestAuthenticationSuccessHandler extends SimpleUrlAuthenticationSuccessHandler {
#Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
Authentication authentication) throws IOException {
// handler returns body JSON with JWT
}
}
#Configuration
class SecurityConfig extends WebSecurityConfigurerAdapter {
// ...
JsonObjectAuthenticationFilter authenticationFilter() throws Exception {
var authFilter = new JsonObjectAuthenticationFilter();
authFilter.setAuthenticationSuccessHandler(restAuthenticationSuccessHandler );
authFilter.setAuthenticationFailureHandler(RestAuthenticationFailureHandler );
authFilter.setAuthenticationManager(super.authenticationManager());
authFilter.setFilterProcessesUrl("/api/v1/login"); // <- custom login URL
return authFilter;
}
}
It works fine, I don't have to put a separate /api/v1/login endpoint in the controller, so it is not taken into account when creating OpenAPI documentation. However, I want to have this endpoint documented and accessible from there as follows:
My first idea was to just create an interface to add appropriate annotations (assume BearerToken and AuthCredentials are my tranfer objects in correct format):
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
#RestController
#RequestMapping("/api/v1/login")
#Tag(name = "login")
interface LoginController {
#PostMapping
BearerToken login(#RequestBody AuthCredentials authCredentials);
}
However Spring does not register the interface as a beana, an implementation has yet to be provided, so OpenAPI does not add an entry to the documentation. So I turned the interface into a normal class:
#RestController
#RequestMapping("/api/v1/login")
#Tag(name = "login")
class LoginController {
#PostMapping
public BearerToken login(#RequestBody AuthCredentials authCredentials){
return new BearerToken();
}
}
Documentation is generated correctly, but I have a problem with this method. The implementation of the login() method suggests a completely different behavior than what actually takes place underneath in onAuthenticationSuccess().
Therefore, I am looking for a different way to achieve the desired effect.
As per OpenAPI implementation, there is a class OpenAPIService which has a build() method that does the following:
this.mappingsMap.putAll(this.context.getBeansWithAnnotation(RestController.class));
this.mappingsMap.putAll(this.context.getBeansWithAnnotation(RequestMapping.class));
this.mappingsMap.putAll(this.context.getBeansWithAnnotation(Controller.class));
The reason as to why what you want to achieve is not possible is that OpenAPI library relies on Spring annotations that I provided above and has no knowledge of custom ant matcher paths like the one you added:
JsonObjectAuthenticationFilter authenticationFilter() throws Exception {
var authFilter = new JsonObjectAuthenticationFilter();
authFilter.setAuthenticationSuccessHandler(restAuthenticationSuccessHandler );
authFilter.setAuthenticationFailureHandler(RestAuthenticationFailureHandler );
authFilter.setAuthenticationManager(super.authenticationManager());
authFilter.setFilterProcessesUrl("/api/v1/login"); // <- custom login URL
return authFilter;
}
Usually in production-ready applications developers write their own /authenticate API that checks username + password pair against a datasource (mySQL/postgreSQL/other).
This /authenticate API would be whitelisted so that security is not required to attempt login (some people store number of attempts per IP in Redis in case they want to block people from bruteforcing the password).
All other APIs would have to go through public class JwtAuthenticationFilter extends AuthenticationFilter { which simply validates the token and allows request to continue (if token is valid).
If you need more hints let me know.
I m working on this project where am I trying to access two different controllers with the same URI. After trying to run it I m getting a BeanCreationException.
So it happens that I m getting an Error while creating a bean.
I hope there is a way to deal with this.
The error message that I m getting:
org.springframework.beans.factory.BeanCreationException: Error
creating bean with name 'requestMappingHandlerMapping' defined in
class path resource
[org/springframework/boot/autoconfigure/web/WebMvcAutoConfiguration$EnableWebMvcConfiguration.class]:
Invocation of init method failed; nested exception is
java.lang.IllegalStateException: Ambiguous mapping. Cannot map
'userController' method public java.lang.String
com.javalanguagezone.interviewtwitter.controller.UserController.overview(java.security.Principal,org.springframework.ui.Model)
to {[/overview],methods=[GET]}: There is already 'tweetController'
bean method
I m using as well Thymleaf for this project. The URI that I m for those two controllers: http://localhost:8080/api/overview.The two controllers are providing my Thymleaf page with information that i have to present at the same time with the URI just mentioned. With this, I m calling both controllers but I m getting a previously mentioned error.
The first controller class(TweetController):
#Controller
#Slf4j
public class TweetController {
private TweetService tweetService;
public TweetController(TweetService tweetService) {
this.tweetService = tweetService;
}
#GetMapping( "/overview")
public String tweetsFromUser(Principal principal, Model model) {
model.addAttribute("tweets",tweetService.tweetsFromUser(principal).size());
return "api/index";
}
}
The second controller class is:
#Controller
public class UserController {
private UserService userService;
public UserController(UserService userService) {
this.userService = userService;
}
#GetMapping("/followers")
public String followers(Principal principal) {
userService.getUsersFollowers(principal);
return "api/index";
}
#GetMapping("/following")
public int following(Principal principal) {
return userService.getUsersFollowing(principal);
}
#GetMapping("/overview")
public String overview(Principal principal, Model model){
model.addAttribute("followers",userService.getUsersFollowers(principal));
model.addAttribute("following",userService.getUsersFollowing(principal));
return "api/index";
} }
My question: is there a way a fix it or I to look for another way around? I m relatively a newbie with Spring. Thank you for your help in advanced.
according to REST conventions, you should not have /overview, but /user/overview. You can set it by supplying #RequestMapping("/user") in your userController.
In the same way you would have "/tweet/overview" endpoint.
#Controller
#Slf4j
#RequestMapping("/tweet")
public class TweetController {
doing it any other way is against conventions, against Spring Rules and probably means you're doing something wrong. Spring does not allow two methods with same uri because it does not know which method exactly you would want to call.
upd: if you need logic, you can send parameters to GET: /overview?customParam=user
#GetMapping( "/overview")
public String tweetsFromUser(#RequestParam(value="customParam") String
param, Principal principal, Model model) {
// logic checking customParam...
But that CANNOT be in two different controllers. The only way to specify the controller, is through base-uri and parameters are not part of it.
Spring determines the method by 2 parameters: Mapping and HTTP method. There is no way, unless you modify Spring manually, to allow 3rd parameter in this case. Also, there is no 3rd parameter.
Alternatively, you can have 3rd controller with Mapping, that calls other 2 controllers when "/overview" endpoint is triggered. In that case, you need to remove the mapping from tweet and user - controllers.
I am using #RepositoryResource annotation on my Reposioptory interface with this code:
#RepositoryRestResource(collectionResourceRel = "rest", path = "rest")
public interface HoliDayRepository extends CrudRepository<HoliDayEntity, Integer> {
HoliDayEntity findOne(Integer id);
}
and i have alsoe added RequestMapping("rest) in controller class
#RestController
#RequestMapping("/rest")
public class DayController {}
but when i start spring boot application and try this link :http://localhost:8080/rest i got 404 error also while building application i have ResourceNotFoumd exceptions how should i manage these errors?
with spring boot you don't need to create your own controller; also make sure your web application mapping is different to the one you use for spring data, for example you can set in application.properties spring.data.rest.base-path: /api
Have a look at this example:
public interface PersonRepository extends JpaRepository<Person, UUID> {
List<Person> findByAddress(String address);
}
with just this code you should able to access spring data repositories here: http://localhost:8080/api and the person endpoint here http://localhost:8080/api/person
Have a look at this tutorial: https://spring.io/guides/tutorials/react-and-spring-data-rest/ or this example: https://github.com/Paizo/SpringBootCamelStreamsExample
You need a method which should be called when you hit your endpoint.
try below and also check spring example:
https://spring.io/guides/tutorials/bookmarks/
#Autowired
private HoliDayRepository holiDayRepository; //your repository to execute the query
#GetMapping(value = "/{id}")//you can use #RequestMapping(method = RequestMethod.GET, value = "/{holida}")
public ResponseEntity<HoliDayEntity > getHolidayById(#PathVariable("id") Integer id) {
HoliDayEntity holiDayEntityresponse = productOperations.getProductById(id);
return new ResponseEntity<>(holiDayEntityresponse , HttpStatus.OK);
}
EDIT:
As pointed by Gimby, this is not applicable when #RepositoryRestResource is used. Both the code and the tutorial attached are refering to creating new REST service by creating the controller
I'm trying to implement a controller method similar to how is documented in the latest Gosling release train of Spring Data that supports QueryDsl. I've implemented the controller as shown in the example in the docs at http://docs.spring.io/spring-data/jpa/docs/1.9.0.RELEASE/reference/html/#core.web.type-safe. Everything compiles and when I start the application (using Spring Boot 1.2.5.RELEASE), everything starts fine.
However, when I try to call my rest endpoint, I always get the following exception:
org.springframework.beans.BeanInstantiationException: Failed to instantiate [com.mysema.query.types.Predicate]: Specified class is an interface
at org.springframework.beans.BeanUtils.instantiateClass(BeanUtils.java:101)
at org.springframework.web.method.annotation.ModelAttributeMethodProcessor.createAttribute(ModelAttributeMethodProcessor.java:137)
at org.springframework.web.servlet.mvc.method.annotation.ServletModelAttributeMethodProcessor.createAttribute(ServletModelAttributeMethodProcessor.java:80)
My guess is that the QuerydslPredicateArgumentResolver is not being applied to the request, and thus the exception. But I see that the QuerydslPredicateArgumentResolver is registered as a bean when I query the Spring Boot manage endpoint /manage/beans. I have also ensured that #EnableSpringDataWebSupport is on my #Configuration class to no effect.
I have the controller annotated with #BasePathAwareController, since I'm using this with Spring Data REST and I want the methods to be under a similar path as the ones that Spring Data REST exposes. I also tried using #RepositoryRestController, but that didn't seem to matter. However, when using #RestController and putting it under a path that was different then the base path that Spring Data REST is using, things worked. So the question is, should it work?
The entire controller right now is:
#RestController
#RequestMapping(value = "/query")
public class AvailController
{
private final AvailRepository repo;
#Autowired
public AvailController(AvailRepository repository)
{
this.repo = repository;
}
#RequestMapping(value = "/avails", method = GET)
public #ResponseBody Page<Avail> getAvails(Model model,
#QuerydslPredicate(root = Avail.class) Predicate predicate,
Pageable pageable,
#RequestParam MultiValueMap<String, String> parameters)
{
return repo.findAll(predicate, pageable);
}
}
I had the same problem with instantiation of Predicate. In the example:
#Controller
#RequiredArgsConstructor(onConstructor = #__(#Autowired) )
class UserController {
private final UserRepository repository;
#RequestMapping(value = "/", method = RequestMethod.GET)
String index(Model model, //
#QuerydslPredicate(root = User.class) Predicate predicate, //
#PageableDefault(sort = { "lastname", "firstname" }) Pageable pageable, //
#RequestParam MultiValueMap<String, String> parameters) {
(...)
(https://github.com/spring-projects/spring-data-examples/blob/master/web/querydsl/src/main/java/example/users/web/UserController.java#L42 ) is using just #Controller and I was using #RepositoryRestController, that seems to be the reason. #RestController also works for me.
I created https://jira.spring.io/browse/DATAREST-838
I also had this issue when trying to implement a custom controller that mimics the returned value as Spring Data REST. I wanted to inject QuerydslPredicate to the controller method and got the annoying 'BeanInstantiationException'.
I found a work around for this by adding the following configuration file to my application:
#Configuration
#Order(Ordered.HIGHEST_PRECEDENCE )
public class MvcConfig extends WebMvcConfigurerAdapter {
#Autowired
#Qualifier("repositoryExporterHandlerAdapter")
RequestMappingHandlerAdapter repositoryExporterHandlerAdapter;
#Override
public void addArgumentResolvers(
List<HandlerMethodArgumentResolver> argumentResolvers) {
List<HandlerMethodArgumentResolver> customArgumentResolvers = repositoryExporterHandlerAdapter.getCustomArgumentResolvers();
argumentResolvers.addAll(customArgumentResolvers);
}
}
See here for reference: https://jira.spring.io/browse/DATAREST-657