Spring boot swagger multipart with json content and attachment - spring-boot

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.

Related

Spring boot OpenAPI 3 download multipart with JSON content and attachment

I am seeing a 406 error when I try to download a file with Swagger 3.0 schema definition. The schema definition for a multipart file download which looks something like the below.
"get": {
"operationId": "getAttachment",
"summary": "Retrieve attachments to a existing Ticket",
"tags": [
"changeRequest"
],
"parameters": [
{
"required": true,
"name": "id",
"in": "path",
"description": "Identifier of the Change Request",
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"description": "Ok",
"headers": {
},
"content": {
"multipart/form-data": {
"schema": {
"type": "object",
"properties": {
"metadata": {
"$ref": "#/components/schemas/Attachment"
},
"file": {
"type": "string",
"format":"binary",
"description": "Actual File Attachment"
}
}
}
}
}
}
}
}
It generates the following class when built using the swagger plugin which seems to be apt:
public class InlineResponse200 {
#JsonProperty("metadata")
private Attachment metadata = null;
#JsonProperty("file")
private Resource file;
Following is the implementation generated:
#ApiOperation(value = "Retrieve attachments to a existing Ticket", nickname = "getAttachment", notes
= "", response = InlineResponse200.class, tags={ "changeRequest", })
#RequestMapping(value = "/changeRequest/attachment/{id}",
produces = { "multipart/form-data", "application/json" },
method = RequestMethod.GET)
public ResponseEntity<InlineResponse200> getAttachment(#PathVariable("id") String id) {
Attachment lAttachmentMetadata = new Attachment();
lAttachmentMetadata.setDescription("This is a sample description");
lAttachmentMetadata.setSize(2000);
FileSystemResource fileSysResource = new FileSystemResource(new File("C:\\Projects\\Service Assurance\\Chnage Mgmt\\Attachments\\attachment.txt"));
InlineResponse200 responseObject = new InlineResponse200();
responseObject.setFile(fileSysResource);
responseObject.setMetadata(lAttachmentMetadata);
return ResponseEntity.ok().header(HttpHeaders.CONTENT_DISPOSITION,
"attachment; filename=\"" + fileSysResource.getFilename() + "\"").header("Content-Type", "multipart/form-data").body(responseObject);
}
When I invoke the service I see a 406 error returned
Thu Oct 15 03:46:39 IST 2020:DEBUG:<< "{"timestamp":"2020-10-14T22:16:39.258Z","status":406,"error":"Not Acceptable","message":"Could not find acceptable representation","path":"/changeManagement/api/v1/changeRequest/attachment/1234"}"
SOAPUI REST SERVICE TEST
Any help or pointers in the right direction would be much appreciated.

How to parse json with mixed child objects using Sprint Rest Template

I have a json response which looks like below
{
"resourceType": "Topic",
"metadata": {
"lastUpdated": "2016-12-15T14:51:33.490-06:00"
},
"entry": [
{
"resource": {
"resourceType": "Outcome",
"issue": [
{
"response": "error",
"code": "exception"
},
{
"response": "success",
"code": "informational"
},
{
"response": "success",
"code": "informational"
}
]
}
},
{
"resource": {
"resourceType": "Data",
"id": "80",
"subject": {
"reference": "dataFor/80"
},
"created": "2016-06-23T04:29:00",
"status": "current"
}
},
{
"resource": {
"resourceType": "Data",
"id": "90",
"subject": {
"reference": "dataFor/90"
},
"created": "2016-06-23T04:29:00",
"status": "current"
}
}
]
}
Data and Outcome Class extends Resource.
I am using Spring RestTemplate.getForObject(url, someClass). I get below error
has thrown exception, unwinding now
org.apache.cxf.interceptor.Fault: Could not read JSON: Unrecognized field "response" (Class com.model.Resource), not marked as ignorable
at [Source: sun.net.www.protocol.http.HttpURLConnection$HttpInputStream#77a3e67a;
I understand that the json is not getting parsed to the child class of Resource. I want to do something like RestTemplate.getForObject(url, someClass) but this is not supported by java generics (wildcard). Please help
You'll want to use jackson to deserialize to a dynamic type, using resourceType as the field to indicate the actual type. Add these to your Resource class.
#JsonTypeInfo(property = "resourceType", use = Id.NAME)
#JsonSubTypes({ #Type(Data.class),
#Type(Outcome.class)
})
Here is a unit test that will prove out the behavior.
#Test
public void deserializeJsonFromResourceIntoData () throws IOException {
Data data = (Data) new ObjectMapper().readValue("{" +
" \"resourceType\": \"Data\"," +
" \"id\": \"80\"," +
" \"subject\": {" +
" \"reference\": \"dataFor/80\"" +
" }," +
" \"created\": \"2016-06-23T04:29:00\"," +
" \"status\": \"current\"" +
" }", Resource.class);
assertEquals(Integer.valueOf(80), data.getId());
assertEquals("dataFor/80", data.getSubject().getReference());
}
As for the cast, I've done it here just to demonstrate that it works, however, to be truly polymorphic, you probably want to have Resource contain all the behavior you need, and then everything is just a Resource.

