Preso by Ed Burns:
https://javaserverfaces.dev.java.net/presentations/demystifyingjsf.pdf
Tutorial from IBM (requires registration but is worth the hassle):
https://www6.software.ibm.com/developerworks/education/j-jsf1/section7.html
JSF Anti-Patterns and Pitfalls
http://www.theserverside.com/tt/knowledgecenter-is/knowledgecenter-is.tss?l=JSFAnti-PatternsandPitfalls
Java EE 5 Tutorial
http://java.sun.com/javaee/5/docs/tutorial/doc/bnaph.html
Declare Faces Servlet in web.xml:
<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>*.jsf</url-pattern> </servlet-mapping> <servlet-mapping> <servlet-name>Faces Servlet</servlet-name> <url-pattern>/faces/*</url-pattern> </servlet-mapping>
JSF Configuration File
If you name your Faces configuration file faces-config.xml and place it in your Web application's WEB-INF directory, then the Faces Servlet picks it up and uses it automatically (because it's the default). Alternatively, you can load one or more application-configuration files through an initialization parameter — javax.faces.application.CONFIG_FILES — in your web.xml file with a comma-separated list of files as the body. You will likely use the second approach for all but the simplest JSF applications (but be sure to not list faces-config.xml in that initialization parameter, or any registered phase listeners will fire twice).
Sample JSF config file:
<?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_1_2.xsd" version="1.2"> <managed-bean> <managed-bean-name>myBean</managed-bean-name> <managed-bean-class>com.mydomain.MyBean</managed-bean-class> <managed-bean-scope>request</managed-bean-scope> </managed-bean> </faces-config>JSF configuration files are specified via the javax.faces.CONFIG_FILES context parameter in web.xml:
<context-param> <description>comma separated list of JSF conf files</description> <param-name>javax.faces.CONFIG_FILES</param-name> <param-value> /WEB-INF/menu-config.xml, /WEB-INF/services-config.xml </param-value> </context-param>Again: do not specify faces-config.xml in this context-param -- if you do, any registered phases listeners will fire twice.
Declare standard JSF tags:
<%@ taglib uri="http://java.sun.com/jsf/html" prefix="h" %> <%@ taglib uri="http://java.sun.com/jsf/core" prefix="f" %>The html taglib contains all the tags for dealing with forms and other HTML-specific items. The core taglib contains all the logic, validation, controller, and other tags specific to JSF.
An <h:panelGrid> can contain only components, whereas <h:form>, <f:view> and <h:panelgroup> can contain both HTML and components.
Import stylesheets:
<head> <title>My Application</title> <link rel="stylesheet" type="text/css" href="<%=request.getContextPath()%>/css/styles.css" /> </head>
User-Facing Messages
Customize messages (this approach does not scale, since it must be repeated for each section needing it):
<%-- case by case basis; message appears immediately to right of field --%> <h:outputLabel value="Zip Code" for="zipcode" /> <h:inputText id="zipcode" label="Zip Code" value="#{myBean.zipcode}" required="true" requiredMessage="required" converterMessage="not a valid zip code"/> <h:message for="zipcode" />Change messages globally (this scales well, can be overridden as needed using case-by-case approach):
- Add this tag to beginning of JSF config file(s):
<application> <message-bundle>mymessages</message-bundle> </application>
- Add entries to mymessages.properties resource bundle:
javax.faces.component.UIInput.REQUIRED_detail=value is required javax.faces.converter.IntegerConverter.INTEGER_detail=that's not a valid integerThe complete list of default messages are found in the JSF 1.2 RI jarfile jsf-impl.jar, under javax.faces.Messages.properties.
In case the standard error messages don’t meet your needs, create new ones in resource bundles and configure the resource bundles in your application configuration resource file. For example, this message is stored in the resource bundle, MyMessages.properties:
invalidZipCode=The value you entered is not a zip code.
The resource bundle is configured in the application configuration file:
<application> <resource-bundle> <base-name>com.mydomain.MyMessages</base-name> <var>msgs</var> </resource-bundle> </application>The base-name element indicates the fully-qualified name of the resource bundle. The var element indicates the name by which page authors refer to the resource bundle with the expression language:
<h:inputText id="zipcode" label="Zip Code" value="#{myBean.zipcode}" converterMessage="#{msgs.invalidZipCode}"> ... </h:inputText>Add messages to be displayed based on outcomes in server-side processing:
<h:messages infoClass="infoClass" errorClass="errorClass" layout="table" globalOnly="true"/> .... <h:inputText id="minimum" label="Minimum" value="#{myBean.modelObject.minimum}" required="true" binding="#{myBean.minimum}" /> <h:message for="minimum" errorClass="errorClass"/>....
acesContext facesContext = FacesContext.getCurrentInstance(); try { ... facesContext.addMessage(null, new FacesMessage( FacesMessage.SEVERITY_INFO, "Completed successfully", null)); ... } catch (Exception ex) { facesContext.addMessage(null, new FacesMessage(FacesMessage.SEVERITY_ERROR, ex.getMessage(), null)); ... }
Adjust styles dynamically
<h:outputLabel value="Minimum" for="minimum" styleClass="#{myBean.minimumStyleClass}"/>......
public String getMinimumStyleClass() { if (minimum.isValid()) { return "labelClass"; } else { return "errorClass"; } }
Access Component from Java
Factor out logic from JSP into managed bean (bind component to managed bean; lets you manipulate the component's state programatically without traversing the component tree to get to the component):
<h:panelGroup binding="#{myBean.panel}" rendered="false">.......
private UIPanel panel; public UIPanel getPanel() { return panel; } public void setPanel(UIPanel panel) { this.panel = panel; } public String execute() { ... try { modelObject.doSomething(); panel.setRendered(true); ... } catch (Exception ex) { ... panel.setRendered(false); } return null; }
Injection into managed beans:
Using standard JSF Inversion of Control configuration:
<managed-bean> <managed-bean-name>myBean</managed-bean-name> <managed-bean-class> com.myDomain.MyBean </managed-bean-class> <managed-bean-scope>request</managed-bean-scope> <managed-property> <property-name>modelObject</property-name> <value>#{modelObject}</value> </managed-property> </managed-bean> <managed-bean> <managed-bean-name>modelObject</managed-bean-name> <managed-bean-class> com.myDomain.MyModel </managed-bean-class> <managed-bean-scope>none</managed-bean-scope> </managed-bean>If you need to explicitly manage the order in which properties are set:
- The specification states that a JSF implementation must inject the dependencies of a managed bean in the order in which they are configured
- Applications that are using JSF 1.2 can take advantage of the PostConstruct annotation. Below, the PostConstruct annotation instructs the JSF implementation to invoke the initialize method after the managed bean has been created.
private ModelAttrs min, max; // injected // no setters and getters for min, max @javax.annotation.PostConstruct public void initialize() { if(min == null || max == null) { throw new NullPointerException("init failed - min or max is null"); } if(min > max)) { throw new IllegalStateException("min cannot be larger than max"); } }However, it's still possible to call the no-arg constructor from somewhere other than the JSF framework, so this is not bulletproof.
- Use a full blown dependency injection framework. Using Spring is as easy as placing the following lines of code in your JSF deployment descriptor.
<application> <variable-resolver> org.springframework.web.jsf.DelegatingVariableResolver </variable-resolver> </application>A JSF implementation is not required to warn you about misspelled class names or cyclical references in managed bean declarations at startup, but both will result in a runtime exception. JSF 1.2 will warn you about duplicate managed bean declarations but 1.1 will not.
Use JSFUnit to help address these types of problems (although I've yet to try it, so I'm unclear about the status of JSFUnit static analysis features, but it appears that the runtime testing is GA). You can also avoid some of these problems with annotations libraries found in Seam or Shale.
Navigation
Navigate via comandLink and navigation rule -from *any* view - URL in browser doesn't change (can't bookmark):
<navigation-rule> <from-view-id>*</from-view-id> <navigation-case> <from-outcome>services</from-outcome> <to-view-id>/pages/services.jsp</to-view-id> </navigation-case> </navigation-rule> ... <h:commandLink action="services" value="Go To Services"/>More specific but still a generic "from-view" configuration:
<navigation-rule> <from-view-id>/pages/*</from-view-id> <navigation-case> <from-outcome>services</from-outcome> <to-view-id>/pages/services.jsp</to-view-id> </navigation-case> </navigation-rule>Navigation with change to URL in browser address bar:
<navigation-rule> <navigation-case> <from-outcome>services</from-outcome> <to-view-id>/pages/services.jsp</to-view-id> <redirect/> </navigation-case> </navigation-rule>Navigate directly (considered an antipattern: does not go through controller, thus no opportunity to initialize the model, etc.)
<h:outputLink value="pages/services.jsp"> <h:outputText value="Go To Services"/> </h:outputLink>
EL implicit objects
These give access to web scopes and more: cookie, facesContext, header, headerValues, param, paramValues, request, requestScope, view, application, applicationScope, initParam, session, sessionScope.
Lifecycle
The Lifecycle dictates how an incoming request is handled and how a response is generated. Two "portions" of lifecycle are "execute" and "render", each of which has phases:
- Execute
- Finding the View on which to operate (Restore View - initialize new view or restore existing)
- Allowing components to get their values (Apply Request Values; process events - if conversion errors (from request to local value of component), store message in FacesContext, goto Render Response; if immediate=true, then validation/conversion/event processing is done here)
- Ensuring the values are converted and validated (Process Validations; if validation errors, store message in FacesContext and goto Render Response)
- Updating the model (Update Model Values - apply component local values to corresponding server-side object properties; process events; if conversion errors, goto Render Response)
- Invoke Application - handle application-level events, e.g. submit a form, link to a page, etc.
- Render
- process events from Invoke Application phase
- Selecting and rendering the new view (Render Response)
When the life cycle handles an initial request, it only executes the restore view and render response phases because there is no user input or actions to process. Conversely, when the life cycle handles a postback, it executes all of the phases.
You can install PhaseListeners into the Lifecycle to do whatever you want before or after each or every phase. PhaseListeners are registered in a JSF configuration file:
<lifecycle> <phase-listener>com.myDomain.MyPhaseListener</phase-listener> </lifecycle>Each PhaseListener is global to the application and it subscribes to at least one phase event for every request, thus it is NOT thread-safe.
Event Handlers
- Action Listeners use the observer pattern - they listen for events on a component (clicked, scrolled, etc). Accept one ActionEvent arg, return void.
- Value Change Listeners listen for events around component value being changed. Accept one ValueChangeEvent arg, return void.
- Actions return navigation outcomes. Accept zero-args, return String.
Steps in developing a JSF application
- Create development directory structure
- typical webapp layout
- need JSF 1.2 jars
- Create config files
- web.xml
- add servlet/servlet mapping for Faces Servlet
- add context-param elements as needed
- JSF configuration file(s) (a single faces-config.xml by default)
- nav rules, managed beans, etc.
- declare custom message.properties file for user-facing messaging
- create many smaller config files vs one large one, partitioned as appropriate
- messages.properties
- override standard JSF messages as needed
- JSF-specific steps
- create pages
- JSF tags: event handlers, validators, converters, messaging, etc.
- define navigation
- develop managed beans
- provide properties, handle events, delegate to business classes, navigation logic
- Tip: aggregate multiple models into a single managed bean (via compose-and-delegate) to present a facade to JSP
- Add managed bean declarations
- Build, deploy, and test the application
- Iterate on JSF-specific steps and build/deploy/test
Thread Safety
The component will get a new Converter instance each time it is needed when you register a Converter and use a converter tag, thus this is a thread-safe approach:
<converter> <converter-id>myConverter</converter-id> <converter-class>com.myDomain.myConverter</converter-class> </converter> <h:inputText value="#{managedBean.value}" > <f:converter converterId="myConverter" > </h:inputText>Using the converter attribute however could introduce a race condition because it is possible the same Converter instance will be used simultaneously by more than one request.
<managed-bean> <managed-bean-name>myConverter</managed-bean-name> <managed-bean-class>com.myDomain.myConverter</managed-bean-class> <managed-bean-scope>session</managed-bean-scope> </managed-bean> <h:inputText value="#{myBean.value}" converter="#{myConverter}" />Custom Validators have the same thread safety constraints as custom Converters.
View-State Encryption
By default, view state will not be encrypted. However, there is a way to do this with Mojarra. Specify a environment entry like so:
<env-entry> <env-entry-name>ClientStateSavingPassword</env-entry-name> <env-entry-type>java.lang.String</env-entry-type> <env-entry-value>[SOME VALUE]</env-entry-value> </env-entry>The presence of this JNDI entry will cause the state to be encrypted using the specified password...this isn't the most secure way of conveying a password, however, this cannot be accessed easily without having code executed on the server side.
Please note change to ClientStateSavingPassword as noted here...
ReplyDeletehttps://javaserverfaces.dev.java.net/issues/show_bug.cgi?id=1214
Thanks Richard! I'll get to updating my post ASAP -
ReplyDeleteHey Gary,
ReplyDeleteI used to bind panelGroups to UIPanels in my backing bean, like your example here ("Access Component from Java"). It worked well, until someone found a particularly odd behaviour: after some navigation, if you hit the refresh button (F5) in the browser, components using UIPanel binding would not be shown. When I removed the binding and used a simple boolean (rendered="#{backingBean.panel1Rendered}"), everything behaved normally again, even with F5s.
Do you know of anything that could cause this?
TIA
Hi Khristian -
ReplyDeleteSorry for my delayed response.
This could be caused by ... any number of things. Without having ability to reproduce that behavior on my own, I'd be reluctant to guess -- but I did find some discussions that might offer you some clues:
http://www.icefaces.org/JForum/posts/list/7302.page
http://jira.icefaces.org/browse/ICE-3632
http://stackoverflow.com/questions/1712050/full-page-refresh-in-icefaces-1-8-2
http://stackoverflow.com/questions/1527242/icefaces-page-refresh
I'd experiment with some of the suggestions here to see if you have better luck. I'd also try adding the entry in your navigation rules to see if that helps.
Sorry for no definitive answer here. I've found that IceFaces problems are frequently pretty unique to one's environment and config, and that just experimenting around with what others have found is the only way to solve things. So, I hope the suggestions referenced here can help you out.
Updating my previous comment:
ReplyDelete...try adding the <redirect/> entry in your navigation rules...