Inject UriComponentsBuilder or use static ServletUriComponentsBuilder? - java

Does it make any difference if I inject UriComponentsBuilder as parameter into the #RestController method, or if I access the uri from static ServletUriComponentsBuilder context (fetching it from RequestContextHolder underneath)?
#RestController
public class MyServlet {
#GetMapping
public List<Person> persons(UriComponentsBuilder uri) {
System.out.println(uri.build().toUriString());
System.out.println(ServletUriComponentsBuilder.fromCurrentContextPath().toUriString());
}
}

The static factory method that Spring calls behind the scene to provide UriComponentsBuilder as method parameter to your class is fromServletMapping
public static ServletUriComponentsBuilder fromServletMapping(HttpServletRequest request) {
ServletUriComponentsBuilder builder = fromContextPath(request);
if (StringUtils.hasText(UrlPathHelper.defaultInstance.getPathWithinServletMapping(request))) {
builder.path(request.getServletPath());
}
return builder;
}
And here is the code for fromContextPath static factory method that you called: (actually fromCurrentContextPath method calls this method with the current request)
public static ServletUriComponentsBuilder fromContextPath(HttpServletRequest request) {
ServletUriComponentsBuilder builder = initFromRequest(request);
builder.replacePath(request.getContextPath());
return builder;
}
So you can see their result are different when you have a request that contains servlet-mapping: In most Spring applications we have a single servlet and the output of these two are the same.
But what if our request belongs to a servlet with servlet-path: first/?
Imagine the following controller:
#RestController
public class MyServlet {
#GetMapping("test")
public void persons(UriComponentsBuilder uri) {
System.out.println(
uri.path("/second/third").build().toUriString());
System.out.println(
ServletUriComponentsBuilder.fromCurrentContextPath().path("/second/third").build().toUriString());
}
}
When you send a request to this path Your output will be like this:
http://localhost:8081/first/second/third
http://localhost:8081/second/third
So the first one includes the servlet-path but the second one ignores it.
To have the same result you can use fromCurrentServletMapping static fatory method.

Related

Junit Test case Spring boot controller returning null value in ResponseEntity

In my project I am creating a rest endpoint which is responsible to consume grpc service response.
Now I want to write testcase for the controller class but the Junit test cases returning me null null values .
MyController.java
#RestController
#RequestMapping("/v1")
public class MyController {
#Autowired
private MyConsumerService consumer;
public MyController(MyConsumerService consumer) {
this.consumer=consumer;
}
#GetMapping("/data")
public ResponseEntity<Records> getData(#RequestParam("data") String data) {
Records records = consumer.getGrpcResponse(data);
return new ResponseEntity<>(Records, HttpStatus.OK);
}
}
MyConsumerServiceImpl.java:
public class MyConsumerServiceImpl implements MyConsumerService {
#GrpcClient("user-grpc-service")
private userGrpc.userBlockingStub stub;
#Override
public Records getGrpcResponse(data) {
Records records = new Records();
UserRequest request = UserRequest.newBuilder()
.setUserName(data)
.build();
APIResponse response = stub.userRequest(request);
records.setUserName(response.getUserName());
return records;
}
}
MyControllerTest.java:
#ExtendWith(MockitoExtension.class)
public class MyControllerTest {
private MyConsumerService mockerService;
private MyController controller;
#BeforeEach
void setup(){
mockerService = mock(MyConsumerService.class);
controller = new MyController(mockerService);
}
#Test
public void shouldGet(){
final var data="Hello";
when(mockerService.getGrpcResponse(data)).thenReturn(new Records());
final var responseEntity=controller.getData(data);
assertEquals(responseEntity.getBody(),new Records());
}
}
responseEntity.getBody() is returning null.
Normal flow is working fine but with Junit when I am mocking the client service call, it is returning null.
I am confused why always it is returning null.
Any idea where I am getting wrong.
you have not added when then statement for service.getData(),
and below stubbing have not been called any where
when(mockerService.getGrpcResponse(data)).thenReturn(new Records());
use when then to mock service.getData() like this,
when(mockerService.getData(data)).thenReturn(new Records());
annotate this 'MyControllerTest' class with #WebMvcTest(MyController.class) and the rerun it will work, otherwise its not able to mock actual controller class.

Getting 406 Could not find acceptable representation /Spring JSON Test. How to ignore .htm extension in tests?

