It's possible to integrate the swagger with #JsonView? I have one model that I use the #JsonView to return only a few fields, but the swagger-ui shows the hole model.
This is my model:
public class Intimacao extends EntityBase {
#Embedded
#JsonView({View.Intimacao_Lista.class})
private Devedor devedor;
#Embedded
private Sacador sacador;
#Embedded
private Apresentante apresentante;
#Embedded
private Titulo titulo;
}
This is my controller:
#GetMapping("/")
#PreAuthorize("hasRole('ADMINISTRADOR') or hasRole('MOTOBOY')")
#JsonView({View.Intimacao_Lista.class})
public List<Intimacao> listar(Principal principal){
System.out.println(principal.getName());
return null;
}
This is the result in swagger-ui
[
{
"apresentante": {
"documento": "string",
"nome": "string"
},
"devedor": {
"bairro": "string",
"cep": "string",
"cidade": "string",
"complemento": "string",
"documento": "string",
"estado": "string",
"logradouro": "string",
"nome": "string",
"numero": "string",
"tipoLogradorouo": "string"
},
"id": 0,
"sacador": {
"chave": "string",
"documento": "string",
"especie": "string",
"nome": "string"
},
"titulo": {
"custas1": 0,
"custas2": 0,
"custas3": 0,
"custas4": 0,
"custas5": 0,
"custas6": 0,
"custas7": 0,
"custas8": 0,
"custas9": 0,
"numero": "string",
"vencimento": "string"
}
}
]
But if I GET my API only will return the devedor properties, because the #JsonView
It's possible to integrate the swagger with #JsonView?
Yes (partially).
After this pull request was merged you can use it for:
Response Objects (you got that part working).
#GetMapping("/")
#PreAuthorize("hasRole('ADMINISTRADOR') or hasRole('MOTOBOY')")
#JsonView({View.Intimacao_Lista.class})
public List<Intimacao> listar(Principal principal){
System.out.println(principal.getName());
return null;
}
RequestBody Objects (not yet, pull request on its way, see #2918 and an example in comment at #2079). In your case:
#GetMapping("/")
#PreAuthorize("hasRole('ADMINISTRADOR') or hasRole('MOTOBOY')")
#JsonView({View.Intimacao_Lista.class})
// replace `Views.Principal.class` for the proper value
public List<Intimacao> listar(#JsonView(Views.Principal.class) Principal principal){
System.out.println(principal.getName());
return null;
}
Related
I've a requirement to upload any file content using Swagger-3 along with some metadata information as a JSON within a single request. Therefore I configured following into my swagger:
"requestBody": {
"content": {
"multipart/form-data": {
"schema": {
"type": "object",
"properties": {
"metadata": {
"$ref": "#/components/schemas/Attachment"
},
"file": {
"type": "string",
"format":"binary",
"description": "Actual File Attachment"
}
}
}
}
},
"description": "The Attachment record / entry be created",
"required": true
}
It translates to following when I build the controller object:
#ApiOperation(value = "Upload attachments to a existing Ticket", nickname = "uploadAttachment", notes = "", response = Attachment.class, responseContainer = "List", tags={ "changeRequest", })
#RequestMapping(value = "/changeRequest/attachment/{id}",
produces = { "application/json" },
consumes = { "multipart/form-data" },
method = RequestMethod.POST)
public ResponseEntity<List<Attachment>> uploadAttachment(#ApiParam(value = "Identifier of the Change Request",required=true) #PathVariable("id") String id,#ApiParam(value = "Application ID invoking the call" ,required=true) #RequestHeader(value="X-App-Id", required=true) String xAppId,#NotNull #ApiParam(value = "To track unique transaction across multiple systems for audit trail", required = true) #Valid #RequestParam(value = "X-Transaction-Id", required = true) String xTransactionId,#ApiParam(value = "Authorization header" ) #RequestHeader(value="authorization", required=false) String authorization,#ApiParam(value = "", defaultValue="null") #RequestParam(value="metadata", required=false) Attachment metadata, #ApiParam(value = "file detail") #Valid #RequestPart("file") MultipartFile file) {
ResponseEntity<List<Attachment>> responseEntity = new ResponseEntity<>(HttpStatus.OK);
responseEntity.getBody().add(metadata);
return responseEntity;
}
Following is the Attachment schema definition:
"Attachment": {
"type": "object",
"description": "Attachment Metadata definition",
"properties": {
"description": {
"type": "string",
"description": "A narrative text describing the content of the attachment"
},
"href": {
"type": "string",
"description": "Reference of the attachment"
},
"id": {
"type": "string",
"description": "Unique identifier of the attachment"
},
"mimeType": {
"type": "string",
"description": "The mime type of the document as defined in RFC 2045 and RFC 2046 specifications."
},
"name": {
"type": "string",
"description": "The name of the file"
},
"path": {
"type": "string",
"description": "The path of the attached file"
},
"size": {
"type": "integer",
"description": "The size of the file (sizeUnit if present indicates the unit, otherwise kilobytes is the default)."
},
"sizeUnit": {
"type": "integer",
"description": "The unit size for expressing the size of the file (MB,kB...)"
},
"url": {
"type": "string",
"description": "Uniform Resource Locator, is a web page address (a subset of URI)"
},
"validFor": {
"$ref": "#/components/schemas/TimePeriod",
"description": "Period of validity of the attachment"
},
"#type": {
"type": "string",
"description": "The class type of the actual resource (for type extension)."
},
"#schemaLocation": {
"type": "string",
"description": "A link to the schema describing a resource (for type extension)."
},
"#baseType": {
"type": "string",
"description": "The base type for use in polymorphic collections"
}
}
}
In the example above, Attachment metadata is what I am trying to pass as part of the SOAP API test. However I keep getting following error:
Mon Oct 12 17:06:28 IST 2020:DEBUG:<< "{"timestamp":"2020-10-12T11:36:28.371Z","status":500,"error":"Internal Server Error","message":"Failed to convert value of type 'java.lang.String' to required type 'com.bell.na.nt.change.swagger.model.Attachment'; nested exception is java.lang.IllegalStateException: Cannot convert value of type 'java.lang.String' to required type 'com.bell.na.nt.change.swagger.model.Attachment': no matching editors or conversion strategy found","path":"/changeManagement/api/v1/changeRequest/attachment/1234"}"
Why is the string not being converted and mapped to the JSON object. Not sure I am missing anything. Following is what my json looks like.
{"#baseType": "string", "#schemaLocation": "string", "#type": "string", "description": "string", "href": "string", "id": "string", "mimeType": "string", "name": "string", "path": "string", "size": 0, "sizeUnit": 0, "url": "string", "validFor": { "endDateTime": "2020-10-11T19:06:40.586Z", "startDateTime": "2020-10-11T19:06:40.586Z"}}
Postman Request
Turns out I had to add the a converter for converting the string representation of the JSON to desired Swagger generated Model object something like:
#Component
public class StringToAttachmentObjectConverter implements Converter<String, Attachment> {
Logger logger = LoggerFactory.getLogger(StringToAttachmentObjectConverter.class);
#Autowired
private ObjectMapper objectMapper;
DocumentContext docContext = null;
#Override
public Attachment convert(String source) {
try {
String sourceString = JsonPath.using(NetsUtilityJSONDatumUtils.jsonPathConfig).parse(source).jsonString();
return objectMapper.readValue(sourceString, Attachment.class);
} catch (JsonParseException e) {
logger.error("Error While converting the String: \n" + source, e);
} catch (JsonMappingException e) {
logger.error("Error While converting the String: \n" + source, e);
} catch (IOException e) {
logger.error("Error While converting the String: \n" + source, e);
}
return null;
}
}
Not sure if there is any better way or if I am defying any best practice(s) but this did the trick for me.
I have these two classes:
Quota Class
#Entity
#Data
#EqualsAndHashCode(callSuper=false)
#Table(name="quotas")
#Relation(collectionRelation = "quotas", itemRelation = "quota")
#JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "id")
public class Quota extends RepresentationModel<Quota> {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
#OneToMany(fetch = FetchType.LAZY, mappedBy = "quota")
#JsonIdentityReference(alwaysAsId = true)
private List<Customer> customers;
private String type;
private boolean flatrate;
#CreatedDate
private long createdDate;
#LastModifiedDate
private long lastModifiedDate;
And Customer:
#Entity
#Data
#EqualsAndHashCode(callSuper=false)
#Table(name = "customers")
#Relation(collectionRelation = "customers", itemRelation = "customer")
#JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "id")
public class Customer extends RepresentationModel<Customer> {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
private String name;
private String customerNumber;
#CreatedDate
private long createdDate;
#LastModifiedDate
private long lastModifiedDate;
#ManyToOne
#JoinColumn(name = "quota_id", referencedColumnName = "id")
#JsonProperty("quotaId")
#JsonIdentityReference(alwaysAsId = true)
private Quota quota;
}
What i get when i make a GET request on all Customers:
{
"_embedded": {
"customers": [
{
"id": 3,
"name": "Customer Test3",
"customerNumber": "45678",
"createdDate": 1596117132045,
"lastModifiedDate": 1596117132045,
"_links": {
"self": {
"href": "http://localhost:2502/seminars/3"
}
},
"quotaId": 1
},
{
"id": 1,
"name": "test3",
"customerNumber": "12345",
"createdDate": 1596111304535,
"lastModifiedDate": 1596186450456,
"_links": {
"self": {
"href": "http://localhost:2502/seminars/1"
}
},
"quotaId": 1
},
{
"id": 2,
"name": "Customer Test2",
"customerNumber": "23456",
"createdDate": 1596112131934,
"lastModifiedDate": 1596112131934,
"_links": {
"self": {
"href": "http://localhost:2502/seminars/2"
}
},
"quotaId": 2
},
{
"id": 4,
"name": "Customer Test4",
"customerNumber": "34567",
"createdDate": 1596117145795,
"lastModifiedDate": 1596117145795,
"_links": {
"self": {
"href": "http://localhost:2502/seminars/4"
}
},
"quotaId": 2
},
{
"id": 6,
"name": "Customer Test6",
"customerNumber": "12345",
"createdDate": 1596187250598,
"lastModifiedDate": 1596187250598,
"_links": {
"self": {
"href": "http://localhost:2502/seminars/6"
}
},
"quotaId": null
}
]
},
"_links": {
"self": {
"href": "http://localhost:2502/seminars?page=0&size=20&sort=quota,asc"
}
},
"page": {
"size": 20,
"totalElements": 5,
"totalPages": 1,
"number": 0
}
}
What i get when i make a GET request in all Quotas:
{
"_embedded": {
"quotas": [
{
"id": 1,
"customers": [
{
"id": 3,
"name": "Customer Test3",
"customerNumber": "45678",
"createdDate": 1596117132045,
"lastModifiedDate": 1596117132045,
"quotaId": 1
},
{
"id": 1,
"name": "test3",
"customerNumber": "12345",
"createdDate": 1596111304535,
"lastModifiedDate": 1596186450456,
"quotaId": 1
}
],
"type": "paid",
"createdDate": 1596111304535,
"lastModifiedDate": 1596111304535,
"flatrate": true,
"_links": {
"self": {
"href": "http://localhost:2502/quotas/1"
}
}
},
{
"id": 2,
"customers": [
{
"id": 2,
"name": "Customer Test2",
"customerNumber": "23456",
"createdDate": 1596112131934,
"lastModifiedDate": 1596112131934,
"quotaId": 2
},
{
"id": 4,
"name": "Customer Test4",
"customerNumber": "34567",
"createdDate": 1596117145795,
"lastModifiedDate": 1596117145795,
"quotaId": 2
}
],
"type": "demo",
"createdDate": 1596111304535,
"lastModifiedDate": 1596111304535,
"flatrate": true,
"_links": {
"self": {
"href": "http://localhost:2502/quotas/2"
}
}
}
]
},
"_links": {
"self": {
"href": "http://localhost:2502/quotas?page=0&size=10&sort=id,asc"
}
},
"page": {
"size": 10,
"totalElements": 2,
"totalPages": 1,
"number": 0
}
}
I am happy with the result from Customers, but the result from quotas i would want to look like this:
{
"_embedded": {
"quotas": [
{
"id": 1,
"customers": [3,1],
"type": "paid",
"createdDate": 1596111304535,
"lastModifiedDate": 1596111304535,
"flatrate": true,
"_links": {
"self": {
"href": "http://localhost:2502/quotas/1"
}
}
},
{
"id": 2,
"customers": [2,4],
"type": "demo",
"createdDate": 1596111304535,
"lastModifiedDate": 1596111304535,
"flatrate": true,
"_links": {
"self": {
"href": "http://localhost:2502/quotas/2"
}
}
}
]
},
"_links": {
"self": {
"href": "http://localhost:2502/quotas?page=0&size=10&sort=id,asc"
}
},
"page": {
"size": 10,
"totalElements": 2,
"totalPages": 1,
"number": 0
}
}
For json serialization i use 'com.fasterxml.jackson.annotation.*'
I already tried it with using the #JsonBackRefference, #JsonIgnore, #JsonIngoreProperties and #JsonIdentityReference annotations but never got the wanted result.
I would recommend you introduce DTOs to model this rather than trying to annotate the entity classes with JSON annotations. At some point you will probably have conflicting needs i.e. one use case needs a field and another doesn't and then you will have to think about abstracting this anyway.
Having said that, this is a perfect use case for Blaze-Persistence Entity Views.
Blaze-Persistence is a query builder on top of JPA which supports many of the advanced DBMS features on top of the JPA model. I created Entity Views on top of it to allow easy mapping between JPA models and custom interface defined models, something like Spring Data Projections on steroids. The idea is that you define your target structure the way you like and map attributes(getters) via JPQL expressions to the entity model. Since the attribute name is used as default mapping, you mostly don't need explicit mappings as 80% of the use cases is to have DTOs that are a subset of the entity model.
A DTO mapping for your model could look as simple as the following
#EntityView(Quota.class)
public abstract class QuotaDto extends RepresentationModel<QuotaDto> {
public abstract Integer getId();
#Mapping("customers.id")
public abstract List<Integer> getCustomers();
public abstract String getType();
public abstract boolean isFlatrate();
public abstract long getCreatedDate();
public abstract long getLastModifiedDate();
}
#EntityView(Customer.class)
public abstract class CustomerDto extends RepresentationModel<CustomerDto> {
public abstract Integer getId();
public abstract String getName();
public abstract String getCustomerNumber();
public abstract long getCreatedDate();
public abstract long getLastModifiedDate();
#Mapping("quota.id")
public abstract Integer getQuotaId();
}
Querying is a matter of applying the entity view to a query, the simplest being just a query by id.
CustomerDto dto = entityViewManager.find(entityManager, CustomerDto.class, id);
But the Spring Data integration allows you to use it almost like Spring Data Projections: https://persistence.blazebit.com/documentation/entity-view/manual/en_US/index.html#spring-data-features
You are in control of the structure and can map anything to the properties. It will only fetch the mappings that you tell it to fetch.
Im using spring boot on my proyect. Basically, all i want is to include inline entities in a response of a GET method while still using the advantages of spring-hateoas (_embedded and links).
Having this class:
#Entity
#Table(name = "sub_specialty", schema = "public", catalog = "icorelab")
public class SubSpecialty {
private Integer id;
private String name;
private Boolean active;
private Date createdAt;
private Date deletedAt;
private Specialty specialty;
private String description;
#ManyToOne
#JoinColumn(name = "specialty_id", referencedColumnName = "id", nullable = false)
public Specialty getSpecialtyBySpecialtyId() {
return specialty;
}
I set up repository with PagingAndSorting capabilities, also it extends CustomRepository to provide custom methods. This repository uses a projection so an iternal entity gets serialized as mentioned here
https://docs.spring.io/spring-data/rest/docs/current/reference/html/#projections-excerpts.excerpts
#RepositoryRestResource(collectionResourceRel = "sub_specialties", path = "sub_specialties",
excerptProjection = InlineSpecialty.class)
public interface SubSpecialtyRepository extends PagingAndSortingRepository<SubSpecialty, Integer>,SubSpecialtyRepositoryCustom {
The in a controller, i return the response by using Page and PageResourcesAssembler
Page<SubSpecialty> page = subSpecialtyRepository.filter(name,description,active,specialty,limit,offset,pageable.getSort());
PagedResources<SubSpecialty> pagedResources = pagedResourcesAssembler.toResource(page,subSpecialtyResourceAssembler);
HttpHeaders headers = PaginationUtil.generatePaginationHttpHeaders(page, "/sub_specialties/filter", offset, limit);
ResponseEntity<?> response = new ResponseEntity<>(pagedResources, headers, HttpStatus.OK);
return response;
The problem is that the message does not include Specialty info, just the link
{
"_embedded": {
"sub_specialties": [
{
"id": 107,
"name": "PHARMACOLOGY",
"active": false,
"createdAt": "2017-09-30",
"deletedAt": null,
"description": null,
"_links": {
"self": {
"href": "http://localhost:8080/sub_specialties/107{?projection}",
"templated": true
},
"subSpecialty": {
"href": "http://localhost:8080/sub_specialties/107{?projection}",
"templated": true
},
"specialty": {
"href": "http://localhost:8080/specialties/16"
}
}
},
{
"id": 104,
"name": "PHARMACOLOGY",
"active": false,
"createdAt": "2017-09-30",
"deletedAt": null,
"description": null,
"_links": {
"self": {
"href": "http://localhost:8080/sub_specialties/104{?projection}",
"templated": true
},
"subSpecialty": {
"href": "http://localhost:8080/sub_specialties/104{?projection}",
"templated": true
},
"specialty": {
"href": "http://localhost:8080/specialties/16"
}
}
}
]
},
"_links": {
"self": {
"href": "http://localhost:8080/sub_specialties/filter?page=0&size=20"
}
},
"page": {
"size": 20,
"totalElements": 2,
"totalPages": 1,
"number": 0
}
}
Is there anyway to achieve this? I thought i could use an annotation on the entity domain class or a custom jackson serializer.The Specialty entity is included in the response, however is not included in the message that receives the client. I suppose there is some default configuration for jackson that detects a repository of type Specialty, and the just includes the link in the response.
I've been developing a Spring Boot REST API. I've done so many things so far except my problem. I'm using springfox swagger UI for documentation, and I separated models and dtos for better structure.
I have a base dto model:
public class BaseDto {
private int code;
private boolean success;
public BaseDto() {
this.code = HttpStatus.OK.value();
this.success = HttpStatus.OK.is2xxSuccessful();
}
}
And of course I'm using this class by extending it like:
#ApiModel("User")
public class UserDto extends BaseDto {
private String email;
private String username;
// stuffs
}
If I do user requests when I use this structure, I get:
{
code: 200,
success: true,
email: "",
username: ""
}
and so on... That's fine, but in other dtos, like post model, I have List of UserDto and it's showed in that form. In every object, "code" and "success" fields are written; however, this is not I want to.
The goal that I want to achieve is only once "code" and "success" are written in the response JSON body not all returning list objects.
To clarify more this is Post Dto Model and returns like this:
{
"code": 0,
"createdAt": "2016-05-17T21:59:37.512Z",
"id": "string",
"likes": [
{
"code": 0,
"createdAt": "2016-05-17T21:59:37.512Z",
"deviceType": "string",
"email": "string",
"fbAccessToken": "string",
"fbId": "string",
"followers": [
{}
],
"followings": [
{}
],
"id": "string",
"profileImage": "string",
"success": true,
"token": "string",
"udid": "string",
"updatedAt": "2016-05-17T21:59:37.512Z",
"username": "string",
"version": 0
}
],
"pictures": [
"string"
],
"postedBy": {
"code": 0,
"createdAt": "2016-05-17T21:59:37.512Z",
"deviceType": "string",
"email": "string",
"fbAccessToken": "string",
"fbId": "string",
"followers": [
{}
],
"followings": [
{}
],
"id": "string",
"profileImage": "string",
"success": true,
"token": "string",
"udid": "string",
"updatedAt": "2016-05-17T21:59:37.512Z",
"username": "string",
"version": 0
},
"success": true,
"text": "string",
"updatedAt": "2016-05-17T21:59:37.512Z",
"userId": "string",
"userIds": [
"string"
],
"version": 0
}
You can see at Post Dto model where User Dto is used, code and success fields are added redundant.
I don't know most probably I got wrong approach. Perhaps, I should use adding global HTTP status response to all returning DTOs.
Can anyone help?
You can have an AppUtil class were you can create your response and set HttpStatus. In your AppUtil class write a method like so :
public static ResponseEntity<ResponseEnvelope> successResponse(Object data,
int messageCode, String message) {
ResponseEnvelope envelope = new ResponseEnvelope(data, true, message,
messageCode);
ResponseEntity<ResponseEnvelope> responseEntity = new ResponseEntity<>(
envelope, HttpStatus.OK);
return responseEntity;
}
In the successResponse method you can set your data object in the ResponseEnvelope which along with the HttpStatus will be sent in the ResponseEntity that you want to return.
Check out my previous answer here
I'm working in a project with Elasticsearch and Spring Data Elasticsearch.
I need to get the mapping of an object type of my index. My #document class looks like:
#Document(indexName = "esbsdocuments", type = ESBSDocumentEls.MAPPING_TYPE)
public class ESBSDocumentEls extends ESBSDomainModel {
...
#Field(type =FieldType.Object, store = false)
private Object metadata;
...
}
If I try to get it via http://xxx:9200/_mapping I can get the mapping for "metadata" field correctly:
...
"metadata": {
"properties": {
"APP": {
"type": "string"
},
"APPDST": {
"type": "string"
},
"APPSUB": {
"type": "string"
},
"COUNTSUB": {
"type": "string"
},
"DOMINIO": {
"type": "string"
},
"DUPLICATE": {
"type": "string"
},
"EXCLUDEFIELDNAMES": {
"type": "string"
},
"FECHA": {
"type": "string"
},
"ID": {
"type": "string"
},
"IDF": {
"type": "string"
},
"IDSUB": {
"type": "string"
},
"LOCALEDATE": {
"type": "string"
},
"MENSAJE": {
"type": "string"
},
"TAMANYO": {
"type": "string"
},
"TIPO": {
"type": "string"
},
"VERSION": {
"type": "string"
}
}
},
...
But when I try it in code with
Map mapping = elasticsearchTemplate.getMapping(ESBSDocumentEls.class);
I can only get:
... (definition of metadata dynamic templates)
metadata={type=object}
...
How can I get the detailed mapping definition using ElasticSearchTemplate or another Spring Data Elasticsearch class??
Thank you very much!
Finally I got a solution.
Instead of using elasticSearchTemplate, I have used the els java api:
GetMappingsResponse res = elasticSearchClient.admin().indices().getMappings(new GetMappingsRequest().indices(indexName)).get();
ImmutableOpenMap<String, MappingMetaData> indexMappings = res.mappings().get(indexName);
MappingMetaData mappingMetaData = indexMappings.get(mappingType);
Map<String, Object> map = mappingMetaData.getSourceAsMap();
Map<String, Object> metadataProperties = (Map<String, Object>) ((Map<String, Object>) map.get("properties")).get("metadata");
Map<String, Object> metadataList = (Map<String, Object>) metadataProperties.get("properties");
This way I can get a Map with all my "metadata" fields. You can't get the api client from the elasticSearchTemplate (https://jira.spring.io/browse/DATAES-124) so you have to inject it :(
I hope this help someone!
You can now use the public <T> Map getMapping(Class<T> clazz) method which is already available in the template. It returns a map of fields and types.
elasticSearchTemplate.getMapping(ESBSDocumentEls.class);