How to solve Spring swagger Ambiguous mapping? - spring

In my spring controller class i have below two methods
#GetMapping(value = "/published_messages", produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<String> handleEmptyQueryParam() throws Exception
{
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body("Invalid Request , No Request Param received in the request");
}
#GetMapping(value = "/published_messages", produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<String> getEdiDetailsByusername(#RequestParam(required=false, value="username") String username) throws Exception
{
List<String> userList = userService.getUserList(username);
return isAValidResponse(userList);
}
with this when starting the app , got below exception
Caused by: java.lang.IllegalStateException: Ambiguous mapping. Cannot map 'UserAppController' method
public org.springframework.http.ResponseEntity<java.lang.String> com.rby.trans.controller.UserAppController.getEdiDetailsByusername(java.lang.String) throws java.lang.Exception
to {[/published_messages],methods=[GET],produces=[application/json]}: There is already 'UserAppController' bean method
public org.springframework.http.ResponseEntity<java.lang.String> com.tgt.trans.controller.UserAppController.handleEmptyQueryParam() throws java.lang.Exception mapped.
at org.springframework.web.servlet.handler.AbstractHandlerMethodMapping$MappingRegistry.assertUniqueMethodMapping(AbstractHandlerMethodMapping.java:576)
at org.springframework.web.servlet.handler.AbstractHandlerMethodMapping$MappingRegistry.register(AbstractHandlerMethodMapping.java:540)
at org.springframework.web.servlet.handler.AbstractHandlerMethodMapping.registerHandlerMethod(AbstractHandlerMethodMapping.java:264)
at org.springframework.web.servlet.handler.AbstractHandlerMethodMapping.detectHandlerMethods(AbstractHandlerMethodMapping.java:250)
at org.springframework.web.servlet.handler.AbstractHandlerMethodMapping.initHandlerMethods(AbstractHandlerMethodMapping.java:214)
at org.springframework.web.servlet.handler.AbstractHandlerMethodMapping.afterPropertiesSet(AbstractHandlerMethodMapping.java:184)
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping.afterPropertiesSet(RequestMappingHandlerMapping.java:127)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.invokeInitMethods(AbstractAutowireCapableBeanFactory.java:1642)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1579)
... 50 common frames omitted
seems something issue with swagger , which we have below dependencies in our build file
compile group: 'io.springfox', name: 'springfox-swagger2', version: '2.4.0'
compile group: 'io.springfox', name: 'springfox-swagger-ui', version: '2.4.0'
can anyone suggest what to in this case ,
whatever am following and doing in the rest controller is correct only and it is possible to do that in spring , then why swagger giving errors on that ?
Note : I got the answer , we need to use params attribute in the #GetMapping , it solved my issue .
Thanks to all .

