File download using file-saver (angular4 / spring boot) - java

i'm working on a project where i need to save PDF file when i click on a button ,
#RequestMapping(value = "/download/{filename}", method = RequestMethod.GET)
public ResponseEntity<InputStreamResource> download(#PathVariable String filename) throws IOException{
String fullPath=FILE_PATH+""+filename+".pdf";
System.out.println(fullPath);
File file =new File(fullPath);
HttpHeaders respHeaders = new HttpHeaders();
respHeaders.setContentType(MediaType.APPLICATION_PDF);
respHeaders.add("Content-Disposition", "attachment; filename=" + filename);
respHeaders.add("Accept","application/pdf");
InputStreamResource isr = new InputStreamResource(new FileInputStream(file));
return new ResponseEntity<InputStreamResource>(isr,respHeaders,HttpStatus.OK);
testing it with ARC it shows something like this
testing my rest
and for angular i have for my html
<button (click)="saveF(a)">Export</button></div>
my component.ts
a="a.pdf";
saveF(fileName){
this.authService.saveFile(fileName)
.subscribe(response=> {
this.saveToFileSystem(response)
},
err=>{
console.log(err);
console.log("not ok")
}
)
}
which call the function
private saveToFileSystem(response){
const contentDispositionHeader: string =response.headers.get('Content-Disposition');
const parts:string[]=contentDispositionHeader.split('.');
const filename=parts[1].split('=')[1];
const blob =new Blob([response._body]);
saveAs(blob,filename);
}
and finally my service
saveFile(fileName){if (this.jwtToken== null){
this.loadToken()}
return this.http.get('http://localhost:8080/download/'+fileName,{headers:new HttpHeaders({'Authorization':this.jwtToken})})}
}
and in my browser i get something like this but it doesnt download the file
enter image description here

#Daniomi ty for the answer , now it works , i had to add responseType , this is how i change it
saveF(fileName) {
this.authService.saveFile(fileName)
.subscribe(response => {
const name =fileName;
const blob = response;
saveAs(blob,name);
},
err => {
console.log(err);
console.log("not ok")
}
)
}
and in my service
saveFile(fileName) {
if (this.jwtToken == null) {
this.loadToken()
}
// const options = new RequestOptions({responseType: ResponseContentType.Blob });
return this.http.get('http://localhost:8080/download/' + fileName, {
headers: new HttpHeaders({'Authorization': this.jwtToken}), responseType: 'blob'
})
}

Related

Return a zip (or any file) from the server on the client browser (REST)

