I want to export a report as pdf and it should ask the user for a download location. How do I do this in grails?
This is my code:
def exportToPdf(JasperPrint jasperPrint,String path,request){
String cur_time =System.currentTimeMillis();
JRExporter pdfExporter = null;
pdfExporter = new JRPdfExporter();
log.debug("exporting to file..."+JasperExportManager.exportReportToPdfFile(jasperPrint, "C:\\pdfReport"+cur_time+".pdf"));
return ;
}
In jasper controller:
/**
* Generate a html response.
*/
def generateResponse = {reportDef ->
if (!reportDef.fileFormat.inline && !reportDef.parameters._inline) {
response.setHeader("Content-disposition", "attachment; filename=\"" + reportDef.name + "." + reportDef.fileFormat.extension + "\"");
response.contentType = reportDef.fileFormat.mimeTyp
response.characterEncoding = "UTF-8"
response.outputStream << reportDef.contentStream.toByteArray()
} else {
render(text: reportDef.contentStream, contentType: reportDef.fileFormat.mimeTyp, encoding: reportDef.parameters.encoding ? reportDef.parameters.encoding : 'UTF-8');
}
}
Have you looked at the Jasper Plugin? It seems to have the tools already built for you. As far as asking the user for a download location the browser has some controller over how files are received from a web page. Is your real issue that you want control over the download location?
[UPDATE]
Using the location 'c:\' is on your server not the client and this is why it is not downloading.
try something like this...
def controllerMethod = {
def temp_file = File.createTempFile("jasperReport",".pdf") //<-- you don't have to use a temp file but don't forget to delete them off the server at some point.
JasperExportManager.exportReportToPdfFile(jasperPrint, temp_file.absolutePath));
response.setContentType("application/pdf") //<-- you'll have to handle this dynamically at some point
response.setHeader("Content-disposition", "attachment;filename=${temp_file.getName()}")
response.outputStream << temp_file.newInputStream() //<-- binary stream copy to client
}
I have not tested this and there are better ways of handling the files and streams but i think you'll get the general idea.
Related
I have a data-API to receive data from the file system. The data-API is a nodeJS server. Our webserver is a Rails server. From the webserver I send parameters to the API which files I want to download. The data-API then zips the request files and sends the zip back as binary data.
So far everything works fine. Now come the part where I'm stuck. I want to present the binary data as a download to the browser. I can either convert the binary data into a zip file and send that to the browser or have another clever solution that will present the binary data as a download.
Here's what I've got so far
Rails webserver side:
app/controllers/simulation_controller.rb
def download_multiple
files = JSON.parse(params[:files])
file_list = #simulation.sweep_points.find(params[:sweep_point_id]).file_list
send_data file_list.zip(files: files), filename: 'archive.zip', type: 'application/zip', disposition: 'attachment'
end
app/models/file_list.rb
def zip
uuid = SecureRandom.uuid
simulation = sweep_point.simulation
files = files[:files].join(" ")
url = URI.parse("http://localhost:3003/files/zip/#{simulation.name}/#{uuid}");
req = Net::HTTP::Get.new(url.to_s)
req.add_field("files", files)
res = Net::HTTP.start(url.host, url.port) { |http| http.request(req) }
content = res.body
content.force_encoding("ASCII-8BIT")
end
nodeJS data-API side:
exports.zip_simulation_files = (req, res) => {
const { headers, method, url } = req;
var simulation = req.params.simulation;
var uuid = req.params.uuid;
var files, command;
req.on("error", (err) => {
console.error(err);
});
files = req.headers.files;
command = "cd postprocess/bumblebee-zip " + "run" + simulation + " " + uuid + " " + "'" + files + "'";
execute(command, (data) => {
res.on("error", (err) => {
console.error(err);
});
const zipFilePath = "/home/samuel/test_bumblebee/.zips/run" + simulation + "-files-" + uuid + ".zip"
var zipFile = fs.readFileSync(zipFilePath);
var stats = fs.statSync(zipFilePath);
res.writeHead(200, {
'Content-Type': 'application/zip',
'Content-Disposition': 'attachment; filename="archive.zip"',
'Content-Length': stats.size,
'Content-Transfer-Encoding': 'binary'
});
res.end(zipFile, 'binary');
});
}
So far I'm getting back a response that is a string which seems to be a binary string. Which starts and end with this:
"PK\x03\x04\x14\x00\x00\x00\b\x00\xA8\x89\xDBJ\xB1\xB8\xA1\xBF2\x03\x00\x00\xB7F\x00\x006\x00\x1C\x00runZhangD3FINAL/13.0V/annihilation_abs_profile_001.outUT\t\x00\x0 ....... x06\x00\x00\x00\x00\x05\x00\x05\x00l\x02\x00\x00)\x10\x00\x00\x00\x00"
I've looked into different solutions in trying to turn the binary data string into a zip file or presenting the stream directly to the browser but nothing worked. Either the zip is not created or the binary was not seen as proper data.
What would be a good solution?
I am working on a web app programmed in Grails. I have a page used to display a certain report and these reports can contain attachments. The attachment is stored in documentum and currently, when the user clicks on it, it is only a link to the location in documentum where the attachment is stored and prompts the user for his credentials. My app has documentum credentials stored in the configuration file therefore I want to use those rather than forcing the user to enter his own credentials. I am using RESTful services to retrieve link but I am trying to find a way to use the link to download directly to the users computer.
private def getFileInfo(def id, def subject) {
// product show view needs the following four lists to display the document information correctly
def ATRReportInstance = ATRReport.findByTrackingNumber(id)
def linkList = []
def nameList = []
def formatList = []
def idList = []
// open up a connection to the documentum server
def doc = connectToDocumentum()
if (!doc) return
def rest = doc.rest
def response = doc.response
if (response.status == 200) {
// retrieve the folder for this product (the name of this folder is the product's ID)
def rObjectId = rest.get(documentumServer + "/repositories/" + documentumfilestore + "?dql=select r_object_id from dm_folder where any r_folder_path='" + atrreportfolderpath + "/" + id + "'") {
auth authuser, authpass
}
// get the folder's ID from the folder object retrieved above
def folderObjectID
rObjectId.json.entries.each {
entry - >
folderObjectID = entry.content.properties.r_object_id
}
// get all of the documents in the product's MSDS folder using the folder ID retrieved above
def resp = rest.get(documentumServer + "/repositories/" + documentumfilestore + "?dql=select r_object_id, object_name, a_content_type, subject from cbs_document where any i_folder_id= '" + folderObjectID + "'") {
auth authuser, authpass
}
// cycle through the documents above to populate the four MSDS document information lists
def x = 0
resp.json.entries.each {
entry - >
if (entry.content.properties.subject == subject) {
// get the document's content object from the document's ID
def content = rest.get(documentumServer + "/repositories/" + documentumfilestore + "/objects/" + entry.content.properties.r_object_id + "/contents/content" + "?media-url-policy=local") {
auth authuser, authpass
}
if (entry.content.properties.r_object_id != null && ATRReportInstance.inactiveFiles != null && ATRReportInstance.inactiveFiles.contains(entry.content.properties.r_object_id.toString())) {} else {
linkList[x] = getLink(content.json.links, "enclosure")
if (linkList[x].contains("format=msg"))
linkList[x] = linkList[x].toString().substring(0, linkList[x].toString().indexOf("content-media")) + "content-media.msg"
formatList[x] = entry.content.properties.a_content_type
nameList[x] = entry.content.properties.object_name
idList[x] = entry.content.properties.r_object_id
x++
}
}
}
return [linkList: linkList, nameList: nameList, formatList: formatList, idList: idList]
} else {
// return null if documentum is unavailable
flash.message = message(code: 'error.documentum.unavailable')
return null
}
}
I'm thinking writing another function that can take in a URL and download the document to the user might work, but I can't figure how to retrieve that document within Grails.
If you want to bypass login you could either setup a SSO solution (requires some work for DCTM) or do a function as you suggest. However you should consider the licensing terms when doing this.
Here is the solution I implemented and that worked. It is a method that downloads a file in documentum using authentication credentials found in a configuration file.
def exportAttachment() {
//uses parameters from gsp file
def url = params.url
def name = params.name
def format = params.format
def extension
//find proper extension
for (s in documentumExtMap) {
if (s.value.equals(format)) {
extension = s.key
}
}
def connection = new URL(url).openConnection()
def remoteAuth = "Basic " + "${authuser}:${authpass}".bytes.encodeBase64()
connection.setRequestProperty("Authorization", remoteAuth)
def dataStream = connection.inputStream
response.setContentType("application/octet-stream")
response.setHeader('Content-disposition', 'Attachment; filename=' + name + '.' + extension)
response.outputStream << dataStream
response.outputStream.flush()
}
The method has three parameters: url, name, format.
Url is the location of the file in documentum.
Name is the name of the download client side
Format is the type of file that is being downloaded. In my case, I had to use this to get the proper extension needed for the file.
I'm trying to generate a Excel .xlsx file in a controller action. I would like to have the website show a download prompt to download the resulting file. The controller actions executes fine, but no download prompt is shown. Nothing happens.
I've tried:
MemoryStream mstream = ... //generated file;
return File(mstream.ToArray(), "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", model.DisplayName + ".xlsx");
I've tried:
return new FileStreamResult(mstream, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet") { FileDownloadName = model.DisplayName + ".xlsx" };
I've tried:
Response.Clear();
Response.AddHeader("Content-Disposition", "attachment; filename=" + model.DisplayName + ".xlsx");
Response.ContentType = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
Response.Write(mstream.ToArray());
Response.End();
return Content("");
I even tried saving the file to disk, then returning via the filepath
return File(filepath, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")
What am I doing wrong?
Thanks!
I am using the following code in an MVC project.
public ActionResult GetCSV()
{
string filename = "example";
string csv = MyHelper.GetCSVString();
return File(Encoding.UTF8.GetBytes(csv.ToString()), "text/csv", string.Format("{0}.csv", filename));
}
My csv string could look something like this
"Col1,Col2,Col3\nRow1Val1,Row1Val2,Row1Val3\n"
To trigger this download in a new window I call the following JavaScript
window.open('/MyUrl/GetCSV', 'DownloadWindowName');
Add the header as follows.
var cd = new System.Net.Mime.ContentDisposition
{
FileName = model.DisplayName + ".xlsx",
Inline = false
};
Response.AppendHeader("Content-Disposition", cd.ToString());
then return the file as follows
return File(mstream, ".xlsx");
Regarding download prompt. If you mean a prompt where it asks where to save the file, then it depends on how the user has set it up in their browser settings. For example in chrome, users can choose not to get a prompt when downloading files and have it downloaded to a pre specified location like the download folder.
http://malektips.com/google-chrome-prompt-download-file.html#.VM-DbFWsUm8
I am using jasper plugin to generate my report. All is fine but except my pdf is opening on the current tab, when I want to open it in a new tab.
I have no idea how to do it. Can anybody please help me on this please?
I am using "i-report" to design my pdf. Here is my code below :
def index = {
println(params)
def testModel = this.getProperties().containsKey('chainModel') ?
chainModel : null
JasperReportDef report = null
if (params.containsKey('auto_print_dialog')){
report = docuJasperService.buildReportDefinition(params,
request.getLocale(), testModel)
}
else{
report = jasperService.buildReportDefinition(params,
request.getLocale(), testModel)
}
generateResponse(report)
}
def generateResponse = {reportDef ->
if (!reportDef.fileFormat.inline && !reportDef.parameters._inline) {
//response.characterEncoding = "UTF-8"
//response.setHeader("Content-disposition",
//"inline; filename=${params._name}");
response.setHeader("Content-disposition", "inline;
filename="+(reportDef.parameters._name ?: reportDef.name) +
"."+reportDef.fileFormat.extension);
response.contentType = "application/pdf"
response.outputStream << reportDef.contentStream.toByteArray()
} else {
render(text: reportDef.contentStream, contentType:
reportDef.fileFormat.mimeTyp,
encoding: reportDef.parameters.encoding ?
reportDef.parameters.encoding : 'UTF-8');
}
}
This is going to be handled by the link you use to generate the report. Typically adding a target of _blank will do the trick. For example:
<g:link controller="myReports" action="whateverReport" target="_blank">Click for your report</g:link>
This will open the link in a new tab.
If you are using a form adding target to the form will submit the form to a new tab. For example:
<form name="myForm" action="whateverReport" method="POST" target="_blank">
or
<g:form name="myForm" controller="myReports" action="whateverReport" target="_blank">
You could even do so with javascript window.open if you needed, but I will leave that example out of this answer as it's well documented elsewhere.
I need to redirect after using response.outputStream
I'm new to grails so I may not know if there is a simple way of doing it. Or if it is even possible.
Here is the snippet:
def filename = "ProgramA14_"+DASelected+"_backup.csv"
def filecontent = response.outputStream
response.setHeader("Content-disposition", "attachment; filename="+filename)
response.contentType = "text/csv"
filecontent << "program,da,area,date,forecastedReportedCumulative,forecastedReportedLow,forecastedReportedUpper,forecastedCorrectedCumulative,openPronto,openProntoLow,openProntoUpper,forecastedReportedWeekly,forecastedCorrectedWeekly\n"
flash.message = "Sample Flash message."
redirect(action:list, params:[programA14InstanceList: programA14DA, programA14InstanceTotal: programA14DA.count()])
}
I think you need to use One-Time Data plugin