Trying to send Http response to the frontend before method logic happens - spring-boot

What I am trying to accomplish is I have a controller that gets accessed from a frontend (Angular). The users uploads an array of images from the frontend and those images are sent and processed through the backend (Spring Boot). Before the images are processed, I would like to send a response (200) to the frontend so the user does not have to wait for the images to be processed. The code looks like so:
#CrossOrigin
#RestController
public class SolarController {
#Autowired
SolarImageServiceImpl solarImageService;
#Autowired
SolarVideoServiceImpl solarVideoService;
#ApiOperation(value = "Submit images")
#PostMapping(value="/solarImage", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
public void getUploadImages(#ApiParam(value = "Upload images", required = true) #RequestPart(value = "files") MultipartFile[] files,
#ApiParam(value = "User's LanId", required = true) #RequestParam(value = "lanID") String lanId,
#ApiParam(value = "Site name", required = true) #RequestParam(value = "siteName") String siteName,
#ApiParam(value = "User email", required = true) #RequestParam(value = "userEmail") String userEmail,
#ApiParam(value = "Inspection ID", required = true) #RequestParam(value = "inspectionID") String inspectionID) throws IOException{
if (!ArrayUtils.isEmpty(files)) {
this.solarImageService.uploadImages(files, lanId, siteName, userEmail, inspectionID);
}
I have looked at multiple other examples, as in using #Async over the method, using HttpServletResponse, and setting my own responses. But nothing is working.

Resolved.
#ApiOperation(value = "Submit images")
#PostMapping(value="/solarImage", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
public void getUploadImages(#ApiParam(value = "Upload images", required = true) #RequestPart(value = "files") MultipartFile[] files,
#ApiParam(value = "User's LanId", required = true) #RequestParam(value = "lanID") String lanId,
#ApiParam(value = "Site name", required = true) #RequestParam(value = "siteName") String siteName,
#ApiParam(value = "User email", required = true) #RequestParam(value = "userEmail") String userEmail,
#ApiParam(value = "Inspection ID", required = true) #RequestParam(value = "inspectionID") String inspectionID, HttpServletResponse response) throws IOException{
int code = (!ArrayUtils.isEmpty(files)) ? HttpServletResponse.SC_OK
: HttpServletResponse.SC_NOT_FOUND;
if (code != HttpServletResponse.SC_OK) {
response.sendError(code);
return;
}
PrintWriter wr = response.getWriter();
response.setStatus(code);
wr.flush();
wr.close();
if (!ArrayUtils.isEmpty(files)) {
this.solarImageService.uploadImages(files, lanId, siteName, userEmail, inspectionID);
}
Sending the HttpServletResponse first did the trick. Annotating the method with #Async did not work.

Related

better way to POST multipart files with JSON data springboot

I am working on a spring boot project, I have a customer model which consists of properties, including image paths which I am storing in the file system or folder and uploading the entire form with image paths in DB, I have successfully implemented my target task however I was wondering if there is a better and nicer way to achieve this, your answers, comments, and feedbacks are appreciated here is my code
Customer model:
public class Customer {
private String contactMode = "Mobile Number";
#Pattern(regexp ="(251|0)[0-9]{9}" , message = "Invalid phone number")
private String phoneNumber; // phone number
private String identityType = "101-ID [0]";
#NotNull(message = "ID number is required")
private String idNumber;
private String countryOfIssue = "XXXXXXX";
#NotNull(message = "Issue date is required")
#PastOrPresent(message = "Issue date cannot be future date")
private Date issueDate;
#Future(message = "Expiry date cannot be in the past or present")
#NotNull(message = "Expiry date is required")
private Date expiryDate;
// storing customerImage , customerID and customerSignature paths in DB
private String customerImage;
private String customerID;
private String customerSignature;
}
Customer Service:
private String path = "C:\Users\User\Desktop\docs\uploaded_files\";
public Customer saveCustomer(Customer customer, MultipartFile customerImage, MultipartFile customerID,
MultipartFile customerSignature) throws Exception {
final String PATH = path + customer.getContactDetail();
Customer phoneNumberExists = customerRepository.findByContactDetail(customer.getContactDetail());
byte[] imageBytes = customerImage.getBytes();
byte[] idBytes = customerID.getBytes();
byte[] signatureBytes = customerSignature.getBytes();
Path customerImagePath = Paths.get
(PATH + "_photo_" + customerImage.getOriginalFilename());
Files.write(customerImagePath, imageBytes);
Path customerIDPath =
Paths.get(PATH + "_ID_" + customerID.getOriginalFilename());
Files.write(customerIDPath, idBytes);
Path customerSignaturePath =
Paths.get(PATH + "_Sign_" + customerSignature.getOriginalFilename() + "");
Files.write(customerSignaturePath, signatureBytes);
if (phoneNumberExists != null) {
throw new PhoneNumberTakenException("Phone number is taken ");
}
customer.setAge(new Date().getYear() - customer.getDateOfBirth().getYear());
customer.setCustomerImage(String.valueOf(customerImagePath));
customer.setCustomerID(String.valueOf(customerIDPath));
customer.setCustomerSignature(String.valueOf(customerSignaturePath));
customer.setFromDate(LocalDate.now());
customer.setStatus(Customer.Status.Submitted);
Customer customerRecord = customerRepository.saveAndFlush(customer);
return customerRecord;
}
Customer Controller : look at how iam passing multipart files and other fields in the controller to service
#PostMapping()
public ResponseEntity<Customer> createCustomer(#Valid #RequestPart("customer") String customer, MultipartFile customerImage, MultipartFile customerID, MultipartFile customerSignature
) throws Exception {
ObjectMapper customerMapper = new ObjectMapper();
Customer savedCustomer = customerMapper.readValue(customer, Customer.class);
Customer customerRecord = customerService.saveCustomer(savedCustomer, customerImage, customerID, customerSignature);
log.debug("inside createCustomer() controller : {}", customerRecord);
return ResponseEntity.status(HttpStatus.CREATED).body(customerRecord);
}
Postman post request to the endpoint:
Postman response :

Spring Boot - how to allow CORS on REST Controller HTTP PUT

I'm my Spring Boot REST controller, I'm able to allow CORS for HTTP POST, but for some reason HTTP PUT is still being blocked.
I have placed my CORS decorator at the Controller level - HTTP PUT handler still being blocked.
Here is my controller:
package com.khoubyari.example.api.rest;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import com.khoubyari.example.domain.Hotel;
import com.khoubyari.example.exception.DataFormatException;
import com.khoubyari.example.service.HotelService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
#RestController
#RequestMapping(value = "/example/v1/hotels")
#Api(tags = {"hotels"})
//#CrossOrigin(origins = "http://localhost:4200")
#CrossOrigin( origins = "*" , allowedHeaders = "*")
public class HotelController extends AbstractRestHandler {
#Autowired
private HotelService hotelService;
#RequestMapping(value = "",
method = RequestMethod.POST,
consumes = {"application/json", "application/xml"},
produces = {"application/json", "application/xml"})
#ResponseStatus(HttpStatus.CREATED)
#ApiOperation(value = "Create a hotel resource.", notes = "Returns the URL of the new resource in the Location header.")
public void createHotel(#RequestBody Hotel hotel,
HttpServletRequest request, HttpServletResponse response) {
Hotel createdHotel = this.hotelService.createHotel(hotel);
response.setHeader("Location", request.getRequestURL().append("/").append(createdHotel.getId()).toString());
}
#RequestMapping(value = "",
method = RequestMethod.GET,
produces = {"application/json", "application/xml"})
#ResponseStatus(HttpStatus.OK)
#ApiOperation(value = "Get a paginated list of all hotels.", notes = "The list is paginated. You can provide a page number (default 0) and a page size (default 100)")
public
#ResponseBody
Page<Hotel> getAllHotel(#ApiParam(value = "The page number (zero-based)", required = true)
#RequestParam(value = "page", required = true, defaultValue = DEFAULT_PAGE_NUM) Integer page,
#ApiParam(value = "Tha page size", required = true)
#RequestParam(value = "size", required = true, defaultValue = DEFAULT_PAGE_SIZE) Integer size,
HttpServletRequest request, HttpServletResponse response) {
return this.hotelService.getAllHotels(page, size);
}
#RequestMapping(value = "/{id}",
method = RequestMethod.GET,
produces = {"application/json", "application/xml"})
#ResponseStatus(HttpStatus.OK)
#ApiOperation(value = "Get a single hotel.", notes = "You have to provide a valid hotel ID.")
public
#ResponseBody
Hotel getHotel(#ApiParam(value = "The ID of the hotel.", required = true)
#PathVariable("id") Long id,
HttpServletRequest request, HttpServletResponse response) throws Exception {
Hotel hotel = this.hotelService.getHotel(id);
checkResourceFound(hotel);
return hotel;
}
#RequestMapping(value = "/{id}",
method = RequestMethod.PUT,
consumes = {"application/json", "application/xml"},
produces = {"application/json", "application/xml"})
#ResponseStatus(HttpStatus.NO_CONTENT)
#ApiOperation(value = "Update a hotel resource.", notes = "You have to provide a valid hotel ID in the URL and in the payload. The ID attribute can not be updated.")
public void updateHotel(#ApiParam(value = "The ID of the existing hotel resource.", required = true)
#PathVariable("id") Long id, #RequestBody Hotel hotel,
HttpServletRequest request, HttpServletResponse response) {
checkResourceFound(this.hotelService.getHotel(id));
if (id != hotel.getId()) throw new DataFormatException("ID doesn't match!");
this.hotelService.updateHotel(hotel);
}
//todo: #ApiImplicitParams, #ApiResponses
#RequestMapping(value = "/{id}",
method = RequestMethod.DELETE,
produces = {"application/json", "application/xml"})
#ResponseStatus(HttpStatus.NO_CONTENT)
#ApiOperation(value = "Delete a hotel resource.", notes = "You have to provide a valid hotel ID in the URL. Once deleted the resource can not be recovered.")
public void deleteHotel(#ApiParam(value = "The ID of the existing hotel resource.", required = true)
#PathVariable("id") Long id, HttpServletRequest request,
HttpServletResponse response) {
checkResourceFound(this.hotelService.getHotel(id));
this.hotelService.deleteHotel(id);
}
}
What should I change in order for the HTTP PUT handler to allow updates?
You may need to specify allowed methods explicitely like this in your CORS config (I'm using Kotlin and implementing WebMvcConfigurer):
override fun addCorsMappings(registry: CorsRegistry) {
log.info("CORS: {}", origins)
registry.addMapping("/**").allowedOrigins(*origins)
.allowedMethods("GET", "POST", "PUT", "OPTIONS")
}
PUT is not allowed by default, as can be seen in CorsConfiguration#DEFAULT_PERMIT_METHODS:
private static final List<String> DEFAULT_PERMIT_METHODS = Collections.unmodifiableList(
Arrays.asList(HttpMethod.GET.name(), HttpMethod.HEAD.name(), HttpMethod.POST.name()));

ServiceResponse mocked which gives a null value and not expected this null

I'm writing j-unit Test-cases for my services and in which i couldn't mock service Response properly, Which is giving me a null. can somebody help me in this issue.
public ResponseEntity<Void> lockGet(
#ApiParam(value = "Unique identifier for this request.", required = true) #RequestHeader(value = "service-id", required = true) String serviceId,
#ApiParam(value = "Logged in userid.", required = true) #RequestHeader(value = "user-id", required = true) String userId,
#ApiParam(value = "Unique messageid.", required = true) #RequestHeader(value = "message-id", required = true) String messageId,
#RequestHeader(value = "access-token", required = true) String accessToken,
#ApiParam(value = "Unique id of the doamin of the entity", required = true) #RequestParam(value = "lockDomainId", required = true) Long lockDomainId,
#ApiParam(value = "Unique id of the entity to be fetched", required = true) #RequestParam(value = "lockEntityId", required = true) Long lockEntityId,
HttpServletRequest request, HttpServletResponse response) {
ResponseEntity<Void> result = null;
if (request.getAttribute("user-id") != null)
userId = (String) request.getAttribute("user-id");
String logContext = "||" + lockDomainId + "|" + lockEntityId + "||";
ThreadContext.put("context", logContext);
long t1 = System.currentTimeMillis();
LOG.info("Method Entry: lockGet" + logContext);
ServiceRequest serviceRequest = AppUtils.mapGetRequestHeaderToServiceRequest(serviceId, userId, lockDomainId,
lockEntityId);
try {
ServiceResponse serviceResponse = lockService.getLock(serviceRequest);
// set all the response headers got from serviceResponse
HeaderUtils.setResponseHeaders(serviceResponse.getResponseHeaders(), response);
result = new ResponseEntity<Void>(HeaderUtils.getHttpStatus(serviceResponse));
} catch (Exception ex) {
LOG.error("Error in lockGet", ex);
result = new ResponseEntity<Void>(HttpStatus.INTERNAL_SERVER_ERROR);
}
ThreadContext.put("responseTime", String.valueOf(System.currentTimeMillis() - t1));
LOG.info("Method Exit: lockGet");
return result;
}
#Test
public void testLockGetForError() {
request.setAttribute("user-id","TestUser");
ServiceRequest serviceRequest = new ServiceRequest();
serviceRequest.setUserId("TestUser");
ServiceResponse serviceResponse = new ServiceResponse();
LockService service = Mockito.mock(LockService.class);
when(service.getLock(serviceRequest)).thenReturn(serviceResponse);
// ServiceResponse serviceResponse = lockService.getLock(serviceRequest);
ResponseEntity<Void> result = new ResponseEntity<Void>(HeaderUtils.getHttpStatus(serviceResponse));
ResponseEntity<Void> lockGet = lockApiController.lockGet("1234", "TestUser", "TestMessage", "TestTkn", 12345L, 12345L, request, response);
assertEquals(HttpStatus.INTERNAL_SERVER_ERROR, lockGet.getStatusCode());
}
I tried in different scenario's which couldn't fix this issue. Can someone help me out. Thanks in advance.
From the code that you have put , the issue that i see is that you are actually mocking the LockService object but when calling the lockApiController.lockGet method the code is not actually working with the mocked LockService since lockApiController has an LockService object of it's own.
One way to solve this issue is to inject the mocked LockService
object into the lockApiController object using #Spy. This way
when the getLock() is called it will be actually called on the
mocked object and will return the mock response provided.
So in your test :
#Test
public void testLockGetForError() {
LockService service = Mockito.mock(LockService.class);
LockApiController lockApiController = Mockito.spy(new LockApiController(service));
request.setAttribute("user-id","TestUser");
ServiceRequest serviceRequest = new ServiceRequest();
serviceRequest.setUserId("TestUser");
ServiceResponse serviceResponse = new ServiceResponse();
when(service.getLock(serviceRequest)).thenReturn(serviceResponse);
// ServiceResponse serviceResponse = lockService.getLock(serviceRequest);
ResponseEntity<Void> result = new ResponseEntity<Void>(HeaderUtils.getHttpStatus(serviceResponse));
ResponseEntity<Void> lockGet = lockApiController.lockGet("1234", "TestUser", "TestMessage", "TestTkn", 12345L, 12345L, request, response);
assertEquals(HttpStatus.INTERNAL_SERVER_ERROR, lockGet.getStatusCode());
}
So you can try passing the mocked LockService object to the spy object.
Another way is to try using the #InjectMocks to inject the mocked
object into the LockApiController.
#InjectMocks marks a field on which injection should be performed. Mockito will try to inject mocks only either by constructor injection, setter injection, or property injection – in this order. If any of the given injection strategy fail, then Mockito won’t report failure.
For example:
#Mock
Map<String, String> wordMap;
#InjectMocks
MyDictionary dic = new MyDictionary();
#Test
public void whenUseInjectMocksAnnotation_thenCorrect() {
Mockito.when(wordMap.get("aWord")).thenReturn("aMeaning");
assertEquals("aMeaning", dic.getMeaning("aWord"));
}
For the class:
public class MyDictionary {
Map<String, String> wordMap;
public MyDictionary() {
wordMap = new HashMap<String, String>();
}
public void add(final String word, final String meaning) {
wordMap.put(word, meaning);
}
public String getMeaning(final String word) {
return wordMap.get(word);
}
}
For both of these to work , you must be having a constructor or appropriate setters to set the mock object to the LockApiController class.
Reference : https://howtodoinjava.com/mockito/mockito-annotations/

MockMvc PostRequest Exception

I have following post mapping.
#PostMapping(value = BULK_UPDATE)
#ApiOperation(value = "Bulk Update of Markets by pairs of Market Key and Tier Quantity Id", tags = "Bulk", code = 200)
#ApiImplicitParams({
#ApiImplicitParam(name = "MarketTierQuantityId", value = "List of Market Key and Tier Quantity Id pairs",
paramType = "body", allowMultiple = true, dataType = "MarketTierQuantityId", required = true) })
#ApiResponses({
#ApiResponse(code = 200, message = "Bulk update successful", response = MarketStatus.class, responseContainer = "List") })
#ResponseStatus(org.springframework.http.HttpStatus.OK)
public ResponseEntity<StreamingResponseBody> bulkUpdate(
#RequestParam(name = IGNORE_SYNC_PAUSE_FAILURE, required = false, defaultValue = "false")
#ApiParam(name = IGNORE_SYNC_PAUSE_FAILURE, value = "Ignore failure of the jobs pause command") boolean ignoreJobsPauseFailure,
#RequestBody #ApiParam(name = "MarketTierQuantityId", value = "List of Market Key and Tier Quantity Id pairs", required = true) List<MarketTierQuantityId> marketTierQuantities,
#RequestParam(name = MOVE_TO_PREAUTH_FLAG, required = false, defaultValue = "true")
#ApiParam(name = MOVE_TO_PREAUTH_FLAG, value = "Move new units to Preauth for the markets with active waitlists") boolean moveToPreauth) throws BusinessException {
String requestId = getRequestId();
boolean jobsPaused = pauseJobs(ignoreJobsPauseFailure);
return LoggingStopWatch.wrap(() -> {
return ResponseEntity.ok().contentType(MediaType.APPLICATION_JSON)
.body(outputStream -> process(new SyncBulkProcessorHelper(outputStream),
marketTierQuantities, jobsPaused, requestId, moveToPreauth, LoggingStopWatch.create(LOGGER, "Bulk Update")));
});
}
and i have written the following test.
#RunWith(SpringRunner.class)
#WebMvcTest(BulkUpdateController.class)
#ContextConfiguration(classes = { BulkUpdateController.class, SharedExecutor.class })
#ActiveProfiles("dev")
public class BulkUpdateControllerTest {
#Autowired
private MockMvc mockMvc;
#MockBean
private BulkStatusService bulkStatusService;
#MockBean
private BulkMarketService bulkMarketService;
#MockBean
private HttpService httpService;
#MockBean
private RestClient restClient;
#MockBean
private BulkProcessorHelper helper;
#Test
public void test() throws Exception {
String request = TestHelper.getSerializedRequest(getBulkUpdateRequest(), MarketTierQuantityId.class);
mockMvc.perform(post("/bulkupdate").accept(MediaType.APPLICATION_JSON).contentType(MediaType.APPLICATION_JSON)
.content(request)).andExpect(status().is4xxClientError());
}
public MarketTierQuantityId getBulkUpdateRequest() {
MarketTierQuantityId market = new MarketTierQuantityId();
market.setMarketKey("00601|PR|COBROKE|POSTALCODE|FULL");
market.setTierQuantityId("10");
return market;
}
Getting the following error, have tried every possible way to resolve it but doesnt help.
Request failed. Error response:
{\"responseStatus\":{\"errorCode\":\"BadRequest\",\"message\":\"JSON
parse error: Cannot deserialize instance of java.util.ArrayList out
of START_OBJECT token\",\"stackTrace\":\"BusinessException(JSON parse
error:
P.S -> new to JUnits and mocks

How to hide a session parameters in Swagger with Springfox

#ApiOperation(value = "获取打卡信息", notes = "获取打卡信息")
#RequestMapping(method = RequestMethod.GET, value = "/{mPhone}/{mPassword}/{date}")
#ApiImplicitParams({
#ApiImplicitParam(name = "mPhone", value = "手机号", required = true, dataType = "String",defaultValue="13268690268",paramType="Path"),
#ApiImplicitParam(name = "mPassword", value = "密码", required = true, dataType = "String",defaultValue="111111",paramType="Path"),
#ApiImplicitParam(name = "date", value = "日期", required = true, dataType = "String",defaultValue="2017-07-04",paramType="Path"),
#ApiImplicitParam(name = "httpSession", value = "Session", required = false)})
public #ResponseBody String getSignInfo(#PathVariable String mPhone, #PathVariable String mPassword,
#PathVariable String date,
HttpSession httpSession) {
.......
}
enter image description here
I want to remove this parameter (httpSession) from the document, and I need help。
Springfox won't show these values by default. The reason why httpSession is visible in your case, is because you added it by yourself as an implicit parameter:
#ApiImplicitParam(name = "httpSession", value = "Session", required = false)
If you don't want the httpSession to pop up, remove that from your implicit parameters. Additionally, you don't even have to use #ApiImplicitParam in your case, you can use #ApiParam:
#ApiOperation(value = "获取打卡信息", notes = "获取打卡信息")
#RequestMapping(method = RequestMethod.GET, value = "/{mPhone}/{mPassword}/{date}")
public #ResponseBody String getSignInfo(
#ApiParam(value = "手机号", required = true, dataType = "String",defaultValue="13268690268")
#PathVariable String mPhone,
#ApiParam(value = "密码", required = true, dataType = "String",defaultValue="111111")
#PathVariable String mPassword,
#ApiParam(value = "日期", required = true, dataType = "String",defaultValue="2017-07-04")
#PathVariable String date,
HttpSession httpSession) {
// ...
}

Resources