So I am using Java for my Server and Angular for the Client. I am currently working on a feature where you can select multiple files from a table and when you press on download, it generates a zip file and downloads it to your browser. As of right now, the server now creates the zip file and I can access it in the server files. All that is left to do is to make it download on the client's browser. (the zip file is deleted after the client downloads it)
After doing some research, I found out that you can use a fileOutputStream to do this. I also saw some tools like retrofit... I am using REST and this is what my code looks like. How would I achieve my goal as simply as possible?
Angular
httpGetDownloadZip(target: string[]): Observable<ServerAnswer> {
const params = new HttpParams().set('target', String(target)).set('numberOfFiles', String(target.length));
const headers = new HttpHeaders().set('token', this.tokenService.getStorageToken());
const options = {
headers,
params,
};
return this.http
.get<ServerAnswer>(this.BASE_URL + '/files/downloadZip', options)
.pipe(catchError(this.handleError<ServerAnswer>('httpGetZip')));
}
Java zipping method
public void getDownloadZip(String[] files, String folderName) throws IOException {
[...] // The method is huge but basically I generate a folder called "Download/" in the server
// Zipping the "Download/" folder
ZipUtil.pack(new File("Download"), new File("selected-files.zip"));
// what do I return ???
return;
}
Java context
server.createContext("/files/downloadZip", new HttpHandler() {
#Override
public void handle(HttpExchange exchange) throws IOException {
if (!handleTokenPreflight(exchange)) { return; }
System.out.println(exchange.getRequestURI());
Map<String, String> queryParam = parseQueryParam(exchange.getRequestURI().getQuery());
String authToken = exchange.getRequestHeaders().getFirst("token");
String target = queryParam.get("target") + ",";
String[] files = new String[Integer.parseInt(queryParam.get("numberOfFiles"))];
[...] // I process the data in this entire method and send it to the previous method that creates a zip
Controller.getDownloadZip(files, folderName);
// what do I return to download the file on the client's browser ????
return;
}
});
A possible approach to successfully download your zip file can be the described in the following paragraphs.
First, consider returning a reference to the zip file obtained as the compression result in your downloadZip method:
public File getDownloadZip(String[] files, String folderName) throws IOException {
[...] // The method is huge but basically I generate a folder called "Download/" in the server
// Zipping the "Download/" folder
File selectedFilesZipFile = new File("selected-files.zip")
ZipUtil.pack(new File("Download"), selectedFilesZipFile);
// return the zipped file obtained as result of the previous operation
return selectedFilesZipFile;
}
Now, modify your HttpHandler to perform the download:
server.createContext("/files/downloadZip", new HttpHandler() {
#Override
public void handle(HttpExchange exchange) throws IOException {
if (!handleTokenPreflight(exchange)) { return; }
System.out.println(exchange.getRequestURI());
Map<String, String> queryParam = parseQueryParam(exchange.getRequestURI().getQuery());
String authToken = exchange.getRequestHeaders().getFirst("token");
String target = queryParam.get("target") + ",";
String[] files = new String[Integer.parseInt(queryParam.get("numberOfFiles"))];
[...] // I process the data in this entire method and send it to the previous method that creates a zip
// Get a reference to the zipped file
File selectedFilesZipFile = Controller.getDownloadZip(files, folderName);
// Set the appropiate Content-Type
exchange.getResponseHeaders().set("Content-Type", "application/zip");
// Optionally, if the file is downloaded in an anchor, set the appropiate content disposition
// exchange.getResponseHeaders().add("Content-Disposition", "attachment; filename=selected-files.zip");
// Download the file. I used java.nio.Files to copy the file contents, but please, feel free
// to use other option like java.io or the Commons-IO library, for instance
exchange.sendResponseHeaders(200, selectedFilesZipFile.length());
try (OutputStream responseBody = httpExchange.getResponseBody()) {
Files.copy(selectedFilesZipFile.toPath(), responseBody);
responseBody.flush();
}
}
});
Now the problem is how to deal with the download in Angular.
As suggested in the previous code, if the resource is public or you have a way to manage your security token, including it as a parameter in the URL, for instance, one possible solution is to not use Angular HttpClient but an anchor with an href that points to your ever backend handler method directly.
If you need to use Angular HttpClient, perhaps to include your auth tokens, then you can try the approach proposed in this great SO question.
First, in your handler, encode to Base64 the zipped file contents to simplify the task of byte handling (in a general use case, you can typically return from your server a JSON object with the file content and metadata describing that content, like content-type, etcetera):
server.createContext("/files/downloadZip", new HttpHandler() {
#Override
public void handle(HttpExchange exchange) throws IOException {
if (!handleTokenPreflight(exchange)) { return; }
System.out.println(exchange.getRequestURI());
Map<String, String> queryParam = parseQueryParam(exchange.getRequestURI().getQuery());
String authToken = exchange.getRequestHeaders().getFirst("token");
String target = queryParam.get("target") + ",";
String[] files = new String[Integer.parseInt(queryParam.get("numberOfFiles"))];
[...] // I process the data in this entire method and send it to the previous method that creates a zip
// Get a reference to the zipped file
File selectedFilesZipFile = Controller.getDownloadZip(files, folderName);
// Set the appropiate Content-Type
exchange.getResponseHeaders().set("Content-Type", "application/zip");
// Download the file
byte[] fileContent = Files.readAllBytes(selectedFilesZipFile.toPath());
byte[] base64Data = Base64.getEncoder().encode(fileContent);
exchange.sendResponseHeaders(200, base64Data.length);
try (OutputStream responseBody = httpExchange.getResponseBody()) {
// Here I am using Commons-IO IOUtils: again, please, feel free to use other alternatives for writing
// the base64 data to the response outputstream
IOUtils.write(base64Data, responseBody);
responseBody.flush();
}
}
});
After that, use the following code in you client side Angular component to perform the download:
this.downloadService.httpGetDownloadZip(['target1','target2']).pipe(
tap((b64Data) => {
const blob = this.b64toBlob(b64Data, 'application/zip');
const blobUrl = URL.createObjectURL(blob);
window.open(blobUrl);
})
).subscribe()
As indicated in the aforementioned question, b64toBlob will look like this:
private b64toBlob(b64Data: string, contentType = '', sliceSize = 512) {
const byteCharacters = atob(b64Data);
const byteArrays = [];
for (let offset = 0; offset < byteCharacters.length; offset += sliceSize) {
const slice = byteCharacters.slice(offset, offset + sliceSize);
const byteNumbers = new Array(slice.length);
for (let i = 0; i < slice.length; i++) {
byteNumbers[i] = slice.charCodeAt(i);
}
const byteArray = new Uint8Array(byteNumbers);
byteArrays.push(byteArray);
}
const blob = new Blob(byteArrays, {type: contentType});
return blob;
}
Probably you will need to slightly modify the httpGetDownloadZip method in your service to take into account the returned base 64 data - basically, change ServerAnswer to string as the returned information type:
httpGetDownloadZip(target: string[]): Observable<string> {
const params = new HttpParams().set('target', String(target)).set('numberOfFiles', String(target.length));
const headers = new HttpHeaders().set('token', this.tokenService.getStorageToken());
const options = {
headers,
params,
};
return this.http
.get<string>(this.BASE_URL + '/files/downloadZip', options)
.pipe(catchError(this.handleError<ServerAnswer>('httpGetZip')));
}
You could try using responseType as arraybuffer.
Eg:
return this.http.get(URL_API_REST + 'download?filename=' + filename, {
responseType: 'arraybuffer'
});
In My Project including both front end (angular) and back end (java).
We used the below solution ( hope it work for you ):
Angular:
https://github.com/eligrey/FileSaver.js
let observable = this.downSvc.download(opts);
this.handleData(observable, (data) => {
let content = data;
const blob = new Blob([content], { type: 'application/pdf' });
saveAs(blob, file);
});
Java:
public void download(HttpServletRequest request,HttpServletResponse response){
....
response.setHeader("Content-Disposition",
"attachment;filename=\"" + fileName + "\"");
try (
OutputStream os = response.getOutputStream();
InputStream is = new FileInputStream(file);) {
byte[] buf = new byte[1024];
int len = 0;
while ((len = is.read(buf)) > -1) {
os.write(buf, 0, len);
}
os.flush();
}
You can still use HttpServletRequest on the server...
Then get its OutputStream and write to it.
#RequestMapping(method = RequestMethod.POST , params="action=downloadDocument")
public String downloadDocument(#RequestParam(value="documentId", required=true) String documentId,
HttpServletRequest request,
HttpServletResponse response )
{
try {
String docName = null;
String documentSavePath = getDocumentSavePath();
PDocument doc = mainService.getDocumentById(iDocumentId);
if(doc==null){
throw new RuntimeException("document with id: " + documentId + " not found!");
}
docName = doc.getName();
String path = documentSavePath + ContextUtils.fileSeperator() + docName;
response.setHeader("Content-Disposition", "inline;filename=\"" + docName + "\"");
OutputStream out = response.getOutputStream();
response.setContentType("application/word");
FileInputStream stream = new FileInputStream(path);
IOUtils.copy(stream, out);
out.flush();
out.close();
} catch(FileNotFoundException fnfe){
logger.error("Error downloading document! - document not found!!!! " + fnfe.getMessage() , fnfe);
} catch (IOException e) {
logger.error("Error downloading document!!! " + e.getMessage(),e);
}
return null;
}

Upload images from Angular to SpringBoot

I was trying for hours to send images from Angular to SpringBoot. Now I'm getting this error:
org.springframework.web.multipart.MultipartException: Current request is not a multipart request
Frontend(Angular) code looks like this:
saveProduct(productSave: ProductSave, mainImg: File, imageList: File[]): Observable<any>
{
const formData = new FormData();
const formListData = new FormData();
formData.append('mainImg', mainImg, mainImg.name);
imageList.forEach( img => formListData.append('imageList', img));
return this.httpClient.post<ProductSave>(this.saveUrl, {productSave, mainImg, imageList});
}
mainImg and imageList are images uploaded from user, and initialized like so:
mainImg = event.target.files[0];
imageList.push(event.target.files[0]);
My backend (SpringBoot) code looks like this:
#PostMapping("/save")
public void saveProduct(#RequestBody ProductSave productSave, #RequestParam("mainImg")MultipartFile main, #RequestParam("imageList")MultipartFile[] multipartFiles)
{
System.out.println(productSave.getProduct().getName());
}
I really don't have idea how to send those images, I was trying to look around stack but I faild.
Thanks for any help!
The problem is in the Spring Boot Controller method Arguments.
In multipart request, you can send the JSON Request body. Instead, you will have to send, key-value pairs.
So, you have 2 ways to do what you want to do -
Send JSON String and then deserialize it.
Spring Boot API Sample :-
#PostMapping("/save")
public String create(#RequestPart("file")MultipartFile multipartFile, #RequestPart("files") List<MultipartFile> multipartFiles, #RequestPart("jsonString") String jsonString) {
/** To convert string to POJO
com.fasterxml.jackson.databind.ObjectMapper objectMapper = new com.fasterxml.jackson.databind.ObjectMapper();
ProductSave productSave = this.objectMapper.readValue(jsonString,ProductSave.class); **/
return jsonString;
}
HTML/Javascript Sample: -
<!DOCTYPE html>
<html>
<head>
</head>
<body>
<input type="file" id="file">
<button onclick="submitData()">Submit Data</button>
<script type="text/javascript">
function submitData() {
const formData = new FormData();
const fileField = document.querySelector('input[id="file"]');
formData.append('jsonString', JSON.stringify({document: {data: 'value'}}));
formData.append('file', fileField.files[0]);
Array.from(fileField.files).forEach(f => formData.append('files', f));
fetch('http://localhost:8080/save', {
method: 'POST',
body: formData
})
.then(response => response.json())
.then(result => {
console.log('Success:', result);
})
.catch(error => {
console.error('Error:', error);
});
}
</script>
</body>
And in front end:-
JSON.stringify(product);
Send Files as Byte Arrays, You don't need to use form data in frontend in this case.
You can convert file object to byte arrays in frontend using:-
const fileToByteArray = async (file) => {
return new Promise((resolve, reject) => {
try {
let reader = new FileReader();
let fileByteArray = [];
reader.readAsArrayBuffer(file);
reader.onloadend = (evt) => {
if (evt.target.readyState === FileReader.DONE) {
const arrayBuffer = evt.target.result;
const array = new Uint8Array(arrayBuffer);
array.forEach((item) => fileByteArray.push(item));
}
resolve(fileByteArray);
};
} catch (e) {
reject(e);
}
});
};
in the application.properties try setting up the following -
spring.servlet.multipart.max-file-size=1024KB
spring.servlet.multipart.max-request-size=1024KB
spring.http.multipart.enabled=false
Play around with the max-file-size.

Angular JS - How to get spring returned map value inside angular controller?

I am new to angular, can anyone tell me how to retrieve spring returned map value inside angular's controller?
Here is my code snippet:
app.js
// Service -----------------------------------------------------------------
myApp.service('FolderService', function ($log, $resource, $http) {
return {
onlineView: function(docId) {
var viwerResource = $resource('processOnlineView', {}, {
get: {method: 'POST', params: {'docId' : docId}}
});
return viwerResource.get();
}
}
})
// Controller -----------------------------------------------------------------
.controller('FolderController', function ($scope, $log, FolderService) {
//click online view
$scope.view = function(doc) {
var rtnMap = FolderService.onlineView(doc.cmObjectId);
console.log('rtnMap: ' + rtnMap );
// it shows rtnMap: [object Object]
var key = 'response';
var value = rtnMap[key];
console.log('value: ' + value );
// I want to get map value, but it shows undefined
// I except get "d:/tomcat/bin/hello.swf" here
$scope.rtnFileName = rtnMap;
}
});
my spring controller java code
#RequestMapping(value = "/processOnlineView", method = RequestMethod.POST)
public #ResponseBody Map<String, String> processOnlineView(#RequestParam(value = "docId") String docId) {
String resultDocName = "";
try {
// get File by docId
File file = queryFile(docId);
// set resultDocName value
resultDocName = file.getAbsolutePath(); // real file path, like: d:/tomcat/bin/hello.swf
} catch (Exception e) {
e.printStackTrace();
}
return Collections.singletonMap("response", resultDocName);
}
chrome log:
I can get expect value in html by using this:
rtnFileName: {{rtnFileName.response}}
html shows:
rtnFileName: d:/tomcat/bin/hello.swf
But how to get map value in angular controller directly?
Any suggestion would be appreciated.
Problem solved.
First, use $http post instead of $resource:
onlineView: function(docId) {
$http({
method: 'POST',
url: urlBase + '/processOnlineView',
params: {
docId: docId
}
})
.success(function(data, status, headers, config) {
console.log('success data: ' + data); // result: success data: [object Object]
for (key in data){
console.log('>> data key: ' + key );
console.log('>> data value: ' + data[key] );
}
var resultDocName = data['response'];
console.log('resultDocName: ' + resultDocName);
runFlexpaper(resultDocName);
})
.error(function(data, status, headers, config) {
});
}
Second, retrieve returned map inside 'success' block, because $http post is asynchronous call.
Use a service. For example:
var app = angular.module('myApp', [])
app.service('sharedProperties', function () {
var mapCoord= 'Test';
return {
getProperty: function () {
return mapCoord;
},
setProperty: function(value) {
mapCoord= value;
}
};
});
Inside your Main controller
app.controller('Main', function($scope, sharedProperties) {
$scope.mapCoord= sharedProperties.setProperty("Main");
});
Inside your Map controller
app.controller('Map', function($scope, sharedProperties) {
$scope.mapCoord= sharedProperties.getProperty();
});

How to download CSV file from GUI from java spring controller on the go?

I am new in Software Development, just out of college. Doing my first big project.
I am trying to download the CSV file when the user chooses the start date and end date of the project, so the file is supposed to return the project.csv with project name, date from .. to ..
The most important thing is that I am trying to download the file from GUI after clicking on "export" link. All I know is I have to make a spring controller. I have to be missing some part as it's not working.
My java class is writing the csv file to my C disk but it doesn't do the download part. Also CSV file should be written from the database to users computer, not to my disk.
Hope you understand me. Let me know if that's clear.
my code:
ExportController.java
#RestController
#RequestMapping("/config")
public class ExportController {
private String filePath = "C:\\test\\project.csv";
private static final int BUFFER_SIZE = 4096;
#Autowired
private ExportService exportService;
ServletContext context;
#RequestMapping(value = "export/all", method = RequestMethod.POST)
public String list(#RequestParam("startDate")String date, #RequestParam("endDate")String date2, HttpServletResponse response, HttpServletRequest request) throws ServletException, ParseException, IOException {
DateFormat format = new SimpleDateFormat("dd-MM-yyyy", Locale.ENGLISH);
Date date_obj = format.parse(date);
Date date2_obj = format.parse(date2);
// get absolute path of the application
ServletContext context = request.getServletContext();
String appPath = context.getRealPath("");
System.out.println("appPath = " + appPath);
// construct the complete absolute path of the file
String fullPath = filePath;
File downloadFile = new File(fullPath);
FileInputStream inputStream = new FileInputStream(downloadFile);
// get MIME type of the file
String mimeType = context.getMimeType(fullPath);
if (mimeType == null) {
// set to binary type if MIME mapping not found
mimeType = "application/octet-stream";
}
System.out.println("MIME type: " + mimeType);
CsvWriterProject.savetofile();
String csv = "Employee FN/LN: Eatrick Hughes Contract type: External, Activity: WAR, Effort date: 2016-02-17, Name: udfuhfd, Entity: BA, Start date: 2016-02-17, End_date: 2016-02-18";
response.setContentType("application/csv");
response.setHeader("Content-Disposition", "attachment; filename=project.csv");
response.setHeader("Pragma", "public");
response.setHeader("Expires", "0");
response.setHeader("Content-Length", String.valueOf(csv.length()));
response.setHeader("Content-type","application/csv");
// response.setContentType(mimeType);
// response.setContentLength((int) downloadFile.length());
// get output stream of the response
OutputStream outStream = response.getOutputStream();
PrintWriter pw = new PrintWriter(outStream);
pw.print(pw);
byte[] buffer = new byte[BUFFER_SIZE];
int bytesRead = -1;
// write bytes read from the input stream into the output stream
while ((bytesRead = inputStream.read(buffer)) != -1) {
outStream.write(buffer, 0, bytesRead);
}
inputStream.close();
outStream.flush();
outStream.close();
return csv;
}
}
Here is angularJS
$scope.export_all = function(item){
$http.post('config/export/all?startDate='+item.startDate+"&endDate="+item.endDate)
.success(function(response) {
$scope.export = response;
});
};
Let me know if you need more information.
You can use HttpServletResponse (javax.servlet.http.HttpServletResponse)
Here is the sample code:
package com.export.test;
import javax.servlet.http.HttpServletResponse;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
#RestController
#RequestMapping(value = "/exportTest")
public class ExportControllerTest {
#RequestMapping(value = "/export", method = RequestMethod.GET)
public void exportStream(HttpServletResponse response) {
try {
String responseTosend = "Testing export for rest cliet";
response.getOutputStream()
.write((responseTosend).getBytes("UTF-8"));
} catch (Exception e) {
e.printStackTrace();
}
}
}
Modify as per your requirement.
Check with docs for more info https://docs.oracle.com/javaee/6/api/javax/servlet/http/HttpServletResponse.html

Downloading file through Ajax post request

I'm trying to download a file through Ajax post request. But I'm not able to do this, I tried a lot but I am not getting. The bellow is the code, please guide me how to do this.
Query method
$(document).on({click: function() {
var elementId = $(this).attr('id').split("_");
var air = $('#itineraryAir_${formBean.tripDetailsId}').is(':checked');
var car = $('#itineraryCar_${formBean.tripDetailsId}').is(':checked');
var hotel = $('#itineraryHotel_${formBean.tripDetailsId}').is(':checked');
if(air == true || car == true || hotel == true){
$('#itinerary_modal').modal( 'hide');
$.ajax({
url: "<%=request.getContextPath()%>/viewBooking/downloadItinerary/" +air+"/"+car+"/"+hotel+"/"+elementId[2],
type: 'POST',
dataType: 'text/json',
beforeSend: function() {
},
success: function(data) {
},
complete: function() {
}
}, '.edit_hotel');
}else{
var result="Select itinerary."
showMsg($("#ItineraryMsg"),result,"alert-danger");
}
}
}, '.it_download');
In the above code, I'm getting Element-Id, air, car and hotel details and passing all these value through URL. In spring controller class dynamically I am generating PDF file and the file is generating, But I am not able to display for user to download it.
Server side code
//the below line of code to generate pdf file. It is generating pdf file but I am not able to pass to client.
public #ResponseBody void downloadInPDF(#PathVariable("air") Boolean air,
#PathVariable("car") Boolean car,
#PathVariable("hotel") Boolean hotel,
#PathVariable("tripDetailsId") String tripDetailsId,
HttpServletResponse response,HttpServletRequest request) throws IOException {
TripDetails td = tripDetailsService.get(Integer.parseInt(tripDetailsId));
Users user=userService.getByUserIdNew(String.valueOf(td.getUser().getUserId()));
Employee employee=employeeService.getEmployeeByEmployeeId(user.getEmployee().getEmployeeId());
if(hotel == true){
List<TripHotels> tripHotels = hotelService
.getHotelDetailsByTripId(td);
generatePdf.getPdfFileHotel(AppConstant.ETICKET_PATH, tripHotels, td,employee);
String filePathToBeServed = AppConstant.ETICKET_PATH + td.getTripId()
+ "_Hotel.pdf";
File fileToDownload = new File(filePathToBeServed);
InputStream inputStream = new FileInputStream(fileToDownload);
response.setContentType("application/pdf");
response.setHeader("Content-Disposition", "attachment; filename="+td.getTripId() + "_Hotel.pdf");
IOUtils.copy(inputStream, response.getOutputStream());
response.flushBuffer();
inputStream.close();
}
}

Categories