I have a JSF2 project with a "view user" page that reads the currently selected user from a session bean; userHandler.selectedUser.
The page is intended to be visited by navigating with links in the app.
However, if the user attempts to hit the "view user" page directly by this URL ...
http://localhost:8080/webapp/userView.jsf
... they see the page with no data on it (because there's no selected user in the userHandler).
I think what I'd like to do is redirect the user to the main page of the app if they try to hit it directly like that. What is a simple and elegant way of handling this problem?
Thanks,
Rob
You'd like to hook on the preRenderView event and then send a redirect when this is the case.
<f:metadata>
<f:event type="preRenderView" listener="#{bean.preRenderView}" />
</f:metadata>
with
public void preRenderView() throws IOException {
if (userHandler.getSelectedUser() == null) {
FacesContext.getCurrentInstance().getExternalContext().redirect("home.jsf");
}
}
A way to avoid this problem from the start is to have pages that you don't want to be accessed directly via URL into the WEB-INF folder of your project.
This way, your pages aren't accessible directly via URL.
Related
A Java EE 8/SE 8 web application in deployement runnnig on Glassfish 5 build 25 both production and development, uses jsf 2.3. users create accounts and login. there is a 'logout' button for loging out.
Problem: 'logout' button works as expected everywhere on the website. EXCEPT home page (example.com) & (example.com/home.xhtml). the problem does not exists on my local computer. only in production (example.com).
So I have a template called : index.xhtml . all pages use it, including home.xhtml:
<ui:composition template="index.xhtml">
<ui:define name="top-bar">
<c:if test="#{request.remoteUser ne null}">
<h:form ><h:commandButton value="Log out" action="#{registerAdvanced.logout}"/></h:form>
</c:if>
</ui:define>
and
#Named
#RequestScoped
public class RegisterAdvanced extends BaseBacking implements Serializable {
public String logout() {
try {
getRequest().logout();
getContext().getExternalContext().getSessionMap().remove("user");
return REDIRECT_PAGE;
} catch (ServletException ex) {
return REDIRECT_PAGE;
}
}
}
Users login & logout fairly easily until I noticied that clicking on logout on the home page (home.xhtml) prints a null pointer exception AND redirect to 404 error page.
[[/home.xhtml #450,77 value="#{passesTestBean.displayPassSummaryList}": java.lang.NullPointerException
javax.el.ELException: /home.xhtml #450,77 value="#{passesTestBean.displayPassSummaryList}": java.lang.NullPointerException
at com.sun.faces.facelets.el.TagValueExpression.getValue(TagValueExpression.java:119)
at com.sun.faces.facelets.component.UIRepeat.getValue(UIRepeat.java:314)
at com.sun.faces.facelets.component.UIRepeat.getDataModel(UIRepeat.java:256)
at com.sun.faces.facelets.component.UIRepeat.setIndex(UIRepeat.java:507)
at com.sun.faces.facelets.component.UIRepeat.process(UIRepeat.java:557)
at com.sun.faces.facelets.component.UIRepeat.processDecodes(UIRepeat.java:861)
at javax.faces.component.UIComponentBase.processDecodes(UIComponentBase.java:1258)....
part of jsf where there is a call to value="#{passesTestBean.displayPassSummaryList}" is 100% seperate to logout and PassesTestBean CDI is request scope.
so the problem is SOMEHOW when I click on logout button. PassesTestBean is called for no reason and not since jsf must Initialize (since Request Scoped). it ends up returning null.
Now remember this only happens in: production at example.com AND only home page of all pages.
I'm thinking of writing a page only for loging out: has a log out button only.
Check for null pointer exception
getRequest().logout(); //here
getContext().getExternalContext().getSessionMap().remove("user");//here
Since PrimeFaces push is going to be discontinued, I started to migrate to OmniFaces push. For notification purposes it's all working as expected, but I have a chat on my application where I'm missing two things in OmniFaces:
1) Change the user of the socket on demand. I require this for private conversations. On PrimeFaces I create a channel for each conversation based on the logged users id, and I pass it to the connect method of their socket component. With OmniFaces I tried to place the socket component inside a PrimeFaces output panel, bind the user property to a view scoped bean, update the panel on a ajax request and on the oncomplete event call OmniFaces.Push.open('channelName'), but I can see on the SocketObserver class(from the showcase) that the user has not been updated. As a workaround I created the channel using the logged user id as user, and if someone different from the person that he is talking at the moment sends a message, I ignore it at the JavaScript callback instead of appending it.
2) When I don't specify the user on PushContext.send, I'd like that the message was sent to everyone connected on the channel, similar to "/channel/*" on PrimeFaces. My requirement with this is to update the list off online/offline users off this chat application. A a workaround I could create a separate channel for these notifications, but as the application don't require this to work, I preferred to check here first.
I'll add code if necessary, but first I'd like to check if these concepts are present in OmniFaces push.
Thanks
It was indeed not possible to change the <o:socket user="#{...}"> value while staying in the same JSF view (ajax updates, etc). As per issue 472, it has been improved in OmniFaces 3.2-SNAPSHOT.
Any dynamic change in value of <o:socket user="#{...}"> during any ajax request in the same JSF view will now be reflected in push behavior.
In other words, below construct will now be possible:
<h:form>
<h:selectOneMenu value="#{bean.chat}">
<f:selectItems value="#{bean.chats}" />
<f:ajax render="#form" />
<h:selectOneMenu>
...
<o:socket channel="chat" user="#{bean.chat.id}" />
</h:form>
If you start by using the user attribute on the o:socket like in the example
<o:socket channel="sess" scope="session" user="#{pushTestUser}" />
1) seems possible by using the 'user id' as can be seen in the showcase push test page
From http://showcase.omnifaces.org/push/socket:
#Inject #Push
private PushContext someChannel;
public void sendMessage(Object message, User recipientUser) {
Long recipientUserId = recipientUser.getId();
someChannel.send(message, recipientUserId);
}
In this example the pass the User which is an 'example' object that could be your own or whatever. In the showcase the user is passed on by binding the id to an input field, but that could be done server-side as well. Up to you
2) seems possible with sending messages to the generic channel or even groups as can be seen in http://showcase.omnifaces.org/push/socket.
#Inject #Push
private PushContext someChannel;
public void sendMessage(Object message, Group recipientGroup) {
Collection<Long> recipientUserIds = recipientGroup.getUserIds();
someChannel.send(message, recipientUserIds);
}
The group here is an example Object that could be your own, it could be passed from the 'frontend' or just read in the backend somewhere. It just needs to contain id's of users (can be mapped guids, does not need to be internal user id's) that are subscribed to the channel. All are just example methods.
So both seem possible in my opinion.
In normal way to enter to flow or go from some page to next page in flow we use
<h:commandButton> or <h:commandLink>
But how is it possible to enter to flow or go to the next page in flow (if we currently are in flow) from backing bean?
I try to dispatch from faces context but it doesn't work. Do you know how to do it?
I use only viewnode pages. I configure a flow using a
#FlowDefinition
#Producent
#Flowdefinition
public Flow defineFlow(#FlowbuilderParameter FlowBuilder FlowBuilder){
flowBuilder.id("","registerFlow");
flowBuilder.viewNode("registerStart","/faces/start.xhtm").Markasstartnode();
flowBuilder.viewNode("step2","/faces/step2.xhtm");
…
}
And now i would enter to flow from backing bean. Something like
FacesContext.getCurrenInstance().getExternalContext().dispatch("registerFlow");
I think possible i have to use FlowHandler from context
FlowHandler handler = FacesContext.getApplication().getFlowHandler();
handler.transition(); // and what next?
how here i can redirect user to some viewnode of flow?
I have been experimenting with the substance of this question (JSF / Java EE login without requiring a protected resource).
If I set up a sample application using BASIC authentication, with one public page (/public.xhtml), and one protected page (/protected/private.xhtml), and I have a link from the first page to the second (as shown below), everything works perfectly.
<h:commandButton value="Go Private" action="/protected/private?faces-redirect=true" />
However, if I remove the login-config and replace the above button with:
<h:commandButton value="Go Private" action="#{mybean.login}" />
...and #{mybean.login} looks something like this...
public String login() {
HttpServletRequest request = ...
try {
request.login("known username", "known password");
} catch (Exception e) {
// handle unknown credentials
}
return "/protected/private?faces-redirect=true";
}
In this case, the login succeeds (no exception from request.login()), but the browser shows a "forbidden resource" page.
Can anyone shed any light on the difference between the two scenarios?
The HttpServletRequest#login() programmatic login works only with FORM based authentication configuration. Removing the <login-config> would make it to default to BASIC and thus the login() will never work. The login() basically sets the user in the session, however the BASIC authentication basically checks the Authenticate HTTP request header, not the session.
Put that <login-config> back and set it to FORM if you want to utilize login().
Where it is:
<h:commandButton value="Go Private" action="#{mybean.login}" />
it should be:
<h:commandButton value="Go Private" action="#{mybean.login()}" />
if you want to use the login methode.
But maybe your mybean also has
String private login;
public String getLogin(){ return this.login ;}
and you've loaded the var with the desired response.
I have created a preRender method to be called when the user navigate to that specific page, I use:
<f:metadata>
<f:event listener="#{targetClass.prerender()}"
type="preRenderView">
</f:event>
</f:metadata>
but the problem is that this method is not being called when I navigate to this page, unless I use
<redirect />
in the faces-config.xml file.
But, I don't want to use redirect, so the url always point to the login page.
Thank you.