Controller needs uses .htm extensions for all handlers, including JSON REST endpoints. How should I test for REST endpoints?
Problem:
I cannot disable suffix interpretation and I am getting 406 "Could not find acceptable representation"
Tried attempts:
I reviewed posts on stackoverflow related to 406, but could not find relevant one to the case where 'htm' suffix is used in tests. When you remove '.htm' suffix from both Controller and Test - the test is passing.
Here is controller with /changePassword.htm endpoint:
#Controller
public class MainController {
public static class ResultBean {
private final String result;
public String getResult() {
return result;
}
public ResultBean(String result) {
this.result = result;
}
}
#RequestMapping(value="/changePassword.htm", method= RequestMethod.POST, produces = { "application/json" })
public #ResponseBody ResultBean changePassword (
#RequestParam("username") String username, #RequestParam("password") String password) {
return new ResultBean("OK");
}
}
And here is the test with configuration:
#RunWith(SpringJUnit4ClassRunner.class)
#WebAppConfiguration
#ContextConfiguration(classes = { HomeControllerTest.Config.class })
public class HomeControllerTest {
#InjectMocks
private MainController controller = new MainController();
private MockMvc mvc;
#Configuration
#EnableWebMvc
public static class Config extends WebMvcConfigurerAdapter {
#Override
public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
configurer.favorPathExtension(false)
.favorParameter(true)
.parameterName("mediaType")
.ignoreUnknownPathExtensions(true)
.ignoreAcceptHeader(false)
.useJaf(false)
.defaultContentType(MediaType.APPLICATION_JSON);
}
#Override
public void configurePathMatch(PathMatchConfigurer configurer) {
configurer.setUseSuffixPatternMatch(false);
}
}
#Before
public void setup() {
MockitoAnnotations.initMocks(this);
mvc = MockMvcBuilders.standaloneSetup(controller)
.build();
}
#Test
public void shouldPassChangePasswordBean() throws Exception {
mvc.perform(post("/changePassword.htm")
.accept("*/*")
.param("username", "example")
.param("password", "abcdef")
)
.andExpect(status().isOk()); // Test produces 406 instead of 200
}
}
Any idea?
On newer version of Spring (4+ I think), mime type is determined from suffix first.
So If you use a .htm suffix, Spring will default to produce HTML even if you don't want to.
One way to bypass this is to use a filter that rewrite URL. For instance tuckey URL rewriter filter
With this, you can set some rules like:
/my/page/that/return/json.htm is rewriten to /my/page/that/return/json so that Spring can produce data according to the Accept header.
with Spring 5, try changing your URL of your web service to .json! that is the right fix. great details here http://stick2code.blogspot.com/2014/03/solved-orgspringframeworkwebhttpmediaty.html

Spring PathVariable used outside

I have Spring rest controller that provides operations on Project entity. All methods use same entity accessing code. I don't want to copy&paste #PathVariable parameters in all methods, so I've made something like this.
#RestController
#RequestMapping("/projects/{userName}/{projectName}")
public class ProjectController {
#Autowired
ProjectService projectService;
#Autowired
protected HttpServletRequest context;
protected Project project() {
// get {userName} and {projectName} path variables from request string
String[] split = context.getPathInfo().split("/");
return projectService.getProject(split[2], split[3]);
}
#RequestMapping(method = GET)
public Project get() {
return project();
}
#RequestMapping(method = GET, value = "/doSomething")
public void doSomething() {
Project project = project();
// do something with project
}
// more #RequestMapping methods using project()
}
Is it possible to autowire path variables into controller by annotation so I don't have to split request path and get parts of it from request string for project() method?
In order to do custom binding from request you've got to implement your own HandlerMethodArgumentResolver (it's a trivial example without checking if path variables actually exist and it's also global, so every time you will try to bind to Project class this argument resolver will be used):
class ProjectArgumentResolver implements HandlerMethodArgumentResolver {
#Override
public boolean supportsParameter(MethodParameter methodParameter) {
return methodParameter.getParameterType().equals(Project.class);
}
#Override
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
Map<String, String> uriTemplateVars = (Map<String, String>) webRequest.getAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE, RequestAttributes.SCOPE_REQUEST);
return getProject(uriTemplateVars.get("userName"), uriTemplateVars.get("projectName"));
}
private Project getProject(String userName, String projectName) {
// replace with your custom Project loading logic
Project project = new Project(userName, projectName);
return project;
}
}
and register it using WebMvcConfigurerAdapter:
#Component
public class CustomWebMvcConfigurerAdapter extends WebMvcConfigurerAdapter {
#Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
argumentResolvers.add(new ProjectArgumentResolver());
}
}
In your controller you have to put Project as a method argument, but do not annotate it with #PathVariable:
#Controller
#RequestMapping("/projects/{userName}/{projectName}")
public class HomeController {
#RequestMapping(method = RequestMethod.GET)
public void index(Project project){
// do something
}
}

Write Junit test cases for rest web service by calling it by path

