Spring-Boot Post-Mapping with MultipartFile and Json Data - json

I want to create a REST API to place Files using Spring-Boot. Together with the file I need to get some Metadata which I would like to get as JSON class. At fist I created a class for the metadata:
#Getter
#Setter
#ToString
#RequiredArgsConstructor
#Entity
#Table(name = FileData.TABLE_NAME)
public class FileData {
public static final String TABLE_NAME = "file_data";
private static final long serialVersionUID = 1342709296259278462L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#GenericGenerator(name = "native", strategy = "native")
#Column(updatable = false)
private Long id;
#Column(updatable = false)
private String userId;
#Column(updatable = false, nullable = false)
private String filename;
private String description;
}
Then I created an API:
#RequestMapping("/files")
public interface FileApi {
#Operation(summary = "Add a new File and return its id.", tags = {"File Upload"})
#PostMapping(path = "/upload",
consumes = {MediaType.MULTIPART_FORM_DATA})
ResponseEntity<String> addFile(#RequestPart(value = "file") final MultipartFile file, #RequestBody FileData fileData, #Context final HttpServletRequest request);
}
I also implemented the method in my Controller. Now I am failing to implement a test for this method. I did following:
#Test
public void uploadFile_success() throws Exception {
final MockMultipartFile file = new MockMultipartFile("file", "test.zip", "text/plain", "test".getBytes());
FileData fileData = new FileData();
fileData.setFilename("bla.zip");
fileData.setDescription("A very nice File");
String address = "/files/upload";
mockMvc.perform(MockMvcRequestBuilders.multipart(address)
.file(file)
.content(MAPPER.writeValueAsString(fileData))
.contentType(MediaType.MULTIPART_FORM_DATA))
.andExpect(status().isOK())
.andExpect(content().string("OK"))
.andReturn();
}
When I run the test, I get HTTP error code 415 and the following error message:
Content type 'multipart/form-data' not supported
Regarding Swagger UI the Request Body shall have multipart/fomr-data. I also removed the contentType call (with same result) and changed it to "application/json" (same result, but with this content type).
Can andybody tell me what content type I have to set?
I could run the test with contentType "application/json" by removing the consumes entry in PostMapping, but in Swagger UI I have then to place the data of the file in a string field in JSON data and cannot transfer a binary file. I think this is not the right way to do it...

Related

How to GET data in the JSON format form the DB Repository

I have this JPA Class, where I have 3 columns id, name and date. The Database is already filled with data, where each entry has an id.
#Data
#Entity
#Table(name = "TEST", schema = "TESTSCHEMA")
public class TestDataJpaRecord implements Serializable {
private static final long serialVersionUID = 1L;
TestDataJpaRecord(){
// default constructor
}
public TestDataJpaRecord(
String name,
Date date,
){
this.name = name;
this.date = date;
}
#Id
#Column(name = "ID", nullable = false)
#GeneratedValue(strategy = GenerationType.SEQUENCE,
generator = "TEST_SEQUENCE")
#SequenceGenerator(
sequenceName = "TEST_SEQUENCE", allocationSize = 1,
name = "TEST_SEQUENCEx")
private Long id;
#Column(name = "NAME")
private String name;
#Temporal(TemporalType.TIMESTAMP)
#Column(name = "DATE")
private Date date;
}
I created a JPA repository for all the data.
public interface TestDataJpaRecordRepository extends JpaRepository<TestDataJpaRecord, Long> {
}
I want to get the data from the DB in a JSON format.
Here is my Rest GET Api. Here I return the data as a string just, but I want to return them as JSON.
#GetMapping(value = "data/{id}")
private ResponseEntity<?> getDataFromTheDB(#PathVariable("id") Long id) {
// get one entry form the DB
TestDataJpaRecord testDataJpaRecord =testDataJpaRecordRepository.findOne(id);
// Here I want to return a JSON instead of a String
return new ResponseEntity<>(testDataJpaRecord.toString(), HttpStatus.OK);
}
Any idea on how I could return the data as JSON and not as a string from the DB?
I would very very much appreciate any suggestion.
If you have Jackson on the classpath which you should if you have used the spring-boot-starter-web then simply:
#GetMapping(value = "data/{id}")
private ResponseEntity<TestDataJpaRecord> getDataFromTheDB(#PathVariable("id") Long id) {
TestDataJpaRecord testDataJpaRecord =testDataJpaRecordRepository.findOne(id);
return new ResponseEntity.ok(testDataJpaRecord);
}
This assumes you have annoted your controller with #RestController rather than #Controller. If not then you can either do that or, annotate your controller method with #ResponseBody.
With Spring Data's web support enabled (which it should be by default with Spring Boot) then you can also simplify as below:
#GetMapping(value = "data/{id}")
private ResponseEntity<TestDataJpaRecord>
getDataFromTheDB(#PathVariable("id") TestDataJpaRecord record) {
return new ResponseEntity.ok(record);
}
See:
https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#core.web.basic.domain-class-converter