How to add http status to all responses dto(data transfer object)?

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

Get the mapping of an Object type field in Spring Data Elasticsearch

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);

json deserializing coming back empty

I took suggestions from here and tried to create a class with objects for the parts I need to parse from my json feed. I then tried to deserialize it before mapping the field contents to my objects but the deserialization returns nothing.
Here is the json feed content:
"data": [
{
"id": "17xxxxxxxxxxxxx_xxxxxxxxxxxxxxxxxxx",
"from": {
"name": "Lxxxxxx",
"category": "Sports league",
"id": "17xxxxxxxxxxxxx"
},
"picture": "http://external.ak.fbcdn.net/safe_image.php?d=AQB4GscSy-2RHY_0&w=130&h=130&url=http\u00253A\u00252F\u00252Fwww.ligabbva.com\u00252Fquiz\u00252Farchivos\u00252Fbenzema-quiz-facebook.png",
"link": "http://www.xxxxxva.com/quiz/index.php?qid=34",
"source": "http://www.lxxxxva.com/modulos/redirectQuiz.php?name=benzema&q=34&time=1312827103",
"name": "DEMUESTRA CU\u00c1NTO SABES SOBRE... BENZEMA",
"caption": "www.xxxxxva.com",
"description": "Demuestra cu\u00e1nto sabes sobre Karim Benzema, delantero del Real Madrid.",
"icon": "http://static.ak.fbcdn.net/rsrc.php/v1/yj/r/v2OnaTyTQZE.gif",
"type": "video",
"created_time": "2011-08-08T18:11:54+0000",
"updated_time": "2011-08-08T18:11:54+0000",
"likes": {
"data": [
{
"name": "Jhona Arancibia",
"id": "100000851276736"
},
{
"name": "Luis To\u00f1o",
"id": "100000735350531"
},
{
"name": "Manuel Raul Guerrero Cumbicos",
"id": "100001485973224"
},
{
"name": "Emmanuel Gutierrez",
"id": "100000995038988"
}
],
"count": 127
},
"comments": {
"count": 33
}
},
{
"id": "17xxxxxxxxxxxxxxxx_xxxxxxxxxxxxx",
"from": {
"name"
and here is the code I am trying. I tried to also use the jsontextreader and loop through each tokentype and check each one and then read the value but this looks like it will turn out to be tedious.
....
//fetch the content
responsedata = reader.readtoend()
......
dim pp as facedata = JsonConvert.DeserializeObject(of faceData)(responsedata)
console.writeline(pp.id.tostring)
and the class objects
Public Class faceData
Public Property id() as string
Get
Return f_id
End Get
Set(ByVal value as string)
f_id =value
End Set
End Property
Private f_id as string
......
any response appreciated.
Does anyone have links to full code where it actually works using vb.net.

Resources