I have a jersey web service. I want to write test cases for it.
My service is
#Path(value = "/mock")
#Component
public class MockService
{
private static Log log = LogFactory.getLog(MockService.class);
#POST
#Path(value = "/{mockrequest:ABCD}")
#Produces(MediaType.JSON)
public Response mockOKResponse(#Context ContainerRequestContext request, #PathParam("mockrequest") EnumMockService mockService)
{
return Response.ok().build();
}
#GET
#Path(value = "/{mockrequest}")
#Produces("application/pdf")
public Response mockProducesPDF(#Context ContainerRequestContext request, #PathParam("mockrequest") EnumMockService mockService)
{
MockAccountTable testccount = jdbcTestAcountDao.getTestAccountResponseBlob(mockService.toString());
byte[] responseBlob = testccount != null ? testccount.getResponseBlob() : null;
return Response.ok(responseBlob).build();
}
}
I am planing to write test case for this.Which calls specific method based on the specific reuest.
#RunWith(MockitoJUnitRunner.class)
public class TestMockService
extends TestCaseSrs
{
private MockService mockService = new MockService();
#Override
#Before
public void prepare()
throws Exception
{
MockitoAnnotations.initMocks(this);
}
#Test
public void testGetIncreasedHardwares()
{
//dynamically call method based on request
}
}
I am not sure how to set request type here to call method accordingly rather than calling method directly.
It would be great if some one can help me with the approach.

How do I design a generic Response builder / RESTful Web Service using Spring MVC?

Trying to build a RESTful web service using Spring MVC.
The controller should return specific Java types, but the response body must be a generic envelope. How can this be done?
The following sections of code are what I have so far:
Controller method:
#Controller
#RequestMapping(value = "/mycontroller")
public class MyController {
public ServiceDetails getServiceDetails() {
return new ServiceDetails("MyService");
}
}
Response envelope:
public class Response<T> {
private String message;
private T responseBody;
}
ServiceDetails code:
public class ServiceDetails {
private String serviceName;
public ServiceDetails(String serviceName) {
this.serviceName = serviceName;
}
}
Intended final response to clients should appear as:
{
"message" : "Operation OK"
"responseBody" : {
"serviceName" : "MyService"
}
}
What you can do is having a MyRestController just wrapping the result in a Response like this:
#Controller
#RequestMapping(value = "/mycontroller")
public class MyRestController {
#Autowired
private MyController myController;
#RequestMapping(value = "/details")
public #ResponseBody Response<ServiceDetails> getServiceDetails() {
return new Response(myController.getServiceDetails(),"Operation OK");
}
}
This solution keep your original MyController independant from your REST code. It seems you need to include Jackson in your classpath so that Spring will auto-magically serialize to JSON (see this for details)
EDIT
It seems you need something more generic... so here is a suggestion.
#Controller
#RequestMapping(value = "/mycontroller")
public class MyGenericRestController {
#Autowired
private MyController myController;
//this will match all "/myController/*"
#RequestMapping(value = "/{operation}")
public #ResponseBody Response getGenericOperation(String #PathVariable operation) {
Method operationToInvoke = findMethodWithRequestMapping(operation);
Object responseBody = null;
try{
responseBody = operationToInvoke.invoke(myController);
}catch(Exception e){
e.printStackTrace();
return new Response(null,"operation failed");
}
return new Response(responseBody ,"Operation OK");
}
private Method findMethodWithRequestMapping(String operation){
//TODO
//This method will use reflection to find a method annotated
//#RequestMapping(value=<operation>)
//in myController
return ...
}
}
And keep your original "myController" almost as it was:
#Controller
public class MyController {
//this method is not expected to be called directly by spring MVC
#RequestMapping(value = "/details")
public ServiceDetails getServiceDetails() {
return new ServiceDetails("MyService");
}
}
Major issue with this : the #RequestMapping in MyController need probably to be replaced by some custom annotation (and adapt findMethodWithRequestMapping to perform introspection on this custom annotation).
By default, Spring MVC uses org.springframework.http.converter.json.MappingJacksonHttpMessageConverter to serialize/deserialize JSON through Jackson.
I'm not sure if it's a great idea, but one way of solving your problem is to extend this class, and override the writeInternal method:
public class CustomMappingJacksonHttpMessageConverter extends MappingJacksonHttpMessageConverter {
#Override
protected void writeInternal(Object object, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException {
super.writeInternal(new Response(object, "Operation OK"), outputMessage);
}
}
If you're using XML configuration, you could enable the custom converter like this:
<mvc:annotation-driven>
<mvc:message-converters>
<bean class="path.to.CustomMappingJacksonHttpMessageConverter">
</mvc:message-converters>
</mvc:annotation-driven>
Try the below solution.
Create a separate class such ResponseEnvelop. It must implement ResponseBodyAdvice interface.
Annotate the above class with #ControllerAdvice
Autowire HttpServletRequest
Override methods according to your requirement. Take reference from below.
#Override
public boolean supports(
MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
if (httpServletRequest.getRequestURI().startsWith("/api")) {
return true;
}
return false;
}
#Override
public Object beforeBodyWrite(
Object body,
MethodParameter returnType,
MediaType selectedContentType,
Class<? extends HttpMessageConverter<?>> converterType,
ServerHttpRequest request,
ServerHttpResponse response) {
if (((ServletServerHttpResponse) response).getServletResponse().getStatus()
== HttpStatus.OK.value()
|| ((ServletServerHttpResponse) response).getServletResponse().getStatus()
== HttpStatus.CREATED.value()) {
return new EntityResponse(Constants.SUCCESS, body);
}
return body;
}

Categories

Resources