How to solve "Error while extracting response for type [class com.*" in Spring Boot?

You might want to skip to my UPDATE 2 bellow
I have a RestController that works, because when I access it directly from the browser, it returns a JSON response. However, when I send a request from a Service in a different bounded context, I get the error:
{"timestamp":1579095291446,"message":"Error while extracting response for type
[class com.path.to.contexttwo.client.dto.WorkerDetails] and content type [application/json]; nested exception is
org.springframework.http.converter.HttpMessageNotReadableException: JSON parse error:
Unexpected character ('<' (code 60)): expected a valid value (JSON String, Number, Array, Object or token 'null', 'true' or 'false');
nested exception is com.fasterxml.jackson.core.JsonParseException:
Unexpected character ('<' (code 60)):
expected a valid value (JSON String, Number, Array, Object or token 'null', 'true' or 'false')\n at [Source: (PushbackInputStream);
line: 1, column: 2]","details":"uri=/context-two/register-new"}
Here is my code:
RestController
package com.path.to.contextone.aplication.presentation;
#RestController
#RequestMapping(path = "/iacess", produces = "application/json")
#CrossOrigin(origins = "*")
public class IAccessRestController {
UserRepository userRepo;
IAcessService iaccessService;
EntityLinks entityLinks;
#Autowired
public IAccessRestController(
UserRepository userRepo,
IAcessService iaccessService,
EntityLinks entityLinks) {
this.userRepo = userRepo;
this.iaccessService= iaccessService;
this.entityLinks = entityLinks;
}
#GetMapping("/get-worker-details/{userName}")
public WorkerDetails getWorkerDetails(#PathVariable String userName) {
User user = userRepo.findByUsername(userName);
WorkerDetails workerDetails = new WorkerDetails();
workerDetails.setUserId(userId);
workerDetails.setGender(user.gender());
workerDetails.setFirstName(user.getFirstName());
workerDetails.setLastName(user.getLastName());
workerDetails.setPhoneNumber(user.getPhoneNumber());
if (workerDetails != null) {
return workerDetails;
}
return null;
}
}
RestClient
package com.path.to.contexttwo.client;
// imports omitted, as well as other code
#Service
public class IAcessRestClientImpl implements IAcessRestClient {
private final RestTemplate restTemplate;
#Autowired
public IAcessRestClientImpl(
final RestTemplate restTemplate
) {
this.restTemplate = restTemplate;
}
#Override
public WorkerDetails getWorkerDetailsByName(final String userName) throws URISyntaxException {
Map<String,String> urlVariables = new HashMap<>();
urlVariables.put("userName", userName);
return restTemplate.getForObject(
"http://localhost:8080/iacess/get-worker-details/{userName}",
WorkerDetails.class,
urlVariables
);
}
}
Config
package com.path.to.contexttwo.configuration;
#Configuration
#EnableWebMvc
public class RestClientConfig {
#Bean
public RestTemplate restTemplate() {
final RestTemplate restTemplate = new RestTemplate();
List<HttpMessageConverter<?>> messageConverters = new ArrayList<>();
MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
List<MediaType> mediaTypes = new ArrayList<MediaType>();
mediaTypes.add(MediaType.APPLICATION_JSON);
converter.setSupportedMediaTypes(mediaTypes);
messageConverters.add(converter);
restTemplate.setMessageConverters(messageConverters);
restTemplate.getInterceptors().add(new JsonInterceptor());
return restTemplate;
}
}
WorkerDetails
package com.path.to.contexttwo.client.dto;
import java.io.Serializable;
import java.util.Objects;
public class WorkerDetails implements Serializable {
private long userId;
private String gender;
private String firstName;
private String lastName;
private String phoneNumber;
public WorkerDetails() {
this.userId = -1;
this.gender = null;
this.firstName = null;
this.lastName = null;
this.phoneNumber = null;
}
// omitted all args constructor, getters, setters, equals, hascode, toString for simplicity
}
WorkerDetails also exists in package com.path.to.contextone.ohs_pl;
I've been trying for 3 days, reading and debugging, to no avail. Debugger seems to show that the error happens when RestTemplate is analysing the WorkerDetails.class.
I also tried using ComponentScan in all configuration classes, because files are in separate packages (bounded contexts), without success.
I could just use the UserDetailsRepository from the class that calls IAcessRestClient to get the WorkerDetails, but this would make two different bounded contexts depend on the same database schema.
Any help would be very appreciated.
I can post aditional code per request.
Thanks in advance
UPDATE
#S B ask for input params. here goes the class that sends the params:
CompanyServiceImpl
package com.path.to.contexttwo.domain.services;
// imports
#Service
public class CompanyServiceImpl implements CompanyService {
private CompanyRepository companyRepository;
private CompanyWorkerRepositoery companyWorkerRepositoery;
private WorkerDetailsClient workerDetailsClient;
private WebApplicationContext applicationContext;
#Autowired
CompanyServiceImpl (
CompanyRepository companyRepository,
CompanyWorkerRepositoery companyWorkerRepositoery,
WorkerDetailsClient workerDetailsClient,
WebApplicationContext applicationContext
) {
this.companyRepository = companyRepository;
this.companyWorkerRepositoery = companyWorkerRepositoery;
this.workerDetailsClient = workerDetailsClient;
this.applicationContext = applicationContext;
}
#Transactional
public Company criateCompany(CompanyDTO dto) throws URISyntaxException {
if (dto.getLegalyAuthorized() == true && dto.getTerms() == true) {
Company company = new Company(
dto.getCompanyName(),
dto.getStateId()
);
company = CompanyRepository.save(company);
// when saving the company, we also need some details from the current logged in user which can be
// retrieved from the idendity and access bounded context. We need those details to be saved in this context
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
String name = auth.getName();
WorkerDetails workerDetails = WorkerDetailsClient.getWorkerDetailsByName(
name
);
// ... we can't reach the rest of the code anyway, so we omitted
}
}
And here is the response I get when acessing the RestController directly:
{"userId":127,"gender":"M","firstName":"Primeiro","lastName":"Ăšltimo","phoneNumber":"922222222"}
UPDATE 2
Commented out .anyRequest().authenticated() and everything runned OK! So, it has to do with Spring Security all this time. What a shame. Will now try to make things work with security enabled. I was receiving HTML as response because of redirection to login page. Implemented authentication correctly (token request with basic auth) and everything works well.
Thank you all!
Try:
return restTemplate.getForObject(
"http://localhost:8080/iacess/get-worker-details/" + userName,
WorkerDetails.class);

Status 400 and Error deserializing List of Objects. No default constructor found

I have this Spring Repository:
public interface MessageRepository extends CrudRepository<MessageObject, String>{
public List<MessageObject> findByEmisorOrDestinatario(String emisor, String destinatario);
}
My DAO is:
#Entity
#Table(name = "messages")
public class MessageObject implements Serializable{
private static final long serialVersionUID = 1L;
#Id
private String id;
private String emisor;
private String destinatario;
private String mensaje;
private String tipo;
#JsonFormat(pattern="yyyy-MM-dd")
private LocalDate fecha;
private String id_housing;
public MessageObject() {
}
Now in my Controller I want to receive the Get request and search in my DB so:
#RestController
public class Controller {
#Autowired
private MessageRepository daoMsg;
#RequestMapping(value = "/Mensajes", method = RequestMethod.GET, produces=MediaType.APPLICATION_JSON_VALUE)
public List<MessageObject> enviados (#RequestParam("mail") String mail) {
return daoMsg.findByEmisorOrDestinatario(mail, mail);
}
}
Now I can call the service from my client, so:
ClientConfig config = new ClientConfig();
Client client = ClientBuilder.newClient();
WebTarget webResource = client.target("http://localhost:8082").path("/Mensajes").queryParam(mail);
Invocation.Builder invocationBuilder = webResource.request(MediaType.APPLICATION_JSON);
Response respuesta = invocationBuilder.get();
int status = respuesta.getStatus();
System.out.println(status);
MessageObject[] listMessages = respuesta.readEntity(MessageObject[].class);
Problems: I'm receiving a 400 status code. Also an error deserializing entityRead. Doing the request with Postman returns no errors and return the list of objects in JSON format.
StackTrace:
javax.ws.rs.ProcessingException: Error deserializing object from entity
stream. Caused by: javax.json.bind.JsonbException: Can't create instance of
a class: class [LMessages.MessageObject;
No default constructor found. Caused by: java.lang.NoSuchMethodException:
[LMessages.MessageObject;.<init>()
Question: how can I know where is my code failing? am I using the service invocation well?
Things I tried: changing Mediatype to GenericType
EDIT I tried removing the / from the path, still getting status 400
Solved. Problem was I was using .queryparam without key-value structure. So changing .queryparam(mail) to .queryparam("mail", mail) solved it.
Try to test with this :
#RestController
public class Controller {
#Autowired
private MessageRepository daoMsg;
#RequestMapping(value = "/Mensajes", method = RequestMethod.GET, produces=MediaType.APPLICATION_JSON_VALUE)
#ReponseBody
public ResponseEntity<List<MessageObject>> enviados (#RequestParam("mail") String mail) {
return new ResponseEntity<>(daoMsg.findByEmisorOrDestinatario(mail, mail), HttpStatus.OK);
}

Why 406 sending post request with both path variable and a request body

I am writing REST APIs with Spring Boot. One of my endpoints handles POST request that consumes JSON request body. Meanwhile, another parameter is provided in the url.
#RequestMapping(
value = "/cycle?&visit={visitid}",
method = RequestMethod.POST,
consumes = "application/json",
produces = "text/plain")
#ResponseStatus(HttpStatus.CREATED)
public String persistCycleCount(#Valid #PathVariable Integer visitId, #Valid #RequestBody CycleCount cycleCount)
Entity CycleCount looks like this:
#Entity
#Table(name = "CYCLE_CNT_HIST")
public class CycleCount implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "CYCLE_CNT_ID")
private long id;
#NotNull
#Column(name = "DOOR_ID")
private String activeDoorId;
#Column(name = "VISIT_ID")
private long visitId;
#Temporal(TemporalType.TIMESTAMP)
#Column(name = "SAMPLE_DTM")
private Date sampleDateTime;
#Column(name = "SAMPLE_TIMEZONE")
private int sampleTimeZone;
#NotNull
#Column(name = "SYS_CYCLE_CNT")
private int systemCycleCount;
#NotNull
#Column(name = "CTRLR_CYCLE_CNT")
private int controllerCycleCount;
When test the api by sending a request with following JSON to /cycle?&visit=1, I got 406.
{
"activeDoorId": "d002",
"controllerCycleCount": 15000,
"systemCycleCount": 78000
}
Here is the Swagger output of the request:
When I tried to debug by setting a break point at the first line of the handler method, it did not even hit that line after I send the request.
I have other endpoints similar to this one which does not need a variable in url and they all work. Why am I getting 406?
1- Maybe you could change your approach, you can use a RequestParameter not PathVariable
2- For validate the RequestBody you can put a BindingResult for validation data, and you can show if your request has errors.
#RequestMapping(value = "/cycle",
method = RequestMethod.POST,
consumes = "application/json",
produces = "text/plain",
params = "visit")
#ResponseStatus(HttpStatus.CREATED)
public String persistCycleCount(#RequestParam("visitId") Integer visitId,
#Valid #RequestBody CycleCount cycleCount, BindingResult result){
if(result.hasErrors()){
// Handler request errors
}
// Body method
}

Post JSON to Rest Spring 4 service

I am trying to post a complex/nested json to Rest Spring4 using PostMan rest client but objectMapper.readValue returns null.
#RequestMapping(value = "/loginuser", method = RequestMethod.POST, consumes = MediaType.APPLICATION_JSON_VALUE)
public #ResponseBody
Status Login(#RequestBody String userdata) {
try {
ObjectMapper objectMapper = new ObjectMapper();
LoginData theUser = objectMapper.readValue(userdata, LoginData.class);
String userdata contains Json string but objectMapper.readValue returns null.
JSON {"LoginData":{"id":"1", "username":"kashmir1","password":"kashmir2"}}
POJO:
public class LoginData implements Serializable{
#Id
#GeneratedValue
#Column(name = "id")
#JsonProperty("id")
private String id;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
and soon for username and password
Please provide inputs
I got answer :
follow below lines instead of directly accessing required node
JsonNode rootNode = objectMapper.readValue(jsonString, JsonNode.class) ;
JsonNode userNode=rootNode.path("LoginData");
LoginData theUser = objectMapper.treeToValue(userNode, LoginData.class);
System.out.println("In Login"+theUser.getUsername());