corrupted Excel xlsx when exporting - java

I have spent the last several hours going through different Stack Overflow threads which discuss how to download excel files and passing ByteArrayOutputStreams to the front-end. I have a Spring Boot back-end which creates a custom Excel workbook and the appropriate sheets. However, it appears the binary which is returned from the back-end to the angular 6 front-end maybe incorrectly formatted. I have included the different services and controllers as well as the possibly malformed data.
Response body (Truncated value):
"PK]�uN_rels/.rels���j�0��}
�{㴃1F�^Ơ�2��l%1I,c�[��3�l
l�����H��4�R�l��·����q}*�2������;�*��
t"�^�l;1W)�N�iD)ejuD�cKz[׷:}g����#:�
�3����4�7N�s_ni�G�M*7�����2R�+� �2�/�����b��mC�Pp�ֱ$POyQ�抒�DsZ��IС�'un���~�PK����OPK]�uN[Content_Types].xml�SMO1��+6��m��1���G%��β...."
ExcelWriterService.java
private XSSFWorkbook workbook = new XSSFWorkbook();
public byte[] excelExporter(QueryResultsDataset<DataExportTable> data) {
List<DataExportTable> tableList = data.getTables();
List<Map<String, String>> dataRows = tableList.get(0).getRows();
OutputStream outputStream = new ByteArrayOutputStream();
... Create Excel workbook
workbook.write(outputStream);
outputStream.close();
workbook.close();
} catch (IOException ex) {
// Doing nothing
}
return ((ByteArrayOutputStream) outputStream).toByteArray();
Spring Controller
#RequestMapping(value = "<path>", method = RequestMethod.POST)
public #ResponseBody ResponseEntity<byte[]> export(#RequestBody SavedQuery request, Principal principal) {
//Run the job
QueryResultsDataset dataset = dataExportJob.getQueryResultsDataset();
ExcelWriterService ews = new ExcelWriterService();
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);
StringBuilder filename = new StringBuilder("hello").append(".xlsx");
headers.add("content-disposition", "inline;filename=" + filename.toString());
headers.setCacheControl("must-revalidate, post-check=0, pre-check=0");
QueryResultsDataset<DataExportTable> fixResults = (QueryResultsDataset<DataExportTable>) fixMultiplier(dataset, request);
byte [] bytes = ews.excelExporter(fixResults);
ResponseEntity<byte[]> response = new ResponseEntity<byte[]>(bytes, headers, HttpStatus.OK);
return response;
}
}
Downloader.service.ts (Angular 6 Service)
public exportExcel(search: any) {
const opts = [];
const tables = [];
this.api.download(environment._downloadDataEndPoint, search, {})
.subscribe(response => {
console.log(response);
var contentType = 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet';
let blob = new Blob([response], {type: contentType});
let link = document.createElement('a');
link.href = URL.createObjectURL(blob);
link.download = "dataweb.xlsx";
link.click();
}
...
}
Api Service (Angular 6 Service)
download(url: string, body: any, header: any) {
header = JSON.parse(this._auth.getToken());
const headers = new Headers(header);
headers.append('responseType', ResponseContentType.Blob.toString());
const options = new RequestOptions({headers: headers});
const response = this.http.post(url, body, options)
.pipe(catchError((error: any) => observableThrowError(error)));
return response;
}
Any help would be great! I'm out of ideas at the moment. Thanks!

You are setting the response type in the wrong place.
Instead of
const headers = new Headers(header);
headers.append('responseType', ResponseContentType.Blob.toString());
const options = new RequestOptions({headers: headers});
Try
const headers = new Headers(header);
const options = new RequestOptions({
headers: headers,
responseType: ResponseContentType.Blob
});
See the documentation for the RequestOptions type.

Related

How do I send additional form fields along with a file upload using the FormData object to a Java API using React fetch?

My api is not detecting any of the RequestParams. However it works fine with Postman, just not when being called from React. I've also verified that the formData is being loaded correctly via console.log statements:
formData = file,[object File],uploadId,173,uploadTypeId,1,dateEntered,1625025600000,description,Fourth Addendum testing,enteredBy,769
Does the formData object need to be stringified or something?
Can someone tell me what I'm doing wrong? I'm getting the following error:
Required request parameter 'uploadId' for method parameter type Long is not present
Here is my React snippet:
let formData = new FormData();
formData.append("file", file);
formData.append("uploadId",uploadId);
formData.append("uploadTypeId",uploadTypeId);
formData.append("dateEntered",dateEntered);
formData.append("description",description);
formData.append("enteredBy",enteredBy);
let myHeaders = new Headers();
myHeaders.append("Content-Type", "multipart/form-data; boundary=XXX");
myHeaders.append("type", "formData");
const serverName = "http://localhost:8080/MyApp/api/uploads/update/;
myHeaders.append("Access-Control-Allow-Credentials", 'true');
myHeaders.append("Access-Control-Allow-Origin", '*');
const jwt = sessionStorage.getItem("jwt");
//console.log("fetch jwt = ", jwt) ;
let headerJwt = "Bearer " + jwt;
if (jwt != null) {
myHeaders.append("Authorization", headerJwt);
}
//console.log("headers = ", Object.fromEntries(myHeaders));
let myInit = {method: "PUT"
,headers: myHeaders
};
let url = serverName + endpoint;
//console.log("data = " + data);
if (data)
myInit.body = data;
let returnFetch = fetch(url, myInit);
Here is my Java api snippet:
#PutMapping(value = "/update", produces = MediaType.APPLICATION_JSON_VALUE, consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
public ResponseEntity<Upload> update(
#RequestParam("uploadId") Long uploadId,
#RequestParam("uploadTypeId") Long uploadTypeId,
#RequestParam("dateEntered") Date dateEntered,
#RequestParam("description") String description,
#RequestParam("enteredBy") Long enteredBy,
#RequestPart(value = "file") MultipartFile file)
throws JsonParseException, JsonMappingException, IOException
,SerialException, SQLException, DataAccessException
{
Upload upload = new Upload();
upload.setUploadId(uploadId);
upload.setUploadTypeId(uploadTypeId);
upload.setDateEntered(dateEntered);
upload.setDescription(description);
upload.setEnteredBy(enteredBy);
MultipartFile multipartFile = file;
byte[] bytes = multipartFile.getBytes();
SerialBlob blob = new SerialBlob(bytes);
upload.setFileBlob(blob);
String filename = multipartFile.getOriginalFilename();
upload.setFilename(filename);
long recordsUpdated = this.myService.updateUpload(upload);
return new ResponseEntity<Upload>(upload, HttpStatus.OK);
}
I found the problem!
I had to do 2 things:
Omit the content-type header.
Stringify all the additional parameters.
It works now!

How to consume spring StreamingResponseBody in Angular 8

I have the following code in my springboot backend
#GetMapping(value = "/stream", produces = MediaType.APPLICATION_OCTET_STREAM_VALUE)
public ResponseEntity<StreamingResponseBody> stream() {
File file = new File("C:\\test\\test.mp3");
try {
InputStream is = new FileInputStream(file);
StreamingResponseBody resp = outputStream -> {
IOUtils.copy(is, outputStream);
};
return ResponseEntity.ok()
.contentType(MediaType.APPLICATION_OCTET_STREAM)
.contentLength(file.length())
.body(resp);
} catch(Exception e) {
e.printStackTrace();
}
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.contentType(MediaType.APPLICATION_OCTET_STREAM)
.body(null);
}
I am trying to consume it in my angular app like this
HTML
<p>{{ content }}</p>
Typescript
ngOnInit(): void {
const header = new HttpHeaders().set('Accept', 'application/octet-stream');
this.http.get('http://localhost:8080/stream', {headers: header}).subscribe(
(res: any) => {
this.content = JSON.stringify(res);
},
(err: any) => {
this.content = JSON.stringify(err);
}
);
}
However unless I specify responseType: 'text' for the get method, I get only error. Also, I tried specifying responseType as blob and arraybuffer and specified several combinations of observe: body|response|events. Only {} is shown on the page. Not sure what is wrong here.
I tried following this SO post but not of much use.
Can some one please throw some light on it? Thanks in advance.

How to receive file csv from java with angular

I have a rest api which returns a file.csv
and then I check that the response is 200, and datas are also in responsed.body.
But the brower didn't download the csv file.
Here is the API :
ResponseEntity<Resource> exportCsv() throws IOException {
/*
*
*/
InputStreamResource resource = new InputStreamResource(new FileInputStream(file));
HttpHeaders headers = new HttpHeaders();
headers.add("Content-disposition", "attachment; filename=sample.csv");
headers.add("Cache-Control", "no-cache, no-store, must-revalidate");
headers.add("Pragma", "no-cache");
headers.add("Expires", "0");
return ResponseEntity.ok()
.headers(headers)
.contentLength(file.length())
.contentType(MediaType.parseMediaType("text/csv"))
.body(resource);
}
Here is the Angular
this.stickService.exportCsv( this.stickSearch ).subscribe(
response => this.downloadFile(response),
err => console.log('Error downloading the file.' + err.message
));
downloadFile(data: Response) {
const blob = new Blob([data], { type: 'text/csv' });
const url = window.URL.createObjectURL(blob);
window.open(url);
}
exportCsv( stickSearch: StickSearch ): Observable<any> {
const headers = this.oAuthService.putTokenInHeaders(this.headers);
let params = new HttpParams({encoder: new HttpURIEncoder()});
if (stickSearch.searchString() !== '') {
params = params
.append('search', stickSearch.searchString())
}
return this.httpClient.get(this.exportCsvUrl + '/exportCsv',
{
responseType: 'text',
params: params,
headers
});
}
I got correct data at response body.
But the download failed. 'Error downloading the file.Http failure during parsing for myURL '
Thanks for helping
It work now, this is consequence
Thanks a lot !
I can see that you are taking the output provided by the server and then building an URL off that CSV. That won't be necessary.
If you just want to download the CSV, then all you are missing is the following in your Java code:
headers.add("Content-disposition", "attachment; filename=sample.csv");
Once you have the header in place, you can get rid of the downloadFile method and probably change the this.httpClient.get request into a window.open.
See if this solves your problem and provide feedback in either case.
By default HttpClient expects that data from server will come in json format. Angular tries to parse text body as json. And that leads to exсeption. In the stickService when you do request the data, you have to specify type or result as text:
constructor (private httpClient: HttpClient){
}
public exportCsv(stickSearch: any) {
return httpClient.get('http://someurl', {responseType: 'text'});
}
Another one point that you use window.open(url). Most browsers block popup windows by default. So maybe it would be better to use an anchor element.
downloadFile(data: Response) {
const blob = new Blob([data], { type: 'text/csv' });
const url = window.URL.createObjectURL(blob);
const anchor = document.createElement('a');
anchor.download = 'myfile.txt'; // here you can specify file name
anchor.href = url;
document.body.appendChild(anchor);
anchor.click();
document.body.removeChild(anchor);
}
Here is the I'm using for this feature.
First, use 'Accept' headers.
Second, set responseType to 'text', the default is 'json'.
Third, the download code.
getCsv(): Observable<any> {
let headers = new HttpHeaders();
headers = headers.set('Accept', 'application/csv');
return this.http.get('SOME__URL', {
headers: headers,
responseType: 'text'
});
}
exportReportCSV() {
getCsv().subscribe( response => {
const link = document.createElement('a');
link.href = window.URL.createObjectURL(new Blob([response], {type: 'text/csv'}));
link.download = this.report.name + '.csv';
link.click();
});

Downloading a Csv file From angularjs Jax-Rs and java

Hello everyone i am using angularjs java and rest to implement one report. Based on UI field selected there is a call to Java Layer and from java there is some database call and the returned input stream i am downloading in a csv file.
There is one problem happening if i do the same with hitting the the same url by browser which i m passing through angularjs than i m able to download the file but if by using UI i m making the request than there is no download option and data is returned as a stream in http response to angular.
java code:
enter code here
#Path("/files")
public class DownloadCsvFile {
#GET
#Path("/csv")
#Produces({MediaType.APPLICATION_OCTET_STREAM})
public Response getFile() {
StreamingOutput outp = new StreamingOutput() {
#Override
public void write(OutputStream out) throws IOException,
WebApplicationException {
String url ="http://someurl?
indent=on&q=RCE_POST:2016&sort=id%20asc
&rows=100000&start=0&wt=csv";
final InputStreamReader is = new InputStreamReader(
((HttpURLConnection) (new URL(url)).openConnection())
.getInputStream(),
Charset.forName("UTF-8"));
IOUtils.copy(is, out);
}
};
ResponseBuilder response = Response.ok(outp);
response.header("Content-Disposition", "attachment;
filename=\"testFile_file.csv\"");
return response.build();
} }
AngularJs controller code :
enter code here
var app = angular.module('myApp', ['ngProgress']);
app.controller('myCtrl', function($scope,$http,ngProgressFactory) {
// on submit the fun is called
$scope.LMALLPeriodReport =function()
{
return $http.get("http://localhost:8080/IsaveIdeas/rest/files/csv?
parameters="+parameter)
//parameter contain the selected field in UI
.then(function (response) {
var result = response.data;
alert("printing data");
});
};
The same request from the browser http://localhost:8080/IsaveIdeas/rest/files/csv? parameters={parameter} enable me to download the file.
You can use Blob in your angularjs code like this:
....
.then(function (response) {
var fileName = "yourFileName.csv";
var a = document.createElement("a");
document.body.appendChild(a);
response.data = "\ufeff" + response.data;
var file = new Blob([response.data], {encoding:"UTF-8",type:'application/csv;charset=UTF-8'});
var fileURL = URL.createObjectURL(file);
a.href = fileURL;
a.download = fileName;
a.click();
}

Download local file in angularJS

I have an application that works on Java and AngularJS.
I create pdf files with Java, using FileOutputStream to store them:
#RequestMapping(value = "/getPdf",
method = RequestMethod.GET)
#RolesAllowed(AuthoritiesConstants.USER)
public List<String> getPdf(#RequestParam(value = "id") Long id){
FileOutputStream fileStream = null;
String fileName = textRepository.findOne(id).getTitle() + ".pdf";
String text = textRepository.findOne(id).getUserText();
try {
fileStream = new FileOutputStream(fileName);
} catch (FileNotFoundException e) {
e.printStackTrace();
}
// create an API client instance
Client client = new Client("", "");
client.convertHtml(text, fileName);
try {
fileStream.close();
} catch (IOException e) {
e.printStackTrace();
}
List<String> out = new ArrayList<>();
out.add(fileName);
return out;
}
They are created in the root directory of my application.
Now I want to implement a functionality that lets the user to download a pdf by clicking on a link or a button. I have tried with $window.open(), but I can't manage to get the path to my pdf file.
$scope.getPdf = function (id) {
TextService.getPdf(id).success(function(data){
$window.open('../../../../../../' + data[0], '_blank', 'download');
});
};
Here i get an error saying that Cannot GET /data.pdf
EDIT - solved the problem
I had to do a POST method that sends the file:
#RequestMapping(value = "/getPdf",
method = RequestMethod.POST)
#RolesAllowed(AuthoritiesConstants.USER)
public ResponseEntity<byte[]> getPdf(#RequestBody Long id){
String filename = textRepository.findOne(id).getTitle() + ".pdf";
String text = textRepository.findOne(id).getUserText();
ByteArrayOutputStream pdf = new ByteArrayOutputStream();
// create an API client instance
Client client = new Client("", "");
client.convertHtml(text, pdf);
byte[] content = pdf.toByteArray();
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.parseMediaType("application/pdf"));
headers.setContentDispositionFormData("inline", filename);
headers.setCacheControl("must-revalidate, post-check=0, pre-check=0");
ResponseEntity<byte[]> response = new ResponseEntity<>(content, headers, HttpStatus.OK);
return response;
}
and back to my AngularJS client i have a service that calls the Java method:
angular.module("eddieApp")
.factory("TextService", function($http){
return{
getPdf: function(id){
return $http.post('texts/getPdf', id, { responseType: 'arraybuffer' });
}
};
});
Now in the controller all i had to do is call the service and open a window with the pdf:
$scope.getPdf = function (id) {
TextService.getPdf(id).success(function(data){
var file = new Blob([data], {type: 'application/pdf'});
var fileURL = ($window.URL || $window.webkitURL).createObjectURL(file);
$window.open(fileURL, '_blank', 'download');
});
};
Hope it helps someone!
If you are serving the angular portion from a webserver, you cannot access the filesystem of the server. That would be a severe security problem.
Why not provide another #RequestMapping(value = "/getFile") which serves the file directly to the user, using the proper MIME type as well?
Here is a similar question Return generated pdf using spring MVC with an answer on how to do that.
i hope to help. I think the problem is in the window.open, first should exist a service that makes a post with the url, call the service will pass the id and now if you make the window.open

Categories

Resources