Spring Hateoas / HAL throw Exception - java

I've got weird issue because everything works fine in my application, by after do GET method I always get exception:
2018-05-10 12:17:18.608 WARN 16031 --- [nio-8080-exec-1] tion$ResourceSupportHttpMessageConverter : Failed to evaluate Jackson serialization for type [class com.computeralchemist.domain.components.OpinionDto]: java.lang.IllegalStateException: Cannot override _serializer: had a `org.springframework.hateoas.hal.Jackson2HalModule$HalLinkListSerializer`, trying to set to `org.springframework.data.rest.webmvc.json.PersistentEntityJackson2Module$NestedEntitySerializer`
My DTO object is simple:
#Getter
#Setter
#JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY)
public abstract class ComputerComponent extends ResourceSupport {
....
}
And one of child class:
#Getter
#Setter
#Document(collection = "cpu")
public class Cpu extends ComputerComponent {
#Id
private long productId;
private CpuParameters cpuParameters;
}
And REST controller's method:
#GetMapping(value = "/{id}", produces = "application/json; charset:UTF-8")
#ResponseStatus(HttpStatus.OK)
public ComputerComponent getComponent(#PathVariable("component") String component,
#PathVariable("id") long id) {
ComputerComponent computerComponent = repositoryProvider.findComponent(component, id);
if (computerComponent == null)
throw new ComponentNotFoundException(component, id);
computerComponent.add(linkTo(methodOn(ComponentsController.class)
.getComponent(component, id)).withSelfRel());
computerComponent.add(linkTo(methodOn(ComponentsController.class)
.getListOfComponents(computerComponent.getComponentType().toString()))
.withRel("collection"));
return computerComponent;
}
Json returned (looks correctly, but I don't want null values):
{
"componentType" : "cpu",
"producent" : "AMD",
"model" : "Ryzen 5 1600",
"content" : [ ],
"links" : [ ]
},
"links" : [ {
"rel" : "self",
"href" : "http://localhost:8080/components/cpu/1",
"hreflang" : null,
"media" : null,
"title" : null,
"type" : null,
"deprecation" : null,
"content" : [ ],
"links" : [ ]
}, {
"rel" : "collection",
"href" : "http://localhost:8080/components/cpu",
"hreflang" : null,
"media" : null,
"title" : null,
"type" : null,
"deprecation" : null,
"content" : [ ],
"links" : [ ]
} ]
}
PS: Why in Links property is "hreflang","media" etc?

This answer is here not so much because it is the answer to the OP's problem (which they seem to have solved) but because it is what caused the problem for me. So maybe someone else searching for this will be helped...
It appears that if you have:
'org.springframework.boot:spring-boot-starter-hateoas'
then you have to remove
'org.springframework.boot:spring-boot-starter-data-rest'
At least - that is what solved the problem for me. The exception went away, as did the null fields that the OP reported.

Related

How to generate JsonSchema As Array from Java Class

I'm generating JsonSchema from Java class using fasterxml.jackson.
The generated Jsonschema will be as below
{
"type": "object",
"properties": {
"id": {
"type": "string"
},
"name": {
"type": "string"
}
}
}
My code to generate JsonSchema
public static String getJsonSchema(Class definitionClass) throws JsonProcessingException {
ObjectMapper mapper = new ObjectMapper().disable(
MapperFeature.CAN_OVERRIDE_ACCESS_MODIFIERS);
JavaType javaType = mapper.getTypeFactory().constructType(definitionClass);
JsonSchemaGenerator schemaGen = new JsonSchemaGenerator(mapper);
JsonSchema schema = schemaGen.generateSchema(javaType);
return mapper.writerWithDefaultPrettyPrinter().writeValueAsString(schema);
}
I tried to use ArraySchema arraySchema = schema.asArraySchema(); but it generates invalid schema.
My expected JsonSchema should be like below
{
"type": "array",
"items": {
"type": "object",
"properties": {
"id": {
"type": "string"
},
"name": {
"type": "string"
}
}
}
}
TL;DR: Call getJsonSchema(MyClass[].class)
Works fine if we simply tell it to generate the schema of a Java array.
To demo, I created a MyClass class fitting the shown schema. I used public fields for simplicity of the demo, but we'd use private fields and public getter/setter methods in real life.
class MyClass {
public String id;
public String name;
}
Now, to show that we get the same result as in the question, we call it with MyClass.class:
System.out.println(getJsonSchema(MyClass.class));
Output
{
"type" : "object",
"id" : "urn:jsonschema:MyClass",
"properties" : {
"id" : {
"type" : "string"
},
"name" : {
"type" : "string"
}
}
}
Now, we want the schema to be an array of those, so we will instead call it using MyClass[].class.
System.out.println(getJsonSchema(MyClass[].class));
Output
{
"type" : "array",
"items" : {
"type" : "object",
"id" : "urn:jsonschema:MyClass",
"properties" : {
"id" : {
"type" : "string"
},
"name" : {
"type" : "string"
}
}
}
}
The above was tested using jackson-module-jsonSchema-2.10.3.jar.

