![]() |
A short treatise on
|
JavaServer Faces (JSF) is a user-interface (UI) component based the Java web application framework. JSF is serverbased, that is the JSF UI components and their state are represented on the server. A JSF application run in a standard web container, e.g.: Tomcat, GlassFish, Jetty, etc. JSF defines a standard life-cycle for the UI components.
These notes will one day be incorporated into a formal tutorial on writing applications using JavaServer Faces. For this reason, things get seriously random and out-right bird-brained the further you read. It's up to you when to stop paying attention.
Though these notes are extremely useful (to me) in sorting out problems in JSF, a shorter, more definitive article is my RichFaces for JSF Use.
JSF is part of the Java EE standard, formerly known as J2EE. Sun Microsystems provides a reference implementation but there are also alternatives such as Apache MyFaces.
A JSF application consists of web pages containing JSF UI components. This application requires two configuration files, web.xml and faces-config.xml".
web.xml is identical in use and composition to this file in a JavaServer Pages (JSP) application. JSF builds on JSP as a logical step beyond.
faces-config.xml defines:
A managed bean is a type of JavaBean or reusable software component, that is created and initialized with dependency injection. Dependency injection is a technique for supplying external values upon which a component or bean is dependent, usually for its initialization.
Beginning in Java EE 6, a managed bean is a Java class that...
Consider the class Person, ...
01.
package
com.etretatlogiciels.jsf.treatise;
02.
03.
public
class
Person
04.
{
05.
String firstName;
06.
.
07.
.
08.
.
09.
}
Managed beans are "plain, old Java objects" (POJOs) declared in faces-config.xml. For example, you can define a Java object Person. Once you define the object in faces-config.xml you use the attributes of Person in your UI components by binding the field, say, firstName of an object of this class to a JSF input field.
JSF uses the Java Unified Expression Language, used also by JSP, to bind UI components to object attributes or methods. This "language" consists of the XML tags you will come to recognize in the JSP/JSF code.
A managed bean is specified in faces-config.xml thus:
1.
<
managed-bean
>
2.
<
managed-bean-name
> Person </
managed-bean-name
>
3.
<
managed-bean-class
> com.etretatlogiciels.jsf.treatise.Person </
managed-bean-class
>
4.
<
managed-bean-scope
> session </
managed-bean-scope
>
5.
</
managed-bean
>
The various scopes available for managed beans are:
1.
<
managed-bean-scope
> session|request|application|none </
managed-bean-scope
>
It is the framework within the Tomcat (etc.) container that ensures the communication of values between HTML (user/presentation) and the component (POJO).
A backing bean is not going to be part of the Controller. Although it can contain some controller functions, it's primarily part of the Model. Most of the Controller logic in JSF is in the FacesServlet and the tag implementations.
And action processors aren't controllers in the strict sense of the word. They're more of a business logic function with ties to the dispatcher.
There are three different types of dependency injection.
The benefits of dependency injection are...
Drawbacks...
In Eclipse, edit faces-config.xml and use the Managed Bean tab of the editor to create of your Java class a managed bean. Use the Initialization section to initialize the bean's properties even if they are a Map or List. In addition to the "managed-bean" specification (illustrated previously), a paragraph for "managed-property" can be added that looks like this:
1.
<
managed-property
>
2.
<
property-name
> firstName <
property-name
>
3.
<
property-class
> com.etretatlogiciels.jsf.treatise.Person <
property-class
>
4.
<
value
> #{firstName} </
value
>
5.
</
managed-property
>
In JSF you access the values of a managed bean via value binding rather than writing code to call getters and setters.
JSP begins the concept of separating the efforts of an application between the model, the view and the controller. JSP already separates state between UI components; each component is aware of its data.
JSF introduces a separation of the functionality of a component from its presentation (or display). HTML rendering is responsible for for separate client presentation.
There are versions of JSF. JSF 1.2 is built directly atop JSP and uses JSTL tags. JSF 2.0 uses Facelets.
You can use different sets of libraries for JSF like Apache MyFaces, RealFaces, Mojarra (Sun Reference Implementation), etc. However, these are those I use.
Name | Discovered missing at |
commons-beanutils-1.8.3.jar | Server start-up |
commons-codec-1.4.jar | Application start-up (JSP launch) |
commons-collections-3.2.1.jar | (unsure, but consider it necessary) |
commons-digester-2.1.jar | Server start-up |
commons-discovery-0.4.jar | Application start-up (JSP launch) |
commons-logging-1.1.1.jar | Server start-up |
If using Apache MyFaces, here are the relevant JARs. These are responsible for actual JSF functionality (parsing, rendering, etc.).
Name |
myfaces-api-1.2.8.jar |
myfaces-impl-1.2.8.jar |
These tag libraries are imperative for
jstl-api-1.2.jar |
jstl-impl-1.2.jar |
Less relevant, here are the libraries that come with Tomcat...
annotations-api.jar |
catalina.jar |
catalina-ant.jar |
catalina-ha.jar |
catalina-tribes.jar |
el-api.jar |
jasper.jar |
jasper-el.jar |
jasper-jdt.jar |
jsp-api.jar |
servlet-api.jar |
tomcat-coyote.jar |
tomcat-i18n-es.jar |
tomcat-i18n-fr.jar |
tomcat-i18n-ja.jar |
tomcat-dbcp.jar |
...and with the JDK.
charsets.jar |
dnsns.jar |
jsse.jar |
localedata.jar |
resources.jar |
rt.jar |
sunjce_provider.jar |
sunpkcs11.jar |
These notes here are problems I've encountered then fixed as I get various JavaServer Faces programs running. Eventually, they'll find their way into my own tutorial on JSF.
If you get validation errors in your JSP file (this really isn't about JSF per se) like:
cannot find the tag library descriptor
...it probably refers to you having lost the JSTL library (add it using Build Path and also make certain it's getting deployed by checking it in Java EE Module Dependencies.
Or, you find...
Can not find the tag library descriptor for "http://java.sun.com/jsf/core"
...in...
<%@ taglib prefix="h" uri="http://java.sun.com/jsf/html" %>
...right after having changed JSF libraries (say from JSF 1.2 Apache MyFaces to JSF 2.0 Apache Trinidad or JSF 2.0 Mojarra). This is fixed by going back to JSF 1.2, which distributes a complete implementation. Why this is I don't yet know.
If you're missing a tag such as given in the validation warning/error:
Unknown tag (fmt:setLocale). Unknown tag (fmt:setBundle). Unknown tag (fmt:message).
...adding this will fix it:
1.
<%@ taglib uri='http://java.sun.com/jstl/fmt' prefix='fmt'%>
Here's an answer from the JSF forum on Java Ranch to a question on why a piece of code I wrote wasn't working. One article on the web induced me to think the syntax should be
value="#{msg.login.username}"
...but this never worked in the context in which I was trying to use it. Instead,
01.
<
f:view
>
02.
<
f:loadBundle
basename
=
"com.etretatlogiciels.dvdcatalog.messages"
var
=
"msg"
/>
03.
<
h:form
>
04.
<
table
>
05.
<
tr
>
06.
<
td
> <
h:outputText
value
=
"#{msg['login.username']}"
/> </
td
>
07.
<
td
> <
h:inputText
id
=
"loginname"
value
=
"#{Login.name}"
/> </
td
>
08.
.
09.
.
10.
.
Problem: the displayed page (and Show Source) shows JSF was not rendered (or not parsed).
That can have 2 causes:
If JSF tags are not being parsed, then it simply means that the request has not been passed through the FacesServlet. That servlet is the one responsible for all that JSF stuff. You need to verify if the request URL used matches the url-pattern of the FacesServlet. Note that it is case sensitive.
This may however also happen if you opened the file directly in the builtin Eclipse browser. You shouldn't do that. You need to specify the right URL yourself in the address bar of either the builtin browser or an external browser (e.g. Internet Explorer/Firefox).
Update: one more thing, did you declare the JSF HTML taglib in the <html xmlns> attribute?
It should look like
<html ... xmlns="http://www.w3.org/1999/xhtml" xmlns:h="http://java.sun.com/jsf/html">
This is the diagnosis of the symptom, "Error configuring application listener of class org.apache.myfaces.webapp.StartupServletContextListener".
I appear to have created this error by insisting upon using Build Paths to switch the output folder from <project-name>/build/classes to <project-name>/WebContent/WEB-INF/classes. Do not do this.
My project name (as appearing below) is dvdcatalog-5 and it is certain
that this class, which comes from myfaces-impl-x.x.x.jar, is not or
should not be missing since I've got this JAR in my project, see Build
Path -> Configure Build Path... -> Libraries -> JSF (Apache
MyFaces)
.
Apr 30, 2010 8:55:24 AM org.apache.catalina.core.AprLifecycleListener init INFO: The APR based Apache Tomcat Native library which allows optimal performance in production environments was not found on the java.library.path: C:\Users\russ\dev\downloads\jdk1.6.0_20\bin;.;C:\Windows\Sun\Java\bin;C:\Windows\system32;\ C:\Windows;C:\Program Files (x86)\NVIDIA Corporation\PhysX\Common;C:\Windows\system32;C:\Windows;C:\Windows\System32\Wbem;\ C:\Windows\System32\WindowsPowerShell\v1.0\;C:\Program Files\TortoiseSVN\bin;C:\Program Files (x86)\QuickTime\QTSystem\;\ C:\Program Files\Intel\DMIX;C:\Users\russ\bin;C:\Program Files (x86)\Vim\vim72;C:\Program Files (x86)\SpringSource\roo-1.0.0.RC4\bin;\ C:\Users\russ\apache-maven-2.2.1\bin;C:\Users\russ\dev\downloads\jdk1.6.0_20\bin Apr 30, 2010 8:55:24 AM org.apache.tomcat.util.digester.SetPropertiesRule begin WARNING: [SetPropertiesRule]{Server/Service/Engine/Host/Context} Setting property 'source' to 'org.eclipse.jst.jee.server:dvdcatalog-5' \ did not find a matching property. Apr 30, 2010 8:55:24 AM org.apache.coyote.http11.Http11Protocol init INFO: Initializing Coyote HTTP/1.1 on http-8080 Apr 30, 2010 8:55:24 AM org.apache.catalina.startup.Catalina load INFO: Initialization processed in 292 ms Apr 30, 2010 8:55:24 AM org.apache.catalina.core.StandardService start INFO: Starting service Catalina Apr 30, 2010 8:55:24 AM org.apache.catalina.core.StandardEngine start INFO: Starting Servlet Engine: Apache Tomcat/6.0.26 Apr 30, 2010 8:55:24 AM org.apache.catalina.core.StandardContext listenerStart SEVERE: Error configuring application listener of class org.apache.myfaces.webapp.StartupServletContextListener java.lang.ClassNotFoundException: org.apache.myfaces.webapp.StartupServletContextListener at org.apache.catalina.loader.WebappClassLoader.loadClass(WebappClassLoader.java:1516) at org.apache.catalina.loader.WebappClassLoader.loadClass(WebappClassLoader.java:1361) at org.apache.catalina.core.StandardContext.listenerStart(StandardContext.java:3915) at org.apache.catalina.core.StandardContext.start(StandardContext.java:4467) at org.apache.catalina.core.ContainerBase.start(ContainerBase.java:1045) at org.apache.catalina.core.StandardHost.start(StandardHost.java:785) at org.apache.catalina.core.ContainerBase.start(ContainerBase.java:1045) at org.apache.catalina.core.StandardEngine.start(StandardEngine.java:443) at org.apache.catalina.core.StandardService.start(StandardService.java:519) at org.apache.catalina.core.StandardServer.start(StandardServer.java:710) at org.apache.catalina.startup.Catalina.start(Catalina.java:581) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25) at java.lang.reflect.Method.invoke(Method.java:597) at org.apache.catalina.startup.Bootstrap.start(Bootstrap.java:289) at org.apache.catalina.startup.Bootstrap.main(Bootstrap.java:414) Apr 30, 2010 8:55:24 AM org.apache.catalina.core.StandardContext listenerStart SEVERE: Skipped installing application listeners due to previous error(s) Apr 30, 2010 8:55:24 AM org.apache.catalina.core.StandardContext start SEVERE: Error listenerStart Apr 30, 2010 8:55:24 AM org.apache.catalina.core.StandardContext start SEVERE: Context [/dvdcatalog-5] startup failed due to previous errors
After this I restored build/classes to the root of my project,
respecified it in Build Path -> Configure Build Path... -> Source
-> Default output folder
and deleted
WebContent/WEB-INF/classes.
Then I refreshed, re-built, deconfigured and reconfigured the Tomcat server, cleaned, republished, etc., bounced Eclipse and more. It still would not work.
What finally did the trick was to drop Eclipse, then relaunch it with the -clean option. It has happened to me that I was obliged to do this more than once before getting it to work again.
Nonetheless, I still continue to have this problem from time to time and it got harder and harder to resolve. At last, I removed all the other projects I had to a different (new workspace) to see if that fixes it. The only project left is dvdcatalog-2. This was not a permanent fix either.
The next thing I did was get frustrated and begin to think about a different version of Apache (can't change the Dynamic Web Project's facet, Dynamic Web Module, to 2.4) or even JBoss or GlassFish (no native Eclipse support exists for GlassFish, so you have to download a special Eclipse package in addition to the GlassFish server). Finally, I deleted the server altogether from my Projet Explorer view. Then, I readded it. That worked once.
(See stack trace above more completely illustrating this error:)
SEVERE: Error configuring application listener of class org.apache.myfaces.webapp.StartupServletContextListener ...
Later, this problem persists. I note that this is the only solution that works for me and it works every time. Since beginning to do this, I have no trouble developing in JSF.
I think this is a bug in how Eclipse deploys to Tomcat, but I can't figure out how to work around or fix except following these steps.
New -> Other -> Server -> Next -> Apache ->
Tomcat v6.0 Server -> Next
.
You will have to do this every time you bounce Eclipse or change workspaces.
If you're not settled on which JSF library to use, and you've switched around, or you're using a tutorial that may use a different one, be clear that you'll get a listener missing error if you don't have the right listener referenced in web.xml. The one there must match the one in the library you're using (Apache MyFaces, RichFaces, Mojarra/Sun RI, etc.).
If the paragraph above (and the section before it) didn't help, look into this solution. Frustratingly, I got this when attempting to start Tomcat 6.
Feb 2, 2011 4:09:31 PM org.apache.catalina.core.StandardContext listenerStart SEVERE: Error configuring application listener of class org.apache.myfaces.webapp.StartupServletContextListener java.lang.NoClassDefFoundError: org/apache/commons/logging/LogFactory at org.apache.myfaces.webapp.AbstractMyFacesListener.(AbstractMyFacesListener.java:36) at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method) at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:39) at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:27) at java.lang.reflect.Constructor.newInstance(Constructor.java:513) at java.lang.Class.newInstance0(Class.java:355) at java.lang.Class.newInstance(Class.java:308) at org.apache.catalina.core.StandardContext.listenerStart(StandardContext.java:4079) at org.apache.catalina.core.StandardContext.start(StandardContext.java:4630) at org.apache.catalina.core.ContainerBase.start(ContainerBase.java:1045) at org.apache.catalina.core.StandardHost.start(StandardHost.java:785) at org.apache.catalina.core.ContainerBase.start(ContainerBase.java:1045) at org.apache.catalina.core.StandardEngine.start(StandardEngine.java:445) at org.apache.catalina.core.StandardService.start(StandardService.java:519) at org.apache.catalina.core.StandardServer.start(StandardServer.java:710) at org.apache.catalina.startup.Catalina.start(Catalina.java:581) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25) at java.lang.reflect.Method.invoke(Method.java:597) at org.apache.catalina.startup.Bootstrap.start(Bootstrap.java:289) at org.apache.catalina.startup.Bootstrap.main(Bootstrap.java:414) Caused by: java.lang.ClassNotFoundException: org.apache.commons.logging.LogFactory at org.apache.catalina.loader.WebappClassLoader.loadClass(WebappClassLoader.java:1645) at org.apache.catalina.loader.WebappClassLoader.loadClass(WebappClassLoader.java:1491) ... 22 more Feb 2, 2011 4:09:31 PM org.apache.catalina.core.StandardContext listenerStart SEVERE: Skipped installing application listeners due to previous error(s) Feb 2, 2011 4:09:31 PM org.apache.catalina.core.StandardContext start SEVERE: Error listenerStart Feb 2, 2011 4:09:31 PM org.apache.catalina.core.StandardContext start SEVERE: Context [/de.vogella.jsf.starter] startup failed due to previous errors
This despite that I'm using the following JARs:
...and, clearly, this class is not missing!
$ jar -tvf myfaces-impl-1.2.8.jar | grep StartupServletContextListener 5747 Mon Nov 16 22:34:04 MST 2009 org/apache/myfaces/webapp/StartupServletContextListener.class
Don't keep looking for the Listener! That's nothing to do with it. It's the logging stuff that's missing. What is missing here is the Apache Commons libraries, in particular, commons-logging-1.1.1.jar.
If there's a javax.faces.DEFAULT_SUFFIX in web.xml of, e.g.: .jsp or .jsf, and a <servlet-mapping ... url-pattern> that matches, this will cause infinite recursion:
SEVERE: Servlet.service() for servlet Faces Servlet threw exception java.lang.StackOverflowError at javax.xervlet.ServletResponseWrapper.setContentType(ServletResponseWrapper.java:130)
These are just warnings that nothing's been set in web.xml and a default
These are just warnings that nothing's been set in web.xml and a default is being taken.
. . . INFO - Checking for plugins:org.apache.myfaces.FACES_INIT_PLUGINS INFO - No context init parameter 'org.apache.myfaces.RENDER_CLEAR_JAVASCRIPT_FOR_BUTTON' found, using default value false INFO - No context init parameter 'org.apache.myfaces.SAVE_FORM_SUBMIT_LINK_IE' found, using default value false INFO - No context init parameter 'org.apache.myfaces.READONLY_AS_DISABLED_FOR_SELECTS' found, using default value true INFO - No context init parameter 'org.apache.myfaces.RENDER_VIEWSTATE_ID' found, using default value true INFO - No context init parameter 'org.apache.myfaces.STRICT_XHTML_LINKS' found, using default value true INFO - No context init parameter 'org.apache.myfaces.CONFIG_REFRESH_PERIOD' found, using default value 2 INFO - No context init parameter 'org.apache.myfaces.VIEWSTATE_JAVASCRIPT' found, using default value false . . .
Tomahawk is built upon MyFaces, but it isn't mandatory, crucial or important to include. This just says it looked for it, but it isn't there.
. . . INFO - Tomahawk jar not available. Autoscrolling, DetectJavascript, AddResourceClass and CheckExtensionsFilter are disabled now. INFO - Starting up Tomahawk on the MyFaces-JSF-Implementation . . .
More informative remarks. Surprisingly, it says it's getting myfaces
from WEB-INF/lib. That's not where we installed this library, but it's
the net effect of setting it up using a
Java Build Path -> Libraries -> User Library
plus setting Java EE Module Dependencies.
. . . INFO - Reading standard config META-INF/standard-faces-config.xml INFO - Reading config /WEB-INF/faces-config.xml INFO - Starting up MyFaces-package : myfaces-api in version : 1.2.8 from path : \ file:/C:/Users/russ/dev/workspace/.metadata/.plugins/org.eclipse.wst.server.core/tmp0/wtpwebapps/dvdcatalog-2/WEB-INF/lib/myfaces-api-1.2.8.jar INFO - Starting up MyFaces-package : myfaces-impl in version : 1.2.8 from path : \ file:/C:/Users/russ/dev/workspace/.metadata/.plugins/org.eclipse.wst.server.core/tmp0/wtpwebapps/dvdcatalog-2/WEB-INF/lib/myfaces-impl-1.2.8.jar . . .
Again, just noting that these are missing, but it shouldn't matter...
. . . INFO - MyFaces-package : tomahawk not found. INFO - MyFaces-package : tomahawk12 not found. INFO - MyFaces-package : tomahawk-sandbox not found. INFO - MyFaces-package : tomahawk-sandbox12 not found. INFO - MyFaces-package : tomahawk-sandbox15 not found. INFO - MyFaces-package : myfaces-orchestra-core not found. INFO - MyFaces-package : myfaces-orchestra-core12 not found. INFO - MyFaces-package : trinidad-api not found. INFO - MyFaces-package : trinidad-impl not found. INFO - MyFaces-package : tobago not found. INFO - MyFaces-package : commons-el not found. INFO - MyFaces-package : jsp-api not found. . . .
This is probably due to a "java.lang.StackOverflowError".
INFO: Server startup in 984 ms May 3, 2010 10:08:45 AM org.apache.catalina.core.ApplicationDispatcher invoke SEVERE: Servlet.service() for servlet faces threw exception java.lang.StackOverflowError at org.apache.catalina.core.ApplicationHttpRequest.getAttributeNames(ApplicationHttpRequest.java:243) at org.apache.catalina.core.ApplicationHttpRequest$AttributeNamesEnumerator.(ApplicationHttpRequest.java:905) at org.apache.catalina.core.ApplicationHttpRequest.getAttributeNames(ApplicationHttpRequest.java:243) . . .
This arises from having duplicate <servlet> entries in web.xml, such as:
01.
.
02.
.
03.
.
04.
<
servlet-mapping
>
05.
<
servlet-name
>faces</
servlet-name
>
06.
<
url-pattern
>*.jsf</
url-pattern
>
07.
</
servlet-mapping
>
08.
<
servlet-mapping
>
09.
<
servlet-name
>faces</
servlet-name
>
10.
<
url-pattern
>*.jsp</
url-pattern
>
11.
</
servlet-mapping
>
12.
.
13.
.
14.
.
You cannot configure faces for .jsp and .jsf. I've found that not defining .jsp that way is the solution. For a Faces application, it's not necessary even though you might be led to think it is by reason of the presence of .jsp files (and not files extended with .jsf).
This is a purely Eclipse-caused error. It usually indicates a different project than the one you're running and that this project has been closed.
. . . May 3, 2010 11:08:17 AM org.apache.catalina.core.StandardEngine start INFO: Starting Servlet Engine: Apache Tomcat/6.0.26 May 3, 2010 11:08:17 AM org.apache.catalina.core.StandardContext resourcesStart SEVERE: Error starting static Resources java.lang.IllegalArgumentException: Document base \ C:\Users\russ\dev\workspace\.metadata\.plugins\org.eclipse.wst.server.core\tmp0\wtpwebapps\dvdcatalog-3 does not exist or is not a readable directory at org.apache.naming.resources.FileDirContext.setDocBase(FileDirContext.java:142) at org.apache.catalina.core.StandardContext.resourcesStart(StandardContext.java:4086) at org.apache.catalina.core.StandardContext.start(StandardContext.java:4255) . . .
Be careful where you create your stylesheets and images. The resulting HTML page cannot access anything under WEB-INF, so if you created subdirectories like images and styles or otherwise put files under WEB-INF you're trying to access from your JSP file, they will not be found. The symptoms are a) missing images and b) wrong font, styles, etc.
If you're missing the alt attribute on an image, you'll get this warning:
WARN - ALT attribute is missing for : j_id_jsp_1158619322_2
Add it thus:
1.
<
h:graphicImage
value
=
"#{msg['dvdcatalog.logo']}"
alt
=
"[dvdcatalog-logo.png]"
/>
After typing in username and password, you get this:
. . . INFO: Server startup in 1000 ms ERROR - An exception occurred javax.faces.FacesException: javax.crypto.BadPaddingException: Given final block not properly padded at org.apache.myfaces.shared_impl.util.StateUtils.symmetric(StateUtils.java:474) at org.apache.myfaces.shared_impl.util.StateUtils.symmetric(StateUtils.java:512) . . .
I added this to web.xml. It opens a security hole, but it appears to be a bug in MyFaces.
01.
<
context-param
>
02.
<
description
>
03.
This is my attempt to get rid of the
04.
javax.faces.FacesException: javax.crypto.BadPaddingException: Given final
05.
block not properly padded problem.
06.
</
description
>
07.
<
param-name
>org.apache.myfaces.USE_ENCRYPTION</
param-name
>
08.
<
param-value
>false
09.
</
context-param
>
After typing in username and password, you get this:
. . . INFO: Server startup in 1009 ms ERROR - An exception occurred javax.faces.FacesException: java.io.StreamCorruptedException: invalid stream header: 70DB8AB1 at org.apache.myfaces.shared_impl.util.StateUtils.getAsObject(StateUtils.java:374) at org.apache.myfaces.shared_impl.util.StateUtils.reconstruct(StateUtils.java:264) . . .
I found no solution to this. I went back to a working JSF project (not the same one) to see how it worked. It magically had this same problem. So, I bounced Eclipse passing the -clean option. That cleared both the old, good project and my new one. Incidentally, the old project didn't have the web.xml work-around for the "final block not properly padded" problem, so that was a red herring too.
Sometimes, when there are no console start-up errors and everything should work out alright, you get an error on the web page.
Usually, 404 errors are irrecoverable. Look to solve them just as for the ones discussed here that I've illustrated on the Eclipse console.
However, when they're a JSP/JSF error, they will tell you either a) about a mistake you've made in your HTML/JSP/JSF source or in your bean, or b) they are a red herring left over from an earlier error you did your best to clear.
Let's say you ran and the page complained about not finding the Standard Tag Library for JavaServer Pages (JSTL); you need both the JARs from Sun to satisfy the missing symbols. You check Build Path and discover that your project isn't including them and you rectify this and attempt to run again only to see that the same error is reported.
You might even remove and set up the Server again, or in frustration perform some other heroic action only to find the error is still there. Sometimes, usually in cases like this one, the solution is merely to click to refresh the page.
When Eclipse deploys to the local Tomcat server in the workspace, it creates a JAR in your workspace on the path:
.metadata/.plugins/org.eclipse.wst.server.core/tmp?/wtpwebapps/project-name/WEB-INF/lib
...along with all the other JARs specified for publication via Properties
-> Java EE Module Dependencies
which should match more or less what
you have in Build Path -> Configure Build Path -> Libraries
.
This would be the JAR being used by Tomcat at run-time and in which it would expect to find all needed classes. Normal publishing would copy it (them) once and not re-copy unless a "full" publish occurred. Corruption of this JAR is the only simple explanation of why a class-not-found problem "sticks" once it occurs.
Note that a ClassNotFoundException thrown from line 1516 of WebappClassLoader of Tomcat 6.0.26 is the "I give up" ClassNotFoundException. It's conceivable that the internal attempt to load the class, which should have succeeded, fails due to some problem other than the class not being found. The "search" to load the class could continue with the "real" problem going unreported. logging.properties can be configured to log that internal load failure.
My very first attempt at this "advanced" JSF skill. Imagine a section of JSF code such as:
1.
<
h:selectOneMenu
id
=
"shippingOption"
2.
required
=
"true"
3.
value
=
"#{cashier.shippingOption}"
>
4.
<
f:selectItem
itemValue
=
"2"
itemLabel
=
"#{bundle.QuickShip}"
/>
5.
<
f:selectItem
itemValue
=
"5"
itemLabel
=
"#{bundle.NormalShip}"
/>
6.
<
f:selectItem
itemValue
=
"7"
itemLabel
=
"#{bundle.SaverShip}"
/>
7.
</
h:selectOneMenu
>
Here is the property, in CashierBean.java corresponding to this tag:
1.
protected
String shippingOption =
"2"
;
2.
3.
public
void
setShippingOption( String shippingOption ) {
this
.shippingOption = shippingOption; }
4.
public
String getShippingOption() {
return
this
.shippingOption; }
That was one example I found by Googling, and the only one (believe it or not) that revealed its backing-bean code. The others just gave the JSF and assumed the bean code was simple, clear and unworthy to be featured. Of course, they assumed I already knew how this selectOneListbox construct worked while I needed just a bit more help piercing the veil.
I resorted there especially in an attempt to resolve an Eclipse JSP/JSF
validation error in view.jsp, "JSF attribute expects settable value but
expression is not settable". I couldn't get what goes into value just
right. You see the shippingOption in
value="#{cashier.shippingOption}"
is crucial.
It's not just the initial value to display in the drop-down menu when it hits the browser, but also must be able to gather that value if changed. This input/output relationship in JSF is wired delicately on the backing bean's getters and setters, in this case, getShippingOption() and setShippingOption().
In my own code, selecting different types of query filters went like this:
1.
<
h:selectOneListbox
size
=
"1"
<%-- (show only one option at a time) --%>
2.
title="#{msg['view.filter_prompt']}"
3.
value="#{queryBean.filterMethod}">
4.
<
f:selectItems
value
=
"#{queryBean.filterMethodsList}"
/>
5.
</
h:selectOneListbox
>
This is, I believe, correct. My backing bean is where I had the trouble and was either crashing or, more frequently, merely in some infinite, "I'll never display anything" loop. Here's how it started out the first time it didn't crash or loop out:
01.
private
String filterMethod =
null
;
02.
private
static
LinkedHashMap< Integer, String > filterMethods =
null
;
03.
04.
static
05.
{
06.
redoMessages();
// (default initialization of messages)
07.
}
08.
09.
public
static
void
redoMessages()
// (ChangeLocaleBean.xxAction() calls this when the locale has changed)
10.
{
11.
filterMethods =
new
LinkedHashMap< Integer, String >();
12.
filterMethods.put(
1
, getGenericMessage(
"view.filter.all"
) );
13.
filterMethods.put(
2
, getGenericMessage(
"view.filter.year"
) );
14.
filterMethods.put(
3
, getGenericMessage(
"view.filter.rating"
) );
15.
filterMethods.put(
4
, getGenericMessage(
"view.filter.genre"
) );
16.
filterMethods.put(
5
, getGenericMessage(
"view.filter.language"
) );
17.
}
18.
19.
// this is for handling the <h:selectOneListbox value=...> construct
20.
public
void
setFilterMethod( String newMethod ) {
this
.filterMethod = newMethod; }
21.
public
String getFilterMethod() {
return
this
.filterMethod; }
22.
23.
// this is for building the <f:selectItems ...> construct
24.
public
Map< Integer, String > getFilterMethodsList() {
return
filterMethods; }
This gave me:
Of course, I want very different things in the filter drop-down list, so I
reverse the order thinking I'll get my strings instead of the numbers. On my
second try, which was to change the type from
< Integer, String >
to
< String, Integer >
—how I wanted it in the first
place, I got a crash instead:
I think the important part of the error is "value is no String" which is why I
had changed it to the "unlikely" order in the first place. Another key part of
the error is "HtmlSelectOneListbox" which tells me to look at my
<h:selectOneListbox ...>
construct instead of the
<f:selectItems ...>
construct to find the problem.
But, what's the solution?
On my third try, I changed
< String, Integer >
to
< String, String >
and then adjusted other code:
01.
.
02.
.
03.
.
04.
filterMethods =
new
LinkedHashMap< String, String >();
05.
filterMethods.put( getGenericMessage(
"view.filter.all"
),
"1"
);
06.
filterMethods.put( getGenericMessage(
"view.filter.year"
),
"2"
);
07.
filterMethods.put( getGenericMessage(
"view.filter.rating"
),
"3"
);
08.
filterMethods.put( getGenericMessage(
"view.filter.genre"
),
"4"
);
09.
filterMethods.put( getGenericMessage(
"view.filter.language"
),
"5"
);
10.
.
11.
.
12.
.
...and I got the following, which is closer, missing only the localization:
I went to add the ratings drop-down. I copied the filter method work and then ran only to get:
The reason I'm reproducing this here is out of honesty and to expose another sort of error, as stupid as it might be. In this instance, I failed to add the first line of code here and there was no list ready for items to be put to.
1.
ratings =
new
LinkedHashMap< String, String >();
2.
ratings.put( getGenericMessage(
"view.rating.NR"
),
"1"
);
3.
ratings.put( getGenericMessage(
"view.rating.G"
),
"2"
);
4.
ratings.put( getGenericMessage(
"view.rating.PG"
),
"3"
);
5.
ratings.put( getGenericMessage(
"view.rating.PG_13"
),
"4"
);
6.
ratings.put( getGenericMessage(
"view.rating.NC_17"
),
"5"
);
7.
ratings.put( getGenericMessage(
"view.rating.R"
),
"6"
);
The line number in the error doesn't correspond to where the new JSF code was put for the rating drop-down, but here. Line 27 isn't too enlightening, but think about it: changing the locale is what reaches the Java code above. (Or, I could have refreshed; it's the same.)
Making large-scale changes such as these may not even appear in the browser when you run the JSP. When this happens, do the following, including the last one when it just really persists:
Ultimately, coolness will begin to prevail:
Onward and upward!
Now switching our year list from selectOneListbox to selectManyListbox, we crash with this error, doubtless linked to what we need to do beyond how the other kind of list is done.
This is because we're not passing the right sort of animal to value; it's going to be different from what we pass to selectOneListbox. Please study the code below for the differences between selectOneListbox and selectManyListbox.
Then see the result which has been heavily reformatted to make room for everything. Also, the years field became something that will have to be parsed (and therefore some kind of high-level SQL statement), and the "many" list boxes cannot (apparently) be scrollable. Or can they? I need to investigate.
001.
public
class
QueryBean
002.
{
003.
private
static
Logger log = Logger.getLogger( MessageBean.
class
);
004.
005.
private
static
Locale locale =
null
;
006.
private
static
String bundleName =
null
;
007.
008.
private
String filterMethod =
null
;
009.
private
static
LinkedHashMap< String, String > filterMethods =
null
;
010.
private
String[] ratings =
null
;
011.
private
static
LinkedHashMap< String, String > ratingsItems =
null
;
012.
private
String[] genres =
null
;
013.
private
static
LinkedHashMap< String, String > genreItems =
null
;
014.
private
String[] languages =
null
;
015.
private
static
LinkedHashMap< String, String > languageItems =
null
;
016.
private
String sortedBy =
null
;
017.
private
static
LinkedHashMap< String, String > sortings =
null
;
018.
private
String years =
null
;
019.
020.
static
021.
{
022.
redoMessages();
023.
}
024.
025.
private
static
final
void
getFacesContext()
026.
{
027.
FacesContext context = FacesContext.getCurrentInstance();
028.
029.
if
( context !=
null
)
030.
{
031.
locale = context.getViewRoot().getLocale();
032.
bundleName = context.getApplication().getMessageBundle();
033.
}
034.
}
035.
036.
/**
037.
* Call this when the locale has changed.
038.
*/
039.
public
static
void
redoMessages()
040.
{
041.
getFacesContext();
042.
043.
if
( locale ==
null
|| bundleName ==
null
)
044.
return
;
045.
046.
log.info(
"Redoing messages..."
);
047.
048.
filterMethods =
new
LinkedHashMap< String, String >();
049.
filterMethods.put( getGenericMessage(
"view.filter.all"
),
"1"
);
050.
filterMethods.put( getGenericMessage(
"view.filter.year"
),
"2"
);
051.
filterMethods.put( getGenericMessage(
"view.filter.rating"
),
"3"
);
052.
filterMethods.put( getGenericMessage(
"view.filter.genre"
),
"4"
);
053.
filterMethods.put( getGenericMessage(
"view.filter.language"
),
"5"
);
054.
055.
ratingsItems =
new
LinkedHashMap< String, String >();
056.
ratingsItems.put( getGenericMessage(
"view.rating.NR"
),
"1"
);
057.
ratingsItems.put( getGenericMessage(
"view.rating.G"
),
"2"
);
058.
ratingsItems.put( getGenericMessage(
"view.rating.PG"
),
"3"
);
059.
ratingsItems.put( getGenericMessage(
"view.rating.PG_13"
),
"4"
);
060.
ratingsItems.put( getGenericMessage(
"view.rating.NC_17"
),
"5"
);
061.
ratingsItems.put( getGenericMessage(
"view.rating.R"
),
"6"
);
062.
063.
genreItems =
new
LinkedHashMap< String, String >();
064.
genreItems.put( getGenericMessage(
"view.genre.action"
),
"1"
);
065.
genreItems.put( getGenericMessage(
"view.genre.adventure"
),
"2"
);
066.
genreItems.put( getGenericMessage(
"view.genre.allegory"
),
"3"
);
067.
genreItems.put( getGenericMessage(
"view.genre.cartoon"
),
"4"
);
068.
genreItems.put( getGenericMessage(
"view.genre.chick"
),
"5"
);
069.
genreItems.put( getGenericMessage(
"view.genre.comedy"
),
"6"
);
070.
genreItems.put( getGenericMessage(
"view.genre.drama"
),
"7"
);
071.
genreItems.put( getGenericMessage(
"view.genre.fantasy"
),
"8"
);
072.
genreItems.put( getGenericMessage(
"view.genre.history"
),
"9"
);
073.
genreItems.put( getGenericMessage(
"view.genre.military"
),
"10"
);
074.
genreItems.put( getGenericMessage(
"view.genre.music"
),
"11"
);
075.
genreItems.put( getGenericMessage(
"view.genre.musical"
),
"12"
);
076.
genreItems.put( getGenericMessage(
"view.genre.mystery"
),
"13"
);
077.
genreItems.put( getGenericMessage(
"view.genre.politics"
),
"14"
);
078.
genreItems.put( getGenericMessage(
"view.genre.religious"
),
"15"
);
079.
genreItems.put( getGenericMessage(
"view.genre.science"
),
"16"
);
080.
genreItems.put( getGenericMessage(
"view.genre.suspense"
),
"17"
);
081.
genreItems.put( getGenericMessage(
"view.genre.thriller"
),
"18"
);
082.
083.
languageItems =
new
LinkedHashMap< String, String >();
084.
languageItems.put( getGenericMessage(
"view.language.chinese"
),
"zh"
);
085.
languageItems.put( getGenericMessage(
"view.language.finnish"
),
"su"
);
086.
languageItems.put( getGenericMessage(
"view.language.french"
),
"fr"
);
087.
languageItems.put( getGenericMessage(
"view.language.german"
),
"de"
);
088.
languageItems.put( getGenericMessage(
"view.language.greek"
),
"el"
);
089.
languageItems.put( getGenericMessage(
"view.language.italian"
),
"it"
);
090.
languageItems.put( getGenericMessage(
"view.language.japanese"
),
"ja"
);
091.
languageItems.put( getGenericMessage(
"view.language.korean"
),
"ko"
);
092.
languageItems.put( getGenericMessage(
"view.language.portuguese"
),
"pt"
);
093.
languageItems.put( getGenericMessage(
"view.language.russian"
),
"10"
);
094.
languageItems.put( getGenericMessage(
"view.language.spanish"
),
"es"
);
095.
languageItems.put( getGenericMessage(
"view.language.thai"
),
"th"
);
096.
097.
sortings =
new
LinkedHashMap< String, String >();
098.
sortings.put( getGenericMessage(
"view.sort.title"
),
"1"
);
099.
sortings.put( getGenericMessage(
"view.sort.year"
),
"2"
);
100.
sortings.put( getGenericMessage(
"view.sort.rating"
),
"3"
);
101.
}
102.
103.
// this is for handling the <h:selectOneListbox value=...> construct for filter methods...
104.
public
void
setFilterMethod( String newValue ) {
this
.filterMethod = newValue; }
105.
public
String getFilterMethod() {
return
this
.filterMethod; }
106.
107.
// this is for building the <f:selectItems ...> construct
108.
public
Map< String, String > getFilterMethodsList() {
return
filterMethods; }
109.
110.
// this is for handling the <h:selectManyListbox value=...> construct for ratings...
111.
public
void
setRatings( String[] newValue ) {
this
.ratings = newValue; }
112.
public
String[] getRatings() {
return
this
.ratings; }
113.
114.
// this is for building the <f:selectItems ...> construct
115.
public
Map< String, String > getRatingsItems() {
return
ratingsItems; }
116.
117.
// and for genre (etc.)...
118.
public
void
setGenres( String[] newValue ) {
this
.genres = newValue; }
119.
public
String[] getGenres() {
return
this
.genres; }
120.
public
Map< String, String > getGenreItems() {
return
genreItems; }
121.
122.
// and for languages...
123.
public
void
setLanguages( String[] newValue ) {
this
.languages = newValue; }
124.
public
String[] getLanguages() {
return
this
.languages; }
125.
public
Map< String, String > getLanguagesItems() {
return
languageItems; }
126.
127.
// and for sortings...
128.
public
void
setSortedBy( String newValue ) {
this
.sortedBy = newValue; }
129.
public
String getSortedBy() {
return
this
.sortedBy; }
130.
public
Map< String, String > getSortingsList() {
return
sortings; }
131.
132.
public
void
setYears( String newValue ) {
this
.years = newValue; }
133.
public
String getYears() {
return
this
.years; }
134.
135.
private
static
final
String getGenericMessage( String messageKey )
136.
{
137.
String text = MessageUtils.getMessageResourceString( bundleName, messageKey,
null
, locale );
138.
139.
if
( text ==
null
)
140.
{
141.
text =
"No message bundle or no property \"dvdcatalog.title\""
;
142.
log.info( text );
143.
}
144.
145.
return
text;
146.
}
147.
}
...and the JSF code illustrates outputText, inputText, selectOneListbox, selectManyListbox and selectItems:
01.
.gutter { width: 20px; }
02.
03.
.query-prompt
04.
{
05.
color: maroon;
06.
font-size: small;
07.
font-weight: bold;
08.
text-align: right;
09.
}
001.
.
002.
.
003.
.
004.
<
center
>
005.
<
h2
> <
h:outputText
value
=
"#{msg['dvdcatalog.title']}"
/> </
h2
>
006.
007.
<
div
class
=
"textbox"
>
008.
<
p
>
009.
<
h:outputText
value
=
"#{msg['view.last']}"
/>
010.
<
h:outputText
value
=
" #{statisticsBean.lastUpdate}"
/>
011.
<
br
/>
012.
<
h:outputText
value
=
"#{msg['view.total']}"
/>
013.
<
h:outputText
value
=
" #{statisticsBean.totalTitles}"
/>
014.
<
br
/>
015.
<
h:outputText
value
=
"#{msg['view.foreign']}"
/>
016.
<
h:outputText
value
=
" #{statisticsBean.totalForeignTitles}"
/>
017.
</
p
>
018.
019.
<
p
class
=
"notes"
>
020.
<
h:outputText
value
=
"#{msg['view.legend']}"
/>
021.
<
h:outputText
value
=
"#{msg['view.notes_1']}"
/>
022.
<
h:outputText
value
=
"#{msg['view.notes_2']}"
/>
023.
</
p
>
024.
025.
<
table
>
026.
<
tr
><
td
>
027.
<
table
>
028.
<
tr
><
td
class
=
"query-prompt"
> <%-- Filter this list by: --%>
029.
<
h:outputText
value
=
"#{msg['view.filter_prompt']}"
/>
030.
</
td
>
031.
<
td
>
032.
<
h:selectOneListbox
size
=
"1"
033.
title
=
"#{msg['view.filter_prompt']}"
034.
value
=
"#{queryBean.filterMethod}"
>
035.
<
f:selectItems
value
=
"#{queryBean.filterMethodsList}"
/>
036.
</
h:selectOneListbox
>
037.
</
td
>
038.
<
td
class
=
"gutter"
>
039.
</
td
>
040.
<
td
class
=
"query-prompt"
> <%-- Sort this list by: --%>
041.
<
h:outputText
value
=
"#{msg['view.sort_prompt']}"
/>
042.
</
td
>
043.
<
td
>
044.
<
h:selectOneListbox
size
=
"1"
045.
title
=
"#{msg['view.sort_prompt']}"
046.
value
=
"#{queryBean.sortedBy}"
>
047.
<
f:selectItems
value
=
"#{queryBean.sortingsList}"
/>
048.
</
h:selectOneListbox
>
049.
</
td
>
050.
</
tr
>
051.
</
table
>
052.
</
td
>
053.
</
tr
>
054.
<
tr
><
td
> </
td
></
tr
>
055.
<
tr
><
td
valign
=
"top"
>
056.
<
table
>
057.
<
tr
><
td
valign
=
"top"
>
058.
<
table
>
059.
<
tr
><
td
class
=
"query-prompt"
valign
=
"top"
> <%-- Rating: --%>
060.
<
h:outputText
value
=
"#{msg['view.rating_prompt']}"
/>
061.
062.
</
td
>
063.
<
td
valign
=
"top"
>
064.
<
h:selectManyListbox
title
=
"#{msg['view.rating_prompt']}"
065.
value
=
"#{queryBean.ratings}"
>
066.
<
f:selectItems
value
=
"#{queryBean.ratingsItems}"
/>
067.
</
h:selectManyListbox
>
068.
</
td
>
069.
</
tr
>
070.
<
tr
><
td
> </
td
>
071.
</
tr
>
072.
<
tr
>
073.
<
td
class
=
"query-prompt"
valign
=
"top"
> <%-- Years: --%>
074.
<
h:outputText
value
=
"#{msg['view.year_prompt']}"
/>
075.
</
td
>
076.
<
td
>
077.
<
h:inputText
value
=
"#{queryBean.years}"
/>
078.
</
td
>
079.
</
tr
>
080.
</
table
>
081.
</
td
>
082.
<
td
class
=
"gutter"
>
083.
</
td
>
084.
<
td
class
=
"query-prompt"
valign
=
"top"
> <%-- Genre: --%>
085.
<
h:outputText
value
=
"#{msg['view.genre_prompt']}"
/>
086.
</
td
>
087.
<
td
valign
=
"top"
>
088.
<
h:selectManyListbox
title
=
"#{msg['view.genre_prompt']}"
089.
value
=
"#{queryBean.genres}"
>
090.
<
f:selectItems
value
=
"#{queryBean.genreItems}"
/>
091.
</
h:selectManyListbox
>
092.
</
td
>
093.
<
td
class
=
"gutter"
>
094.
</
td
>
095.
<
td
class
=
"query-prompt"
valign
=
"top"
> <%-- Language: --%>
096.
<
h:outputText
value
=
"#{msg['view.language_prompt']}"
/>
097.
</
td
>
098.
<
td
valign
=
"top"
>
099.
<
h:selectManyListbox
title
=
"#{msg['view.language_prompt']}"
100.
value
=
"#{queryBean.languages}"
>
101.
<
f:selectItems
value
=
"#{queryBean.languagesItems}"
/>
102.
</
h:selectManyListbox
>
103.
</
td
>
104.
</
tr
>
105.
</
table
>
106.
</
td
>
107.
</
tr
>
108.
<
tr
><
td
colspan
=
"3"
/>
109.
</
tr
>
110.
<
tr
>
111.
<
td
colspan
=
"3"
align
=
"center"
>
112.
<
table
>
113.
<
tr
><
td
>[button]</
td
>
114.
<
td
class
=
"gutter"
></
td
>
115.
<
td
>[button]</
td
>
116.
</
tr
>
117.
</
table
>
118.
</
td
>
119.
</
tr
>
120.
</
table
>
121.
</
div
>
122.
</
center
>
123.
.
124.
.
125.
.
It's easy to get lost in all the attributes passed to the likes of
Here's how I think of it: Consider what the construct does. If it's just to force some string to the page when renderedi or it's the title of a control (button or link), then the value attribute is likely going to be an expression that evaluates to a string, number or graphic. It probably comes out of messages.properties or similar file.
<h:outputLabel value="#{msg.username}" />
This includes command links and buttons because of the text item associated with them.
On the other hand, if the tag is looking for dynamically created values (at run-time), it likely indicates a bean property (method). For example, here, the first value is a list of supported (or considered) languages that could (or may not necessarily) fluctuate at run-time.
The second value is an array of the same languages, except as items in a selection control and it is composed for being set (as state) in the bean. It is the "result" of the user's answering the form. So, when done, it's a list of zero, one or more languages. From the bean, the code...
01.
private
String[] languages = {
"French"
,
"Latin"
,
"Greek"
,
"German"
};
02.
private
static
LinkedHashMap< String, String > languageItems =
null
;
03.
04.
.
05.
.
06.
.
07.
languageItems =
new
LinkedHashMap< String, String >();
08.
languageItems.put( getGenericMessage(
"view.language.french"
),
"fr"
);
09.
languageItems.put( getGenericMessage(
"view.language.french"
),
"la"
);
10.
languageItems.put( getGenericMessage(
"view.language.french"
),
"el"
);
11.
languageItems.put( getGenericMessage(
"view.language.french"
),
"de"
);
12.
.
13.
.
14.
.
15.
16.
public
String[] getLanguages() {
return
this
.languages; }
17.
public
Map< String, String > getLanguageItems() {
return
languageItems; }
...underlies the JSF constructs. languages may be initialized statically at compile-time or at run-time or even dynamically at run-time.
<h:selectManyListbox title="#{msg['view.language_prompt']}" value="#{queryBean.languages}"> <f:selectItems value="#{queryBean.languageItems}" /> </h:selectManyListbox>
I need to consume SelectItem instead of String[]. See discussion at "selectManyListbox value attribute". This may also affect selectOneListbox.
In a selectOneListbox, value is a little easier. First, it's the same thing as as for the selectManyListbox, but the second one is a single thing and not an array since one and only one must be chosen.
<h:selectOneListbox size="1" title="#{msg['view.filter_prompt']}" value="#{queryBean.filter}"> <f:selectItems value="#{queryBean.filterList}" /> </h:selectOneListbox>
The action attribute is always going to be a bean method that returns a String.
<h:commandButton value="#{msg['view.reset_button']}" action="#{queryBean.clear}" type="reset" />
The "action" method is going to return a String. This is all important since it's this string that determines JSF navigation as described in the from-outcome element of the navigation-case of a navigation-rule in faces-config.xml.
public String clear() { log.info( "Resetting query parameters..." ); this.filter = null; this.languages = null; return "reset"; }
It's important to note that the type attribute of an <h:commandButton> can never be anything but "submit" or "reset". See the Tag commandButton documentation.
Set up navigation to look like this:
You can do this visually using the faces-config.xml Navigation Rule editor or simply add these lines to faces-config.xml by hand:
01.
<
navigation-rule
>
02.
<
display-name
>login</
display-name
>
03.
<
from-view-id
>/login.jsp</
from-view-id
>
04.
<
navigation-case
>
05.
<
from-outcome
>succeeded</
from-outcome
>
06.
<
to-view-id
>/validlogin.jsp</
to-view-id
>
07.
</
navigation-case
>
08.
</
navigation-rule
>
09.
10.
<
navigation-rule
>
11.
<
display-name
>login</
display-name
>
12.
<
from-view-id
>/login.jsp</
from-view-id
>
13.
<
navigation-case
>
14.
<
from-outcome
>failed</
from-outcome
>
15.
<
to-view-id
>/invalidlogin.jsp</
to-view-id
>
16.
</
navigation-case
>
17.
</
navigation-rule
>
18.
19.
<
navigation-rule
>
20.
<
display-name
>login</
display-name
>
21.
<
from-view-id
>/login.jsp</
from-view-id
>
22.
<
navigation-case
>
23.
<
from-outcome
>power</
from-outcome
>
24.
<
to-view-id
>/powerlogin.jsp</
to-view-id
>
25.
</
navigation-case
>
26.
</
navigation-rule
>
27.
28.
<
navigation-rule
>
29.
<
display-name
>validlogin</
display-name
>
30.
<
from-view-id
>/validlogin.jsp</
from-view-id
>
31.
<
navigation-case
>
32.
<
from-outcome
>query</
from-outcome
>
33.
<
to-view-id
>/query.jsp</
to-view-id
>
34.
</
navigation-case
>
35.
</
navigation-rule
>
36.
37.
<
navigation-rule
>
38.
<
display-name
>powerlogin</
display-name
>
39.
<
from-view-id
>/powerlogin.jsp</
from-view-id
>
40.
<
navigation-case
>
41.
<
from-outcome
>query</
from-outcome
>
42.
<
to-view-id
>/query.jsp</
to-view-id
>
43.
</
navigation-case
>
44.
</
navigation-rule
>
45.
46.
<
navigation-rule
>
47.
<
display-name
>query</
display-name
>
48.
<
from-view-id
>/query.jsp</
from-view-id
>
49.
<
navigation-case
>
50.
<
from-outcome
>view</
from-outcome
>
51.
<
to-view-id
>/view.jsp</
to-view-id
>
52.
</
navigation-case
>
53.
</
navigation-rule
>
54.
55.
<
navigation-rule
>
56.
<
display-name
>view</
display-name
>
57.
<
from-view-id
>/view.jsp</
from-view-id
>
58.
<
navigation-case
>
59.
<
from-outcome
>query</
from-outcome
>
60.
<
to-view-id
>/query.jsp</
to-view-id
>
61.
</
navigation-case
>
62.
</
navigation-rule
>
Note: The "power-login" stuff is for a putative power user that will have the ability to add, delete or modify entries in the database. This work won't appear in this tutorial, but is already stubbed in and I don't want to remove it.
Here's the log-in page. You can control the user interface lnaguage at this point (and again from the query page).
And here's the welcome page. It for me, a power user and signals this by reprising a line from Hunt for Red October.
The ordinary welcome page appears thus—without the power statement about being authorized to launch missiles.
If we had failed, if we had entered user "snagglepuss" with password "the lion", we'd have seen an error message on the login page rather than invalidlogin.jsp getting used. We may take out this additional, unused JSP.
Finally, clicking with a valid log-in on the Configure query button gets us to our more or less finished and working query page. We just have to write and debug our view page and wire up the Hibernate guts.
It appears that the following warning cannot be fixed. There are three
occurrences in query.jsp; this one is flagged on the construct
#{queryBean.ratings}
.
<h:selectManyListbox title="#{msg['view.rating_prompt']}" value="#{queryBean.ratings}"> Cannot coerce type java.lang.String[] to java.lang.String, java.lang.Short, java.lang.Character, java.lang.Boolean, java.lang.Double, java.lang.Byte, java.lang.Long, java.lang.Float, java.lang.Integer
See Tag Library Documentation for entry point to both h and f tags.
A good reference here. The Javadoc is at h Tag dataTable.
...rowClass="oddRow,evenRow">
...where styles might be defined thus:
.evenRow { background-image: url('images/bg1.jpg'); } .oddRow { background-image: url('images/bg2.jpg'); }
Alternates every other row according to "odd-row" / "even-row" attributes. In other words, ...
These row styles are applied to each row in the table in turn. If the list has two elements ("odd, even"), the first style class in the list is applied to the first row, the second to the second row, the first to the third row, the second to the fourth row, etc. If the list has three elements, then the alternance is based on three. Etc.
var...
Use it as a "cookie" according to the following pseudocode. Imagine a Java class, TupleBean, that returns tuples by name and value. The utility of prefacing the cookie name with an underscore, as suggested by one tutorial I read, is to avoid it confusing the reader with other entities (classes, methods, bean names, etc.). TupleBean furnishes the getters getName() and getValue().
01.
<
h:dataTable
var
=
"_tuple"
02.
value
=
"#{tupleBean.all}"
>
03.
<
h:column
>
04.
<
f:facet
name
=
"header"
>
05.
<
h:outputText
value
=
"Name"
/>
06.
</
f:facet
>
07.
<
h:outputText
value
=
"#{_tuple.name}"
/>
08.
</
h:column
>
09.
<
h:column
>
10.
<
f:facet
name
=
"header"
>
11.
<
h:outputText
value
=
"Value"
/>
12.
</
f:facet
>
13.
<
h:outputText
value
=
"#{_tuple.value}"
/>
14.
</
h:column
>
15.
</
h:dataTable
>
Note: if the table as displayed is empty, it's only because the getAll() method (from TupleBean) failed to produce any rows.
Consider the following <h:dataTable> construct from a JSF file. This should help you visualize how what's coded in JSF and CSS is used to generate the page dynamically.
01.
<
h:dataTable
value
=
"#{dvdTitle.all}"
02.
var
=
"_title"
03.
styleClass
=
"dvdStyles"
04.
headerClass
=
"dvdHeader"
05.
columnClasses
=
"dvdId,dvdBluray,dvdTitle,dvdYear,dvdMinutes,dvdRating,dvdGenre,dvdLanguage,dvdUrl,dvdStars"
06.
rowClasses
=
"oddRow,evenRow"
>
07.
<
h:column
>
08.
<
f:facet
name
=
"header"
>
09.
<
h:outputText
value
=
"#{msg['view.id']}"
/>
10.
</
f:facet
>
11.
<
h:outputText
value
=
"#{_title.id}"
/>
12.
</
h:column
>
13.
<
h:column
>
14.
<
f:facet
name
=
"header"
>
15.
<
h:outputText
value
=
"#{msg['view.bluray']}"
/>
16.
</
f:facet
>
17.
<
h:outputText
value
=
"#{_title.getBluray}"
/>
18.
</
h:column
>
19.
<
h:column
>
20.
<
f:facet
name
=
"header"
>
21.
<
h:outputText
value
=
"#{msg['view.title']}"
/>
22.
</
f:facet
>
23.
<
h:outputText
value
=
"#{_title.title}"
/>
24.
</
h:column
>
25.
<
h:column
>
26.
<
f:facet
name
=
"header"
>
27.
<
h:outputText
value
=
"#{msg['view.year']}"
/>
28.
</
f:facet
>
29.
<
h:outputText
value
=
"#{_title.year}"
/>
30.
</
h:column
>
31.
<
h:column
>
32.
<
f:facet
name
=
"header"
>
33.
<
h:outputText
value
=
"#{msg['view.minutes']}"
/>
34.
</
f:facet
>
35.
<
h:outputText
value
=
"#{_title.minutes}"
/>
36.
</
h:column
>
37.
<
h:column
>
38.
<
f:facet
name
=
"header"
>
39.
<
h:outputText
value
=
"#{msg['view.rating']}"
/>
40.
</
f:facet
>
41.
<
h:outputText
value
=
"#{_title.rating}"
/>
42.
</
h:column
>
43.
<
h:column
>
44.
<
f:facet
name
=
"header"
>
45.
<
h:outputText
value
=
"#{msg['view.genre']}"
/>
46.
</
f:facet
>
47.
<
h:outputText
value
=
"#{_title.genre}"
/>
48.
</
h:column
>
49.
<
h:column
>
50.
<
f:facet
name
=
"header"
>
51.
<
h:outputText
value
=
"#{msg['view.language']}"
/>
52.
</
f:facet
>
53.
<
h:outputText
value
=
"#{_title.language}"
/>
54.
</
h:column
>
55.
<
h:column
>
56.
<
f:facet
name
=
"header"
>
57.
<
h:outputText
value
=
"#{msg['view.url']}"
/>
58.
</
f:facet
>
59.
<
h:outputText
value
=
"#{_title.url}"
/>
60.
</
h:column
>
61.
<
h:column
>
62.
<
f:facet
name
=
"header"
>
63.
<
h:outputText
value
=
"#{msg['view.stars']}"
/>
64.
</
f:facet
>
65.
<
h:outputText
value
=
"#{_title.stars}"
/>
66.
</
h:column
>
67.
</
h:dataTable
>
Notice that each element (data cell, header, etc. has its own style setting). The <f:facet> tag is either "header" or "footer" ("caption", etc.) and thus the dvdHeader style class is associated with our usage (we have no footer material here). This is what's generated for display in the browser.
01.
<
table
class
=
"dvdStyles"
>
02.
<
thead
>
03.
<
tr
>
04.
<
th
class
=
"dvdHeader"
scope
=
"col"
> id </
th
>
05.
<
th
class
=
"dvdHeader"
scope
=
"col"
> Blu-ray? </
th
>
06.
<
th
class
=
"dvdHeader"
scope
=
"col"
> Title </
th
>
07.
<
th
class
=
"dvdHeader"
scope
=
"col"
> Year </
th
>
08.
<
th
class
=
"dvdHeader"
scope
=
"col"
> Length </
th
>
09.
<
th
class
=
"dvdHeader"
scope
=
"col"
> Rating </
th
>
10.
<
th
class
=
"dvdHeader"
scope
=
"col"
> Genres </
th
>
11.
<
th
class
=
"dvdHeader"
scope
=
"col"
> Languages </
th
>
12.
<
th
class
=
"dvdHeader"
scope
=
"col"
> Link </
th
>
13.
<
th
class
=
"dvdHeader"
scope
=
"col"
> Stars </
th
>
14.
</
tr
>
15.
</
thead
>
16.
<
tbody
id
=
"j_id_jsp_458090977_1:j_id_jsp_458090977_7:tbody_element"
>
17.
<
tr
class
=
"oddRow"
>
18.
<
td
class
=
"dvdId"
> 1 </
td
>
19.
<
td
class
=
"dvdBluray"
> x </
td
>
20.
<
td
class
=
"dvdTitle"
> Lord of the Rings: Fellowship of the Ring</
td
>
21.
<
td
class
=
"dvdYear"
> 2001 </
td
>
22.
<
td
class
=
"dvdMinutes"
> 180 </
td
>
23.
<
td
class
=
"dvdRating"
> PG-13 </
td
>
24.
<
td
class
=
"dvdGenre"
> Adventure </
td
>
25.
<
td
class
=
"dvdLanguage"
>Spanish </
td
>
26.
<
td
class
=
"dvdUrl"
> www.lordoftherings.net </
td
>
27.
<
td
class
=
"dvdStars"
> Elijah Wood, Sean Austin, Dominique Monahan, Billy Boyd, Viggo Mortensen, Sean Bean, Orlando Gibbons, John Rhys-Davies, Ian McKellen</
td
>
28.
</
tr
>
29.
<
tr
class
=
"evenRow"
>
30.
<
td
class
=
"dvdId"
> 2 </
td
>
31.
<
td
class
=
"dvdBluray"
> x </
td
>
32.
<
td
class
=
"dvdTitle"
> Lord of the Rings: The Two Towers</
td
>
33.
<
td
class
=
"dvdYear"
> 2002 </
td
>
34.
<
td
class
=
"dvdMinutes"
> 180 </
td
>
35.
<
td
class
=
"dvdRating"
> PG-13 </
td
>
36.
<
td
class
=
"dvdGenre"
> Adventure </
td
>
37.
<
td
class
=
"dvdLanguage"
>Spanish </
td
>
38.
<
td
class
=
"dvdUrl"
> www.lordoftherings.net </
td
>
39.
<
td
class
=
"dvdStars"
> Elijah Wood, Sean Austin, Dominique Monahan, Billy Boyd, Viggo Mortensen, Sean Bean, Orlando Gibbons, John Rhys-Davies, Ian McKellen</
td
>
40.
</
tr
>
41.
<
tr
>
42.
.
43.
.
44.
.
45.
</
tr
>
46.
</
tbody
>
47.
</
table
>
And here is the relevant portion of the cascading-styles sheet:
01.
/* DVD titles list... */
02.
.dvdStyles
/* class for the <table> tag rendering an <h:dataTable> */
03.
{
04.
border
:
thin
solid
black
;
05.
}
06.
.dvdHeader
/* class for <th> tags in the <thead> rendering an <h:dataTable> */
07.
{
08.
text-align
:
left
;
09.
vertical-align
:
text-top
;
10.
font-style
:
italic
;
11.
color
:
maroon
;
12.
}
13.
.dvdId
14.
{
15.
width
:
10px
;
16.
text-align
:
left
;
17.
}
18.
.dvdTitle
19.
{
20.
width
:
300px
;
21.
text-align
:
left
;
22.
}
23.
24.
.dvdBluray {
width
:
80px
; }
25.
.dvdYear {
width
:
20px
; }
26.
.dvdMinutes {
width
:
20px
; }
27.
.dvdRating {
width
:
20px
; }
28.
.dvdGenre {
width
:
80px
; }
29.
.dvdLanguage{
width
:
80px
; }
30.
.dvdUrl {
width
:
80px
; }
31.
.dvdStars {
width
:
80px
; }
32.
33.
.evenRow
34.
{
35.
background-image
:
url
(
'bg1.jpg'
);
36.
}
37.
.oddRow
38.
{
39.
background-image
:
url
(
'bg2.jpg'
);
40.
}
Here's the view page at one point with four titles in the database, JSF and CSS work done closer to what I hope to end up with. Notice that the "Movie site" links are actual links and if you click on them, they take you to the movie site (if the link isn't stale or bogus).
This is the application's web.xml that works both for the Tomcat Eclipse uses and the "real" one installed. Despite the *.jsf entry below, we have only .jsp files; this works anyway and, in fact, does not if we change the entry to specify .jsp.
01.
<?
xml
version
=
"1.0"
encoding
=
"UTF-8"
?>
02.
<
web-app
xmlns:xsi
=
"http://www.w3.org/2001/XMLSchema-instance"
03.
xmlns
=
"http://java.sun.com/xml/ns/javaee"
04.
xmlns:web
=
"http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
05.
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
06.
http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
07.
id
=
"WebApp_ID"
08.
version
=
"2.5"
>
09.
<
display-name
>dvdcatalog</
display-name
>
10.
<
welcome-file-list
>
11.
<
welcome-file
>index.html</
welcome-file
>
12.
<
welcome-file
>index.htm</
welcome-file
>
13.
<
welcome-file
>index.jsp</
welcome-file
>
14.
<
welcome-file
>default.html</
welcome-file
>
15.
<
welcome-file
>default.htm</
welcome-file
>
16.
<
welcome-file
>default.jsp</
welcome-file
>
17.
</
welcome-file-list
>
18.
<
servlet
>
19.
<
servlet-name
>Faces Servlet</
servlet-name
>
20.
<
servlet-class
>javax.faces.webapp.FacesServlet</
servlet-class
>
21.
<
load-on-startup
>1</
load-on-startup
>
22.
</
servlet
>
23.
<
servlet-mapping
>
24.
<
servlet-name
>Faces Servlet</
servlet-name
>
25.
<
url-pattern
>/faces/*</
url-pattern
>
26.
</
servlet-mapping
>
27.
<
context-param
>
28.
<
param-name
>javax.servlet.jsp.jstl.fmt.localizationContext</
param-name
>
29.
<
param-value
>resources.application</
param-value
>
30.
</
context-param
>
31.
<
context-param
>
32.
<
param-name
>javax.faces.STATE_SAVING_METHOD</
param-name
>
33.
<
param-value
>client</
param-value
>
34.
</
context-param
>
35.
<
context-param
>
36.
<
param-name
>org.apache.myfaces.ALLOW_JAVASCRIPT</
param-name
>
37.
<
param-value
>true</
param-value
>
38.
</
context-param
>
39.
<
context-param
>
40.
<
param-name
>org.apache.myfaces.PRETTY_HTML</
param-name
>
41.
<
param-value
>true</
param-value
>
42.
</
context-param
>
43.
<
context-param
>
44.
<
param-name
>org.apache.myfaces.DETECT_JAVASCRIPT</
param-name
>
45.
<
param-value
>false</
param-value
>
46.
</
context-param
>
47.
<
context-param
>
48.
<
param-name
>org.apache.myfaces.AUTO_SCROLL</
param-name
>
49.
<
param-value
>true</
param-value
>
50.
</
context-param
>
51.
<
servlet
>
52.
<
servlet-name
>faces</
servlet-name
>
53.
<
servlet-class
>org.apache.myfaces.webapp.MyFacesServlet</
servlet-class
>
54.
<
load-on-startup
>1</
load-on-startup
>
55.
</
servlet
>
56.
<
servlet-mapping
>
57.
<
servlet-name
>faces</
servlet-name
>
58.
<
url-pattern
>*.jsf</
url-pattern
>
59.
</
servlet-mapping
>
60.
<
servlet-mapping
>
61.
<
servlet-name
>faces</
servlet-name
>
62.
<
url-pattern
>*.faces</
url-pattern
>
63.
</
servlet-mapping
>
64.
<
listener
>
65.
<
listener-class
>org.apache.myfaces.webapp.StartupServletContextListener</
listener-class
>
66.
</
listener
>
67.
<
context-param
>
68.
<
param-name
>org.apache.myfaces.USE_ENCRYPTION</
param-name
>
69.
<
param-value
>false</
param-value
>
70.
</
context-param
>
71.
</
web-app
>
Eclipse deploys to "its" Tomcat on a peculiar path. It's sometimes useful to go looking for this in diagnosing problems such as working on a project from more than one computer (say, at work and at home). The path to which Eclipse deploys your web application is as shown below and has the structure, part of which depends on your application packages obviously, shown:
C:\Users\russ\dev\workspace\.metadata\.plugins\org.eclipse.wst.server.core\tmp0\wtpwebapps>tree Folder PATH listing Volume serial number is BE79-0AEB C:. +---dvdcatalog � +---images � +---META-INF � +---styles � +---WEB-INF � +---classes � � +---com � � +---etretatlogiciels � � +---dvdcatalog � � +---model � � +---schema � � +---util � � +---validator � +---lib +---ROOT +---WEB-INF
For example, dvdcatalog, my web application, uses a number of user libraries all of which are copied to ..../dvdcatalog/WEB-INF/lib. The name of the user library (as defined by me in Eclipse Build Path) is given in brackets.
04/05/2010 10:44 AM 443,432 antlr-2.7.6.jar 04/16/2010 10:26 AM 322,362 cglib-nodep-2.2.jar [XStream] 04/21/2010 08:29 AM 188,671 commons-beanutils-1.7.0.jar [JSF 1.2 (Apache MyFaces)] 04/21/2010 08:29 AM 46,725 commons-codec-1.3.jar [JSF 1.2 (Apache MyFaces)] 04/05/2010 10:44 AM 559,366 commons-collections-3.1.jar [JSF 1.2 (Apache MyFaces)] 04/21/2010 08:29 AM 571,259 commons-collections-3.2.jar [JSF 1.2 (Apache MyFaces)] 04/21/2010 08:29 AM 143,602 commons-digester-1.8.jar [JSF 1.2 (Apache MyFaces)] 04/21/2010 08:29 AM 76,685 commons-discovery-0.4.jar [JSF 1.2 (Apache MyFaces)] 04/21/2010 08:29 AM 60,686 commons-logging-1.1.1.jar [JSF 1.2 (Apache MyFaces)] 04/05/2010 10:44 AM 313,898 dom4j-1.6.1.jar [Hibernate 3.5.1] 04/05/2010 10:44 AM 3,893,179 hibernate3.jar [Hibernate 3.5.1] 04/05/2010 10:44 AM 597,476 javassist-3.9.0.GA.jar [Hibernate 3.5.1] 04/16/2010 10:26 AM 153,115 jdom-1.1.jar [XStream] 04/16/2010 10:26 AM 56,702 jettison-1.0.1.jar [XStream] 04/16/2010 10:26 AM 534,827 joda-time-1.6.jar [XStream] 04/23/2010 09:29 AM 30,691 jstl-api-1.2.jar [JSTL 1.2] 04/23/2010 09:29 AM 392,435 jstl-impl-1.2.jar [JSTL 1.2] 04/05/2010 10:44 AM 10,899 jta-1.1.jar [Hibernate 3.5.1] 02/23/2010 04:05 PM 391,834 log4j-1.2.15.jar [Log4j] 04/21/2010 08:29 AM 378,921 myfaces-api-1.2.8.jar [JSF 1.2 (Apache MyFaces)] 04/21/2010 08:29 AM 801,254 myfaces-impl-1.2.8.jar [JSF 1.2 (Apache MyFaces)] 03/02/2010 09:22 AM 732,695 mysql-connector-java-5.1.12-bin.jar [MySQL JDBC Driver] 04/05/2010 10:44 AM 23,445 slf4j-api-1.5.8.jar [Hibernate 3.5.1] 04/09/2010 10:30 AM 7,600 slf4j-simple-1.5.11.jar [Slf4j] 04/16/2010 10:26 AM 179,346 stax-1.2.0.jar [XStream] 04/16/2010 10:26 AM 26,514 stax-api-1.0.1.jar [XStream] 04/16/2010 10:26 AM 520,969 wstx-asl-3.2.7.jar [XStream] 04/16/2010 10:26 AM 5,234 xml-writer-0.2.jar [XStream] 04/16/2010 10:26 AM 431,568 xom-1.1.jar [XStream] 04/16/2010 10:26 AM 24,956 xpp3_min-1.1.4c.jar [XStream] 04/16/2010 10:26 AM 431,406 xstream-1.3.1.jar [XStream] 31 File(s) 12,351,752 bytes
If I'm getting ClassNotFound exceptions, something that seems to plague JSTL, and don't have the same libraries on both computers (work and home), this is a place to start asking why.
The preferred way in JSF to create grid alignment is to use this construct. However, it is very weak (JSF 1.2) as compared to, say, <dataTable> in that it's not really possible to get vertical-align: top onto the <td> elements generated. If you don't care to align things to the top, then it doesn't matter. Here's how you should do it:
01.
<
h:panelGrid
columns
=
"3"
rowStyles
=
"query-prompt,query-prompt,query-prompt"
>
02.
<
h:panelGroup
style
=
"query-prompt"
>
03.
<
h:panelGrid
rowClasses
=
"query-prompt,query-prompt,query-prompt"
styleClass
=
"query-prompt"
>
04.
<
h:panelGroup
style
=
"query-prompt"
>
05.
<
h:outputText
value
=
"#{msg['query.rating_prompt']}"
/>
06.
<
h:selectManyListbox
title
=
"#{msg['query.rating_prompt']}"
07.
value
=
"#{queryBean.ratings}"
>
08.
<
f:selectItems
value
=
"#{queryBean.ratingsItems}"
/>
09.
</
h:selectManyListbox
>
10.
</
h:panelGroup
>
11.
12.
<
h:panelGroup
style
=
"query-prompt"
>
13.
<
h:outputText
value
=
"#{msg['query.year_prompt']}"
/>
14.
<
h:inputText
value
=
"#{queryBean.years}"
/>
15.
</
h:panelGroup
>
16.
</
h:panelGrid
>
17.
</
h:panelGroup
>
18.
19.
<
h:panelGroup
style
=
"query-prompt"
>
20.
<
h:outputText
value
=
"#{msg['query.genre_prompt']}"
/>
21.
<
h:selectManyListbox
title
=
"#{msg['query.genre_prompt']}"
22.
value
=
"#{queryBean.genres}"
>
23.
<
f:selectItems
value
=
"#{queryBean.genreItems}"
/>
24.
</
h:selectManyListbox
>
25.
</
h:panelGroup
>
26.
27.
<
h:panelGroup
style
=
"query-prompt"
>
28.
<
h:outputText
value
=
"#{msg['query.language_prompt']}"
/>
29.
<
h:selectManyListbox
title
=
"#{msg['query.language_prompt']}"
30.
value
=
"#{queryBean.languages}"
>
31.
<
f:selectItems
value
=
"#{queryBean.languageItems}"
/>
32.
</
h:selectManyListbox
>
33.
</
h:panelGroup
>
34.
</
h:panelGrid
>
Instead, despite opinions to the contrary, the good old HTML construct, <table> turned out still to be the best. (Note that CSS class query-prompt contains vertical-align: top.
01.
<
table
>
02.
<
tr
>
03.
<
td
class
=
"query-prompt"
> <%-- column 1 --%>
04.
<
table
>
05.
<
tr
>
06.
<
td
class
=
"query-prompt"
>
07.
<
h:outputText
value
=
"#{msg['query.rating_prompt']}"
/>
08.
</
td
>
09.
<
td
class
=
"query-prompt"
>
10.
<
h:selectManyListbox
title
=
"#{msg['query.rating_prompt']}"
11.
value
=
"#{queryBean.ratings}"
>
12.
<
f:selectItems
value
=
"#{queryBean.ratingsItems}"
/>
13.
</
h:selectManyListbox
>
14.
</
td
>
15.
</
tr
>
16.
<
tr
>
17.
<
td
> <
br
/> </
td
>
18.
</
tr
>
19.
<
tr
>
20.
<
td
class
=
"query-prompt"
>
21.
<
h:outputText
value
=
"#{msg['query.year_prompt']}"
/>
22.
</
td
>
23.
<
td
class
=
"query-prompt"
>
24.
<
h:inputText
value
=
"#{queryBean.years}"
/>
25.
</
td
>
26.
</
tr
>
27.
</
table
>
28.
</
td
>
29.
<
td
class
=
"query-prompt"
> <%-- column 2 --%>
30.
<%-- Genre: --%>
31.
<
table
>
32.
<
tr
>
33.
<
td
class
=
"query-prompt"
>
34.
<
h:outputText
value
=
"#{msg['query.genre_prompt']}"
/>
35.
</
td
>
36.
<
td
class
=
"query-prompt"
>
37.
<
h:selectManyListbox
title
=
"#{msg['query.genre_prompt']}"
38.
value
=
"#{queryBean.genres}"
>
39.
<
f:selectItems
value
=
"#{queryBean.genreItems}"
/>
40.
</
h:selectManyListbox
>
41.
</
td
>
42.
</
tr
>
43.
</
table
>
44.
</
td
>
45.
<
td
class
=
"query-prompt"
> <%-- column 3 --%>
46.
<%-- Language: --%>
47.
<
table
>
48.
<
tr
>
49.
<
td
class
=
"query-prompt"
>
50.
<
h:outputText
value
=
"#{msg['query.language_prompt']}"
/>
51.
</
td
>
52.
<
td
class
=
"query-prompt"
>
53.
<
h:selectManyListbox
title
=
"#{msg['query.language_prompt']}"
54.
value
=
"#{queryBean.languages}"
>
55.
<
f:selectItems
value
=
"#{queryBean.languageItems}"
/>
56.
</
h:selectManyListbox
>
57.
</
td
>
58.
</
tr
>
59.
</
table
>
60.
</
td
>
61.
</
tr
>
62.
</
table
>
Of course, this last representation is what gives us:
...instead of seeing the prompts at the bottom of the lists instead of the top.
faces-config.xml describes where link- and button-clicks will take the user, in general, but there is also corresponding information in two places that tie the elements of navigation together.
In this discussion, we'll be talking about exactly three buttons on two pages. The pages inter-refer in that one leads to the other and the other gives the option to return to the first starting over as if just getting there.
Here are two pages, query.jsp and view.jsp. The user tailors the query on the first and clicks either a button, Reset Query, that calls QueryBean.clear() and whose result it isn't necessary to specify here (a sort of internal operation), or Submit Query, which takes him to the viewing page where the results of the query will be displayed.
01.
<
navigation-rule
>
02.
<
display-name
> query </
display-name
>
03.
<
from-view-id
> /query.jsp </
from-view-id
>
04.
<
navigation-case
>
05.
<
from-outcome
> submit </
from-outcome
>
06.
<
to-view-id
> /view.jsp </
to-view-id
>
07.
</
navigation-case
>
08.
</
navigation-rule
>
09.
10.
<
navigation-rule
>
11.
<
display-name
> view </
display-name
>
12.
<
from-view-id
> /view.jsp </
from-view-id
>
13.
<
navigation-case
>
14.
<
from-outcome
> query </
from-outcome
>
15.
<
to-view-id
> /query.jsp </
to-view-id
>
16.
</
navigation-case
>
17.
</
navigation-rule
>
For the navigation rule above, here's what to expect in terms of JSF code.
One button submits the query as configured on this page navigating to the view page where an <h:dataTable> displays the results. The other button zeroes out the configuration so the user can start over tailoring his query. The second button does not need to "go anywhere" despite a type of "reset".
1.
<
h:commandButton
value
=
"#{msg['query.submit_button']}"
2.
action
=
"#{queryBean.submitQuery}"
3.
type
=
"submit"
/>
4.
5.
<
h:commandButton
value
=
"#{msg['query.reset_button']}"
6.
action
=
"#{queryBean.clear}"
7.
type
=
"reset"
/>
This button, Reset Query, returns the user to the previous (query) page.
1.
<
h:commandButton
value
=
"#{msg['query.reset_button']}"
2.
action
=
"#{queryBean.clear}"
3.
type
=
"query"
/>
The query bean code establishes the query in a static place where the schema bean, in this case, the one that defines and handles the DVD catalog table, can find and consume it.
Note the strings returned from Java. To a old C programmer, this looks cheesy, but it's critically necessary. These strings must match
01.
public
String clear()
02.
{
03.
log.info(
"Resetting query parameters..."
);
04.
05.
this
.ratings =
null
;
06.
this
.genres =
null
;
07.
this
.languages =
null
;
08.
this
.sortedBy =
null
;
09.
this
.years =
null
;
10.
return
"query"
;
11.
}
12.
13.
public
String submitQuery()
14.
{
15.
log.info(
"Submitting request..."
);
16.
17.
chosenSortedBy =
new
String(
this
.sortedBy );
18.
chosenYears =
new
String(
this
.years );
19.
20.
int
length =
0
;
21.
String[] newArray =
null
;
22.
23.
length =
this
.ratings.length;
24.
newArray =
new
String[ length ];
25.
System.arraycopy(
this
.ratings,
0
, newArray,
0
, length );
26.
chosenRatings = newArray;
27.
28.
length =
this
.genres.length;
29.
newArray =
new
String[ length ];
30.
System.arraycopy(
this
.genres,
0
, newArray,
0
, length );
31.
chosenGenres = newArray;
32.
33.
length =
this
.languages.length;
34.
newArray =
new
String[ length ];
35.
System.arraycopy(
this
.languages,
0
, newArray,
0
, length );
36.
chosenLanguages = newArray;
37.
38.
log.info(
"--------- To be sorted by: "
+
this
.sortedBy );
39.
log.info(
"--------- Ratings: "
+ Arrays.toString(
this
.ratings ) );
40.
log.info(
"--------- Genres: "
+ Arrays.toString(
this
.genres ) );
41.
log.info(
"--------- Languages: "
+ Arrays.toString(
this
.languages ) );
42.
log.info(
"--------- Years: "
+
this
.years );
43.
return
"submit"
;
44.
}
If your navigation doesn't proceed to the expected location, ...
For reference, here is one working incantation of the getAll() method invoked by view.jsp's <h:dataTable>.
001.
public
Result getAll()
throws
SQLException, NamingException
002.
{
003.
log.info(
"Performing SQL select to build view.jsp..."
);
004.
005.
try
006.
{
007.
ResultSet rs =
null
;
008.
Result r =
null
;
009.
String query =
null
;
010.
String[] ratings = QueryBean.getChosenRatings(),
011.
genres = QueryBean.getChosenGenres(),
012.
languages = QueryBean.getChosenLanguages();
013.
String years = QueryBean.getChosenYears(),
014.
sortedBy = QueryBean.getChosenSortedBy();
015.
016.
openConnection();
017.
018.
Statement stmt =
this
.conn.createStatement();
019.
020.
if
( ( ratings ==
null
|| ratings.length ==
0
)
021.
&& ( genres ==
null
|| genres.length ==
0
)
022.
&& ( languages ==
null
|| languages.length ==
0
)
023.
&& ( years ==
null
|| years.length() ==
0
) )
024.
{
025.
// the query isn't to be qualified in any way...
026.
query =
"SELECT * FROM "
+ TABLE_NAME;
027.
028.
if
( sortedBy !=
null
)
029.
query +=
" ORDER BY "
+ sortedBy;
030.
031.
query +=
";"
;
032.
033.
log.info( query );
034.
035.
rs = stmt.executeQuery( query );
036.
r = ResultSupport.toResult( rs );
037.
038.
if
( r ==
null
)
039.
log.info(
"Query returned nothing."
);
040.
041.
return
r;
042.
}
043.
044.
// these are crucial to helping us build SQL syntax in the query...
045.
boolean
gotRating =
false
;
046.
boolean
gotGenre =
false
;
047.
boolean
gotLanguage =
false
;
048.
049.
// the query will be qualified in some way...
050.
query =
"SELECT * FROM "
+ TABLE_NAME +
" WHERE "
;
051.
052.
if
( ratings !=
null
&& ratings.length >
0
)
053.
{
054.
// Ratings, could be one or more: [g, pg, pg-13, r]
055.
for
( String rating : ratings )
056.
{
057.
query += ( gotRating ) ?
" OR "
:
"( "
;
058.
query += RATING +
" = '"
+ rating +
"'"
;
059.
gotRating =
true
;
060.
}
061.
062.
if
( gotRating )
063.
query +=
" )"
;
064.
}
065.
066.
if
( genres !=
null
&& genres.length >
0
)
067.
{
068.
if
( gotRating )
069.
query +=
" AND "
;
070.
071.
// Genres, could be one or more: [action, drama, history, music, politics, suspense]
072.
for
( String genre : genres )
073.
{
074.
query += ( gotGenre ) ?
" OR "
:
"( "
;
075.
query += GENRE +
" = '"
+ genre +
"'"
;
076.
gotGenre =
true
;
077.
}
078.
079.
if
( gotGenre )
080.
query +=
" )"
;
081.
}
082.
083.
if
( languages !=
null
&& languages.length >
0
)
084.
{
085.
if
( gotRating || gotGenre )
086.
query +=
" AND "
;
087.
088.
// Languages, could be one or more: [su, el, it, ja, pt]
089.
for
( String language : languages )
090.
{
091.
query += ( gotLanguage ) ?
" OR "
:
"( "
;
092.
query += LANGUAGE +
" = '"
+ language +
"'"
;
093.
gotLanguage =
true
;
094.
}
095.
096.
if
( gotLanguage )
097.
query +=
" )"
;
098.
}
099.
100.
/* Do years stuff here--very ugly, very nasty.
101.
*
102.
* What we support, e.g.:
103.
* single year, i.e.: 2010
104.
* year range, i.e.: 1969-1993
105.
* year list, i.e.: 1963, 1983, 1997
106.
*
107.
* What is used here, no matter how simple or complex, is an array.
108.
*/
109.
String[] yearList = getYearQuery( years );
110.
111.
if
( yearList !=
null
&& yearList.length >
0
)
112.
{
113.
if
( gotRating || gotGenre || gotLanguage )
114.
query +=
" AND "
;
115.
116.
if
( yearList.length ==
3
&& yearList[
1
].equals(
"-"
) )
117.
{
118.
// manufacture a BETWEEN query
119.
query += YEAR +
" BETWEEN '"
+ yearList[
0
] +
"' AND '"
+ yearList[
2
] +
"'"
;
120.
}
121.
else
122.
{
123.
// manufacture an OR ... OR ... OR ... query
124.
boolean
gotYear =
false
;
125.
126.
for
( String y : yearList )
127.
{
128.
query += ( gotYear ) ?
" OR "
:
"( "
;
129.
query += YEAR +
" = '"
+ y +
"'"
;
130.
gotYear =
true
;
131.
}
132.
133.
if
( gotYear )
134.
query +=
" )"
;
135.
}
136.
}
137.
138.
if
( sortedBy !=
null
)
139.
query +=
" ORDER BY "
+ sortedBy;
140.
141.
query +=
";"
;
142.
143.
log.info( query );
144.
145.
rs = stmt.executeQuery( query );
146.
r = ResultSupport.toResult( rs );
147.
148.
if
( r ==
null
)
149.
log.info(
"Query returned nothing."
);
150.
151.
return
r;
152.
}
153.
finally
154.
{
155.
close();
156.
}
157.
}
Here's how to display the results in JSF.
01.
<
h:dataTable
value
=
"#{dvdTitle.all}"
02.
var
=
"_title"
03.
styleClass
=
"dvdStyles"
04.
headerClass
=
"dvdHeader"
05.
rowClasses
=
"oddRow,evenRow"
>
06.
<
h:column
>
07.
<
div
style
=
"margin-left: 10px"
>
08.
<
p
>
09.
<
span
style
=
"font-style: italic"
>
10.
<
h:outputText
value
=
"#{_title.title}"
/>
11.
12.
</
span
>
13.
<
h:outputText
value
=
"(#{_title.year})"
/>
14.
</
p
>
15.
<
p
>
16.
<
h:outputText
value
=
"#{_title.minutes} #{msg['view.minutes.prompt']}"
/>
17.
</
p
>
18.
<
p
>
19.
<
h:outputText
value
=
"#{_title.rating}"
/>
20.
<%-- >h:outputText value=", #{_title.genre}" /< --%>
21.
</
p
>
22.
<
p
>
23.
<
h:outputText
value
=
"#{msg['view.languages']} "
/>
24.
<
h:outputText
value
=
"#{_title.language}"
/>
25.
</
p
>
26.
<
p
>
27.
<
h:outputText
value
=
"#{msg['view.site.prompt']}"
/>
28.
<
h:outputText
value
=
"#{_title.url}"
/>
29.
</
p
>
30.
<
p
><
h:outputText
value
=
"#{msg['view.stars']}"
/></
p
>
31.
<
p
style
=
"margin-left: 20px"
>
32.
<
h:outputText
value
=
"#{_title.stars}"
/>
33.
</
p
>
34.
</
div
>
35.
</
h:column
>
36.
</
h:dataTable
>
Early on, in imitation of view.jsp's value attribute to <h:dataTable>, "#{dvdTitle.all}", I unthinkingly added such a reference to an additional method that looked for just one title and returned a Result. The problem was that this method originally took String title. Hence, it was not a "property". I got:
ERROR - An exception occurred org.apache.jasper.el.JspPropertyNotFoundException: /view-one.jsp(38,1) '#{dvdTitle.single}' Property 'single' not found on \ type com.etretatlogiciels.dvdcatalog.schema.DvdTitle
Later, as I struggled with it, I began to get another warning in the JSP:
'single' cannot be resolved as a member of 'DvdTitle'
This was solved of course by removing the argument. A property, by definition, has a getter and a setter. This is a getter and must have no argument. Ultimately, peace ensued in view-one.jsp:
01.
.
02.
.
03.
.
04.
<%-- usually this is just one other film, but it can be more --%>
05.
<
h:dataTable
value
=
"#{dvdTitle.single}"
06.
var
=
"_title"
07.
styleClass
=
"dvdStyles"
08.
headerClass
=
"dvdHeader"
09.
rowClasses
=
"oddRow,evenRow"
>
10.
<
h:column
>
11.
.
12.
.
13.
.
At this point, view-one.jsp presents the option of adding the new title despite its already existing or returning to addtitle.jsp to add a different title.
We're obviously adding the power user stuff (by which the power user can add titles to the database). Later, addtitle.jsp will be linked to via a button from powerlogin.jsp.
The button "not to add" a duplicate DVD title to the database on this page would not work with type as "reset" and, of course, the button to add it anyway was typed "submit". The solution, after exploring immediate="true", changing the bean type for AddBean to "request" or "none" from "session", etc. was the third possible option, that is, "button". Prior to this, clicking on it resulted in nothing, not even AddBean.clear() was called.
See discussion at "No transition next page button".
01.
<%-- Don't Add This Title --%>
02.
<
h:commandButton
value
=
"#{msg['view-one.reset_button']}"
03.
action
=
"#{addBean.clear}"
04.
type
=
"button"
/>
05.
06.
<%-- Add This Title --%>
07.
<
h:commandButton
value
=
"#{msg['view-one.confirm_button']}"
08.
action
=
"#{addBean.retainAddition}"
09.
type
=
"submit"
/>
Here's a representation of the final details of application navigation (between pages).
The following, whether we do them or not, are left to do to make this a real application.