I am using the openapi-generator to create a multipart/form-data. In an ideal situation I should be able to upload a file, and specify in the options what should happen with the file.
I would like the options to be an object. For one or another reason, this does not seem to work. The openapi-generator generates the API interface, etc, but it does not generate the model for the options object.
I can specify the options individually, but I prefer the options to be an object, with the necessary model to it. I believe this provides a more structured way to deal with the options.
My yaml file looks like this (I specified what works and what doesn't work):
/fileuploadwithoptions:
post:
summary: Upload a file and processes it according to the options specified.
requestBody:
content:
multipart/form-data:
schema:
required:
- file
type: object
properties:
file:
type: string
format: binary
option1: <-- this works
type: string
description: A descriptions for option 1.
options: <-- this does not work
#type: application/json
type: object
description: The options.
properties:
option1:
type: string
description: A descriptions for option 1.
option2:
type: string
description: A descriptions for option 2.
encoding:
file:
contentType: application/octet-stream
This generates the following API:
/**
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech) (6.0.1).
* https://openapi-generator.tech
* Do not edit the class manually.
*/
package com.teradact.tokenizerplusserver.api;
import com.teradact.tokenizerplusserver.model.FileuploadwithoptionsPostRequestOptions;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.Parameters;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.multipart.MultipartFile;
import javax.validation.Valid;
import javax.validation.constraints.*;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import javax.annotation.Generated;
#Generated(value = "org.openapitools.codegen.languages.SpringCodegen", date = "2022-10-11T08:11:04.909499+02:00[Europe/Brussels]")
#Validated
#Tag(name = "fileuploadwithoptions", description = "the fileuploadwithoptions API")
public interface FileuploadwithoptionsApi {
default Optional<NativeWebRequest> getRequest() {
return Optional.empty();
}
/**
* POST /fileuploadwithoptions : Upload a file and processes it according to the options specified.
*
* #param file (required)
* #param option1 A descriptions for option 1. (optional)
* #param options (optional)
* #return The file. (status code 200)
* or bad input parameter (status code 400)
*/
#Operation(
operationId = "fileuploadwithoptionsPost",
summary = "Upload a file and processes it according to the options specified.",
responses = {
#ApiResponse(responseCode = "200", description = "The processed file.", content = {
#Content(mediaType = "application/octet-stream", schema = #Schema(implementation = org.springframework.core.io.Resource.class))
}),
#ApiResponse(responseCode = "400", description = "bad input parameter")
}
)
#RequestMapping(
method = RequestMethod.POST,
value = "/fileuploadwithoptions",
produces = { "application/octet-stream" },
consumes = { "multipart/form-data" }
)
default ResponseEntity<org.springframework.core.io.Resource> fileuploadwithoptionsPost(
#Parameter(name = "file", description = "", required = true) #RequestPart(value = "file", required = true) MultipartFile file,
#Parameter(name = "option1", description = "A descriptions for option 1.") #Valid #RequestParam(value = "option1", required = false) String option1,
#Parameter(name = "options", description = "") #Valid #RequestParam(value = "options", required = false) FileuploadwithoptionsPostRequestOptions options
) {
return new ResponseEntity<>(HttpStatus.NOT_IMPLEMENTED);
}
}
This gives however the following error:
"Cannot resolve symbol 'FileuploadwithoptionsPostRequestOptions", since the model for the object is simply not created.
Thanks in advance for pointing out where I am wrong!
The interface has the name 'FileuploadwithoptionsApi', YAML file has /fileuploadwithoptions and the response entity has name fileuploadwithoptionsPost but the RequestParam has object 'FileuploadwithoptionsPostRequestOptions' is it because of this it cannot resolve the symbol.
#Parameter(name = "options", description = "") #Valid #RequestParam(value = "options", required = false) FileuploadwithoptionsPostRequestOptions options)
Is the object 'FileuploadwithoptionsPostRequestOptions' instantiated or does it have a similar class or interface matching the object present in #Request Param.
Ok, you have imported the required class for the object try using #RequestPart as you have used for file. Try if it works.
Related
I'm trying to build a spring controller which essentially acts as a reverse-proxy for a geoserver instance.
For example if the client wants to access geoserver/wms?PARAM1=foo&PARAM2=bar, the controller will simply forward the request to the actual geoserver instance and serve back the response. In my case, geoserver either returns an XML payload or an image.
When testing this controller with an URL which returns an image, I am able to process the initial client request, forward it to geoserver and then process it but I'm getting the following error when serving the response to the client :
There was an unexpected error (type=Internal Server Error, status=500).
No converter for [class [B] with preset Content-Type 'image/png'
org.springframework.http.converter.HttpMessageNotWritableException: No converter for [class [B] with preset Content-Type 'image/png'
Full stack trace
Here is the controller class:
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.rest.webmvc.BasePathAwareController;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.MultiValueMap;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.reactive.function.client.WebClient;
import javax.servlet.http.HttpServletRequest;
import java.util.List;
import java.util.Map;
#Slf4j
#RestController
#BasePathAwareController
public class GeoServerProxyController {
//only used to check which converters are present
#Autowired
List<HttpMessageConverter<?>> converters;
#RequestMapping(value = "/geoserver/**")
#ResponseBody
public ResponseEntity<byte[]> forwardRequest(#RequestParam MultiValueMap<String, String> requestParams, #RequestHeader Map<String, String> headers, HttpServletRequest request) {
WebClient client = WebClient.builder()
.baseUrl("http://127.0.0.1:8090/geoserver/")
.build();
String url = new AntPathMatcher().extractPathWithinPattern("/geoserver/**", request.getRequestURI());
WebClient.ResponseSpec response = client.get()
.uri(uriBuilder -> uriBuilder
.path(url)
.queryParams(requestParams)
.build())
.headers(httpHeaders -> {
headers.forEach(httpHeaders::set);
})
.retrieve();
HttpHeaders responseHeaders = response.toBodilessEntity().map(HttpEntity::getHeaders).block();
byte[] responseBody = response.bodyToMono(byte[].class).block();
return ResponseEntity.ok().headers(responseHeaders).body(responseBody);
}
As advised in another thread, I have tried registering a byte array http message converter, which I was able to confirm is added to the list of http message converters.
#Configuration
public class WebMVCConfig implements WebMvcConfigurer {
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
final ByteArrayHttpMessageConverter arrayHttpMessageConverter = new ByteArrayHttpMessageConverter();
final List<MediaType> list = new ArrayList<>();
list.add(MediaType.IMAGE_JPEG);
list.add(MediaType.IMAGE_PNG);
list.add(MediaType.APPLICATION_OCTET_STREAM);
arrayHttpMessageConverter.setSupportedMediaTypes(list);
converters.add(arrayHttpMessageConverter);
}
}
This resulted in the same error.
I have also tried using InputStreamResource as a return type, as recommended by this article. It resulted in the same kind of error except with InputStreamResource instead of class [B].
I have also tried adding the following annotation to my controller method (which wasn't optimal as I would prefer not specifying a constant content type) :
#RequestMapping(value = "/geoserver/**", produces=MediaType.IMAGE_PNG_VALUE)
This also results in the exact same error.
I was not able to find a solution to this problem in other threads or in spring web documentation. The most common problem that somewhat resembles this deals with a "Content-Type=null" header, which is not my case.
Does anyone know how to solve this error ? Alternatively, is there a better way to serve distant image files through a Spring controller ?
The #ResponseBody annotation is used to serialize a Java object to the response (typically as JSON or XML).
Here you don't want to convert anything, just send raw binary content. Remove the annotation.
You should also change #RestController to #Controller, because #RestController automatically adds #ResponseBody.
It seems like the problem you are facing is due to #BasePathAwareController , remove it and you are good to go.
Description:
Based on the stack trace shared:
it occurs that SpringDataRest is trying to map an appropriate object to the content-type which it cannot find. hence throwing an generic 500 exception.
For those who are interested: i've had same issue and solve it like this.
This code is not the final, only for test in my case.
Angular frontend openlayers
Tile layer : i defined custom tileLoadFunction (example can be found in openlayers docs), url call my springboot backend api witch is proxying geoserver backend, a jwt token is sent in the fetch header (for security purpose, not mandatory)
new TileLayer({
source: new TileWMS({
url: 'http://localhost:8080/api/v1/map/wms',
tileLoadFunction: (tile, src) => {
const retryCodes = [408, 429, 500, 502, 503, 504];
const retries: any = {};
const image: any = (tile as ImageTile).getImage();
fetch(src, {
headers: {
Authorization: 'Bearer ' + this.oauthService.getAccessToken(),
},
})
.then((response) => {
if (retryCodes.includes(response.status)) {
retries[src] = (retries[src] || 0) + 1;
if (retries[src] <= 3) {
setTimeout(() => tile.load(), retries[src] * 1000);
}
return Promise.reject();
}
return response.blob();
})
.then((blob) => {
const imageUrl = URL.createObjectURL(blob);
image.src = imageUrl;
setTimeout(() => URL.revokeObjectURL(imageUrl), 5000);
})
.catch(() => tile.setState(3)); // error
},
params: {
LAYERS: 'TEST:section_33',
TILED: true,
},
serverType: 'geoserver',
transition: 0,
}),
Springboot backend
I have a RestController endpoint witch is close to the first post, and everythind is ok for me : wms geoserver tiles are called by my frontend, the bakend intercept these calls and make geoserver request and finally send back the tiles to the frontend and the wms layer appears to the map
#RestController
#RequestMapping("/api/v1/map")
#Slf4j
public class MapController {
#GetMapping(value = "/wms")
public ResponseEntity<byte[]> getWmsTile(#RequestParam MultiValueMap<String, String> requestParams, #RequestHeader Map<String, String> headers, HttpServletRequest request) throws IOException {
WebClient client = WebClient.builder()
.baseUrl("http://localhost:7070/geoserver/TEST/wms")
.build();
WebClient.ResponseSpec response = client.get()
.uri(uriBuilder -> uriBuilder
.path("")
.queryParams(requestParams)
.build())
.headers(httpHeaders -> {
headers.forEach(httpHeaders::set);
})
.retrieve();
HttpHeaders responseHeaders = response.toBodilessEntity().map(HttpEntity::getHeaders).block();
byte[] responseBody = response.bodyToMono(byte[].class).block();
return ResponseEntity.ok().contentType(MediaType.IMAGE_PNG).body(responseBody);
}
}
Java 11 and Spring Boot 2.5.x here. I understand that I can set up a controller to return the contents of a file like so:
#GetMapping(
value = "/get-image-with-media-type",
produces = MediaType.IMAGE_JPEG_VALUE
)
public #ResponseBody byte[] getImageWithMediaType() throws IOException {
InputStream in = getClass()
.getResourceAsStream("/path/to/some/image.jpg");
return IOUtils.toByteArray(in);
}
But what if I want to control the name of the file that is sent back? For instance, on the server-side the file name might be stored as "image.jpg" but say I want to have it returned as "<userId>-<YYYY-mm-DD>-image.jpg", where <userId> is the user ID of the authenticated user making the request, and where <YYYY-mm-DD> is the date the request is made at?
For instance, if user 123 made the request on 12/10/2021, the file would be downloaded as "123-2021-12-10-image.jpg" and if user 234 made the request on 1/17/2022 it would be downloaded as "234-2022-01-17-image.jpg". Is this possible to control on the Spring/Java/server-side, or is it up to the HTTP client (browser, PostMan, whatever) to decide on the file name?
Please try this, comments inline:
package com.example;
import java.io.IOException;
import java.security.Principal;
import java.util.Date;
import org.apache.commons.io.IOUtils;
import org.springframework.core.io.FileSystemResource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
#Controller
public class SomeController {
#GetMapping(
value = "/get-image-with-media-type",
produces = MediaType.IMAGE_JPEG_VALUE
) // we can inject user like this (can be null, when not secured):
public ResponseEntity<byte[]> getImageWithMediaType(Principal user) throws IOException {
// XXXResource is the "spring way", FileSystem- alternatively: ClassPath-, ServletContext-, ...
FileSystemResource fsr = new FileSystemResource("/path/to/some/image.jpg");
HttpHeaders responseHeaders = new HttpHeaders();
responseHeaders.set(HttpHeaders.CONTENT_DISPOSITION,
// for direct downlad "inline", for "save as" dialog "attachment" (browser dependent)
// filename placeholders: %1$s: user id string, 2$tY: year 4 digits, 2$tm: month 2 digits, %2$td day of month 2 digits
String.format("inline; filename=\"%1$s-%2$tY-%2$tm-%2$td-image.jpg\"",
// user name, current (server) date:
user == null ? "anonymous" : user.getName(), new Date()));
// and fire:
return new ResponseEntity<>(
IOUtils.toByteArray(fsr.getInputStream()),
responseHeaders,
HttpStatus.OK
);
}
}
Relevant reference:
Method Arguments(Principal)
Formatter
ResponseEntity(+ headers sample)
RFC2616 (Section 19.5.1 Content-Disposition)
With ContentDisposition it can look (just) like:
responseHeaders.setContentDisposition(
ContentDisposition
.inline()// or .attachment()
.filename(// format file name:
String.format(
"%1$s-%2$tY-%2$tm-%2$td-image.jpg",
user == null ? "anonymous" : user.getName(),
new Date()
)
)
.build()
);
TYVM: How to set 'Content-Disposition' and 'Filename' when using FileSystemResource to force a file download file?
I am creating an application where I can create a car object.
Car class without setters and getters:
package github.KarolXX.demo.model;
import org.hibernate.annotations.GenericGenerator;
import javax.persistence.*;
import javax.validation.constraints.NotBlank;
import java.time.LocalDateTime;
#Entity
#Table(name = "cars")
public class Car {
#Id
#GeneratedValue(generator = "inc")
#GenericGenerator(name = "inc", strategy = "increment")
private int id;
#NotBlank(message = "car name`s must be not empty")
private String name;
private LocalDateTime productionYear;
private boolean tested;
public Car() {
}
public Car(#NotBlank(message = "car name`s must be not empty") String name, LocalDateTime productionYear) {
this.name = name;
this.productionYear = productionYear;
}
}
I would like to know how to test the POST method in Spring. Below is a code snippet for the POST method which just create Java object named Car (first snippet)
#PostMapping("/cars")
ResponseEntity<Car> createCar(#RequestBody #Valid Car newCar) {
logger.info("Creating new car");
var result = repository.save(newCar);
return ResponseEntity.created(URI.create("/" + result.getId())).body(result);
}
I am trying to test it this way:
package github.KarolXX.demo.controller;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import github.KarolXX.demo.TestConfiguration;
import github.KarolXX.demo.model.Car;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.MediaType;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.springframework.test.web.servlet.result.MockMvcResultHandlers;
import org.springframework.test.web.servlet.result.MockMvcResultMatchers;
import java.time.LocalDateTime;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
#SpringBootTest
#AutoConfigureMockMvc
#ActiveProfiles("integration")
class CarControllerIntegrationServerSideTest {
#Autowired
private MockMvc mockMvc;
#Test
public void httpPost_createsNewCar_returnsCreatedCar() throws Exception {
//given
Car car = new Car("second server side test", LocalDateTime.parse("2021-01-02T13:34:54"));
//when + then
mockMvc.perform(post("/cars")
.content(asJsonString(car))
.contentType(MediaType.APPLICATION_JSON)
.accept(MediaType.APPLICATION_JSON))
.andDo(MockMvcResultHandlers.print())
.andExpect(status().isCreated());
}
public static String asJsonString(final Car objectCar) {
try {
return new ObjectMapper().writeValueAsString(objectCar);
} catch (JsonProcessingException e) {
e.printStackTrace();
}
return null;
}
}
I did it based on this article
Unfortunately my test does not pass. Although in the logs I can see that the request body and the request headers are set correctly, I get the status 400
2021-03-26 12:05:01.532 WARN 10696 --- [ main] .w.s.m.s.DefaultHandlerExceptionResolver : Resolved [org.springframework.http.converter.HttpMessageNotReadableException: JSON parse error: Expected array or string.; nested exception is com.fasterxml.jackson.databind.exc.MismatchedInputException: Expected array or string.
at [Source: (PushbackInputStream); line: 1, column: 59] (through reference chain: github.KarolXX.demo.model.Car["productionYear"])]
MockHttpServletRequest:
HTTP Method = POST
Request URI = /cars
Parameters = {}
Headers = [Content-Type:"application/json;charset=UTF-8", Accept:"application/json", Content-Length:"279"]
Body = {"id":0,"name":"second server side test","productionYear":{"month":"JANUARY","dayOfWeek":"SATURDAY","dayOfYear":2,"nano":0,"year":2021,"monthValue":1,"dayOfMonth":2,"hour":13,"minute":34,"second":54,"chronology":{"id":"ISO","calendarType":"iso8601"}},"tested":false,"brand":null}
Session Attrs = {}
Handler:
Type = github.KarolXX.demo.controller.CarController
Method = github.KarolXX.demo.controller.CarController#createCar(Car)
Async:
Async started = false
Async result = null
Resolved Exception:
Type = org.springframework.http.converter.HttpMessageNotReadableException
ModelAndView:
View name = null
View = null
Model = null
FlashMap:
Attributes = null
MockHttpServletResponse:
Status = 400
Error message = null
Headers = []
Content type = null
Body =
Forwarded URL = null
Redirected URL = null
Cookies = []
MockHttpServletRequest:
HTTP Method = POST
Request URI = /cars
Parameters = {}
Headers = [Content-Type:"application/json;charset=UTF-8", Accept:"application/json", Content-Length:"279"]
Body = {"id":0,"name":"second server side test","productionYear":{"month":"JANUARY","dayOfWeek":"SATURDAY","dayOfYear":2,"nano":0,"year":2021,"monthValue":1,"dayOfMonth":2,"hour":13,"minute":34,"second":54,"chronology":{"id":"ISO","calendarType":"iso8601"}},"tested":false,"brand":null}
Session Attrs = {}
Handler:
Type = github.KarolXX.demo.controller.CarController
Method = github.KarolXX.demo.controller.CarController#createCar(Car)
Async:
Async started = false
Async result = null
Resolved Exception:
Type = org.springframework.http.converter.HttpMessageNotReadableException
ModelAndView:
View name = null
View = null
Model = null
FlashMap:
Attributes = null
MockHttpServletResponse:
Status = 400
Error message = null
Headers = []
Content type = null
Body =
Forwarded URL = null
Redirected URL = null
Cookies = []
java.lang.AssertionError: Status expected:<201> but was:<400>
Expected :201
Actual :400
I have no idea what could be wrong but when I change this line of code mockMvc.perform(post("/cars") to this one mockMvc.perform(post("/") then I got status 404
I have solved the problem, but I don't know why the previous version is not working. I removed the static method from the test class and changed the POST method checking: namely I created a variable holding JSON as a String and passed it to the content method. In previous version this String was returned by ObjectMapper().writeValueAsString() in static method. My modified test class:
#Test
public void httpPost_createsNewCar_returnsCreatedCar() throws Exception {
//given
//Car car = new Car("second server side test", LocalDateTime.parse("2021-01-02T13:34:54"));
String id = String.valueOf(repo.getSize());
String jsonString = new JSONObject()
.put("id", id)
.put("tested", false)
.put("productionYear", "2017-06-18T12:12:12")
.put("name", "Toyota")
.toString();
//when + then
mockMvc.perform(post("/cars")
.content(jsonString)
.contentType(MediaType.APPLICATION_JSON)
.accept(MediaType.APPLICATION_JSON))
.andDo(MockMvcResultHandlers.print())
.andExpect(status().isCreated());
}
So we're trying to use the OpenAPI generator and so far we've had mixed results.
Steps to reproduce:
Download openapi generator jar: wget https://repo1.maven.org/maven2/org/openapitools/openapi-generator/4.0.3/openapi-generator-4.0.3.jar
Generate springboot server for the petstore example: java -jar openapi-generator-cli-4.0.3.jar generate -g spring -i https://raw.githubusercontent.com/OAI/OpenAPI-Specification/master/examples/v3.0/petstore.yaml
You'll end up with controller classes that look like this:
package org.openapitools.api;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.context.request.NativeWebRequest;
import java.util.Optional;
#javax.annotation.Generated(value = "org.openapitools.codegen.languages.SpringCodegen", date = "2019-08-06T15:08:49.070+01:00[Europe/London]")
#Controller
#RequestMapping("${openapi.swaggerPetstore.base-path:/v1}")
public class PetsApiController implements PetsApi {
private final NativeWebRequest request;
#org.springframework.beans.factory.annotation.Autowired
public PetsApiController(NativeWebRequest request) {
this.request = request;
}
#Override
public Optional<NativeWebRequest> getRequest() {
return Optional.ofNullable(request);
}
}
/**
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech) (4.0.3).
* https://openapi-generator.tech
* Do not edit the class manually.
*/
package org.openapitools.api;
import org.openapitools.model.Error;
import org.openapitools.model.Pet;
import io.swagger.annotations.*;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RequestPart;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.multipart.MultipartFile;
import javax.validation.Valid;
import javax.validation.constraints.*;
import java.util.List;
import java.util.Map;
import java.util.Optional;
#javax.annotation.Generated(value = "org.openapitools.codegen.languages.SpringCodegen", date = "2019-08-06T15:08:49.070+01:00[Europe/London]")
#Validated
#Api(value = "pets", description = "the pets API")
public interface PetsApi {
default Optional<NativeWebRequest> getRequest() {
return Optional.empty();
}
#ApiOperation(value = "Create a pet", nickname = "createPets", notes = "", tags={ "pets", })
#ApiResponses(value = {
#ApiResponse(code = 201, message = "Null response"),
#ApiResponse(code = 200, message = "unexpected error", response = Error.class) })
#RequestMapping(value = "/pets",
produces = { "application/json" },
method = RequestMethod.POST)
default ResponseEntity<Void> createPets() {
return new ResponseEntity<>(HttpStatus.NOT_IMPLEMENTED);
}
#ApiOperation(value = "List all pets", nickname = "listPets", notes = "", response = Pet.class, responseContainer = "List", tags={ "pets", })
#ApiResponses(value = {
#ApiResponse(code = 200, message = "A paged array of pets", response = Pet.class, responseContainer = "List"),
#ApiResponse(code = 200, message = "unexpected error", response = Error.class) })
#RequestMapping(value = "/pets",
produces = { "application/json" },
method = RequestMethod.GET)
default ResponseEntity<List<Pet>> listPets(#ApiParam(value = "How many items to return at one time (max 100)") #Valid #RequestParam(value = "limit", required = false) Integer limit) {
getRequest().ifPresent(request -> {
for (MediaType mediaType: MediaType.parseMediaTypes(request.getHeader("Accept"))) {
if (mediaType.isCompatibleWith(MediaType.valueOf("application/json"))) {
ApiUtil.setExampleResponse(request, "application/json", "null");
break;
}
}
});
return new ResponseEntity<>(HttpStatus.NOT_IMPLEMENTED);
}
#ApiOperation(value = "Info for a specific pet", nickname = "showPetById", notes = "", response = Pet.class, tags={ "pets", })
#ApiResponses(value = {
#ApiResponse(code = 200, message = "Expected response to a valid request", response = Pet.class),
#ApiResponse(code = 200, message = "unexpected error", response = Error.class) })
#RequestMapping(value = "/pets/{petId}",
produces = { "application/json" },
method = RequestMethod.GET)
default ResponseEntity<Pet> showPetById(#ApiParam(value = "The id of the pet to retrieve",required=true) #PathVariable("petId") String petId) {
getRequest().ifPresent(request -> {
for (MediaType mediaType: MediaType.parseMediaTypes(request.getHeader("Accept"))) {
if (mediaType.isCompatibleWith(MediaType.valueOf("application/json"))) {
ApiUtil.setExampleResponse(request, "application/json", "null");
break;
}
}
});
return new ResponseEntity<>(HttpStatus.NOT_IMPLEMENTED);
}
}
So my question is this: everything I can find implies that Spring Controllers are multi-threaded and may handle multiple requests at once. Is the code generator broken? Am I interpreting this completely wrong?
The constructor for PetsApiController gives me pause. If it's being autowired once per request then that implies that there's only one per request?
#org.springframework.beans.factory.annotation.Autowired
public PetsApiController(NativeWebRequest request) {
this.request = request;
}
The swagger code generators are notoriously bad, friend of mine said that the generators have the breadth but not the depth. You can generate skeletons for all sorts of languages and frameworks, but they have severe limitations. For example, try generating a good skeleton from a SwaggerDoc with Page<Something> or other Generics. I would very sadly say that they have almost no utility, and the tools tend to only work reliably the other way around, that is coding first and then generating the SwaggerDoc.
A place I worked at had a great concept I really liked whereby you would design your API first before implementing it, which sounds like you are trying to do. Some IDEs even support generated code, and there are plugins for build tools such as maven gradle etc to generate the code from your yaml.
But in practice I spent days trying to get desirable results from these tools and gave up. I think the real problem is Swagger/OpenAPI is still heavily viewed as documentation tool, not a design tool. I also think that trying to create an all encompassing project generator was setup to fail from the get-go.
I myself tried to customize the moustache templates which the generator used, but generics in Java were a nightmare, and you couldn't get the proper workflow working whereby I would change the SwaggerDoc and then update my code, as my approach was to generate an interface, and then implement that interface, but Annotations weren't inherited so I had to duplicate all the code anway meaning there was no benefit.
I an trying to integrate a file upload service, which is registered with a Eureka discovery infrastructure.
My Service, say /myfile/upload is having below 6 parameters, below is the YML:
/myfile/upload:
put:
operationId: "uploadUsingPUT"
consumes:
- "multipart/form-data"
produces:
- "*/*"
parameters:
- name: "file"
in: "formData"
required: true
type: "file"
- name: "filename"
in: "formData"
required: true
type: "string"
- name: "path"
in: "formData"
required: true
type: "string"
- name: "header1"
in: "header"
required: true
type: "string"
- name: "header2"
in: "header"
required: false
type: "string"
allowEmptyValue: true
responses:
200:
description: "OK"
400:
description: "Bad Request"
I have created a client interface for this service, below is the API that I created:
import java.io.File;
import java.util.Map;
import org.springframework.util.MultiValueMap;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RequestPart;
#org.springframework.cloud.netflix.feign.FeignClient(value = "SERVICE-NAME", configuration = {
com.MyConfiguration.class})
public interface UploadControllerAPINew extends ApiClient.Api {
#RequestMapping(value = "/myfile/upload",
method = RequestMethod.PUT,
produces = "*/*",
consumes = "multipart/form-data"
)
FileUploadResponse uploadUsingPUT(#RequestPart("file") File file,
#RequestParam("filename") String filename, #RequestParam("path") String path,
#RequestHeader("header1") String header1,
#RequestHeader("header2") String header2);
#RequestMapping(value = "/myfile/upload",
method = RequestMethod.PUT,
produces = "*/*",
consumes = "multipart/form-data"
)
FileUploadResponse uploadUsingPUT1(#RequestBody Map<String, ?> formParams,
#RequestHeader("header1") String header1,
#RequestHeader("header2") String header2);
#RequestMapping(value = "/myfile/upload",
method = RequestMethod.PUT,
produces = "*/*",
consumes = "multipart/form-data"
)
FileUploadResponse uploadUsingPUT2(#RequestPart("file") byte[] file,
#RequestParam("filename") String filename, #RequestParam("path") String path,
#RequestHeader("header1") String header1,
#RequestHeader("header2") String header2);
}
to provide it with an encoder, I have added below encoder:
#Bean
public Encoder feignEncoder() {
ObjectFactory<HttpMessageConverters> objectFactory = () ->
new HttpMessageConverters(new FormHttpMessageConverter());
// return new SpringEncoder(objectFactory);
return new FormEncoder(new SpringEncoder(objectFactory));
}
still I am getting exceptions with all the three approaches:
uploadUsingPUT:
Could not write request: no suitable HttpMessageConverter found for
request type [java.io.File] and content type [multipart/form-data]
uploadUsingPUT1:
Could not write request: no suitable HttpMessageConverter found for
request type [java.util.LinkedHashMap] and content type
[multipart/form-data]
uploadUsingPUT2:
Required request part 'file' is not present
PLEASE SUGGEST
This issue seems to be resolved now, I was on 2.0.x version for feign-form, when I upgraded to 3.4.1 it started working.