I had a similar error some time ago when I was creating a demo app during a presentation. The problem why this issue occurred was because in one of my endpoints mappings (e.g. other #GetMapping) I used 'name' instead of 'value' attribute so the problem might be in the part of the code which you did not provide.

We had the same error specifically for SpringBoot 2.5, but with the following setup
Setup:
Controller Api:
#Api(value = "items", description = "the items API")
public interface ItemsApi {
#GetMapping(value = "items/{itemId}",
produces = {"application/json"})
Item getItem(#ApiParam(value = "The item Id number in the app, a UUID", required = true) #PathVariable("itemId") UUID itemId,
HttpServletRequest httpServletRequest);
}
Feign Client Api:
#FeignClient(value = "Foo-v1-0-Server", contextId = "Foo-v1-0-Server-ItemClient")
#RequestMapping("/")
public interface FooFeignClient {
#ApiOperation(value = "Gets a list of item metadata")
#ApiResponses(value = {
#ApiResponse(code = 200, message = "Success", responseContainer = "List", response = FooItemResponse.class),
#ApiResponse(code = 401, message = "Unauthorized"),
#ApiResponse(code = 404, message = "Not Found"),
#ApiResponse(code = 500, message = "Failure")})
#RequestMapping(value = "items/{itemId}", method = RequestMethod.GET, produces = "application/json")
List<FooItemResponse> getDocumentList(#PathVariable("itemId") String itemId);
Error:
java.lang.IllegalStateException: Ambiguous mapping. Cannot map ‘com.contoso.foo.client.feign.FooFeignClient
Cause:
We tracked it down to feign client having the #RequestMapping annotation at the class level. This was causing it to be picked up for WebMvcRegistrations.
Resolution:
Remove the #RequestMapping annotation at the class level on the feign client and republish a new version.

Related

Unable to generate access token

I am getting exception while generating an Access Token using feign client. The same payload is working fine in the Postman.
MultiValueMap<String, Object> map = new LinkedMultiValueMap<>();
map.add("grant_type", "client_credentials");
map.add("client_id", "67881e5b-f5d5-4085-8762-c35b7b6aeede");
map.add("client_secret", "D-85Pg3wN63dmznxa-puB_89Po~o5CsKhA");
map.add("scope", "https://graph.microsoft.com/.default");
AccessTokenResponse openIdTokenResponse = graphAPILoginFeignClient.getAccessIdToken("5494cc2e-fb14-4a2d-bb5e-bf164d9141cf",request);
Feignclient code:
#FeignClient(name = "GraphAPILoginFeignClient", url = "${graphApiLoginUrl}")
public interface GraphAPILoginFeignClient {
#PostMapping(value = "/{tenantID}/oauth2/v2.0/token",consumes = MediaType.APPLICATION_JSON_VALUE)
AccessTokenResponse getAccessIdToken(#PathVariable("tenantID") String tenantID,
#RequestBody MultiValueMap<String, Object> request);
}
Exception:
{
"timestamp": "2021-01-27T17:30:34.456+00:00",
"message": "[400 Bad Request] during [POST] to [https://login.microsoftonline.com/5494cc2e-fb14-4a2d-bb5e-bf164d9141cf/oauth2/v2.0/token] [GraphAPILoginFeignClient#getAccessIdToken(String,AuthorizationTokenRequest)]:
[{\"error\":\"invalid_request\",
\"error_description\":\"AADSTS900144: The request body must contain the
following parameter: 'grant_type'.\\r\\n
Trace ID: b8ef5f37-95f7-4427-8f0e-146a34b65000\\r\\n
Correlation ID: ... (503 bytes)]","details": "uri=/accessmanagement/allusers"
}
Same request payload working from Postman:
I had the same problem and the same code.
I was able to successfully execute the code by replacing the class method MultiValueMap::put with MultiValueMap::set.
In the LinkedMultiValueMap class, these methods are implemented differently.

"Could not find acceptable representation" when testing spring download link with MockMvc

I have a controller that should allow downloading files with arbitrary content type:
#GetMapping(value="/download/{directory}/{name}",
consumes=MediaType.ALL_VALUE)
#Timed
public ResponseEntity<byte[]> downloadFile(#PathVariable String directory,
#PathVariable String name) {
log.debug("REST request to download File : {}/{}", directory, name);
byte[] content = "it works".getBytes();
HttpHeaders headers = new HttpHeaders();
headers.add(HttpHeaders.CONTENT_TYPE, "text/plain");
return new ResponseEntity<>(content, headers, HttpStatus.OK);
}
I want to test that in a unit test like this:
...
private MockMvc restFileMockMvc;
#Before
public void setup() {
MockitoAnnotations.initMocks(this);
final FileResource fileResource = new FileResource(fileService);
this.restFileMockMvc = MockMvcBuilders.standaloneSetup(fileResource)
.setCustomArgumentResolvers(pageableArgumentResolver)
.setControllerAdvice(exceptionTranslator)
.setConversionService(createFormattingConversionService())
.setMessageConverters(jacksonMessageConverter)
.setValidator(validator).build();
}
#Test
#Transactional
public void downloadFile() throws Exception {
String url = "/api/download/it/works.txt";
restFileMockMvc.perform(get(url).header(HttpHeaders.ACCEPT, "*/*"))
.andDo(MockMvcResultHandlers.print()) // Debugging only!
.andExpect(status().isOk());
}
But obviously, there is a problem with the content type, resp. the accept header. MockMvcResultHandlers.print() produces the following:
MockHttpServletRequest:
HTTP Method = GET
Request URI = /api/download/DIRDIR/NAMENAME
Parameters = {}
Headers = {Accept=[*/*]}
Body = <no character encoding set>
Session Attrs = {}
Handler:
Type = com.example.storage.web.rest.FileResource
Method = public org.springframework.http.ResponseEntity<byte[]> com.example.storage.web.rest.FileResource.downloadFile(java.lang.String,java.lang.String)
Async:
Async started = false
Async result = null
Resolved Exception:
Type = org.springframework.web.HttpMediaTypeNotAcceptableException
ModelAndView:
View name = null
View = null
Model = null
FlashMap:
Attributes = null
MockHttpServletResponse:
Status = 406
Error message = null
Headers = {Content-Type=[application/problem+json]}
Content type = application/problem+json
Body = {"type":"https://www.jhipster.tech/problem/problem-with-message","title":"Not Acceptable","status":406,"detail":"Could not find acceptable representation","path":"/api/download/DIRDIR/NAMENAME","message":"error.http.406"}
Forwarded URL = null
Redirected URL = null
Cookies = []
It looks like the request is sent with Accept: */*. What does Spring complain about then?
It could be an issue with your message converter, used in your test case. I too faced similar issue and resolved it by passing additional parameter in messageConverter for my mockMvc
this.restMockMvc = MockMvcBuilders.standaloneSetup(testResource)
.setCustomArgumentResolvers(pageableArgumentResolver)
.setControllerAdvice(exceptionTranslator)
.setMessageConverters(jacksonMessageConverter,new
ByteArrayHttpMessageConverter()).build();
You need to overload message converter property for MockMVC. for more info , relevant question
I was already using #SpringJUnitWebConfig(...) and included the #EnableWebMvc annotation to my imported Config. This seemed to add all the necessary converters. E.g.
#SpringJUnitWebConfig(MyTestConfig.class)
class MyTest {
#Inject
private WebApplicationContext wac;
private MockMvc mockMvc;
...
}
#EnableWebMvc
class MyTestConfig {
#Bean
...
}

Handling my custom exception in Spring MVC integration test

I have the following method in a controller class:
#PostMapping("employees")
#ResponseStatus(HttpStatus.CREATED)
public Employee addEmployee(#Valid #RequestBody Employee employee) {
try {
return employeeRepository.save(employee);
} catch (DataIntegrityViolationException e) {
e.printStackTrace();
Optional<Employee> existingEmployee = employeeRepository.findByTagId(employee.getTagId());
if (!existingEmployee.isPresent()) {
//The exception root cause was not due to a unique ID violation then
throw e;
}
throw new DuplicateEntryException(
"An employee named " + existingEmployee.get().getName() + " already uses RFID tagID " + existingEmployee.get().getTagId());
}
}
Where the Employee class has a string field called tagId which has a #NaturalId annotation on it. (Please ignore that there is no dedicated service layer, this is a small and simple app).
Here is my custom DuplicateEntryException:
#ResponseStatus(HttpStatus.CONFLICT)
public class DuplicateEntryException extends RuntimeException {
public DuplicateEntryException() {
super();
}
public DuplicateEntryException(String message) {
super(message);
}
public DuplicateEntryException(String message, Throwable cause) {
super(message, cause);
}
}
Thanks to the #ResponseStatus(HttpStatus.CONFLICT) line, when I manually test the method, I get the default spring boot REST message with the timestamp, status, error, message and path fields.
I'm still getting familiar with testing in Spring and I have this test:
#Test
public void _02addEmployee_whenDuplicateTagId_thenExceptionIsReturned() throws Exception {
Employee sampleEmployee = new Employee("tagId01", "John Doe");
System.out.println("Employees in the database: " + repository.findAll().size()); //prints 1
// #formatter:off
mvc.perform(post("/employees").contentType(MediaType.APPLICATION_JSON).content(JsonUtil.toJson(sampleEmployee)))
.andExpect(status().isConflict())
.andExpect(content().contentTypeCompatibleWith(MediaType.APPLICATION_JSON))
.andExpect(jsonPath("$.message").value("An employee named John Doe already uses RFID tagID tagId01"));
// #formatter:on
int employeeCount = repository.findAll().size();
Assert.assertEquals(1, employeeCount);
}
As you can guess, there is another test that runs first, called _01addEmployee_whenValidInput_thenCreateEmployee(), which inserts an employee with the same tagID, which is used in test #2. Test #1 passes, but test #2 does not, because the HTTP response looks like this:
MockHttpServletResponse:
Status = 409
Error message = null
Headers = {}
Content type = null
Body =
Forwarded URL = null
Redirected URL = null
Cookies = []
And in the console before the above response, I see this:
Resolved Exception:
Type = ai.aitia.rfid_employee.exception.DuplicateEntryException
So my 2nd test fails because java.lang.AssertionError: Content type not set.
What causes the different behaviour compared to the manual testing? Why isn't this returned?
{
"timestamp": "2019-01-03T09:47:33.371+0000",
"status": 409,
"error": "Conflict",
"message": "An employee named John Doe already uses RFID tagID tagId01",
"path": "/employees"
}
Update: I experienced the same thing with a different REST endpoint as well, where the test case resulted in my own ResourceNotFoundException, but the actual JSON error object was not received by the MockMvc object.
Update2: Here are my class level annotations for the test class:
#RunWith(SpringRunner.class)
#SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT, classes = RfidEmployeeApplication.class)
#AutoConfigureMockMvc
#AutoConfigureTestDatabase
#FixMethodOrder(MethodSorters.NAME_ASCENDING)
#TestPropertySource(locations = "classpath:application-test.properties")
org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController#error
fills full information for error response body, but for MockMvc it is not working. I just checked you can easily use in this case TestRestTemplate.
First just #Autowired private TestRestTemplate testRestTemplate; in test class.
and modify your test method for example:
ResponseEntity<String> response = testRestTemplate.postForEntity("/employees", sampleEmployee, String.class);
String message = com.jayway.jsonpath.JsonPath.read(response.getBody(), "$.message");
String expectedMessage = "An employee named John Doe already uses RFID tagID tagId01";
Assert.assertEquals(expectedMessage, message);
Assert.assertTrue(response.getStatusCode().is4xxClientError());
for example.

upgrading spring boot with groovy controller returns 406 causing HttpMediaTypeNotAcceptableException

I have a Groovy application that I am dealing with which is having some odd behavior when upgrading from spring-boot 1.3.0.RELEASE to 1.4.0.RELEASE. The controller always returns a 406 on any error and I am not sure what type of content it expects to return. The code is below:
SomeController.groovy:
#RestController
#RequestMapping('/some/mapping')
class SomeController extends AbstractController {
#Autowired
private SomeService someService
#RequestMapping(path = '/abc/{some_param}/some_action', method = RequestMethod.PUT, consumes = MediaType.TEXT_PLAIN_VALUE)
#ResponseStatus(HttpStatus.NO_CONTENT)
#PreAuthorize('isAuthenticated() && (principal.username == #username || principal.admin)')
void setValue(#PathVariable String some_param, #RequestBody String body_content) throws ValidationException, NotFoundException {
handleViolations(validate(AnObject, [some_param: some_param, body: body_content]))
try {
someService.setValue(some_param, body_content)
} catch(AlreadyExistsException e) {
throw new ValidationException([body: 'IN_USE'])
}
}
}
SomeControllerSpec.groovy < The test...
class AccountControllerSpec extends AbstractControllerSpec {
static final BASE_URL = 'http://localhost:8080/api/'
def client = new CustomRESTClient(BASE_URL)
// This test fails
def 'testing api'() {
//Expected 400 bad request but receiving a 406 not acceptable
client.put(
path: "/api/abc/fake_param/some_action",
// The body doesn't conform to the expectations of the API
body: 'blah',
contentType: MediaType.TEXT_PLAIN_VALUE
).status == HttpStatus.SC_BAD_REQUEST
// Exception thrown:
// INFO 22125 --- [tp1838490665-22] c.c.w.c.RestEndpointsConfiguration : org.springframework.web.HttpMediaTypeNotAcceptableException: Could not find acceptable representation
}
}
The Exception in the logs:
INFO 22125 --- [tp1838490665-22] c.c.w.c.RestEndpointsConfiguration : org.springframework.web.HttpMediaTypeNotAcceptableException: Could not find acceptable representation
I have tried many things including setting the expected header type:
client.setHeaders(accept: MediaType.TEXT_PLAIN_VALUE)
I have been trying various other things but to no avail. The exception persists.
Note: The action at the endpoint completes as expected.

ID Should Not Show Up for Model Schema with Swagger + Spring

I'm using Swagger2 with Springfox and Spring Boot. I have an endpoint defined like so:
#ApiOperation(value = "save", nickname = "Save Store")
#ApiResponses(value = {
#ApiResponse(code = 201, message = "Created"),
#ApiResponse(code = 401, message = "Unauthorized"),
#ApiResponse(code = 403, message = "Forbidden"),
#ApiResponse(code = 500, message = "Failure", response = ErrorResource.class)})
#RequestMapping(method = RequestMethod.POST, consumes = MediaType.APPLICATION_JSON_VALUE)
#ResponseStatus(HttpStatus.CREATED)
public void save(#Valid #RequestBody Store store, BindingResult bindingResult, HttpServletRequest request, HttpServletResponse response) {
if (bindingResult.hasErrors()) {
throw new InvalidRequestException("Invalid Store", bindingResult);
}
this.storeService.save(store);
response.setHeader("Location", request.getRequestURL().append("/").append(store.getId()).toString());
}
The generated API docs are showing the id of Store in the Model Schema. Technically, when creating a Store the JSON should not contain the id. I'm trying to figure out how to tell Swagger/Springfox to ignore the id but only for this endpoint.
You can hide a field from a model by annotating the property of the class with #ApiModelProperty and setting its hidden property to true.
import io.swagger.annotations.ApiModelProperty;
public class Store {
#ApiModelProperty(hidden = true)
private Long id;
}
Unfortunately, by doing so, you will hide the id field on every endpoint which uses the Store class as an input. Showing the field for another endpoint would require a separate class.

Resources