Full WebApplication JSF EJB JPA JAAS – Part 2
This tutorial continues from part 1.
Let us create a new Dynamic Web Project. Create it like the image bellow:
Pay attention: in some moment the Eclipse will ask you if you want to add the JSF Capabilities (auto complete), enable it. Like the screens bellow:
After the project creation, let us edit the “web.xml” file; it should have the same code as bellow:
<?xml version='1.0' encoding='UTF-8'?> <web-app xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance' xmlns='http://java.sun.com/xml/ns/javaee' xmlns:web='http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd' xsi:schemaLocation='http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd' id='WebApp_ID' version='3.0'> <display-name>CrudJSF</display-name> <welcome-file-list> <welcome-file>pages/protected/user/listAllDogs.xhtml</welcome-file> </welcome-file-list> <servlet> <servlet-name>Faces Servlet</servlet-name> <servlet-class>javax.faces.webapp.FacesServlet</servlet-class> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>Faces Servlet</servlet-name> <url-pattern>/faces/*</url-pattern> <url-pattern>*.jsf</url-pattern> <url-pattern>*.xhtml</url-pattern> </servlet-mapping> <!-- Protected area definition --> <security-constraint> <web-resource-collection> <web-resource-name>Restricted Area - ADMIN Only</web-resource-name> <url-pattern>/pages/protected/admin/*</url-pattern> </web-resource-collection> <auth-constraint> <role-name>ADMIN</role-name> </auth-constraint> </security-constraint> <security-constraint> <web-resource-collection> <web-resource-name>Restricted Area - USER and ADMIN</web-resource-name> <url-pattern>/pages/protected/user/*</url-pattern> </web-resource-collection> <auth-constraint> <role-name>USER</role-name> <role-name>ADMIN</role-name> </auth-constraint> </security-constraint> <!-- Login page --> <login-config> <auth-method>FORM</auth-method> <form-login-config> <form-login-page>/pages/public/login.xhtml</form-login-page> <form-error-page>/pages/public/loginError.xhtml</form-error-page> </form-login-config> </login-config> <!-- System roles --> <security-role> <role-name>ADMIN</role-name> </security-role> <security-role> <role-name>USER</role-name> </security-role> </web-app>
You do not have to worry if some warning/error shows up; we will solve them later. Notice that I have added all the JAAS code that we will need (If you want a detailed post about these JAAS configurations you can check it here: User Login Validation with JAAS and JSF).
According to the JAAS configurations a regular user (USER role) will only see the files inside the user folder, that will be only the listing of the dogs recorded in our database; the ADMIN will be able to do all the CRUD actions because all the pages are inside the admins folder.
Our “faces-config.xml” should have the code bellow:
<?xml version='1.0' encoding='UTF-8'?> <faces-config xmlns='http://java.sun.com/xml/ns/javaee' xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance' xsi:schemaLocation='http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-facesconfig_2_0.xsd' version='2.0'> <navigation-rule> <navigation-case> <from-outcome>logout</from-outcome> <to-view-id>/pages/protected/user/listAllDogs.xhtml</to-view-id> <redirect/> </navigation-case> </navigation-rule> <navigation-rule> <navigation-case> <from-outcome>listAllDogs</from-outcome> <to-view-id>/pages/protected/user/listAllDogs.xhtml</to-view-id> </navigation-case> </navigation-rule> <navigation-rule> <navigation-case> <from-outcome>createDog</from-outcome> <to-view-id>/pages/protected/admin/createDog.xhtml</to-view-id> <redirect/> </navigation-case> </navigation-rule> <navigation-rule> <navigation-case> <from-outcome>updateDog</from-outcome> <to-view-id>/pages/protected/admin/updateDog.xhtml</to-view-id> </navigation-case> </navigation-rule> <navigation-rule> <navigation-case> <from-outcome>deleteDog</from-outcome> <to-view-id>/pages/protected/admin/deleteDog.xhtml</to-view-id> </navigation-case> </navigation-rule> <application> <resource-bundle> <base-name>messages</base-name> <var>msgs</var> </resource-bundle> </application> </faces-config>
Notice that to some actions I used the redirect action. With this action we will update the requested link in the URL bar of the browser, after the URL get updated the JAAS will deny access to an illegal user.
We also have a file that will contain all the messages of our system. You will notice that all the texts displayed in our pages are in this file (create a file named “messages.properties” inside the src folder):
#Dog dog=Dog dogName=Name dogWeight=Weight #Dog messages dogCreateHeader=Create a new Dog dogUpdateHeader=Update the Dog dogDeleteHeader=Delete this Dog dogNameRequired=The dog needs a name. dogWeightRequired=The dog needs a weight. #Actions update=Update create=Create delete=Delete cancel=Cancel #Login loginHello=Hello loginErrorMessage=Could not login. Check you UserName/Password loginUserName=Username loginPassword=Password logout=Log Out
View – Creation and JSF set up
Let us now create the ManagedBeans.
First, we need to add the EJB to the Web Project. Right click with your mouse on the JSF project > Properties:
Java Build Path > Projects > Add > Check CrudEJB > OK
First, let us create the DogMB:
package com.mb; import java.util.List; import javax.ejb.EJB; import javax.ejb.EJBException; import javax.faces.application.FacesMessage; import javax.faces.bean.ManagedBean; import javax.faces.bean.RequestScoped; import javax.faces.context.FacesContext; import com.facade.DogFacade; import com.model.Dog; @ManagedBean @RequestScoped public class DogMB { @EJB private DogFacade dogFacade; private static final String CREATE_DOG = 'createDog'; private static final String DELETE_DOG = 'deleteDog'; private static final String UPDATE_DOG = 'updateDog'; private static final String LIST_ALL_DOGS = 'listAllDogs'; private static final String STAY_IN_THE_SAME_PAGE = null; private Dog dog; public Dog getDog() { if(dog == null){ dog = new Dog(); } return dog; } public void setDog(Dog dog) { this.dog = dog; } public List<Dog> getAllDogs() { return dogFacade.findAll(); } public String updateDogStart(){ return UPDATE_DOG; } public String updateDogEnd(){ try { dogFacade.update(dog); } catch (EJBException e) { sendErrorMessageToUser('Error. Check if the weight is above 0 or call the adm'); return STAY_IN_THE_SAME_PAGE; } sendInfoMessageToUser('Operation Complete: Update'); return LIST_ALL_DOGS; } public String deleteDogStart(){ return DELETE_DOG; } public String deleteDogEnd(){ try { dogFacade.delete(dog); } catch (EJBException e) { sendErrorMessageToUser('Error. Call the ADM'); return STAY_IN_THE_SAME_PAGE; } sendInfoMessageToUser('Operation Complete: Delete'); return LIST_ALL_DOGS; } public String createDogStart(){ return CREATE_DOG; } public String createDogEnd(){ try { dogFacade.save(dog); } catch (EJBException e) { sendErrorMessageToUser('Error. Check if the weight is above 0 or call the adm'); return STAY_IN_THE_SAME_PAGE; } sendInfoMessageToUser('Operation Complete: Create'); return LIST_ALL_DOGS; } public String listAllDogs(){ return LIST_ALL_DOGS; } private void sendInfoMessageToUser(String message){ FacesContext context = getContext(); context.addMessage(null, new FacesMessage(FacesMessage.SEVERITY_INFO, message, message)); } private void sendErrorMessageToUser(String message){ FacesContext context = getContext(); context.addMessage(null, new FacesMessage(FacesMessage.SEVERITY_ERROR, message, message)); } private FacesContext getContext() { FacesContext context = FacesContext.getCurrentInstance(); return context; } }
About the code above:
- All navigation you will find in the faces-config.xml. You should use constants or a resource bundle with the navigations of your pages; this approach is a better approach than just leave strings in your methods.
- Notice that we are only using @EJB to inject the EJB inside of the MB. This happens because we are using everything inside the same EAR. The JBoss 7 turns easy this localization.
- If the injection does not work with JBoss 6 (or if you are using the EJB jar outside the EAR) you can use the injection like this: @EJB(mappedName=“DogFacadeImp/local”).
- Notice that a message is displayed to the user of our system. We have a try/catch to each action that we execute in the Façade, if some error happens we will send an error message to the user.
- The correct actions would be to validate the data in the ManagedBean and in the Façade. Those validations have a low CPU cost.
- If you are using the JBoss 4.2 you will need to do a JNDI lookup like the code bellow(just like a said earlier in this post use the LocalBinding annotation). Annotate your class like this:
@Stateless @LocalBinding(jndiBinding='MyBean') public class MyBeanImp implements MyBean{ @Override public String hello() { return 'Value From EJB'; } } // In your Servlet class you would lookup like the code bellow: public class Inject extends HttpServlet { private MyBean local; protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { try { InitialContext iniCtx = new InitialContext(); local = (MyBean) iniCtx.lookup('MyBean'); } catch (NamingException e) { e.printStackTrace(); } System.out.println(local.hello()); request.getRequestDispatcher('/finish.jsp').forward(request, response); } /** * @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response) */ protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { } }
Now Let us see the UserMB:
package com.mb; import javax.ejb.EJB; import javax.faces.bean.ManagedBean; import javax.faces.bean.SessionScoped; import javax.faces.context.ExternalContext; import javax.faces.context.FacesContext; import javax.servlet.http.HttpServletRequest; import com.facade.UserFacade; import com.model.User; @SessionScoped @ManagedBean public class UserMB { private User user; @EJB private UserFacade userFacade; public User getUser(){ if(user == null){ ExternalContext context = FacesContext.getCurrentInstance().getExternalContext(); String userEmail = context.getUserPrincipal().getName(); user = userFacade.findUserByEmail(userEmail); } return user; } public boolean isUserAdmin(){ return getRequest().isUserInRole('ADMIN'); } public String logOut(){ getRequest().getSession().invalidate(); return 'logout'; } private HttpServletRequest getRequest() { return (HttpServletRequest) FacesContext.getCurrentInstance().getExternalContext().getRequest(); } }
About the code above:
- This MB is only used to store the session user of our application. You will use this MB to display the user name or any other action regarding the user of the application.
- Notice that this is a Session MB; we check just one time if the user is null, and if returns true, we will go to the database. With this condition we will go just once to the database saving performance.
- If the @EJB injection raises an exception, check the tips given in the DogMB above.
View – Pages
Bellow the pages, css and its respective paths:
Do not mind with the interrogation icons or any other icon type that is displayed in the picture above. Those are versioning icons that points to my code. Always save your code.
I am using RequestScope in the ManagedBean, which is why you will see the h:inputHidden in all my pages. I think it is a better approach for you to repeat this field with RequestScope MBs because you will have more free memory in your server instead using SessionScope MBs.
/WebContent/resources/css/main.css
.table { border-collapse: collapse; } .tableColumnsHeader { text-align: center; background: none repeat scroll 0 0 #E5E5E5; border-bottom: 1px solid #BBBBBB; padding: 16px; } .tableFirstLine { text-align: center; background: none repeat scroll 0 0 #F9F9F9; border-top: 1px solid #BBBBBB; } .tableNextLine { text-align: center; background: none repeat scroll 0 0 #FFFFFFF; border-top: 1px solid #BBBBBB; } .panelGrid { border: 1px solid; } .panelFirstLine { text-align: center; border-top: 1px solid #BBBBBB; } .panelNextLine { text-align: center; border-top: 1px solid #BBBBBB; }
/WebContent/pages/public/login.xhtml
<!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'> <html xmlns='http://www.w3.org/1999/xhtml' xmlns:f='http://java.sun.com/jsf/core' xmlns:h='http://java.sun.com/jsf/html' xmlns:ui='http://java.sun.com/jsf/facelets'> <h:head> <h:outputStylesheet library='css' name='main.css' /> </h:head> <h:body> <p>Login to access secure pages:</p> <form method='post' action='j_security_check'> <h:messages layout='table' errorStyle='background: #AFEEEE;' infoStyle='background: #AFEEEE;' globalOnly='true' /> <h:panelGrid columns='2'> <h:outputLabel value='Username: ' /> <input type='text' id='j_username' name='j_username' /> <h:outputLabel value='Password: ' /> <input type='password' id='j_password' name='j_password' /> <h:outputText value='' /> <h:panelGrid columns='1'> <input type='submit' name='submit' value='Login' /> </h:panelGrid> </h:panelGrid> <br /> </form> </h:body> </html>
Notice how we import the css like if it was a library. The action that you see in the form tag, points to an unknown action to us, but is the JAAS the responsible to manage that.
/WebContent/pages/public/loginError.xhtml
<!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'> <html xmlns='http://www.w3.org/1999/xhtml' xmlns:f='http://java.sun.com/jsf/core' xmlns:h='http://java.sun.com/jsf/html' xmlns:ui='http://java.sun.com/jsf/facelets'> <h:head> <h:outputStylesheet library='css' name='main.css' /> </h:head> <h:body> #{msgs.loginErrorMessage} </h:body> </html>
/WebContent/pages/protected/user/listAllDogs.xhtml
<!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'> <html xmlns='http://www.w3.org/1999/xhtml' xmlns:f='http://java.sun.com/jsf/core' xmlns:h='http://java.sun.com/jsf/html' xmlns:ui='http://java.sun.com/jsf/facelets'> <h:head> <h:outputStylesheet library='css' name='main.css' /> </h:head> <h:body> <h:form> <h3>#{msgs.loginHello}: #{userMB.user.name} || <h:commandLink action='#{userMB.logOut()}' value='#{msgs.logout}' /> </h3> <h:messages /> <h:dataTable value='#{dogMB.allDogs}' var='dog' styleClass='table' headerClass='tableColumnsHeader' rowClasses='tableFirstLine,tableNextLine' > <h:column> <f:facet name='header'> #{msgs.dogName} </f:facet> #{dog.name} </h:column> <h:column> <f:facet name='header'> #{msgs.dogWeight} </f:facet> #{dog.weight} </h:column> <h:column> <h:panelGrid columns='2'> <!-- Always save the id as hidden when you use a request scope MB --> <h:inputHidden value='#{dog.id}' /> <h:commandButton action='#{dogMB.updateDogStart()}' value='#{msgs.update}' rendered='#{userMB.userAdmin}' > <f:setPropertyActionListener target='#{dogMB.dog}' value='#{dog}' /> </h:commandButton> <h:commandButton action='#{dogMB.deleteDogStart()}' value='#{msgs.delete}' rendered='#{userMB.userAdmin}' > <f:setPropertyActionListener target='#{dogMB.dog}' value='#{dog}' /> </h:commandButton> </h:panelGrid> </h:column> </h:dataTable> <!-- This button is displayed to the user, just to you see the error msg --> <h:commandButton action='createDog' value='#{msgs.create} #{msgs.dog}' /> </h:form> </h:body> </html>
About the code above:
- Always remember to wrap your code with the h:form tag. There are frameworks (like Primefaces) that will not work without the h:form, h:head and h:body.
- We use the UserMB to display the user name and to logout our user.
- The <h:messages /> tag will display the messages sent by the DogMB.
- Notice that in the line 33 the id is hidden. It is a necessary value if you use RequestScope instead SessionScope. I rather use RequestScope than SessionScope, your server memory will have less data in it.
- Notice that the buttons have the rendered=”#{userMB.userAdmin}” to indicate that only the ADMIN role will have access to the delete/update.
- I am passing to my MB the selected dog through the tag : “f:setPropertyActionListener”.
- The “create” button does not have the rendered option. It is just to display to you if a regular user tries to access a page.
/WebContent/pages/protected/admin/createDog.xhtml
<!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'> <html xmlns='http://www.w3.org/1999/xhtml' xmlns:f='http://java.sun.com/jsf/core' xmlns:h='http://java.sun.com/jsf/html' xmlns:ui='http://java.sun.com/jsf/facelets'> <h:head> <h:outputStylesheet library='css' name='main.css' /> </h:head> <h:body> <h:form> <h:messages/> <h3>${msgs.dogCreateHeader}</h3> <h:panelGrid columns='2' styleClass='panelGrid' rowClasses='panelFirstLine,panelNextLine' > <h:outputLabel for='dogName' value='#{msgs.dogName}' /> <h:inputText id='dogName' value='#{dogMB.dog.name}' required='true' requiredMessage='#{msgs.dogNameRequired}' /> <h:outputLabel for='dogWeight' value='#{msgs.dogWeight}' /> <h:inputText id='dogWeight' value='#{dogMB.dog.weight}' required='true' requiredMessage='#{msgs.dogWeightRequired}' > <f:convertNumber /> </h:inputText> </h:panelGrid> <h:panelGrid columns='2'> <h:commandButton action='#{dogMB.createDogEnd()}' value='#{msgs.create}' /> <h:commandButton action='#{dogMB.listAllDogs()}' value='#{msgs.cancel}' immediate='true' /> </h:panelGrid> <br/> </h:form> </h:body> </html>
About the code above:
- The fields name and weight are required and will print an error message if you leave it empty.
- The cancel button needs the option immediate=“true”; with this option the JSF will not validate any field.
/WebContent/pages/protected/admin/deleteDog.xhtml
<!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'> <html xmlns='http://www.w3.org/1999/xhtml' xmlns:f='http://java.sun.com/jsf/core' xmlns:h='http://java.sun.com/jsf/html' xmlns:ui='http://java.sun.com/jsf/facelets'> <h:head> <h:outputStylesheet library='css' name='main.css' /> </h:head> <h:body> <h:form> <h:messages/> <h3>#{msgs.dogDeleteHeader}: #{dogMB.dog.name}?</h3> <h:inputHidden value='#{dogMB.dog.id}' /> <h:panelGrid columns='2'> <h:commandButton action='#{dogMB.deleteDogEnd()}' value='#{msgs.delete}' /> <h:commandButton action='#{dogMB.listAllDogs()}' value='#{msgs.cancel}' immediate='true' /> </h:panelGrid> <br/> </h:form> </h:body> </html>
Notice that in the line 15 the id is hidden. It is a necessary value if you use RequestScope instead SessionScope. I rather use RequestScope than SessionScope, your server memory will have less data in it.
/WebContent/pages/protected/admin/updateDog.xhtml
<!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'> <html xmlns='http://www.w3.org/1999/xhtml' xmlns:f='http://java.sun.com/jsf/core' xmlns:h='http://java.sun.com/jsf/html' xmlns:ui='http://java.sun.com/jsf/facelets'> <h:head> <h:outputStylesheet library='css' name='main.css' /> </h:head> <h:body> <h:form> <h:messages/> <h3>#{msgs.dogUpdateHeader}: #{dogMB.dog.name}</h3> <h:inputHidden value='#{dogMB.dog.id}' /> <h:panelGrid columns='2' styleClass='panelGrid' rowClasses='panelFirstLine,panelNextLine' > <h:outputLabel for='dogName' value='#{msgs.dogName}' /> <h:inputText id='dogName' value='#{dogMB.dog.name}' required='true' requiredMessage='#{msgs.dogNameRequired}' /> <h:outputLabel for='dogWeight' value='#{msgs.dogWeight}' /> <h:inputText id='dogWeight' value='#{dogMB.dog.weight}' required='true' requiredMessage='#{msgs.dogWeightRequired}' > <f:convertNumber /> </h:inputText> </h:panelGrid> <h:panelGrid columns='2'> <h:commandButton action='#{dogMB.updateDogEnd()}' value='#{msgs.update}' /> <h:commandButton action='#{dogMB.listAllDogs()}' value='#{msgs.cancel}' immediate='true' /> </h:panelGrid> <br/> </h:form> </h:body> </html>
About the code above:
- Notice that in the line 15 the id is hidden. It is a necessary value if you use RequestScope instead SessionScope. I rather use RequestScope than SessionScope, your server memory will have less data in it.
- The fields name and weight are required and will print an error message if you leave it empty.
- The cancel button needs the option immediate=“true”; with this option the JSF will not validate any field.
View – JBoss 7 JAAS Configuration
Now we need just a few more steps to finish our software (Finally!).
We need to edit the JBoss configurations and to add our JAAS configurations.
Open again the file “YOUR_JBOSS/standalone/configuration/standalone.xml” and search for the key: “<security-domains>”. Add the code bellow (In this post I show how to do this set up for JBoss 6 – User Login Validation with JAAS and JSF):
<subsystem xmlns='urn:jboss:domain:security:1.0'> <security-domains> <!-- add me: begin --> <security-domain name='CrudJSFRealm' cache-type='default'> <authentication> <login-module code='org.jboss.security.auth.spi.DatabaseServerLoginModule' flag='required'> <module-option name='dsJndiName' value='CrudDS'/> <module-option name='principalsQuery' value='select password from users where email=?' /> <module-option name='rolesQuery' value='select role, 'Roles' from users u where u.email=?' /> </login-module> </authentication> </security-domain> <!-- add me: end --> <!-- Other data... --> </security-domains> </subsystem>
Running our Application
Let us create an EAR to unite our projects.
File > New > Other >EnterpriseApplication Project
We just need to have in our JBoss the EAR added.
Let us run our application. Start the JBoss and access our application by the URL: http://localhost:8080/CrudJSF/.
I wrote the pages with a simple CSS to make easier the understanding.
Login as USER and you will not see the update/delete buttons; you will only see the Create button that we left there just to see an exception off illegal access.
Take a look bellow at our pages:
Logged as ADMIN:
Logged as USER:
That is all for today
To download the source code of this post, click here.
I hope this post might help you.
If you have any doubt or comment just post it bellow.
See you soon. \o_
Links that helped me:
http://7thursdays.wordpress.com/2008/03/18/dependency-injection-in-jboss-42-hold-your-excitement/
http://jan.zawodny.pl/blog/2011/07/jboss-7-postgresql-9
http://blog.xebia.com/2011/07/19/developing-a-jpa-application-on-jboss-as-7/
http://community.jboss.org/wiki/DataSourceConfigurationInAS7
http://www.mkyong.com/jsf2/jsf-2-datatable-example/
Reference: Full WebApplication JSF EJB JPA JAAS from our JCG partner Hebert Coelho at the uaiHebert blog.
nice article but I have a question why not use the scope view or scope conversation (with CDI)?
The “allDogs” List used in listAllDogs.xhtml is not defined in the DogsMB, isn’t it?
In my case it fails to list the entities in my Database, as it cannot find “allDogs”
I implemented the above steps on Eclipse juno ,Jboss 7.1 with Sqlserver 2008 r2 .But ,it is not deployed successfully.Can u help me?
Hey Hebert (or reader), I’m new to Java and am trying to build a prototype application from this tutorial. Because of the direction, I have extended this awesome tutorial with the following: http://www.developerscrappad.com/111/java/java-ee/ejb3-jpa-entity-one-to-many-relationship/ Like I said, I’m new to Java so please excuse me for the lack of words to describe my question. So, taking the link into consideration (or without if you’re experienced), I ended up defining a foreign key as a local attribute of table 2 that maps to the related table (table 1) by using the @JoinColumn property. The result of this is I can save that… Read more »