For some reason, my Spring controller is returning different responses if I access it via a browser or via my MockMVC test class. Could someone help me spot why?
First the controller method:
#RequestMapping(value = APPLICATIONS_ROOT, method = GET)
public HttpEntity<ApplicationsListResource> listApplications(#PageableDefault(page = DEFAULT_START,
size = DEFAULT_HITS_PER_PAGE) Pageable pageable) {
Page<Application> applications = applicationRepository.findAll(pageable);
ApplicationsListResource applicationListResource = new ApplicationsListResource(applications, pageable);
return new ResponseEntity<ApplicationsListResource>(applicationListResource, HttpStatus.OK);
}
Obviously there's a few unknown classes in there. ApplicationListResource extends ResourceSupport and contains a list of ApplicationResource called applications. This ApplicationResource also extends ResourceSupport.
When I access the code via the browser, I'll get something along the lines of:
{
"_links": {
"self": {
"href": "http://localhost:10000/applications{?page,size,sort}",
"templated": true
}
},
"_embedded": {
"applications": [{
"displayname": "One",
"description": "My Test Application!",
"locations": ["http://foo.com"],
"_links": {
"self": { "href": "http://localhost:10000/applications/one" }
}
}, {
...
}]
},
"page": {
"size": 20,
"totalElements": 7,
"totalPages": 1,
"number": 0
}
}
Looks HATEOAS compliant to me. But when I go via a MockMVC request...
getMockMvc().perform(get(APPLICATIONS_ROOT)).andExpect(status().isOk()).andExpect(content().contentType(MediaTypes.HAL_JSON)).andExpect(jsonPath("$._embedded.applcations", hasSize(5))).andReturn();
The responses have no HATEOAS compliant elements in them so my tests fail on the jsonPath check:
{
"page" : 0,
"size" : 10,
"sort" : null,
"total" : 5,
"applications" : [ {
"name" : "one",
"version" : "1.0",
...
I've tried changing the ContentType on the GET request for the MockMVC method but it makes no difference. In the browser, I'm not setting any specific content type, headers etc.
I know the MockMVC class makes it HTTP requests with certain differences from the usual RestTemplate so perhaps it's something like this? Can anyone see anything obvious I am missing?
I will add additional code if needs be but it would have made the question even more long winded than it is currently.
Spring HATEOAS adds additional configuration for rendering hal properly, check this for details: http://docs.spring.io/spring-hateoas/docs/0.19.0.RELEASE/reference/html/#configuration.
In a nutshell it adds proper MixIns added by Jackson2HalModule and HalHandlerInstantiator to the ObjectMapper. It's all configured in HypermediaSupportBeanDefinitionRegistrar.java (https://github.com/spring-projects/spring-hateoas/blob/master/src/main/java/org/springframework/hateoas/config/HypermediaSupportBeanDefinitionRegistrar.java)
If you're using standalone mockMvc configuration you have to configure the ObjectMapper manually to mimic spring's behaviour. I ran into same problem and ended up adding following configuration to my tests:
mockMvc = MockMvcBuilders.standaloneSetup(controller)
.setMessageConverters(
new MappingJackson2HttpMessageConverter(configureObjectMapper()))
.build();
and
private ObjectMapper configureObjectMapper() {
return Jackson2ObjectMapperBuilder.json()
.modules(new Jackson2HalModule())
.handlerInstantiator(new Jackson2HalModule.HalHandlerInstantiator(
new DelegatingRelProvider(
OrderAwarePluginRegistry.create(Arrays.asList(
new EvoInflectorRelProvider(),
new AnnotationRelProvider()))),
null))
.build();
}
Related
Im working in a project using Spring Cloud Contract and Wiremock to create integration tests. Everything was fine until we need to add multiple responses to the same request in Wiremock. Reading some docs, i think Scenarios from Wiremock is the way to go.... but since we are using the 'new' #AutoConfigureWiremock annotation, i can't figure out how to work with wiremock scenarios...
Doesnt matter what i do, wiremock always fallback to the 'Started' Scenario everytime. I can't use my 'Expired' Scenario....
My BaseClassCOnfiguration
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.MOCK)
#AutoConfigureWireMock
#AutoConfigureStubRunner(stubsMode = StubRunnerProperties.StubsMode.LOCAL)
#Import(value = TestConfigClass.class)
#ContextConfiguration(initializers = {Properties.class})
#AutoConfigureMockMvc(addFilters = true)
public abstract class SpringCloudContractBase {
My WireMockClassRule
#ClassRule
public static WireMockClassRule wiremock =
new WireMockClassRule(options()
.dynamicPort()
.extensions(new ResponseTemplateTransformer(true)));
My mapping scenarios
"mappings": [
{
"priority": 1,
"scenarioName": "att",
"requiredScenarioState": "Started",
"request": {
"method": "GET",
"urlPath": "/v1/authtoken/exchange"
},
"response": {
"headers": {
"Content-Type": "application/json"
},
"status": 200,
"bodyFileName": "{{request.pathSegments.[1]}}/{{request.pathSegments.[2]}}/index.json"
}
},
{
"priority": 2,
"scenarioName": "att",
"requiredScenarioState": "Expired",
"request": {
"method": "GET",
"urlPath": "/v1/authtoken/exchange"
},
"response": {
"status": 403
}
}
]
}
And finally my test
#Test
public void test__stub__return403() {
// THIS DOES NOT WORK ! Will pick the first scenario under my mappings instead.....
wiremock.stubFor(get(anyUrl()).inScenario("att").whenScenarioStateIs("Expired"));
Auth auth = service.exchange("token");
}
How can i achieve this ?
Thank you !
I would like to deserialize the following json object with jackson:
{
"_embedded": {
"endpoints": [
{
"name": "Tester",
"id": "48aba1b3-3585-4327-a20f-627a1749611b",
"componentId": "Darwin2",
"_links": {
"self": {
"href": "www.google.com"
},
"network": {
"href": "www.google.com"
},
"appWans": {
"href": "www.google.com"
},
"services": {
"href": "www.google.com"
},
"endpointGroups": {
"href": "www.google.com"
},
"geoRegion": {
"href": "www.google.com"
},
"dataCenter": {
"href": "www.google.com"
}
}
}
]
},
"_links": {
"first": {
"href": "www.google.com"
},
"last": {
"href": "www.google.com"
}
},
"page": {
"size": 2000,
"totalElements": 1,
"totalPages": 1,
"number": 1
}
}
My goal is to implement an Embedded object then within this object add another object called Endpoints. Ideally, I'd be able to access the id property off of the endpoints object. However, I keep getting deserialization errors. For the moment I am using this class:
import java.util.HashMap;
import java.util.Map;
import com.fasterxml.jackson.annotation.JsonProperty;
#lombok.Value
public class Endpoints {
#JsonProperty("_embedded")
private Map<String, Object> embedded = new HashMap<>();
}
This at least affords me the opportunity to do the following:
Endpoints result = apiRequest.get();
if (result != null) System.out.println(result.getEmbedded().get("endpoints"));
Which prints out the array of endpoints, but I can't use this. I must implement a java object. Any help would be greatly appreciated with this issue.
So I have no idea what frameworks you use, or where this JSON data comes from, but you mentioned Spring HATEOS, so here is a solution using Jackson and Spring HATEOAS:
#Data
public class Endpoint extends RepresentationModel<Endpoint> {
private String name;
private String id; // Could be a UUID type instead??
private String componentId;
}
#Service
public class MyService {
#Autowired
private ObjectMapper objectMapper;
public void foo() {
String mysteryString = apiRequest.get();
PagedModel<Endpoint> endpointsPage = objectMapper.readValue(mysteryString, new TypeReference<PagedModel<Endpoint>>);
for (Endpoint e : endpointsPage) {
System.out.println(e.getName());
}
}
}
Spring HATEOAS docs and a guide. Also look at the Javadoc of the classes I've used.
If you want Endpoint to be a Lombok Value (i.e., all final), you need a constructor with appropriate ´#JsonCreator´ and ´#JsonProperty´ annotations, so Jackson knows how to build your Object (see 'guide' link).
And some more reading on Jackson.
Then again, if you are using Spring and you could just use Spring RestTemplate to fetch the Data from the remote API, you don't even need to manually use Jackson ObjectMapper:
PagedModel<Endpoint> endpointsPage =
restTemplate.exchange(apiUrl,
HttpMethod.GET,
null, // RequestEntity (body of request)
new ParameterizedTypeReference<PagedModel<Endpoint>>() {}).getBody();
The whole TypeReference and ParameterizedTypeReference business is only needed bc. we are dealing with generics.
{
"context": {
"headers": {},
"entity": {
"validationDetailsEntityList": [
{
"createTimestamp": 1512653225936,
"modifyTimestamp": 1512653225936,
"version": 0,
"auditTimestamp": "2"
},
{
"createTimestamp": 1512652876650,
"modifyTimestamp": 1512652876650,
"version": 0,
"auditTimestamp": "2"
},
{
"createTimestamp": 1512652955832,
"modifyTimestamp": 1512652955832,
"version": 0,
"auditTimestamp": "2"
}
]
}
"entityType":"com.example.demo.wrapper.ABCDWrapper",
"entityAnnotations": [],
Class Written below to get the response on the http mapped request
#RequestMapping(value = "/fetch", method = RequestMethod.POST)
public Response getAllXYZDetails(#RequestBody QueryDetails queryDetailsPayLoad) {
List<XYZEntity> xyzEntityList = xyzService.getAllXYZDetails();
return Response.ok(xyzEntityList)
.build();
}
I am trying to build a generic response type from my controller class on http REST calls, and so my return type is Response.
Now, what's happening is that:
The response generated not only has the details that I want in json but also it is having a lot of extra information like
entityType and entityAnnotations etc etc(SEE ABOVE RESPONSE), which I don't want.
How to get rid of those ans get only the entities in response?
If you are using Jackson annotations you can configure pretty much everything. In your case, #JsonIgnore should suffice.
If you are using JAX-RS/JAXB then Add #JsonIgnoreProperties(ignoreUnknown = true) on top of your class
In MVC, you would expect that controller will receive models as input and produce models as output. In Swagger, the latter is not a problem but I have troubles with former. I can't understand how to make Swagger build an input model from incoming GET parameters.
Consider:
"paths": {
"/search": {
"get": {
"consumes": [],
"produces": [
"application/json"
],
"parameters": [
// What goes here?
],
"responses": {
"200": {
"description": "Success",
"schema": {
"$ref": "#/definitions/SearchResponse"
}
},
}
}
}
}
How do I make the generated controller's method to have a signature like:
public ResponseEntity<ResultModel> controllerGet(ModelFromParameters input);
where ModelFromParameters will have several fields corresponding to different GET parameters.
Examples mostly focus either on POST requests, or GET requests where every of 20-odd parameters are stuffed in the arguments list of method, which is obviously anti-MVC.
The API in question is a complex stateless querying system with a lot of parameters.
Assumption: you want to create an object as parameter to your service method but still pass each of the fields of the object as query parameter in the actual http request and be able to document individual fields in swagger.
e.g. GET http://localhost:8080/search?parameter1=value1¶meter2=value2
Typical Service Method Definition where each query param is defined as a parameter in the actual method
#RequestMapping(method = RequestMethod.GET)
public ResponseEntity<List<String>> search(#RequestParam parameter1, #RequestParam parameter2) {
...
}
Modified service method with single parameter using Object (a.k.a Bean) annotated with #ModelAttribute. Though it is an object, as far as the REST API is concerned it is still the same as above.
#RequestMapping(method = RequestMethod.GET)
public ResponseEntity<List<String>> search(#ModelAttribute FormParam formParam) {
...
}
FormParam class. You can document each field using #ApiParam
public class FormParam {
#ApiParam(value = "parameter1 - description here", required = true)
private String parameter1;
#ApiParam(value = "parameter2 - description here", required = true)
private String parameter2;
//define getter setters below
}
This is how it appears in swagger
This is the generated swagger.json snippet
"paths": {
"/search": {
"get": {
"tags": ["search-service"],
"summary": "Search with Object as parameter",
"description": "Search with Object as parameter",
"operationId": "searchUsingGET",
"consumes": ["application/json"],
"produces": ["*/*"],
"parameters": [{
"name": "parameter1",
"in": "query",
"description": "parameter1 - description here",
"required": true,
"type": "string"
},
{
"name": "parameter2",
"in": "query",
"description": "parameter2 - description here",
"required": true,
"type": "string"
}],
"responses": {
"200": {
"description": "OK",
"schema": {
"type": "array",
"items": {
"type": "string"
}
}
},
"401": {
"description": "Unauthorized"
},
"403": {
"description": "Forbidden"
},
"404": {
"description": "Not Found"
}
}
}
},
If you are manually creating the swagger.json the query parameters can be documented like
"parameters": [{
"name": "parameter1",
"in": "query",
"description": "parameter1 - description here",
"required": true,
"type": "string"
},
{
"name": "parameter2",
"in": "query",
"description": "parameter2 - description here",
"required": true,
"type": "string"
}],
I think you can't. I had also a similar problem, for list page + search, using GET with body. No way to make swagger represent this, also if it works in Tomcat; also Elasticsearch supports it. It looks like there is no plan to change this aspect in swagger. I resorted to split the two in swagger: list-without-search as GET and list+search as POST, just to put the page in the swagger documentation, also if the latter actually works also as GET.
I have no experience with swagger code generation but, if your configuration generates what you expect with POST but it doesn't with GET, then you are probably hitting the same limitation.
https://github.com/swagger-api/swagger-ui/issues/2867
https://github.com/swagger-api/swagger-ui/issues/2136
https://github.com/OAI/OpenAPI-Specification/blob/OpenAPI.next/versions/3.0.md#operationRequestBody
The requestBody is only supported in HTTP methods where the HTTP 1.1 specification RFC7231 has explicitly defined semantics for request bodies. In other cases where the HTTP spec is vague, requestBody SHALL be ignored by consumers.
In usual, http server(eg. tomcat, jetty) will not accept a body message within a get request. If client needs pass parameters to server by http get method, it should use query string . Last part of the url after '?' character. Query string details you can see query string
But with spring mvc help, your http request parameters in query string will be bound to your controller method parameter`s fields.So it seems like client passed a pojo parameter to server side.
Any way the parameter part should look like this:
"parameters" : [ {
"name" : "age",
"in" : "query",
"required" : false,
"type" : "integer"
}, {
"name" : "firstName",
"in" : "query",
"required" : false,
"type" : "string"
}]
Please note that "in" filed's value is "query". That means parameter is passed by query string.
I supose what you require is posting your arguments in request body, it goes like this:
...
{
"name": "files",
"in": "body",
"required": true,
"schema": {
"type": "array",
"items": {
"$ref": "#/definitions/FileRequest"
}
}
}
which translates to:
public Response storageFilePost(
#NotEmpty(message = "systemID is required!") #QueryParam("systemID") final String systemID,
#NotNull(message = "qParam is required!") #QueryParam("qParam") final Long qParam,
#NotNull(message = "files are required!") final List<FileRequest> files) {
I have several controllers that are automatically creating REST endpoints.
#RepositoryRestResource(collectionResourceRel = "books", path = "books")
public interface BooksRepository extends CrudRepository<Books, Integer> {
public Page<Books> findTopByNameOrderByFilenameDesc(String name);
}
When I visit: http://localhost:8080/Books
I get back:
{
"_embedded": {
"Books": [{
"id": ,
"filename": "Test123",
"name": "test123",
"_links": {
"self": {
"href": "http://localhost:8080/books/123"
},
"Books": {
"href": "http://localhost:8080/books/123"
}
}
}]
},
"_links": {
"self": {
"href": "http://localhost:8080/books"
},
"profile": {
"href": "http://localhost:8080/profile/books"
},
"search": {
"href": "http://localhost:8080/books/search"
},
"page": {
"size": 20,
"totalElements": 81,
"totalPages": 5,
"number": 0
}
}
}
When I create my own controller:
#Controller
#RequestMapping(value = "/CustomBooks")
public class CustomBooksController {
#Autowired
public CustomBookService customBookService;
#RequestMapping("/search")
#ResponseBody
public Page<Book> search(#RequestParam(value = "q", required = false) String query,
#PageableDefault(page = 0, size = 20) Pageable pageable) {
return customBookService.findAll();
}
}
I'll get a response back that looks nothing like the automatically generated controller response:
{
"content": [{
"filename": "Test123",
"name" : "test123"
}],
"totalPages": 5,
"totalElements": 81,
"size": 20,
"number": 0,
}
What do I need to do to make my response look like the automatically generated response? I want to keep it consistent, so I don't have to rewrite code for a different response. Should I be doing it a different way?
Edit: Found this: Enable HAL serialization in Spring Boot for custom controller method
But I don't understand what I need to change in my REST Controller to enable: PersistentEntityResourceAssembler. I've searched on Google for PersistentEntityResourceAssembler, but it keeps leading me back to similar pages without much of an example (or the example doesn't seem to work for me).
As #chrylis suggested you should replace your #Controller annotation with #RepositoryRestController for spring-data-rest to invoke it's ResourceProcessors for customizing the given resource.
For you resource to follow the HATEOAS specification (like your spring-data-rest BooksRepository) your method declaration return type should be like HttpEntity<PagedResources<Resource<Books>>>
For converting your Page object to PagedResources:
You need to autowire this object.
#Autowired
private PagedResourcesAssembler<Books> bookAssembler;
Your return statement should be like
return new ResponseEntity<>(bookAssembler.toResource(customBookService.findAll()), HttpStatus.OK);
These changes should help you to get a org.springframework.hateoas.Resources compliant response containing the "_embedded" and "_links" attribute.