Spring - Download excel with POI - spring-mvc

I have this controller which create an empty sheet and I want to return the excel file to the navigator. The problem is, the excel file is corrupted.
If I create the file on my computer the file isn't corrupted, so my HSSFWorkbook is valid. Seems a problem of encodage/encapsulation added by the spring context ?
#Controller
public class ExportController {
#RequestMapping(value = "/export/test/excel", method = RequestMethod.POST)
public void downloadExcelTestFile(
HttpServletRequest request,
HttpServletResponse response) throws IOException {
HSSFWorkbook wb = new HSSFWorkbook();
wb.createSheet("Sheet1");
//response.reset();
//response.setStatus(HttpServletResponse.SC_OK);
response.setContentType("application/vnd.ms-excel");
response.setHeader("Content-Disposition", "attachment; filename=test.xls");
OutputStream out = response.getOutputStream();
wb.write(out);
out.flush();
out.close();
wb.close();
}
The download start well, I receive the file test.xls, but I can't open it. Is there a Spring way to achiev a proper download inside a #Controller ?
I use Spring 4.2.4
UPDATE 1
I tried a Spring way but it's not working better
HSSFWorkbook wb = new HSSFWorkbook();
wb.createSheet("Sheet1");
ByteArrayOutputStream bos = new ByteArrayOutputStream();
try {
wb.write(bos);
} finally {
bos.close();
}
byte[] bytes = bos.toByteArray();
HttpHeaders headers = new HttpHeaders();
headers.set("Content-Type", "application/vnd.ms-excel;");
headers.set("content-length",Integer.toString(bytes.length));
headers.set("Content-Disposition", "attachment; filename=test.xls");
return new ResponseEntity<byte[]>(bytes, headers, HttpStatus.CREATED);
UPDATE 3
I found a reason but I don't understand why.
If I build my war file and deploy it manually in the very same tomcat 7.0.70 it works. My Excel is not corrupted.
If I download from the dev environnement in eclipse, it doesn't work. Seems a tomcat + eclipse issue.

Ok that wasn't a Spring issue, not even a tomcat issue.
The problem was from my grunt-connect-proxy, when I run my front throught localhost:9000 : files that I downloaded were corrupted. If I build the project in a war file or run the front from localhost:8080 ( same port than the server ) without "grunt serve" and so without the proxy it works.
I have not fix the problem with grunt ... I just ignore it, but this answer can save your time.

Sample Spring Backed Code to create an excel and return it using Spring REST. The input parameters may change as per your requirement
#RequestMapping(value = "/convertFlatFileToExcel.do", method = RequestMethod.POST)
public HttpEntity<byte[]> convertFlatFileToExcel(#RequestParam(value="file") MultipartFile file,#RequestParam(value="jobid") String jobid) {
ByteArrayOutputStream archivo = new ByteArrayOutputStream();
XSSFWorkbook workbook = new XSSFWorkbook();
workbook.write(archivo);
if(null!=workbook && null!=archivo) {
workbook.close();
archivo.close();
}
byte[] documentContent = archivo.toByteArray();
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.parseMediaType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"));
headers.set(HttpHeaders.CONTENT_DISPOSITION, "inline; filename=\"myexcelfile.xls\"");
headers.setContentLength(documentContent.length);
response = new ResponseEntity<byte[]>(documentContent, headers, HttpStatus.OK);
}
**Sample UI Code:
Below is the sample code to call to Rest Service using Angular JS. Import the FileSaver js file using https://cdnjs.cloudflare.com/ajax/libs/FileSaver.js/2014-11-29/FileSaver.min.js
This will have the method saveAs() to save the given excel blob data with a given name.
**
$http.post(urlBase+'/convertFlatFileToExcel.do', formData,{
transformRequest : angular.identity,
responseType: 'arraybuffer',
headers : {
'Content-Type' : undefined,
'Accept': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
}})
.then(
function (response) {
$window.sessionStorage.showProgress = "";
var file = new Blob([response.data], { type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"});
saveAs(file, jobid.toUpperCase()+'.xlsx');
},
function (errResponse) {
$window.sessionStorage.showProgress = "";
$mdDialog.show($mdDialog.alert({title: 'Invalid Job ID!',textContent: 'Please enter a valid Job ID. For any issues, please contact the admin!',ok: 'GOT IT!'}));
deferred.reject(errResponse);
});

Related

Angular / Asp.Net Web Api request binary data

I have an Angular 4 application which consumes an Asp.Net Web Api, and I want the Api to return a binary file. The Api route seems to be working correctly - I tested it using a rest console and the response is as expected. However, when trying to use the same route in the Angular app, the request sends but returns an error. I can see with the C# debugger that the request is executing completely and doesn't fail on the server. Here's the error in the JS console:
This error occurs on all browsers tested (Chrome, Firefox, IE, Edge, Safari).
Here's the server side code:
[Route("api/getfile")]
public IHttpActionResult GetFile()
{
byte[] file = <file generator code>;
System.Net.Http.HttpResponseMessage responseMessage = new System.Net.Http.HttpResponseMessage
{
Content = new System.Net.Http.StreamContent(new System.IO.MemoryStream(file))
};
responseMessage.Content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/octet-stream");
responseMessage.Content.Headers.ContentDisposition = new System.Net.Http.Headers.ContentDispositionHeaderValue("attachment");
responseMessage.Content.Headers.ContentDisposition.FileName = "file.pdf";
return this.ResponseMessage(responseMessage);
}
And here's the Angular code:
let headers = new Headers({
"Accept": "application/octet-stream",
"X-Requested-With": "XMLHttpRequest",
"Authorization": `Bearer ${token}`
});
let opts = new RequestOptions({headers = headers});
opts.responseType = ResponseContentType.Blob;
// Uses old #angular/http, not HttpClient
this.http
.get(`${apiUrl}/getfile`, opts)
.map(res => res.blob())
.catch(err => handleError(err));
EDIT: I tried using a plain XMLHttpRequest instead of Angular's Http service and it works. What do I need to do to get this to work with Angular?
EDIT 2: It works if I fetch an actual file on the file system that's accessible using the same host that the Angular app is running on. The Angular app is on localhost:8080, while the api is on a different port. If I expose a file on localhost:8080 (e.g., in the build folder) than I can fetch that file. This makes me wonder if it's a security issue, or maybe has to do with the headers or the way Web Api returns the binary data.
On your Api that will return your PDF
FileContentResult result;
if(!string.IsNullOrEmpty(fileName))
{
string absoluteFileName = Path.Combine(pathToFile, fileName);
byte[] fileContents = System.IO.File.ReadAllBytes(absoluteFileName);
result = new FileContentResult(fileContents, "application/pdf");
}
And then on Angular:
downloadFile(api: string) {
window.open(this.endPoint + api);
}
Try the old Way:
FileInfo fileInfo = New FileInfo(filePath)
Response.Clear()
Response.ClearHeaders()
Response.ClearContent()
Response.AddHeader("Content-Disposition", "attachment;filename=" + fileInfo.Name)
Response.AddHeader("Content-Type", "application/pdf")
Response.ContentType = "application/pdf"
Response.AddHeader("Content-Length", fileInfo.Length.ToString())
Response.TransmitFile(fileInfo.FullName)
Response.Flush()
Response.End()
This is for an image that I was keeping in a blob column in a db, but process should be similar. I ended up doing something like this back in Angular 4 (although this was 4.3+ which means HttpClient, not Http) to handle downloading files on clicking a button:
public downloadImage(id: number, imageName: string, imageType: string) {
this.http.get(urlToApiHere, { responseType: 'blob' }).subscribe((image: Blob) => {
if (isPlatformBrowser(this.platformId)) {
let a = window.document.createElement("a");
document.body.appendChild(a);
let blobUrl = window.URL.createObjectURL(image);
a.href = blobUrl;
a.download = imageName;
a.click();
window.URL.revokeObjectURL(blobUrl);
document.body.removeChild(a);
}
})
}
This API is .Net Core, but should be similar in .Net MVC, I believe:
[HttpGet]
public FileResult DisplayLineItemImage(int lineItemID)
{
using (var db = new Context())
{
var image = //retrieve blob binary, type, and filename here
if (image.Image == null)
{
//throw error
}
return File(image.Image, image.Type, image.Name);
}
}
The second answer by Crying Freeman, using Response directly, does work, but it bypasses Owin's processing and would mean having to manually implement things like CORS or anything else normally handled using CORS.
I found another solution, to use a custom formatter to allow returning a byte array from the controller method. This is also nicer because I don't need to set any headers manually, not even Content-Type.

JxBrowser not connecting

I am having troubles to make the jxbrowser work outside of the development environment. When I run it in eclipse it works fine, but when I compile it and run the screen doesn't seems to load. Here is the code that I'm using:
browser = new Browser();
com.teamdev.jxbrowser.chromium.swing.BrowserView view = new com.teamdev.jxbrowser.chromium.swing.BrowserView(browser);
javax.swing.JFrame frame = new javax.swing.JFrame();
frame.add(view, java.awt.BorderLayout.CENTER);
frame.setSize(800, 450);
frame.setVisible(true);
browser.loadURL(Main.class.getResource("/assets/engine.html").toExternalForm());
> When I run from eclipse <
> When I compile and run <
Am I missing something?
If your HTML resource "/assets/engine.html" is located inside the RPGItems.jar after build, the path to it will not be resolved properly by the Chromium engine by default. To be able to load resources located inside JAR archive you must register custom ProtocolHandler with the following implementation:
BrowserContext browserContext = browser.getContext();
ProtocolService protocolService = browserContext.getProtocolService();
protocolService.setProtocolHandler("jar", new ProtocolHandler() {
#Override
public URLResponse onRequest(URLRequest request) {
try {
URLResponse response = new URLResponse();
URL path = new URL(request.getURL());
InputStream inputStream = path.openStream();
DataInputStream stream = new DataInputStream(inputStream);
byte[] data = new byte[stream.available()];
stream.readFully(data);
response.setData(data);
String mimeType = getMimeType(path.toString());
response.getHeaders().setHeader("Content-Type", mimeType);
return response;
} catch (Exception ignored) {}
return null;
}
});
The getMimeTypemethod here returns appropriate mime type for the given resource extension:
private static String getMimeType(String path) {
if (path.endsWith(".html")) {
return "text/html";
}
if (path.endsWith(".css")) {
return "text/css";
}
if (path.endsWith(".js")) {
return "text/javascript";
}
return "text/html";
}
Once you register ProtocolHandler and define what mime types are supported, you can load resources from JAR archive using standard Java and JxBrowser API:
browser.loadURL(Main.class.getResource("/assets/engine.html").toString());

Null response creating file using Google Drive .NET API

I am trying to upload a file onto my Drive using Google Drive .NET API v3. My code is below
static string[] Scopes = { DriveService.Scope.Drive,
DriveService.Scope.DriveAppdata,
DriveService.Scope.DriveFile,
DriveService.Scope.DriveMetadataReadonly,
DriveService.Scope.DriveReadonly,
DriveService.Scope.DriveScripts };
static string ApplicationName = "Drive API .NET Quickstart";
public ActionResult Index()
{
UserCredential credential;
using (var stream =
new FileStream("C:/Users/admin1/Documents/visual studio 2017/Projects/TryGoogleDrive/TryGoogleDrive/client_secret.json", FileMode.Open, FileAccess.Read))
{
string credPath = Environment.GetFolderPath(
System.Environment.SpecialFolder.Personal);
credPath = Path.Combine(credPath, ".credentials/drive-dotnet-quickstart.json");
credential = GoogleWebAuthorizationBroker.AuthorizeAsync(
GoogleClientSecrets.Load(stream).Secrets,
Scopes,
"user",
CancellationToken.None,
new FileDataStore(credPath, true)).Result;
Debug.WriteLine("Credential file saved to: " + credPath);
}
// Create Drive API service.
var service = new DriveService(new BaseClientService.Initializer()
{
HttpClientInitializer = credential,
ApplicationName = ApplicationName,
});
// Define parameters of request.
FilesResource.ListRequest listRequest = service.Files.List();
listRequest.PageSize = 10;
listRequest.Fields = "nextPageToken, files(id, name)";
// List files.
IList<Google.Apis.Drive.v3.Data.File> files = listRequest.Execute()
.Files;
Debug.WriteLine("Files:");
if (files != null && files.Count > 0)
{
foreach (var file in files)
{
Debug.WriteLine("{0} ({1})", file.Name, file.Id);
}
}
else
{
Debug.WriteLine("No files found.");
}
var fileMetadata = new Google.Apis.Drive.v3.Data.File()
{
Name = "report.csv",
MimeType = "text/csv",
};
FilesResource.CreateMediaUpload request;
using (var stream = new FileStream("C:/debugging/report.csv",
FileMode.Open))
{
request = service.Files.Create(
fileMetadata, stream, "text/csv");
request.Fields = "id";
request.Upload();
}
var response = request.ResponseBody;
Console.WriteLine("File ID: " + response.Id);
return View();
}
The problem I'm facing is that response is always null. I looked into it a bit further and found that the request returned a 403 resultCode. I also took a look at some other questions on SO this and this but neither were of any help.
Edit: I forgot to mention that the first part of the code is working correctly - it lists all the files in my Drive. Only the second part is not working (the upload file part)
string[] Scopes = { DriveService.Scope.Drive };
Change the Drive scope then delete the file token.json
in vs2017 you can see token.json file in token.json folder when client_secret.json file present.
Try to visit this post from ASP.NET forum.
The same idea as what you want to do in your app, since you are dealing with uploading a file in Google Drive using .net.
You may try to call rest api directly to achieve your requirement :
The quickstart from .net will help you to make requests from/to the Drive API.
Upload Files:
The Drive API allows you to upload file data when create or
updating a File resource.
You can send upload requests in any of the following ways:
Simple upload: uploadType=media. For quick transfer of a small file (5 MB or less). To perform a simple upload, refer to Performing
a Simple Upload.
Multipart upload: uploadType=multipart. For quick transfer of a small file (5 MB or less) and metadata describing the file, all in a
single request. To perform a multipart upload, refer to Performing a
Multipart Upload.
Resumable upload: uploadType=resumable. For more reliable transfer, especially important with large files. Resumable uploads are
a good choice for most applications, since they also work for small
files at the cost of one additional HTTP request per upload. To
perform a resumable upload, refer to Performing a Resumable
Upload.
You may try this code from the documentation on uploading sample file.
var fileMetadata = new File()
{
Name = "photo.jpg"
};
FilesResource.CreateMediaUpload request;
using (var stream = new System.IO.FileStream("files/photo.jpg",
System.IO.FileMode.Open))
{
request = driveService.Files.Create(
fileMetadata, stream, "image/jpeg");
request.Fields = "id";
request.Upload();
}
var file = request.ResponseBody;
Console.WriteLine("File ID: " + file.Id);
You may check the errors you may encounter in this documentation.
Have a look at what request.Upload() returns. For me when I was having this issue it returned:
Insufficient Permission Errors [Message[Insufficient Permission] Location[ - ]
I changed my scope from DriveService.Scope.DriveReadonly to DriveService.Scope.Drive and I was in business.
Change static string[] Scopes = { DriveService.Scope.DriveReadonly }; to static string[] Scopes = { DriveService.Scope.Drive };.
After changes, take a look into token.json file and check does it change its scope from DriveReadonly to Drive.
If you are seeing DriveReadonly then delete the token.json file and run the application again.

HttpMediaTypeNotAcceptableException: Could not find acceptable representation spring mvc

This is driving me crazy! I'm trying to serve a JPG image. I am sure this method was working fine the other day, so I don't know what's changed. I've tried many different things to get it to work but I can't seem to get past the exception.
Basically I'm trying to serve an image from the database.
I thought maybe the actual bytes are corrupt so I wrote them to a file, and checked the file content. Just in Finder on Mac, the file in the temp directory looks fine in the preview application so I'm pretty sure it's not the content itself causing the problem.
This is the controller method:
#RequestMapping(value="/binaries/**", method = RequestMethod.GET, produces={MediaType.APPLICATION_JSON_VALUE, MediaType.IMAGE_GIF_VALUE,
MediaType.IMAGE_JPEG_VALUE, MediaType.IMAGE_PNG_VALUE, "application/javascript"})
public #ResponseBody
ResponseEntity<byte[]> serveResource(WebRequest webRequest, HttpServletResponse response, String uri) throws IOException {
String path = (String)request.getAttribute( HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE );
BinaryFile bf = binaryService.findByUri(path);
String tmpdir = System.getProperty("java.io.tmpdir");
File dest = new File(tmpdir + File.separator + bf.getFileName());
FileUtils.writeByteArrayToFile(dest, bf.getResource());
logger.debug("file written: " + dest.getAbsolutePath());
// response.addHeader("Cache-Control", "public, max-age=3600");
if (webRequest.checkNotModified(bf.getLastModifiedDate().toDate().getTime()))
{
return null;
};
return ResponseEntity.ok().contentType(MediaType.IMAGE_JPEG).body(bf.getResource());
}
This is the exception:
Request: http://localhost:8080/binaries/products/shortcode_1/test_image2.jpg raised org.springframework.web.HttpMediaTypeNotAcceptableException: Could not find acceptable representation org.springframework.web.HttpMediaTypeNotAcceptableException: Could not find acceptable representation
Anyone have any ideas? It's Spring 4.1.4.RELEASE
Oh never mind, I figured out what changed. I'd overridden the MessageConverters because I was working on some Jackson stuff, so the fix was that I needed to manually add back the ByteArrayHttpMessageConverter.
#Bean
public ByteArrayHttpMessageConverter byteArrayHttpMessageConverter(){
ByteArrayHttpMessageConverter bam = new ByteArrayHttpMessageConverter();
List<org.springframework.http.MediaType> mediaTypes = new LinkedList<org.springframework.http.MediaType>();
mediaTypes.add(org.springframework.http.MediaType.APPLICATION_JSON);
mediaTypes.add(org.springframework.http.MediaType.IMAGE_JPEG);
mediaTypes.add(org.springframework.http.MediaType.IMAGE_PNG);
mediaTypes.add(org.springframework.http.MediaType.IMAGE_GIF);
mediaTypes.add(org.springframework.http.MediaType.TEXT_PLAIN);
bam.setSupportedMediaTypes(mediaTypes);
return bam;
}
#Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
MappingJackson2HttpMessageConverter mapper = new MappingJackson2HttpMessageConverter();
ObjectMapper om = new ObjectMapper();
om.registerModule(new JodaModule());
om.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
mapper.setObjectMapper(om);
converters.add(mapper);
converters.add(byteArrayHttpMessageConverter());
super.configureMessageConverters(converters);
}

Apache Abdera Multipart Request throwing nullpointer Exception(IBM connection API)

I am using Apache abdera to post multipart request to IBM connection 4.0 API. I am getting nullpointer exception from Abdera API. Please let me know what's the root cause.
private void createEntryWithAttachment(){
try {
String activityId = "urn:lsid:ibm.com:oa:662d0dc7-0308-48ee-8291-d730c733d2d1";
String activityIdLocal = activityId.substring(activityId.lastIndexOf(":")+1, activityId.length());
String createEntryLocal = createEntry+activityIdLocal;
Abdera abdera = new Abdera();
AbderaClient client = new AbderaClient(abdera);
AbderaClient.registerTrustManager();
System.out.println("pd --->"+pd);
client.addCookie("poktam2cl.iespc.ibm.com", "PD-S-SESSION-ID", pd, "/", null, true);
RequestOptions requestOptions = client.getDefaultRequestOptions();
requestOptions.setUseChunked(true);
requestOptions.setHeader("Connection", "close");
requestOptions.setHeader("Content-Type", "multipart/related;type=\"application/atom+xml\"");
requestOptions.setContentType("multipart/related;type=\"application/atom+xml\"");
requestOptions.setSlug("Sample.txt");
Credentials credentials = new UsernamePasswordCredentials(username, password);
client.addCredentials(createEntryLocal, AuthScope.ANY_REALM,AuthScope.ANY_SCHEME, credentials);
Entry entry = abdera.getFactory().newEntry();
entry.setTitle("create entry with attachment title ");
entry.setContent("create entry with attachment content");
javax.xml.namespace.QName field = new QName("http://www.ibm.com/xmlns/prod/sn", "field", "snx");
org.apache.abdera.model.Element fieldElement = entry.addExtension(field);
fieldElement.setAttributeValue("type", "file");
fieldElement.setAttributeValue("name", "sampletextfile1");
fieldElement.setAttributeValue("position", "3000");
FileInputStream fis = new FileInputStream(filepath);
requestOptions.setHeader("Content-Length", "35");
entry.addCategory("http://www.ibm.com/xmlns/prod/sn/type","entry", "Entry");
ClientResponse response = client.post(createEntryLocal, entry, fis, "multipart/related;type=\"application/atom+xml\"", requestOptions );
System.out.println("Entry Created with attachment's resp: " + response.getStatus());
if(response.getStatus() == 201){
System.out.println("Entry Created with attachment successfully .....");
printIBMConnectionErrorMessage(response);
}else{
System.out.println("Entry with attachment creation failed");
printIBMConnectionErrorMessage(response);
//System.exit(0);
}
} catch (Exception e) {
e.printStackTrace();
}
}
Output
java.lang.NullPointerException
at org.apache.abdera.protocol.client.util.MultipartRelatedRequestEntity.writeInput(MultipartRelatedRequestEntity.java:74)
at org.apache.abdera.protocol.client.util.MultipartRelatedRequestEntity.writeRequest(MultipartRelatedRequestEntity.java:59)
at org.apache.commons.httpclient.methods.EntityEnclosingMethod.writeRequestBody(EntityEnclosingMethod.java:499)
at org.apache.commons.httpclient.HttpMethodBase.writeRequest(HttpMethodBase.java:2114)
at org.apache.commons.httpclient.HttpMethodBase.execute(HttpMethodBase.java:1096)
at org.apache.commons.httpclient.HttpMethodDirector.executeWithRetry(HttpMethodDirector.java:398)
at org.apache.commons.httpclient.HttpMethodDirector.executeMethod(HttpMethodDirector.java:171)
at org.apache.commons.httpclient.HttpClient.executeMethod(HttpClient.java:397)
at org.apache.commons.httpclient.HttpClient.executeMethod(HttpClient.java:323)
at org.apache.abdera.protocol.client.AbderaClient.execute(AbderaClient.java:688)
at org.apache.abdera.protocol.client.AbderaClient.post(AbderaClient.java:306)
at JavaAgentEntryWithAttachment.createEntryWithAttachment(JavaAgentEntryWithAttachment.java:157)
at JavaAgentEntryWithAttachment.main(JavaAgentEntryWithAttachment.java:66)
This exception is coming from abdera API, class called MultipartRelatedRequestEntity.java, Line no 74. I have placed line no 74 source code below. So its clear that contentSrc is null & Abdera API not allowing me to set this value. Please let me know what I am missing here.
String contentId = entry.getContentSrc().toString();
I did in two steps:
Send the file
Call to update the data
Each with the good mime type. You can not send the file with XML mime type. And put the length of the file.
It is possible to avoid the nullpointer and do it in one request. I had the same issue and created another issue and managed to find a solution. You can find it here.
It comes down to the following code example where you create a HttpClient Part which can contain a StringPart and a FilePart
final Entry entry = // ... Create your Entry
final RequestOptions options = this.client.getDefaultRequestOptions();
options.setHeader("Content-Type", "multipart/related;type=\"application/atom+xml\"");
StringPart entryPart = new StringPart("entry", entry.toString());
entryPart.setContentType("application/atom+xml");
FilePart filePart = new FilePart("file", new File(resource.getFile()));
RequestEntity request = new MultipartRequestEntity(new Part[] { entryPart, filePart}, this.client.getHttpClientParams());
ClientResponse response = client.post(this.url + this.activityId, request, options);
Hope this will help people in the future if they are using Abdera.

Resources