JSF file download exception handling (how to prevent view rerendering) - ajax

Good day, friends!
First of all, I'm sorry for my English. Here is my question:
I'm new to JSF (2.0), and I'm trying to use BalusC algorythm for file download from managed bean. Function works correctly and "Save As..." dialog appears in browser. But I don't know how to return file download error message (exception, DB error etc. in backing bean method) without view reload/redirect.
My hidden button on the view:
<h:form id="detailsDecisionMainForm">
<h:inputHidden id="docId" value="" />
<h:commandButton id="downloadAction" action="#{detailsDecisionGridBean.downloadForm()}" style="display: none;" />
</h:form>
My managed bean (which scope can I use? I've tried request and view scopes) method:
public String downloadForm() {
log.fine("downloadForm call");
PdfService pdfService = new PdfServiceImpl();
ArmCommonService armCommonService = new ArmCommonServiceImpl();
String errorMessage = null;
try {
Long opUni = new Long(FacesContext.getCurrentInstance().getExternalContext().getRequestParameterMap().get("detailsDecisionMainForm:docId"));
log.fine("Document opUni = " + opUni);
DocXML docXML = armCommonService.getDocXMLById(opUni);
if (docXML != null) {
File pdfFile = pdfService.getPdfReport(docXML.getXml(), docXML.getType());
if (pdfFile != null) {
DownloadUtils.exportPdf(pdfFile);
} else {
log.log(Level.SEVERE, "downloadForm error: pdf generation error");
errorMessage = "PDF-document generation error.";
}
} else {
log.log(Level.SEVERE, "downloadForm error: xml not found");
errorMessage = "XML-document not found.";
}
} catch (Exception ex) {
log.log(Level.SEVERE, "downloadForm exception: " + ex.getMessage());
errorMessage = "File download exception.";
}
if (errorMessage != null) {
FacesContext.getCurrentInstance().addMessage("detailsDecisionMainForm:downloadAction", new FacesMessage(errorMessage));
}
return null;
}
DownloadUtils.exportPdf() procedure works correctly:
public static void exportPdf(File file) throws IOException {
InputStream fileIS = null;
try {
log.fine("exportPdf call");
fileIS = new FileInputStream(file);
FacesContext fc = FacesContext.getCurrentInstance();
ExternalContext ec = fc.getExternalContext();
ec.responseReset();
ec.setResponseContentType(APPLICATION_PDF_UTF_8);
byte[] buffer = ByteStreams.toByteArray(fileIS);
ec.setResponseContentLength(buffer.length);
ec.setResponseHeader(HttpHeaders.CONTENT_DISPOSITION, String.format(CONTENT_DISPOSITION_VALUE, new String(file.getName().getBytes(StandardCharsets.UTF_8))));
ec.getResponseOutputStream().write(buffer);
fc.responseComplete();
} catch (Exception ex) {
log.log(Level.SEVERE, "exportPdf exception: " + ex.getMessage());
} finally {
if (fileIS != null) {
fileIS.close();
log.fine("exportPdf inputstream file closed");
}
}
}
What can I do to prevent view rerendering after downloadForm() error/exception? And how can I show javascript alert() with message text (in future - jQuery.messager panel with error text)?
Thank you!

In order to prevent a full page reload, you have to submit form by ajax. But, in order to be able to download a file, you have to turn off ajax. This doesn't go well together.
Your best bet is to split the action in two requests. First send an ajax request which creates the file on a temporary location in the server side. When this fails, you can just display a faces message the usual way. When this succeeds, you can just automatically trigger the second request by submitting a hidden non-ajax command button via conditionally rendered JavaScript. This second request can then just stream the already successfully created file from the temporary location to the response.
A similar question was already asked and answered before: Conditionally provide either file download or show export validation error message. But this involves PrimeFaces and OmniFaces. Below is the standard JSF approach:
<h:form id="form">
...
<h:commandButton value="Export" action="#{bean.export}">
<f:ajax ... render="result" />
</h:commandButton>
<h:panelGroup id="result">
<h:messages />
<h:commandButton id="download" action="#{bean.download}"
style="display:none" />
<h:outputScript rendered="#{not facesContext.validationFailed}">
document.getElementById("form:download").onclick();
</h:outputScript>
</h:panelGroup>
</h:form>
And use this #ViewScoped bean (logic is based on your existing logic). Basically, just get hold of the File as instance variable during export action (ajax) and then stream it during download action (non-ajax).
private File pdfFile;
public void export() {
try {
pdfFile = pdfService.getPdfReport(...);
} catch (Exception e) {
context.addMessage(...);
}
}
public void download() throws IOException {
DownloadUtils.exportPdf(pdfFile);
}
See also:
How to invoke a JSF managed bean on a HTML DOM event using native JavaScript?
Store PDF for a limited time on app server and make it available for download

Related

JSF + Primefaces + StreamedContent + documentViewer + ajax

I'm using Primefaces 5.2 with extensions 3.1.
I have my datatable and on click on each row I'd like to display modal and in this modal use documentViewer which will display document based on parameter passed from selected row.
With this I'm invoking my modal from within datatable
<p:ajax event="rowSelect" update=":previewDataForm" oncomplete="$('.previewDataModal').modal();" immediate="true">
<f:param name="pdfFile" value="#{row.dataPath}"/>
</p:ajax>
this is my modal:
<b:modal id="previewDataModal" title="Preview" styleClass="orderPreviewModalPseudoClass">
<h:form id="previewDataForm">
<pe:documentViewer height="550" value="#{contentStreamHelperBean.pdfFromFileSystem}" />
</h:form>
</b:modal>
and this is my stream helper
#Component("contentStreamHelperBean")
#Scope("request")
public class ContentStreamHelperBean extends BaseBean {
private static final Logger log = LoggerFactory.getLogger(ContentStreamHelperBean.class);
public StreamedContent getPdfFromFileSystem() {
String pdfFile = getRequestAttribute("pdfFile");
if (getFacesContext().getCurrentPhaseId() == PhaseId.RENDER_RESPONSE) {
return new DefaultStreamedContent();
} else {
if (StringUtils.isNotEmpty(pdfFile))
try {
return new DefaultStreamedContent(new FileInputStream(new File(pdfFile)), "application/pdf");
} catch (FileNotFoundException e) {
log.error("Unable to get pdf", e);
}
}
return new DefaultStreamedContent();
}
}
Problem is, that when onclick is invoked I can't get pdfFile param in my ContentStreamHelper so:
how to pass parameter from dataTable rowSelect to my backingBean?
is this good approach how to display PDF with documentViewer?

how to use custom validation message for html form in liferay6 with custom portlet?

I am using liferay to develop a custom portlet (mvc portlet) my problem is that i have one form in jsp page in HTML and and what i want is to validate some field of the forms.and on submit button its redirecting to another page.the validation i have done is working but what happen is when am clicking the submit button its redirect to another page and on that page its just showing default error from liferay. What i want is to display same page if request not completed with the error message near the field which validation has false.
How can i solve this issue?
this is my code to checking validation while add method in action class
public void addRestaurant(ActionRequest request, ActionResponse response) {
log.info("Inside addRegistration");
List<String> errors = new ArrayList<String>();
restaurant rest = RestaurantActionUtil
.getRestaurantFromRequest(request);
boolean restValid = RestaurantValidator
.validateRestaurant(rest, errors);
if (restValid) {
try {
log.info(rest);
restaurant test = restaurantLocalServiceUtil
.addrestaurant(rest);
if (test == null) {
log.error("Restaurant was Found Null");
response.setRenderParameter("jspPage", errorJSP);
return;
}
} catch (SystemException e) {
log.error(e);
/*
* If there is an error then divert the control to the error
* page.
*/
response.setRenderParameter("jspPage", errorJSP);
return;
}
SessionMessages.add(request, "restaurant-added");
return;
} else {
for (String error : errors) {
SessionErrors.add(request, error);
}
SessionErrors.add(request, "error-while-adding");
request.setAttribute("rest", rest);
return;
}
}
This is my validator class
public class RestaurantValidator {
public static boolean validateRestaurant(restaurant rest, List errors) {
boolean valid=true ;
if (Validator.isNull(rest.getName())) {
errors.add("Name-required");
valid = false;
}
if (Validator.isNull(rest.getLicensekey())) {
errors.add("license-key-required");
valid = false;
}
following is my view.jsp code
Restaurant Name*
" />
<span class="help-block">help block</span>
</div>
</div>
<div class="row-fluid">
<div class="span12">
<label>License Key<span class="f_req">*</span></label>
<liferay-ui:error key="license-key-required" message="license-key-required" />
<input type="text" name="licensekey" class="span8" value="<%=restaurantOBJ.getLicensekey() %>"/>
</div>
</div>
the error message is deisplaying on the redirected page with following way rather then on same page i want the error near textbox of name with the error of "Name_required"
The error message is displaying on the redirected page the following way rather then on same page, I want the error near the name-textbox with the error of "Name_required".
what I want is when name is blank then it should not submit the form and give error near text box of name in my view.jsp page.
I would try to answer your question, try to set a actionResponse.setRenderParameter("valid", "NO"); in your addRestaurant method if the validation fails.
And get this parameter in the doView method and set the renderPage accordingly in this method: include(renderPage, renderRequest, renderResponse); at the end of the doView method.
If you can provide all the information in your question with nice formatting like the validator method, <aui:form> and the javascript method called on onSubmit then we can go ahead with looking for other solution.
In the mean-while you can try this out and see if it works. :-)

How to use p:graphicImage with StreamedContent within p:dataTable? [duplicate]

This question already has answers here:
Display dynamic image from database or remote source with p:graphicImage and StreamedContent
(4 answers)
Closed 7 years ago.
I want to dynamically load images from a database withing a PrimeFaces data table. Code looks like as follows which is based on this PF forum topic:
<p:dataTable id="tablaInventario" var="inv" value="#{registrarPedidoController.inventarioList}" paginator="true" rows="10"
selection="#{registrarPedidoController.inventarioSelected}" selectionMode="single"
update="tablaInventario tablaDetalle total totalDesc" dblClickSelect="false" paginatorPosition="bottom">
<p:column sortBy="producto.codigo" filterBy="producto.codigo">
<f:facet name="header">#{msg.codigo}</f:facet>
#{inv.producto.codProducto}
</p:column>
<p:column>
<f:facet name="header">Foto</f:facet>
<p:graphicImage id="photo" value="#{registrarPedidoController.streamedImageById}" cache="FALSE">
<f:param name="inv" value="#{inv.id}" />
</p:graphicImage>
</p:column>
</p:dataTable>
with
public StreamedContent getStreamedImageById() {
DefaultStreamedContent image = null;
String get = FacesContext.getCurrentInstance().getExternalContext().getRequestParameterMap().get("inv");
System.out.println("[Param]: " + get); // This prints null.
Long id = new Long(get);
List<Inventario> listInventarios = controladorRegistrarPedido.listInventarios();
for (Inventario i : listInventarios) {
if (i.getId().compareTo(id) == 0) {
byte[] foto = i.getProducto().getFoto();
image = new DefaultStreamedContent(new ByteArrayInputStream(foto), "image/png");
}
}
return image;
}
However I can't get it work. My param is passing "null" to my backing bean. How is this caused and how can I solve it?
I am using Netbeans 6.9.1, JSF 2.0 and Primefaces 2.2.RC2.
I went on using BalusC first solution, it worked fine but images aren't being rendered in the UI. Exceptions Glassfish is throwing up:
WARNING: StandardWrapperValve[Faces Servlet]: PWC1406: Servlet.service() for servlet Faces Servlet threw exception
java.lang.NullPointerException
at com.sun.faces.mgbean.BeanManager$ScopeManager$ViewScopeHandler.isInScope(BeanManager.java:552)
Well seems I get working thanks to BalusC. I've to used RequestScoped, SessionScoped or ApplicationScoped for managing the getStreamedImageId. However in the UI is always setting the default image (for the null cases) and not as expected the image that correspondes to each row. The new code is:
public StreamedContent streamedById(Long id) {
DefaultStreamedContent image = null;
System.out.println("[ID inventario]: " + id);
List<Inventario> listInventarios = controladorRegistrarPedido.listInventarios();
for (Inventario i : listInventarios) {
if (i.getId().equals(id)) {
byte[] foto = i.getProducto().getFoto();
if (foto != null) {
System.out.println(" [Foto]: " + foto);
image = new DefaultStreamedContent(new ByteArrayInputStream(foto), "image/png");
break;
}
}
}
if (image == null) {
System.out.println(" [Image null]");
byte[] foto = listInventarios.get(0).getProducto().getFoto();
image = new DefaultStreamedContent(new ByteArrayInputStream(foto), "image/png");
}
System.out.println(" [Foto Streamed]: " + image);
return image;
}
The <p:graphicImage> will call the getter method twice. First time is when the <img> element is to be rendered to HTML and thus requires an URL in the src attribute. If you just return new DefaultStreamedContent(), then it will autogenerate the right URL in src attribute. Second time is when the browser really requests the image, this is the moment when you should return the actual image.
So, the getter method should basically look like this:
public StreamedContent getStreamedImageById() {
FacesContext context = FacesContext.getCurrentInstance();
if (context.getCurrentPhaseId() == PhaseId.RENDER_RESPONSE) {
// So, we're rendering the view. Return a stub StreamedContent so that it will generate right URL.
return new DefaultStreamedContent();
}
else {
// So, browser is requesting the image. Get ID value from actual request param.
String id = context.getExternalContext().getRequestParameterMap().get("id");
Image image = service.find(Long.valueOf(id));
return new DefaultStreamedContent(new ByteArrayInputStream(image.getBytes()));
}
}
images are saved in the database as byte[] if we save them through hibernate. I uploaded the images with <p:fileUpload... tag then I save the image alongwith other data values using hibernate.
On second page, I'm displaying the whole table data (of course with images) using
<p:dataTable var="data" value="#{three.all}" ....
and dynamic Images using
<p:graphicImage alt="image" value="#{three.getImage(data)}" cache="false" >
<f:param id="image_id" name="image_id" value="#{data.number}" />
</p:graphicImage></p:dataTable>
Here "three" is the name of Backing Bean. In method getAll(), I'm retrieving data from table through hibernate and in the same method, I've created a HashMap<Integer, byte[]>. HashMap is an instance variable of the bean and The Bean is SessionScoped. I'm putting the images (which are in byte[] form) alongwith an integer image_id.
code:
for (int i=0; i<utlst.size(); i++ ){
images.put(utlst.get(i).getNumber(), utlst.get(i).getImage());}
//utlst is the object retrieved from database. number is user-id.
In the view getImage.xhtml, <p:graphicImage alt="image" value="#{three.getImage(data)}" cache="false" > it calls the method getImage(data /*I am passing current object of the list which is being iterated by*/ )
code of getImage:
public StreamedContent getImage(Util ut) throws IOException {
//Util is the pojo
String image_id = FacesContext.getCurrentInstance().getExternalContext().getRequestParameterMap().get("image_id");
System.out.println("image_id: " + image_id);
if (image_id == null) {
defaultImage=new DefaultStreamedContent(FacesContext.getCurrentInstance().getExternalContext().getResourceAsStream("/Capture.PNG"), "image/png");
return defaultImage;
}
image= new DefaultStreamedContent(new ByteArrayInputStream(images.get(Integer.valueOf(image_id))), "image/png");
return image;
}
just keep your dynamic Images with ids in A HashMap in the session then they will be correctly streamed.
Thanks & regards,
Zeeshan
In PrimeFaces 3.2 the bug is still present. I do the workaround with
<p:graphicImage value="#{company.charting}">
<f:param id="a" name="a" value="#{cc.attrs.a}" />
<f:param id="b" name="b" value="#{cc.attrs.b}" />
</p:graphicImage>
and
ExternalContext externalContext = FacesContext.getCurrentInstance().getExternalContext();
String a= externalContext.getRequestParameterMap().get("a");
String b= externalContext.getRequestParameterMap().get("b");
But even with this the bean is called 2 times. But in the second call variable a + b is filled ;-)
Damn bug

Can't create a PDF with iText and JSF

I want to create a pdf using iText in my JSF + Spring web app.
When I click on a button the pdf should be generated. The method that is fired:
public void createPDF() {
log.debug("entered createPDF");
FacesContext context = FacesContext.getCurrentInstance();
HttpServletResponse response = (HttpServletResponse)context.getExternalContext().getResponse();
response.setContentType("application/pdf");
response.setHeader("Content-disposition", "inline=filename=file.pdf");
try {
// Get the text that will be added to the PDF
String text = "test";
// step 1
Document document = new Document();
// step 2
ByteArrayOutputStream baos = new ByteArrayOutputStream();
PdfWriter.getInstance(document, baos);
// step 3
document.open();
// step 4
document.add(new Paragraph(text));
// step 5
document.close();
// setting some response headers
response.setHeader("Expires", "0");
response.setHeader("Cache-Control",
"must-revalidate, post-check=0, pre-check=0");
response.setHeader("Pragma", "public");
// setting the content type
response.setContentType("application/pdf");
// the contentlength
response.setContentLength(baos.size());
// write ByteArrayOutputStream to the ServletOutputStream
OutputStream os = response.getOutputStream();
baos.writeTo(os);
os.flush();
os.close();
log.debug("flushed and closed the outputstream");
}
catch(DocumentException e) {
log.error("error: "+e);
}
catch (IOException e) {
log.error("error: "+e);
}
catch (Exception ex) {
log.debug("error: " + ex.getMessage());
}
context.responseComplete();
log.debug("context.responseComplete()");
}
this is the page with the button:
<!DOCTYPE composition PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<ui:composition xmlns="http://www.w3.org/1999/xhtml"
xmlns:s="http://jboss.org/seam/faces"
xmlns:ui="http://java.sun.com/jsf/facelets"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:rich="http://richfaces.org/rich"
xmlns:a4j="http://richfaces.org/a4j"
xmlns:c="http://java.sun.com/jsp/jstl/core"
template="/pages/layout/layout.xhtml">
<ui:define name="content">
<h:form>
<rich:panel style="width: 785px; height: 530px; ">
<a4j:commandButton value="Afdrukken" execute="#form"
action="#{huishoudinkomenAction.print}" style="float:right;" />
</rich:panel>
</h:form>
</ui:define>
I see the debug messages in the log but nothing happens to the web app. I don't see a pdf.
What am I doing wrong?
Regards,
Derk
EDIT:
When I changed the <a4j:commandButton /> to a <h:commandButton /> it worked.
I've never used RichFaces, but with Primefaces controls, you can set the attribute ajax="false".
<p:commandButton id="someid" value="Text for user" action="someConfiguredAction" ajax="false"/>
or
<h:commandButton id="someid" value="Text for user" action="someConfiguredAction"/>
When you use <a4j:commandButton> a new XmlHttpRequest will be created on your browser, and your serverside method will be called via JS. The output PDF will be written into the output stream, but the actual result will be read out of XmlHttpRequest, and interpreted by jsf.ajax.response() javascript function.
Since JSF ajax responses are always XML with a root of <partial-response>, you're basically sending junk back to the JSF ajax handler. (PDF != XML with <partial-response> root). Obviously this fails parsing so it appears that "nothing happens".
So you must use the <h:commandButton/> to do a real request. You need also to do:
response.setHeader("Content-disposition", "attachment; filename=mycool.pdf");
serverside in order to inform the browser that it receives a new file, and should download it, and not display it instead of the page.
This will have the end behavior of an "ajax" call, where you do a call, you receive the response (and save it), but your page content stays there.
You can't download files with ajax. Ajax is fired and handled by JavaScript code. However, JavaScript has for obvious security reasons no way to force a Save As dialogue with arbitrary content in a JavaScript variable (such as the response of an ajax request).
Make sure that the download button fires a synchronous (non-ajax) request. Use a normal command button or turn off ajax in the ajax command button.

Value remains in form field after it is cleared in bean in JSF2

In my JSF2 application, I have "Clear" button, which is supposed to clear all the fields. However, it doesn't always work.
My page fragment:
<h:form id="bi">
<h:inputText value="#{bean.entity.firstname}" />
<h:inputText value="#{bean.entity.surname}" />
<h:commandButton value="Clear" immediate="true" action="#{bean.clear}">
<f:ajax render="bi" />
</h:commandButton>
<h:commandButton value="Submit" action="#{bean.submit}" />
</h:form>
And clear() method in my bean:
public void clear() {
entity = new Entity();
}
If I enter values in the fields, and click "Clear", everything is cleared as expected. However, consider such scenario:
1. Enter value only in one field (both are required by JSR303 annotations on entity).
2. Click "Submit". Error message appears.
3. Click "Clear".
Entered value remains. Why is it not cleared?
Moreover, if I clear it by hand, and click "Clear", it returns to the field. I checked that it comes to the browser in partial response after clicking "Clear" button. I suspect it has something to do with view state.
Moreover, if I add validator="#{bean.validate}" to the field, it enter this validation. Even if button has immediate="true" attribute. Why? Shouldn't immediate button ommit validation?
You've run into a more or less well-known issue regarding updating components for which validation has already happened.
This post is rather old, but still relevant: http://ishabalov.blogspot.com/2007/08/sad-story-about-uiinput.html
There is a community created solution for A4J in JSF 1.2 posted here: http://community.jboss.org/thread/8446?start=15&tstart=0
But unfortunately, this doesn't work directly in JSF 2.0 and in your case it wouldn't work at all since it's A4J specific. Nevertheless it might be a source of inspiration.
Basically you need to walk the component tree and clear its state. The neatest thing is to clear exactly the state of the components that you are going to re-render. But you might take the brute-force approach and just clear all if your particular application or page can tolerate that.
I wound up having to avoid submit or action to get the form to clear properly. I used actionListener with a void bean method instead.
But then I faced the problem of conditionally needing navigation which is usually done with a String method from action. I used ExternalContext.redirect() to accomplish that which I learned from the following:
JSF PostConstruct Exception Handling - Redirect
JSF navigation redirect to previous page
my page code:
<p:commandButton value="Login" update=":loginForm"
actionListener="#{loginBean.login}"/>
my bean code:
public void login() {
RtsLDAPAD laLdap = new RtsLDAPAD();
boolean lbAuthenticated = false;
try
{
lbAuthenticated = laLdap.login(userName, password);
System.out.println(
"The Result is " + lbAuthenticated + " for " + userName);
}
catch (Exception aeRTSEx)
{
aeRTSEx.printStackTrace();
}
if (lbAuthenticated) {
try {
FacesContext.getCurrentInstance().getExternalContext().redirect("taskform.jsf");
} catch (IOException e) {
e.printStackTrace();
}
} else {
FacesContext facesContext = FacesContext.getCurrentInstance();
facesContext.addMessage(null,
new FacesMessage("Login failed for " + userName + "."));
UIViewRoot uiViewRoot = facesContext.getViewRoot();
HtmlInputText inputText = null;
Password pwd = null;
inputText = (HtmlInputText) uiViewRoot.findComponent("loginForm:username");
inputText.setSubmittedValue(null);
inputText.setValue(null);
inputText.setLocalValueSet(false);
inputText.setValid(true);
pwd = (Password) uiViewRoot.findComponent("loginForm:password");
pwd.setSubmittedValue(null);
pwd.setValue(null);
pwd.setLocalValueSet(false);
pwd.setValid(true);
userName = null;
password = null;
}
}

Resources