Lets say I have the following Hibernate Entities (fields ommitted)
#Entity
#DiscriminatorColumn(name = "T")
#Inheritance(strategy = InheritanceType.JOINED)
public abstract class SuperClass {
}
#Entity
#DiscriminatorValue(name = "subClassA")
public SubClassA extends SuperClass {
}
#Entity
#DiscriminatorValue(name = "subClassB")
public SubClassB extends SuperClass {
}
With Spring Data REST I would get the following JSON representation:
{
"_links": {
},
"_embedded": {
"subclassA": [
{
"field1": "",
"field2": ""
}
],
"subclassB": [
{
"field1": "",
"field2": "",
"field3": ""
}
]
}
}
Again ommiting the _links attributes. Is there some sort of configuration I can use so the Serializer can ignore the subclasses and do a representation like this:
{
"_links": {
},
"_embedded": {
"superClass": [
{
"field1": "",
"field2": ""
},
{
"field1": "",
"field2": "",
"field3": ""
}
]
}
}
One way to solve the problem would be the implementation of a RelProvider. All you need to do is to implement it and add it to spring container (this could be done but i.e. annotating the implementation with #Component).
Considering that you would be able to get the response you are expecting simply by adding the following implementation (considering it will be scanned by spring):
#Component
public class MessageRelProvider implements RelProvider {
public boolean supports(Class<?> arg0) {
return SuperClass.class.isAssignableFrom(arg0);
}
public String getItemResourceRelFor(Class<?> type) {
return "superClass";
}
public String getCollectionResourceRelFor(Class<?> type) {
return "superClasses";
}
}
Related
I am using Java annotations to build our swagger documentation. In one case, we want to provide output in either JSON or csv depending on the "Accepts" header. I created the rest interface like this:
#RestController
#EnableAutoConfiguration
#RequestMapping(path="/api/v2/swaggerTest")
#Api
public class SwaggerDocResource {
private static class ItemDto {
String name;
Integer age;
}
#ApiOperation(value = "Get the requested items in json")
#GetMapping(produces = "application/json")
public ResponseEntity<ItemDto> getItems() {
return null;
}
#ApiOperation(value = "Get the requested items in csv")
#GetMapping(produces = "text/csv")
public ResponseEntity<String> exportItems() {
return null;
}
}
If I comment out the csv method, the generated Swagger doc generates a schema for my DTO class and references it:
...
"responses": {
"200": {
"description": "OK",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ItemDto"
}
}
}
},
...
However, if I do include the csv method, the schema for my DTO class is no longer generated and both types of response are given the same schema:
...
"responses": {
"200": {
"description": "OK",
"content": {
"application/json": {
"schema": {
"type": "string"
}
},
"text/csv": {
"schema": {
"type": "string"
}
}
}
},
...
Is it possible to assign a different schema to these different content types? I have been unable to figure out how.
I want to enable compression features on jersey. I have a webservice that must return a list of events on json format.
For exemple :
{
"page": 1,
"pageCount": 1,
"list": [
{
"pk": 1,
"state": "ACTIVE",
"locationType": "ADDRESS",
"title": "titre",
"description": "hello",
"country": "osef",
"city": "osef",
"address": "osef",
"inAgendaCount": 0,
"dateBeginning": 1498734400758
}
]
}
But when I enable gzip compression, the response is like this :
{
"page": 1,
"pageCount": 1,
"list": [
{
}
]
}
All the events in the list are cut off.
Here is my webservice :
#Component
#Path("/")
public class EventSearchWebService {
#Autowired
private EventSearchController controller;
#GET
#Path("/event/search")
#Produces(MediaType.APPLICATION_JSON)
#JsonView(SmallView.class)
public Response search(#QueryParam("keywords") String keywords, #DefaultValue("1") #QueryParam("page") Long page) {
if (keywords != null && !keywords.trim().isEmpty()) {
return Response.status(200).entity(controller.search(keywords, page)).build();
} else {
return Response.status(200).entity(controller.search(page)).build();
}
}
}
My resourceConfig with compression :
#Configuration
#ApplicationPath("/rest")
public class JerseyConfig extends ResourceConfig {
public JerseyConfig() {
// Compression
register(EntityFilteringFeature.class);
EncodingFilter.enableFor(this, GZipEncoder.class);
register(EventSearchWebService.class);
register(MultiPartFeature.class);
register(ValidationFeature.class);
register(ValidationExceptionMapper.class);
register(CrossDomainContainerResponseFilter.class);
registerClasses(AccessDeniedExceptionMapper.class,
GeneralUserExceptionMapper.class,
NoResultExceptionMapper.class,
UnknowExceptionMapper.class,
ValidationExceptionMapper.class);
}
}
And my pageableList class :
public class PageableList<E> {
#Getter
#Setter
#JsonView(value = {SmallView.class, FullView.class})
private long page;
#Getter
#Setter
#JsonView(value = {SmallView.class, FullView.class})
private long pageCount;
#Getter
#Setter
#JsonView(value = {SmallView.class, FullView.class})
private List<E> list;
public PageableList() {
super();
}
public PageableList(List<E> list) {
super();
this.list = list;
}
}
There is no errors in the logs. Is there someone that have a clue of what's happening ?
Thank you
EDIT : after further investigations, I found out that if I send directly my list of events without PageableList, I have the error :
Can not resolve PropertyFilter with id 'org.vagrant.server.entity.EventEntity'; no FilterProvider configured (through reference chain: java.util.ArrayList[0])
The problem was in the ResourceConfig.
This line :
register(EntityFilteringFeature.class);
is useless and cause the bug.
I use MongoRepository in Spring boot data rest and it is working just fine without implementing my own controller. But I want to put "Register Date" in my newly created objects and default implementation is not supporting that. I need to implement my own custom controller to put extra fields in every new objects. The problem is that HATEOAS stop working when I implement my own controller.
Repository class:
#RepositoryRestResource(collectionResourceRel = "users", path = "users")
public interface UserRepository extends MongoRepository<User, String> {
}
Controller class:
#RestController
#RequestMapping("/users")
public class UserController {
#Autowired
UserRepository repository;
#RequestMapping(method = RequestMethod.GET)
public ResponseEntity<List<User>> getAll() {
List<User> list = repository.findAll();
return new ResponseEntity<>(list, HttpStatus.OK);
}
Payload with this custom controller looks like this:
[
{
"id": "571de80ebdabf25dd6cdfb73",
"username": "mark",
"password": "mark123",
"email": "mark#gmail.com",
"createdAt": "2016-04-25 11:49"
},
{
...
Payload without my custom controller looks like this:
{
"_embedded": {
"users": [
{
"username": "mark",
"password": "mark123",
"email": "mark#gmail.com",
"createdAt": "2016-04-25 11:49",
"_links": {
"self": {
"href": "http://localhost:8080/users/571de80ebdabf25dd6cdfb73"
},
"user": {
"href": "http://localhost:8080/users/571de80ebdabf25dd6cdfb73"
}
}
},
{
.....
I tried to use #RepositoryRestController instead of #RestController but it didn't help. I wonder if there is another way to put "register date" in newly created objects without implementing own custom controller? If not, what can I do HATEOAS to work again?
I solved my problem thanks to comments which gave me a perspective :)
1 - Extended User class with ResourceSupport. (Note: Do not use just id for userId because ResourceSupport needs getId() method.)
public class User extends ResourceSupport {
#Id
private String userId;
2 - Updated my controller class as followed
import static org.springframework.hateoas.mvc.ControllerLinkBuilder.linkTo;
#RequestMapping(method = RequestMethod.GET)
public ResponseEntity<List<User>> getAll() {
List<User> list = repository.findAll();
for(User user : list) {
Link self = linkTo(UserController.class).slash(user.getUserId()).withSelfRel();
Link users = linkTo(UserController.class).slash(user.getId()).withRel("users");
user.add(self);
user.add(users);
}
return new ResponseEntity<>(list, HttpStatus.OK);
}
Now, my payloads look like this:
[
{
"userId": "571e44ecbdab7b1ffc668f02",
"username": "newton",
"password": "gravity",
"email": "nevton#gmail.com",
"createdAt": "2016-04-25 18:
"links": [
{
"rel": "self",
"href": "http://localhost:8080/users/571e44ecbdab7b1ffc668f02"
},
{
"rel": "users",
"href": "http://localhost:8080/users"
}
]
},
{
....
I am using Spring Boot and Spring HATEOAS to build a REST API.
I have 2 simple objects. Let's say:
// Model
#Entity
public class Person {
private String name;
private Address address;
// ... usual methods omitted for brievity
}
#Entity
public class Address {
private String street;
private String city;
// ...
}
// Repository. It exposes directly a REST service
public interface PersonRepository extends PagingAndSortingRepository<Person, Long> {}
// Application entry point
#ComponentScan
#EnableAutoConfiguration
#EnableJpaRepositories
#Import(RepositoryRestMvcConfiguration.class)
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
This simple project creates output like the following:
{
"_links": {
"self": {
"href": "http://localhost:8080/persons{?page,size,sort}",
"templated": true
}
},
"_embedded": {
"persons": [
{
"name": "Some name",
"_links": {
"self": {
"href": "http://localhost:8080/persons/1"
},
"address": {
"href": "http://localhost:8080/persons/1/address"
}
}
}
]
}
}
Fine, but I would like the application to send the Address object directly in the response. In order not to have to query the URL of the address.
Something like:
...
"persons": [
{
"name": "Some name",
"address": {
"street": "Some street name"
"city": "Some city name"
}
"_links": {
"self": {
"href": "http://localhost:8080/persons/1"
},
"address": {
"href": "http://localhost:8080/persons/1/address"
}
}
}
]
...
Are there any configuration to do that? I could not find any configuration about it in Spring HATEOAS docs. And this is the default behavior when using only regular Spring controllers.
The latest release of Spring Data REST's docs illustrate excerpt projections. This provides an alternative default view of a collection of data.
Your use case is the exact angle it was originally designed for.
Delete AddressRepository interface, and object Address will be embedded in json in Person class. But it will not be possible to get Address by ID
I'm trying to parse the filter parameters sent by a KendoUI grid to my web service and am having some issues convincing Jackson to parse this JSON. As far as I know, I can control the format of the parameters that Kendo sends, but I do not know how I would marshal the parameters into a better format so they remain unchangeable for now.
I intend to convert these parameters into a SQL query for an Oracle database.
Example JSON:
{
"filters":
[
{
"field": "Name",
"operator": "contains",
"value": "John"
},
{
"filters": [
{
"field": "Age",
"operator": "gt",
"value": 20
},
{
"field": "Age",
"operator": "lt",
"value": 85
}
],
"logic", "and"
},
{
"field": "Address",
"operator": "doesnotcontain",
"value": "street"
}
],
"logic": "or"
}
Filters. Java
public class Filters {
private List<Filter> filters;
private String logic;
// accessors/mutators/toString
}
Filter.java
public class Filter {
private String field;
private String operator;
private String value;
// accessors/mutators/toString
}
Unit Test
public class KendoGridFilterTest {
private ObjectMapper mapper;
#Before
public void before() {
mapper = new ObjectMapper();
}
#Test
public void jsonParseTest() {
final String json = "{\"filters\":[{\"field\":\"Name\",\"operator\":\"contains\",\"value\":\"John\"},{filters: [{\"field\":\"Age\",\"operator\": \"eq\",\"value\": 85},{\"field\": \"Age\",\"operator\": \"eq\",\"value\": 85}]\"logic\", \"and\",},{\"field\": \"Address\",\"operator\": \"doesnotcontain\",\"value\": \"street\"}],\"logic\":\"or\"}";
Filters filters = mapper.readValue(json, Filters.class);
assertTrue(json.equals(filters.writeValueAsString(filters);
}
}
Errors
com.fasterxml.jackson.databind.UnrecognizedPropertyException: Unrecognized field
'logic'(com.example.Filter) not market as ignorable (3 known properties
"value", "field", "operator")
at [Source: java.io.StringReader#3bb2b8; line: 1, column 76] (through reference
chain: com.example.Filters["filters"]->com.example.Filter["logic"]
I've also tried adding #JsonIdentityInfo(generator=ObjectIdGenerators.IntSequenceGenerator.class, property="#id") to the Filters class and get the same errors.
your Filter class is not correct. It should extend Filters.
After correcting your unit test (json is incorrect) it can load your json into a Filters Object.
public class Filter extends Filters {
private String field;
private String operator;
private String value;
// accessors/mutators/toString
}