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.
Related
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
This question already has answers here:
Authorization redirect on session expiration does not work on submitting a JSF form, page stays the same
(2 answers)
Closed 7 years ago.
When my session is expired in my Java EE 7, JSF web app.
I get a ViewExpiredException in ajax requests.
I would like to redirect to a page where it shows the user that the session is expired.
I tried browsing google and stackoverflow for a solution, but I didden't have any luck to get it to work with how I want it.
UPDATE:
I tried the solution posted # Session timeout and ViewExpiredException handling on JSF/PrimeFaces ajax request
It did work at my login page, when using the login_submit button.
<ui:composition
xmlns="http://www.w3.org/1999/xhtml"
xmlns:ui="http://xmlns.jcp.org/jsf/facelets"
xmlns:p="http://primefaces.org/ui"
xmlns:h="http://xmlns.jcp.org/jsf/html"
template="./../templates/login_template.xhtml">
<ui:define name="title">
<h:outputText value="#{bundle.login_title}"/>
</ui:define>
<ui:define name="content">
<h:outputText escape="false" value="#{bundle.login_message}"/>
<h:form>
<h:panelGrid columns="2" cellpadding="5">
<h:outputLabel for="username" value="#{bundle.login_username}"/>
<p:inputText id="username" type="text" value="#{authBean.username}" label="username"/>
<h:outputLabel for="password" value="#{bundle.login_password}"/>
<p:inputText id="password" type="password" value="#{authBean.password}" label="password"/>
<h:outputText value="#{bundle.login_invalid_password}" rendered="#{!authBean.validPassword and authBean.validUsername}"/>
<h:outputText value="#{bundle.login_invalid_username}" rendered="#{!authBean.validUsername}"/>
<p:commandButton value="#{bundle.login_submit}" action="#{authBean.doLogin}"/>
</h:panelGrid>
</h:form>
</ui:define>
</ui:composition>
But it doesn't work with this JSF in a xhtml page that is on a secure page, login page is a public page. For example the following xhtml is on a secure page:
<p:commandButton styleClass="button #{chartBean.isViewButtonActive('MONTH')}" update="dataChart dataTable #form" value="#{bundle.performance_year}" action="#{chartBean.changeModel('MONTH')}" />
This does an AJAX post request, but it gets catched by my #WebFilter. (It also happends with selectOneMenu from Primefaces) This filter checks if an user is logged in if not it redirects them to the login page. But for some reason with the example button I given above it also get catched by the #WebFilter and the ajax requests gets as response redirect specified in the #WebFilter. It doesn't get catched by the ExceptionHandler. The #WebFilter is only applied to secure pages, see below.
#WebFilter(
filterName = "AuthorizationFilter",
urlPatterns = {"/secure/*"},
dispatcherTypes = {DispatcherType.REQUEST, DispatcherType.FORWARD}
)
public class AuthorizationFilter implements Filter {
Does anyone know how to solve this.
I do it like this in the webfilter, and it works fine:
// Check if user logged in, if not redirect to login.xhtml
if (loginBean == null || !((LoginBean) loginBean).isLoggedIn()) {
boolean isAjax = "XMLHttpRequest".equals(req.getHeader("X-Requested-With"));
if (!isAjax) {
res.sendRedirect(req.getContextPath() + "/login.xhtml");
} else {
// Redirecting an ajax request has to be done in the following way:
// http://javaevangelist.blogspot.com/2013/01/jsf-2x-tip-of-day-ajax-redirection-from.html
String redirectURL = res.encodeRedirectURL(req.getContextPath() + "/login.xhtml");
StringBuilder sb = new StringBuilder();
sb.append("<?xml version=\"1.0\" encoding=\"UTF-8\"?><partial-response><redirect url=\"").append(redirectURL).append("\"></redirect></partial-response>");
res.setCharacterEncoding("UTF-8");
res.setContentType("text/xml");
PrintWriter pw = response.getWriter();
pw.println(sb.toString());
pw.flush();
}
} else {
// Let chain of filters continue;
chain.doFilter(request, response);
}
Inspiration
I'm trying to add programatically an Ajax client behavior to a custom component but when I do I get a null.pointer.exception (as an Alert in the browser no error in the logs), if I add the behavior in the .xhtml it works fine but I really need to add them programatically (actually the component is going to be rendered dinamically from a top component (dashboard -> N columns -> N widgets per column all from a Database)
I have tested this creating a simple DataList component
Here is the relevant code...
DataListRenderer
#Override
public void encodeEnd(FacesContext context, UIComponent component) throws IOException{
DataList datalist = (DataList)component;
ResponseWriter writer = context.getResponseWriter();
.. bunch of rendering options....
// If sortable then we add the sortable script.
// This basically ends up generating a call to jsf.ajax.request(source,event,params);
if (sortable != null && sortable.equalsIgnoreCase("true")) {
ScriptUtils.startScript(writer, clientId);
writer.write("$(function() {");
writer.write("$(EasyFaces.escapeId('" + clientId + "')).sortable({");
writer.write("update: function (event, ui) { ");
writer.write(new AjaxRequest().addEvent(StringUtils.addSingleQuotes("update"))
.addExecute(StringUtils.addSingleQuotes(datalist.getClientId()))
.addSource(StringUtils.addSingleQuotes(datalist.getClientId()))
.addOther("sourceId", "ui.item.attr('id')")
.addOther("dataValue", "ui.item.attr('data-value')")
.addOther("dropedPosition", "ui.item.index()")
.getAjaxCall());
writer.write(" } ");
encodeClientBehaviors(context, datalist);
writer.write("});");
writer.write("});");
ScriptUtils.endScript(writer);
}
// This is where I add the behavior
AjaxBehavior ajaxBehavior = (AjaxBehavior) FacesContext.getCurrentInstance().getApplication().createBehavior(AjaxBehavior.BEHAVIOR_ID);
ajaxBehavior.setRender(Arrays.asList("#form"));
ajaxBehavior.setExecute(Arrays.asList("#form"));
MethodExpression listener = FacesContext.getCurrentInstance().getApplication()
.getExpressionFactory().createMethodExpression(context.getELContext(),"#{dataListManager.updateModel}", null,
new Class[] { AjaxBehaviorEvent.class });
ajaxBehavior.addAjaxBehaviorListener(new AjaxBehaviorListenerImpl(listener));
datalist.addClientBehavior(datalist.getDefaultEventName(), ajaxBehavior);
}
... rest of the code
Now the .xhtml is as follows:
...
h:form>
<et:dataList id="dl" value="#{easyfacesBean.data}" itemValue="foo" var="xx" sortable="true">
<h:outputText id="txt" value="#{xx}" />
</et:dataList>
</h:form>
...
This renders the code Ok, the list actually can be ordered but when re-ordered it gives me the null.pointer.exception error
This is what comes back from the server:
<?xml version='1.0' encoding='UTF-8'?>
<partial-response><error><error-name>class java.lang.NullPointerException</error-name><error-message><![CDATA[]]></error-message></error></partial-response>
Now, If I comment this line
datalist.addClientBehavior(datalist.getDefaultEventName(), ajaxBehavior);
And simply add the tag like so:
<h:form>
<et:dataList id="dl" value="#{easyfacesBean.data}" itemValue="foo" var="xx" sortable="true">
<f:ajax />
<h:outputText id="txt" value="#{xx}" />
</et:dataList>
</h:form>
Everything works fine.. any ideas? I would settle for a way to actually know where the null.point.exception is ..
BTW if I don't comment the line that actually add's the behavior and add the < f:ajax > tag the error changes to java.lang.IndexOutOfBoundsException
<?xml version='1.0' encoding='UTF-8'?>
<partial-response><error><error-name>class java.lang.IndexOutOfBoundsException</error-name><error-message><![CDATA[Index: 1, Size: 1]]></error-message>
Regards!
Got a homework assignment that is giving me problems.... Its modifying a JSF project with two pages and a bean to fit MVC2 by adding two more pages and a controller servlet and another bean for the two additional pages. the new main page forwards to either the second new page or the old first page. My issue is response.getParameter() always results in null.
<%#page session="false" import="java.util.Iterator"%>
<%#taglib prefix="f" uri="http://java.sun.com/jsf/core"%>
<%#taglib prefix="h" uri="http://java.sun.com/jsf/html"%>
<jsp:useBean id="status" scope="request" class="JSFRegistration.Status" />
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
<title>JSP Page</title>
</head>
<body>
<% if (status!=null && !status.isSuccessful()){%>
<font color="red">Processing errors:
<ul><%Iterator errors=status.getExceptions();
while (errors.hasNext()){
Exception e = (Exception) errors.next();%>
<li><%= e.getMessage()%><%}%></ul></font><%}%>
<form action="LoginServlet" method="POST">
<% String username = request.getParameter("username");
if (username==null) username="";%>
<input type="text" name="usernameTF" value="<%=username%>" />
<% String password = request.getParameter("password");
if (password==null) password="";%>
<input type="password" name="passwordTF" value="<%=password%>" />
<input type="submit" value="Login" />
</form>
</body>
</html>
this is basically a direct copy from our book but the fields I need for the new main page. Same for the controller servlet, a direct copy except only contains the fields I need.
protected void processRequest(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
RequestDispatcher view = null;
Status status = new Status();
request.setAttribute("status", status);
String username = request.getParameter("username");
String password = request.getParameter("password");
if (username==null || username.length()==0)
status.addException(new Exception("Please enter username"));
if (password==null)
status.addException(new Exception("Please enter password"));
if (!status.isSuccessful()){
view = request.getRequestDispatcher("Login.jsp");
//view.forward(request, response);
}
else
try{
request.setAttribute("username", username);
request.setAttribute("password", password);
view = request.getRequestDispatcher("Registration.jsp");
} catch (Exception e) {
status.addException(new Exception("Error"));
view = request.getRequestDispatcher("Login.jsp");
}
view.forward(request, response);
}
and the Status class, again a direct copy from the book.
public class Status {
private ArrayList exceptions;
public Status(){
exceptions = new ArrayList();
}
public void addException(Exception exception) {
exceptions.add(exception);
}
public boolean isSuccessful(){
return (exceptions.isEmpty());
}
public Iterator getExceptions(){
return exceptions.iterator();
}
regardless of what is typed into the two boxes, stepping through a debug shows the values not getting passed to the parameters. I get the created exceptions printed above the screen for both fields if both have text, if only one has text and when both are empty.
Your request parameter names do not match the input field names. You've assigned the input fields a name of usernameTF and passwordTF. They are then available by exactly those names as request parameter, but you're attempting to get them using the names username and password. So you need either to fix the input field names, or the request parameter names so that they match each other.
By the way, why falling back from a modern MVC framework like JSF to awkward 90's style JSP with mingled business code? Is that really what the homework assignment is asking you? Also the HTML <font> element is deprecated since 1998. Where did you learn about it? Is the quality of the course really good?
This does not work
<h:form style="display: inline;">
<h:outputLink value="#{title.link}" >
#{msg['g.readMore']}
<f:ajax event="click" immediate="true" listener="#{titlesBean.titleClicked(title.id)}" />
</h:outputLink>
</h:form>
What I want it to do is when clicked to call #{titlesBean.titleClicked(title.id)} and then go to the link. The method is called but it doesn't go to the link. There are several other ways to do it (with commandLink and then a redirect, but I would like to know why this is not working).
This is the method itself:
public String titleClicked(long titleId) {
this.titlesManager.addXtoRating(titleId, 1);
return null;
}
Note: this is only a sidenote, but I accidentally found out that this works:
<script type="text/javascript">
$('.share').popupWindow({centerBrowser:1,height:380,width:550});
</script>
<h:form style="display: inline;">
<h:outputLink styleClass="share" value="http://www.facebook.com/sharer.php...">
<img src="images/facebook-icon.jpg" />
<f:ajax event="click" immediate="true" listener="#{titlesBean.titleLiked(title.id)}" />
</h:outputLink>
</h:form>
Check out the styleClass="share"
Update: (I have less than 100 rep, so I cannot answer my own question for 8 hours, this is how to put it delicately - stupid).
I waited for a while, but nobody answered.
So this is my hacked solution ( I don't like it at all, but it works):
<h:form style="display: inline;">
<h:outputLink target="_blank" styleClass="click8" value="#{title.link}" >
#{title.heading}
<f:ajax event="click" immediate="true" listener="#{titlesBean.titleLiked(title.id)}" />
</h:outputLink>
</h:form>
And this is the important part:
<h:head>
<script type="text/javascript">
$(document).ready(function(){
$('.click8').click(function (event){
var url = $(this).attr("href");
window.open(url, "_blank");
event.preventDefault();
});
});
</script>
</h:head>
Note: this has to be in the header, otherwise I had a major bug with the link opening a thousand windows.
It does not work because there's means of a race condition here. Two HTTP requests are been sent simultaneously in the same window. One ajax to the server and one normal to the given link. The one which finishes sooner wins. You want to send the HTTP requests in sync. First the ajax one to the server and when it returns, then the normal one to the given link.
As to your hacky solution, it works because it uses JavaScript to open the URL in a new window instead of the current one and then blocks the link's default action, so the normal response just arrives in a completely separate window while the ajax response still arrives in the initial window. So there's no means of a race condition of two HTTP requests in the initial window anymore.
As to the final solution, this is not possible with standard set of JSF 2.0 components. Using <h:commandLink> and then doing a redirect is indeed doable, but the link is this way not crawlable by searchbots and it fires effectively a POST request, which is IMO more worse than your new window solution.
If you would really like to open the link in the current window, hereby keeping the target URL in the link's href, then I'd suggest to create a simple servlet which does the link tracking and redirecting job and let jQuery manipulate the link target during onclick.
Something like this
<a rel="ext" id="ext_#{title.id}" href="#{title.link}">read more</a>
(HTML element IDs may not start with a digit! Hence the ext_ prefix, you can of course change this whatever way you want.)
with
$(function() {
jQuery('a[rel=ext]').click(function(e) {
var link = jQuery(this);
var url = 'track'
+ '?id=' + encodeURIComponent(link.attr('id'))
+ '&url=' + encodeURIComponent(link.attr('href'));
if (link.attr('target') == '_blank') {
window.open(url);
} else {
window.location = url;
}
e.preventDefault();
});
});
and
#WebServlet(urlPatterns={"/track"})
public class TrackServlet extends HttpServlet {
#EJB
private TrackService trackService;
#Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String id = request.getParameter("id");
String url = request.getParameter("url");
if (id == null || url == null) {
response.sendError(HttpServletResponse.SC_BAD_REQUEST);
return;
}
trackService.track(id.replaceAll("\\D+", "")); // Strips non-digits.
response.sendRedirect(url);
}
}