I have a simple JSF and managed bean and I need to make POST redirect when page render and some condition is true. JSF:
<h:panelGroup rendered="#{myBean.error=null}">
<p>Some data</p>
</h:panelGroup>
In managed bean, the init() method, annotated as #PostConstruct and in this method I do
#PostConstruct
public void init() {
if (someCondition) {
FacesContext context = FacesContext.getCurrentInstance();
String redirectUrl = "http://myurl.com";
ExternalContext ec = FacesContext.getCurrentInstance().getExternalContext();
try {
ec.redirect(redirectUrl);
} catch (IOException e) {
e.printStackTrace();
FacesMessage message = new FacesMessage(e.getMessage());
context.addMessage(null, message);
}
}
}
but I need navigate user to redirectUrl with POST params and cannot find how to do it.
With button it will be like this:
<form method='post' action="myUrl">
<input type='hidden' name='param1' value='value1'/>
<input type='hidden' name='param2' value='value2'/>
<input name='button' type='submit' value="Button">
</form>
What you try to achieve is not possible that way.
A redirect will send a http 302 status code to the client with a location header including an url where to redirect to. Then the client will make a get request to that url and your post data will be lost.
There are alternatives to achieve this, here are some ideas.
You could forward the request using the ExternalContext.html#dispatch method, see this post for the differences. Note that this does not change the url in the browser's address bar.
You could store the post data in the user's session.
Related
Well, i'm using a ConversationScoped and i hope the PostConstruct is called just one time in begin of conversation, see:
#Named("disciplinaDetalheMB")
#ConversationScoped
public class DisciplinaDetalheMBImpl {
private static final long serialVersionUID = 1L;
#Inject
private Conversation conversation;
#Inject
#AnBasicBO
private BasicBO boPadrao;
#PostConstruct
public void postConstruct() {
logger.debug("Iniciando PostConstruct...");
init();
beginConversation();
}
public String salvarAndRedirecionar() {
salvar();
if (!FacesContext.getCurrentInstance().isValidationFailed()) {
return goToLastPage() + "?faces-redirect=true";
} else {
return "";
}
}
private void beginConversation() {
if (!conversation.isTransient()) {
endConversation();
}
conversation.begin();
if (conversation.isTransient()) {
throw new RuntimeException("A conversão não foi iniciada corretamente");
}
SessionContext.getInstance().setAttribute("cid", conversation.getId());
}
public BasicBO getBoPadrao() {
return boPadrao;
}
public void setBoPadrao(BasicBO boPadrao) {
this.boPadrao = boPadrao;
}
}
So, when my backing bean is created, the conversation is initialized and CID is stored in session to be used after. I have a commandButton "save" in my XHTML and when this button is called the PostConstruct is called again i don't know why:
<h:commandLink
action="#{managedBeanName.salvarAndRedirecionar()}"
styleClass="btn btn-info pull-right" value="Salvar">
<f:ajax execute="#form" />
</h:commandLink>
I noted the generated HTML is:
<a id="formManterDisciplina:j_idt44:j_idt46" href="#" onclick="mojarra.ab(this,event,'action','#form',0);return false" class="btn btn-info pull-right" name="formManterDisciplina:j_idt44:j_idt46">Salvar</a>
So, I understand the "href=#" avoid the onlick to be executed. I think this is the problem but i dont't know how to fix. Remenber: The salvarAndRedirecionar() method is never called because postConstruct is always called before.
2) I have another question: If i start a conversation and don't end, there is some problem ? Sometimes i don't want to end conversation manually because i just have ONE PAGE, i just start.
The reason why you are having this issue is because you are invoking the conversation begin method in the postconstruct method of the conversation scoped bean. So, the conversation will be set to long-running state during the Render Response phase and not before it. The problem is that the CID parameter is rendered on the HTML form element, but at this point the conversation is still in transient state, because the postconstruct method has not still been invoked after the request. The postconstruct method is invoked when redenring the commandLink element, and then is too late, and the HTML form element won't carry the CID parameter:
<form id="yourForm" name="yourForm" method="post" action="/path/to/yourPage.xhtml" enctype="application/x-www-form-urlencoded">
So, the solution consists of moving the conversation begin to a point before the Render Response phase. You can do it with the f:viewAction tag if you are using JSF 2.2 or with the f:event tag if you are using an older version.
And then you will see the CID parameter rendered inside your HTML form element, like so:
<form id="yourForm" name="yourForm" method="post" action="/path/to/yourPage.xhtml?cid=1" enctype="application/x-www-form-urlencoded">
If you use f:event tag:
In your page:
<f:metadata>
<f:event listener="#{disciplinaDetalheMB.initConversation}" type="preRenderView" />
</f:metadata>
In your backing bean:
public void initConversation(){
if (!FacesContext.getCurrentInstance().isPostback() && conversation.isTransient()) {
conversation.begin();
}
}
If you use f:viewAction tag:
In your page:
<f:metadata>
<f:viewAction action="#{disciplinaDetalheMB.initConversation}" />
</f:metadata>
In your backing bean:
public void initConversation(){
if (conversation.isTransient()) {
conversation.begin();
}
}
Regarding your second question there is not big problem in not ending a conversation, because it has a timeout like a HTTP session. You can set the timeout value depending on your server resource management strategy and the desired lifetime for an idle conversation. Anyway, when you only have one page you better use a view scoped backing bean.
I have this login form:
<h:form>
<h:panelGrid columns="2" >
<h:outputLabel for="username" value="Login:"/>
<h:inputText id="username" value="#{userController.userName}" required="true"/>
<h:outputLabel for="password" value="#{msg.password}"/>
<h:inputSecret id="password" value="#{userController.password}" required="true"/>
<h:column/>
<h:commandButton value="#{msg.login}" action="#{userController.login}"/>
</h:panelGrid>
</h:form>
With this backing bean:
#ManagedBean(name = "userController")
#SessionScoped
public class UserController {
private String userName = "";
private String password = "";
//getter, setters
public String login(){
FacesContext context = FacesContext.getCurrentInstance();
HttpServletRequest request = (HttpServletRequest)context.getExternalContext().getRequest();
try {
request.login(userName, password);
} catch (ServletException e) {
}
return "next-page.xhtml"; //if login processes is proper, i redirect to next page
}
}
I read in Best practices in JSF: model, actions, getters, navigation, phaselisteners that
I always post back to the same view (return null or void and then render/include the result conditionally. For page-to-page navigation I don't use POST requests (for which navigation cases are mandatory) simply because that's plain bad for UX (User eXperience; browser back button doesn't behave as it should and URL's in browser address bar are always one step behind because it are by default forwards, not redirects) and SEO (Search Engine Optimization; searchbots doesn't index POST requests). I just use outputlinks or even plain HTML elements for page-to-page navigation.
So, what should I do when my login is proper and I want to immediately redirect to next-page.xhtml?
In the end of the try, perform the navigation with ?faces-redirect=true so that a redirect is performed. In the catch, return null so that it stays in the same page.
try {
request.login(userName, password);
return "next-page.xhtml?faces-redirect=true";
} catch (ServletException e) {
context.addMessage(null, new FacesMessage("Unknown login"));
return null;
}
For the sake of completeness, I added a faces message on login failure, otherwise the enduser would have no clue why the page seemingly reloads itself without any form of feedback. This message will be shown in a <h:messages globalOnly="true">.
See also:
Performing user authentication in Java EE / JSF using j_security_check (the 2nd half of the answer)
How to navigate in JSF? How to make URL reflect current page (and not previous one)
I want to send a HTTP post request to another server using <h:form> component.
I can send a POST request to an external site using HTML <form> component, but <h:form> component does not support this.
<form action="http://www.test.ge/get" method="post">
<input type="text" name="name" value="test"/>
<input type="submit" value="CALL"/>
</form>
How can I achieve this with <h:form>?
It's not possible to use <h:form> to submit to another server. The <h:form> submits by default to the current request URL. Also, it would automatically add extra hidden input fields such as the form identifier and the JSF view state. Also, it would change the request parameter names as represented by input field names. This all would make it insuitable for submitting it to an external server.
Just use <form>. You can perfectly fine use plain HTML in a JSF page.
Update: as per the comments, your actual problem is that you have no idea how to deal with the zip file as obtained from the webservice which you're POSTing to and for which you were actually looking for the solution in the wrong direction.
Just keep using JSF <h:form> and submit to the webservice using its usual client API and once you got the ZIP file in flavor of InputStream (please, do not wrap it a Reader as indicated in your comment, a zip file is binary content not character content), just write it to the HTTP response body via ExternalContext#getResponseOutputStream() as follows:
public void submit() throws IOException {
InputStream zipFile = yourWebServiceClient.submit(someData);
String fileName = "some.zip";
FacesContext fc = FacesContext.getCurrentInstance();
ExternalContext ec = fc.getExternalContext();
ec.responseReset();
ec.setResponseContentType("application/zip");
ec.setResponseHeader("Content-Disposition", "attachment; filename=\"" + fileName + "\"");
OutputStream output = ec.getResponseOutputStream();
try {
byte[] buffer = new byte[1024];
for (int length = 0; (length = zipFile.read(buffer)) > 0;) {
output.write(buffer, 0, length);
}
} finally {
try { output.close(); } catch (IOException ignore) {}
try { zipFile.close(); } catch (IOException ignore) {}
}
fc.responseComplete();
}
See also:
How to provide a file download from a JSF backing bean?
I'm performing url redirects between primefaces mobile pages (pm:page). For instance from login.jsf to /secure/myPage.jsf, both pm:pages. After successful authentication the user should be redirect to myPage.jsf. The login is triggered like this:
<pm:commandButton value="login" update="messages"
actionListener="#{loginbean.doLogin}" >
<f:param name="targetUrlParam" value="defaultTarget" />
</pm:commandButton>
and the redirect within the method:
public void doLogin(ActionEvent e) throws IOException {
FacesContext context = FacesContext.getCurrentInstance();
ExternalContext ec = context.getExternalContext();
try {
HttpServletRequest req = (HttpServletRequest) ec.getRequest();
Authentication authentication = SecurityContextHolder.
getContext().getAuthentication();
... // Authentication stuff with Spring Security
try {
HttpSession session = req.getSession(false);
String cp = ec.getRequestContextPath();
String redirectUrl = cp;
... //performing some filtering depending on Roles and target-urls
}
String encodedURL = ec.encodeResourceURL(redirectUrl);
((HttpServletResponse) ec.getResponse()).sendRedirect(encodedURL);
} catch (AuthenticationException ae) {
UtilBean.addErrorMessage("bad_credential");
}
Unfortunately the redirect doesn't occur! It might have to do with the lifecycle of primefaces mobile 3.0M3 because everything works fine with normal JSF pages.
Any suggestions? Thanks
This is not entirely the right way to send a redirect in JSF. I'm not sure why it works in "normal" JSF (that should fail over there as well!). You basically need to call FacesContext#responseComplete() after the redirect to instruct JSF that it should not navigate to the default outcome. However, much better is to perform the redirect using ExternalContext#redirect() as it will do that implicitly.
So in your case, replace
((HttpServletResponse) ec.getResponse()).sendRedirect(encodedURL);
by
ec.redirect(encodedURL);
I have a logout link in my JSF app that invalidates the session to log the user out. It works but it doesn't redirect the user to the logon page. It stays on the same page. If I try to access the same page again it does direct back to the logon. I want this to happen immediately.
logout link:
<h:form>
<h:panelGroup id="loginout">
<h:outputText value="#{todoController.loggedInUser}" />
<h:commandLink value="logout" action="#{todoController.logout}" />
</h:panelGroup>
</h:form>
logout code:
public String logout()
{
System.out.println("testing logout");
FacesContext fc = FacesContext.getCurrentInstance();
ExternalContext ec = fc.getExternalContext();
final HttpServletRequest r = (HttpServletRequest)ec.getRequest();
r.getSession( false ).invalidate();
return "../login.html?faces-redirect=true";
}
This can happen if the outcome is invalid. login.html doesn't seem to be a JSF page, so JSF navigation will simply fail.
You want to use ExternalContext#redirect() instead.
public void logout() throws IOException {
ExternalContext ec = FacesContext.getCurrentInstance().getExternalContext();
ec.invalidateSession();
ec.redirect("../login.html");
}
Note that the above also demonstrates a more JSF-ish way to invalidate the session. Whenever you need to haul the raw javax.servlet.* API from under the JSF hoods, you should always ask yourself twice: "Is there really not a JSF-provided API for this?"