Lombok returns null as a value of response

I've got a problem with my Api tests.
When i'm trying to get data from api, lombok returns null as an acceptance value, but there are values with real numbers in api.
Screenchot: https://prnt.sc/w98nt2
My DTO for the responce:
#Data
#Builder
#EqualsAndHashCode
#NoArgsConstructor
#AllArgsConstructor
#JsonIgnoreProperties(ignoreUnknown = true)
public class PositionStatResponceDto {
private Integer keywordTasksCount;
private Integer doneKeywordTasksCount;
private Integer tasksCount;
private Integer doneTasks;
}
My steps that extacts body and send post request
public class PositionSteps {
PositionsController positionsController = new PositionsController();
#Step("Post body with url: http://prod.position.bmp.rocks/api/aparser/get-statistic")
public PositionStatResponceDto postBody(PositionStatDto positionStatDto) {
return positionsController
.getStatistic(positionStatDto)
.statusCode(200)
.extract().body().as(PositionStatResponceDto.class);
}
}
Api json responce getting properly. it means that the request working right:
{
"period": {
"20201224": {
"startTime": "2020-12-24 00:00:19",
"endTime": "2020-12-24 06:39:30",
"totalRequestsCount": 0,
"totalQueriesCount": 161887,
"totalQueriesDoneCount": 161887,
"totalFailCount": 161,
"successfulQueries": 161726,
"proxiesUsedCount": 6.49,
"retriesUsedCount": 0,
"avgSpeed": 13.74,
"tasksCount": 1537,
"doneTasks": 1537,
"keywordTasksCount": 725,
"doneKeywordTasksCount": 725,
"runTime": "06:39:11",
"avgTimePerKeyword": 0.15,
"keywordsLost": 0.1
}
},
"avg": {
"totalRequestsCount": 0,
"totalQueriesCount": 161887,
"totalQueriesDoneCount": 161887,
"totalFailCount": 161
}
}
I made post request in the similar way to the api:
{
"success": 1,
"data": {
"45.90.34.87:59219": [
"http"
],
"217.172.179.54:39492": [
"http"
],
"144.76.108.82:35279": [
"http"
],
"5.9.72.48:43210": [
"http"
],
"144.76.108.82:47165": [
"http"
],
"45.90.34.87:57145": [
"http"
],
"144.76.108.82:53108": [
"http"
],
...
} }
And it works correctly with dto:
#Data
#Builder
#EqualsAndHashCode(exclude = "success")
#NoArgsConstructor
#AllArgsConstructor
#JsonIgnoreProperties(ignoreUnknown = true)
public class AparsersResponceDto {
private Integer success;
private Map<String, List<String>> data;
}
Help me please. I can't understand what's wrong with the first example. Each of the Dto values returs 'null'.
Your DTO does not match the structure of the response you are parsing. You have a nested structure where on DTO you are expecting only to receive primitive values. On upper level you have a structure with two fields.
{
"period": {...},
"avg": {...}
}
From the example I would assume that period is a key-value pair of dates as a key and your PositionStatResponceDto as a value.
{
"period" : {
"20201224": { <-- nested key with a value matching your DTO
PositionStatResponceDto
}
...
}
So this means only single item in the key-value pairs match the DTO you have defined but is ignoring all other nested structure elements. For this it would make sense to introduce new wrapper DTO to handle the nested structure.
e.g.
public class StatDTO {
private Map<String,PositionStatResponceDto> period;
//add avg if needed
}

Swagger Documentation when the java class is deserialised from String input

In my request body, I have a field which takes a string as input but is converted into a java object while deserializing.
public class UserCredentials {
#NotBlank String username;
#NotNull ReferenceString passwordRef;
}
the passwordRef value is taken as string and I used #JsonCreator to convert the string to ReferenceString object.
public class ReferenceString {
private String identifier;
private String type;
#JsonCreator
public ReferenceString(String secretRefConfigString) {
// logic to break the string into two and set the
// identifier and type value
return;
}
The serialization and deserialization work fine. I am having an issue with the swagger documentation, the documentation looks like
"UserCredentials" : {
"type" : "object",
"required" : [ "passwordRef" ],
"properties" : {
"username" : {
"type" : "string"
},
"passwordRef" : {
"$ref" : "#/definitions/ReferenceString"
}
}
}
"ReferenceString" : {
"type" : "object",
"properties" : {
"identifier" : {
"type" : "string"
},
"type" : {
"type" : "string"
}
}
}
Since the API will take a string as input, I want that in the docs the type of passwordRef should be shown as String and not ReferenceString, also I don't want the user to see the internal class ReferenceString.
I read about #ApiModel and #ApiModelProperty. I can use the #ApiModelProperty on my fields and the documentation will show that field as a string, but in my code, a lot of fields will be of type ReferenceString and I don't want to use #ApiModelProperty on every field.
Can it be done easily some different way? Can I add some annotation/code only on the ReferenceString to solve this issue?

Spring how to keep entity id of all sub entities within the json generated by #Controller?

In spring data, I have a Entity that use EntityStatus.
When I serialized json using my #Controller, the id of EntityStatus is removed.
This is how look the generated entity:
{
"description" : "Some testing",
"version" : null,
"createdDate" : "2020-03-10T05:46:25.516Z",
"createdById" : null,
"lastModifiedById" : null,
"title" : "This is a test",
"content" : "Foo bar fizz buzz",
"userId" : 2,
"category" : {
"description" : "Foobar",
"version" : null,
"createdDate" : "2020-03-10T05:18:30.282Z",
"lastModifiedDate" : "2020-03-10T05:18:41.827Z",
"createdById" : null,
"lastModifiedById" : null,
"deleted" : false
},
"status" : {
"description" : "Created"
},
"deleted" : false,
"_links" : {
"self" : {
"href" : "http://localhost:8080/management/entity/5"
}
}
}
How can I force it to be included for all sub-entities globally?
You can configure this using the RepositoryRestConfigurerAdapter. If you want to have it for all your entities, just iterate over all of them and expose their ids:
#Configuration
public class MyRepositoryRestConfigurerAdapter extends RepositoryRestConfigurerAdapter {
#Autowired
private EntityManager entityManager;
#Override
public void configureRepositoryRestConfiguration(RepositoryRestConfiguration config) {
config.exposeIdsFor(entityManager.getMetamodel().getEntities()
.stream().map(EntityType:getJavaType))
.collect(Collectors.toList())
.toArray(new Class[0]));
}
}
If you just want it for specific entities, filter them out while doing the processing above.
UPDATE: Another more manual approach without using the EntityManager might be to explicitly add all entities:
#Configuration
public class MyRepositoryRestConfigurerAdapter extends RepositoryRestConfigurerAdapter {
#Override
public void configureRepositoryRestConfiguration(RepositoryRestConfiguration config) {
config.exposeIdsFor(YourEntityOne.class);
config.exposeIdsFor(YourEntityTwo.class);
}
}

Swagger-annotation generation

I have a problem generating a swagger.json file with swagger annotations. The problem is with the generation of map with array.
Ex:
I have an #ApiModel with a field:
#JsonProperty
private Map<String, String[]> example;
When it generates it looks like this:
"example" : {
"type" : "object",
"additionalProperties" : {
"$ref" : "#/definitions/Array"
}
}
"Array" : {
"type" : "object"
},
When I create my client class with swagger-codegen based on the generated json, it resolve to:
private Map<String, Array> example = new HashMap<String, Array>();
But Array doesn`t exist.
Based on what I`ve seen, the json should look like this:
"definitions": {
"example": {
"type": "object",
"additionalProperties": {
"type": "array",
"items": {
"type": "string"
}
}
}
}
}
Is there anyway to make it works ?
I use: Dropwizard, Jersey2, Swagger, JAX-RS

Categories

Resources