0

I have an AngularJS project that requests to a Spring Boot app.

Then the Spring Boot app will return data in bytes that can be later be downloaded as an Excel file like so:

Spring Boot controller app:

public ResponseEntity generateExcelReport(@RequestBody ArrayList<SearchCriteria> searchCriteriaList) {
    Query dynamicQuery = new Query();

    HttpHeaders responseHeaders = new HttpHeaders();
    responseHeaders.add("Content-Disposition", "attachment");

    try {
        List<Investment> resultList = investmentService.findByDynamicQuery(dynamicQuery);

        Workbook workbook = myService.generateExcelReport(resultList);
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        workbook.write(out);

        byte[] bArray = out.toByteArray();
        ByteArrayResource resource = new ByteArrayResource(bArray);

        workbook.close();
        out.close();
        out.flush();

        return ResponseEntity
            .ok()
            .headers(responseHeaders)
            .contentLength(resource.getByteArray().length)
            .contentType(MediaType.parseMediaType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"))
            .body(resource);
    } catch (IOException e) {
        return ResponseEntity.badRequest().body(e);
    }
}

But I want to improve this code by returning a ResponseEntity with status 204 if the resultList is empty like so:

Update :

    public ResponseEntity generateExcelReport(@RequestBody ArrayList < SearchCriteria > searchCriteriaList) {
    Query dynamicQuery = new Query();

    HttpHeaders responseHeaders = new HttpHeaders();
    responseHeaders.add("Content-Disposition", "attachment");

    try {
        List < Investment > resultList = investmentService.findByDynamicQuery(dynamicQuery);

        // If there are no results found, throw a 204 No Content back to front-end
        // So that there is no Excel generation involved.
        if (resultList.isEmpty()) {
            return ResponseEntity.status(HttpStatus.NO_CONTENT).body("There are no results found.");
        }

        ...

        return ResponseEntity
            .ok()
            .headers(responseHeaders)
            .contentLength(resource.getByteArray().length)
            .contentType(MediaType.parseMediaType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"))
            .body(resource);
    } catch (IOException e) {
        return ResponseEntity.badRequest().body(e);
    }
}

This is the AngularJS Snippet for the front-end side

ReportService.generateExcelReport($scope.searchCriteriaList)
.then(function (responseObj) {
    if (responseObj.status === 204) {
        return SweetAlert.swal("No results", responseObj.data, "warning");
    } 

    // responseObj.data = Excel bytes returned by Spring in this case
    var blob = new Blob([responseObj.data], {
        type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
    });

    FileSaver.saveAs(blob, "myreport.xlsx");

    swal.close();
})

But my problem is, my $http.post request contains a responseType: "arraybuffer" as part of the request.

Because of this, even if I return a String on the back-end side like this:

return ResponseEntity.status(HttpStatus.NO_CONTENT).body("There are no results found.");

I still get an arraybuffer in the response data. And I can't access the String.

$http.post request snippet:

generateExcelReport: function (searchCriteriaList) {
    var requestConfig = {
        responseType: "arraybuffer",
        headers: {
            "Content-Disposition": "attachment"
        }
    };

    var url =  "my-api/generate-paiwi-reports-excel"

    return $http.post(url, searchCriteriaList, requestConfig);
}

I'm not sure if I'm getting into the right direction. Please help. =) Thank you very much in advance. =)

Ken Flake
  • 585
  • 8
  • 28
  • Could you try the **resultList empty** case using postman, soapui or another rest client tool? This will help you to validate if your api is returning in case of error, plain json or another content type. – JRichardsz May 01 '19 at 14:12
  • Hi @JRichardsz, thank you for your comment. In this case I tested it via Insomnia REST client and I got the response I've been expecting. As such, my problem in my angularJS service is that I always want my response converted to arraybuffer as this is the way in angularJS to download Excel bytes coming from the backend. But I managed to have a solution for this, but I'm not quite sure that it is the best one for the job. Will be posting my solution in a few.. :) – Ken Flake May 03 '19 at 02:51

1 Answers1

0

I managed to have a solution for this one, but I'm not sure if it's the best one for the job.

Since I still have no way (I can think of) to remove the responseType: "arraybuffer" in my $http.post request, I will always have an arraybuffer as a response back to my controller.

Even if I just return a string from Spring back-end, I will have an ArrayBuffer.

But thankfully there is a Web API called TextDecoder that can decode my ArrayBuffer response to a string.

I discovered this thanks to an answer in an existing question here in SO. Converting between strings and ArrayBuffers

This is my whole code now:

Spring back-end;

List<Investment> resultList = investmentService.findByDynamicQuery(dynamicQuery);

// By throwing this HttpStatus, it will be in the .catch function of the Promise.
if (resultList.isEmpty()) {
    return ResponseEntity
        .status(HttpStatus.NOT_ACCEPTABLE)
        .body("There are no results found.");
}

AngularJS Controller:

// AngularJS $http.post request returns an httpPromise containing different properties        
ReportService.generateExcelReport($scope.searchCriteriaList)
.then(function(responseObj) {

    var dateToday = ReportService.getFullDateToday();
    var fileName = "MyReport_" + dateToday + ".xlsx";

    // responseObj.data = Excel bytes returned by Spring in this case
    var blob = new Blob([responseObj.data], {
        type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
    });

    FileSaver.saveAs(blob, fileName);

    swal.close();
})
.catch(function(error) {
    // I forced Spring to throw a 406 - Not Acceptable error when mongoDB query result is 0.
    if (error.status === 406) {
        var decoder = new TextDecoder("utf-8");
        var errorMessage = decoder.decode(error.data);

        return SweetAlert.swal("No Results", errorMessage, "warning");
    }

    console.error(error);
    SweetAlert.swal("Ooops!", "Something went wrong...", "error");
})
Ken Flake
  • 585
  • 8
  • 28