Coming Up for Air

Embedding JSF with Winstone

Monday, May 08, 2006 |

Sometimes, when developing a JSF application, it would be nice not to have to wait for your favorite container to start up. That’s especially true if your container is a full JEE stack like GlassFish or JBoss. Likewise, there are times when you might need to embed a web application in another, say some server process or desktop application. While there are a number of options available, I’d like to demonstrate how to embed a simple JSF application using the Winstone Servlet Container. To help demonstrate how this is done, we’ll use a well know sample application: Duke’s guessNumber game, and, to make things even more interesting, we’ll convert the application to use Facelets (mostly because I love Facelets, but partly because I didn’t care to figure out how to make Winstone compile JSPs ;) ).

First, let’s take a look at the web app itself. To convert the application to a Facelets app, we’ll have to rename the JSPs, then make them conform to XHTML and Facelets specifications. First off, a really easy one: index.html. This page simply redirects us to guessNumber.xhtml. I could probably have done this in web.xml, but it was in the original app, so we’ll just run with it like that.

index.html:

1
2
3
4
5
6
7
<html>
    <head>
        <META HTTP-EQUIV=Refresh CONTENT="0; URL=greeting.jsf">
    </head>
    <body>
    </body>
</html>

Pretty simple and boring. Let’s now take a look at our template file (for a fuller introduction to using Facelets, please see Rick Hightower’s excellent article, as well as Jim Hazen’s Getting Started article):

layout.xhtml:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
    xmlns:ui="http://java.sun.com/jsf/facelets"
    xmlns:h="http://java.sun.com/jsf/html"
    xmlns:f="http://java.sun.com/jsf/core">
<head>
    <title><ui:insert name="title">Default title</ui:insert></title>
    <link rel="stylesheet" type: "text/css" href="./css/main.css"/>
</head>
<body>
    <div style="float: left">
        <h:graphicImage id="waveImg" url="/wave.med.gif" />
    </div>
    <div>
        <ui:insert name="content"></ui:insert>
    </div>
</body>
</html>

Nothing real exciting there either. Next we have greeting.xhtml and response.xhtml.

greeting.xhtml:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
    xmlns:ui="http://java.sun.com/jsf/facelets"
    xmlns:h="http://java.sun.com/jsf/html"
    xmlns:f="http://java.sun.com/jsf/core">
<ui:composition template="layout.xhtml">
    <ui:define name="title">Number Guess</ui:define>
    <ui:define name="content">
        <h:form id="helloForm" >
            <h2>Hi. My name is Duke.  I'm thinking of a number from
            <h:outputText value="#{UserNumberBean.minimum}"/> to
            <h:outputText value="#{UserNumberBean.maximum}"/>.  Can you guess it?</h2>
            <h:inputText id="userNo" value="#{UserNumberBean.userNumber}" validator="#{UserNumberBean.validate}"/>
            <h:commandButton id="submit" action="success" value="Submit" />
            <br />
            <h:message style="color: red; font-family: 'New Century Schoolbook', serif; font-style: oblique; text-decoration: overline" id="errors1" for="userNo"/>
        </h:form>
    </ui:define>
</ui:composition>
</html>

response.xhtml:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
    xmlns:ui="http://java.sun.com/jsf/facelets"
    xmlns:h="http://java.sun.com/jsf/html"
    xmlns:f="http://java.sun.com/jsf/core">
<ui:composition template="layout.xhtml">
    <ui:define name="title">Number Guess</ui:define>
    <ui:define name="content">
        <h:form id="responseForm" >
            <h2><h:outputText id="result" value="#{UserNumberBean.response}"/></h2>
            <h:commandButton id="back" value="Back" action="success"/>
        </h:form>
    </ui:define>
</ui:composition>
</html>

Having fixed up our views to work using Facelets, let’s now see exactly how we do the embedding. That magic is done in the following class.

WinstoneServer.java:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
package embed;
import java.net.URL;
import java.util.HashMap;
import java.util.Map;
import winstone.Launcher;
public class WinstoneServer {
    Launcher winstone;
    public void startup() {
        Map args = new HashMap();
        try {
            URL webdir = getClass().getResource("/WebContent");
            args.put("webroot", webdir.getPath());
            System.out.println(args.toString());
            Launcher.initLogger(args);
            winstone = new Launcher(args); // spawns threads, so your application doesn't block
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    public void shutdown() {
        winstone.shutdown();
    }
    public static void main(String args[]) {
        WinstoneServer server = new WinstoneServer();
        server.startup();
    }
}

That code is taken pretty much verbatim from the Winstone documentation. I split it out into methods on the class to make it prettier, but that’s about it. It’s an extremely simple process. Of course, there are many more options available, all of which are documented on Winstone’s site. My goal here isn’t to be a Winstone tutorial, so we’ll just use the minimum configuration, which is where to find the web root.

The web root probably needs a word here. What I’ve done in my example, which you’ll see in the archive attached to this article, is to put the web root directory in the source directory. By doing it this way, when Eclipse compiles my project it moves my web root to the build directory, making it simple to load it from the classpath. There are at least two other ways to accomplish the task: hard coding a path on the filesystem, and archiving the web root to a .war and telling Winstone to use the war. Each has its pros and cons. Your choice will depend on your intended usage. Ideally, for a packaged and deployed application, it would be nice to be able to jar the web root up with the application, but I have not had any luck getting it to load the web app from inside a jar. Given the limited scope of this demonstration, though, I opted not to pursue that very far.

Now, the UserNumberBean class, the Faces-managed bean that is the actual heart of the application. I’ve included the full source code here for a couple reasons: the upgrade to JSF 1.2 necessitated some source changes, and there was apparently some sort of error in the inclusion of the code in the article linked above resulting in the case of some identifiers being mangled somewhat.

UserNumberBean.java:

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
package guessNumber;
import java.util.Random;
import javax.faces.component.UIComponent;
import javax.faces.context.FacesContext;
import javax.faces.validator.LongRangeValidator;
import javax.faces.validator.ValidatorException;
import com.sun.faces.util.MessageFactory;
public class UserNumberBean {
    protected int maximum = 0;
    protected boolean maximumSet = false;
    protected String[] status = null;
    protected int minimum = 0;
    protected boolean minimumSet = false;
    protected Integer userNumber = null;
    protected Integer randomInt = null;
    protected String response = null;
    Random randomGR = new Random();
    public UserNumberBean() {
        randomInt = new Integer(randomGR.nextInt(10));
        System.out.println("Duke's Number: " + randomInt);
    }
    public void setUserNumber(Integer user_Number) {
        userNumber = user_Number;
        System.out.println("Set userNumber " + userNumber);
    }
    public Integer getUserNumber() {
        System.out.println("get userNumber " + userNumber);
        return userNumber;
    }
    public String getResponse() {
        if (userNumber != null && userNumber.compareTo(randomInt) == 0) {
            randomInt = new Integer(randomGR.nextInt(10));
            return "Yay! You got it!";
        } else {
            return "Sorry, " + userNumber + " is incorrect.";
        }
    }
    public String[] getStatus() {
        return status;
    }
    public void setStatus(String[] newStatus) {
        status = newStatus;
    }
    public int getMaximum() {
        return (this.maximum);
    }
    public void setMaximum(int maximum) {
        this.maximum = maximum;
        this.maximumSet = true;
    }
    public int getMinimum() {
        return (this.minimum);
    }
    public void setMinimum(int minimum) {
        this.minimum = minimum;
        this.minimumSet = true;
    }
    public void validate(FacesContext context, UIComponent component, Object value) throws ValidatorException {
        if ((context == null) || (component == null)) {
            throw new NullPointerException();
        }
        if (value != null) {
            try {
                int converted = intValue(value);
                if (maximumSet &&
                        (converted > maximum)) {
                    if (minimumSet) {
                        throw new ValidatorException(
                                MessageFactory.getMessage
                                (context, LongRangeValidator.NOT_IN_RANGE_MESSAGE_ID,
                                        new Object[]{
                                        new Integer(minimum),
                                        new Integer(maximum)
                                }));
                    } else {
                        throw new ValidatorException(
                                MessageFactory.getMessage
                                (context, LongRangeValidator.MAXIMUM_MESSAGE_ID,
                                        new Object[]{
                                        new Integer(maximum)
                                }));
                    }
                }
                if (minimumSet &&
                        (converted < minimum)) {
                    if (maximumSet) {
                        throw new ValidatorException(MessageFactory.getMessage
                                (context, LongRangeValidator.NOT_IN_RANGE_MESSAGE_ID,
                                        new Object[]{
                                        new Double(minimum),
                                        new Double(maximum)
                                }));
                    } else {
                        throw new ValidatorException(
                                MessageFactory.getMessage
                                (context, LongRangeValidator.MINIMUM_MESSAGE_ID,
                                        new Object[]{
                                        new Integer(minimum)
                                }
                                ));
                    }
                }
            } catch (NumberFormatException e) {
                throw new ValidatorException(
                        MessageFactory.getMessage
                        (context, LongRangeValidator.TYPE_MESSAGE_ID));
            }
        }
    }
    private int intValue(Object attributeValue)    throws NumberFormatException {
        if (attributeValue instanceof Number) {
            return (((Number) attributeValue).intValue());
        } else {
            return (Integer.parseInt(attributeValue.toString()));
        }
    }
}

The web.xml and faces-config.xml files used to wire the web application together are pretty standard, but I’ll include them here for your perusal:

web.xml:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
<?xml version='1.0' encoding='UTF-8'?>
<!DOCTYPE web-app PUBLIC
  "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
  "http://java.sun.com/dtd/web-app_2_3.dtd">
<web-app>
    <display-name>JavaServer Faces Guess Number Sample Application</display-name>
    <description>JavaServer Faces Guess Number Sample Application</description>
    <!-- Use Documents Saved as *.xhtml -->
    <context-param>
        <param-name>javax.faces.DEFAULT_SUFFIX</param-name>
        <param-value>.xhtml</param-value>
    </context-param>
    <!-- Special Debug Output for Development -->
    <context-param>
        <param-name>facelets.DEVELOPMENT</param-name>
        <param-value>true</param-value>
    </context-param>
    <context-param>
        <param-name>com.sun.faces.verifyObjects</param-name>
        <param-value>true</param-value>
    </context-param>
    <context-param>
        <param-name>javax.faces.STATE_SAVING_METHOD</param-name>
        <param-value>client</param-value>
    </context-param>
    <context-param>
        <param-name>com.sun.faces.validateXml</param-name>
        <param-value>true</param-value>
        <description>Set this flag to true if you want the JavaServer Faces Reference Implementation to validate the XML in your faces-config.xml resources against the DTD. Default value is false.</description>
    </context-param>
    <context-param>
        <param-name>com.sun.faces.verifyObjects</param-name>
        <param-value>true</param-value>
        <description>
            Set this flag to true if you want the JavaServer Faces Reference Implementation to verify that all of the application objects you have configured (components, converters, renderers, and validators) can be successfully created. Default value is
            false.
        </description>
    </context-param>
    <listener>
        <listener-class>com.sun.faces.config.ConfigureListener</listener-class>
    </listener>
    <!-- Faces Servlet -->
    <servlet>
        <servlet-name>Faces Servlet</servlet-name>
        <servlet-class>javax.faces.webapp.FacesServlet</servlet-class>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <!-- Faces Servlet Mapping -->
    <servlet-mapping>
        <servlet-name>Faces Servlet</servlet-name>
        <url-pattern>*.jsf</url-pattern>
    </servlet-mapping>
    <security-constraint>
        <!-- This security constraint illustrates how JSP pages
            with JavaServer Faces components can be protected from
            being accessed without going through the Faces Servlet.
            The security constraint ensures that the Faces Servlet will
            be used or the pages will not be processed. -->
        <display-name>Restrict access to JSP pages</display-name>
        <web-resource-collection>
            <web-resource-name>Restrict access to JSP pages</web-resource-name>
            <url-pattern>/greeting.xhtml</url-pattern>
            <url-pattern>/response.xhtml</url-pattern>
        </web-resource-collection>
        <auth-constraint>
            <description>With no roles defined, no access granted</description>
        </auth-constraint>
    </security-constraint>
</web-app>

faces-config.xml:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
<?xml version='1.0' encoding='UTF-8'?>
<!DOCTYPE faces-config PUBLIC
  "-//Sun Microsystems, Inc.//DTD JavaServer Faces Config 1.1//EN"
  "http://java.sun.com/dtd/web-facesconfig_1_1.dtd">
<faces-config>
    <application>
        <locale-config>
            <default-locale>en</default-locale>
            <supported-locale>de</supported-locale>
            <supported-locale>fr</supported-locale>
            <supported-locale>es</supported-locale>
        </locale-config>
        <view-handler>com.sun.facelets.FaceletViewHandler</view-handler>
    </application>
    <navigation-rule>
        <description>The decision rule used by the NavigationHandler to determine which view must be displayed after the current view, greeting.jsp is processed.</description>
        <from-view-id>/greeting.xhtml</from-view-id>
        <navigation-case>
            <description>Indicates to the NavigationHandler that the response.jsp view must be displayed if the Action referenced by a UICommand component on the greeting.jsp view returns the outcome "success".</description>
            <from-outcome>success</from-outcome>
            <to-view-id>/response.xhtml</to-view-id>
        </navigation-case>
    </navigation-rule>
    <navigation-rule>
        <description>The decision rules used by the NavigationHandler to determine which view must be displayed after the current view, response.jsp is processed.</description>
        <from-view-id>/response.xhtml</from-view-id>
        <navigation-case>
            <description>Indicates to the NavigationHandler that the greeting.jsp view must be displayed if the Action referenced by a UICommand component on the response.jsp view returns the outcome "success".</description>
            <from-outcome>success</from-outcome>
            <to-view-id>/greeting.xhtml</to-view-id>
        </navigation-case>
    </navigation-rule>
    <managed-bean>
        <description>The "backing file" bean that backs up the guessNumber webapp</description>
        <managed-bean-name>UserNumberBean</managed-bean-name>
        <managed-bean-class>guessNumber.UserNumberBean</managed-bean-class>
        <managed-bean-scope>session</managed-bean-scope>
        <managed-property>
            <property-name>minimum</property-name>
            <property-class>int</property-class>
            <value>0</value>
        </managed-property>
        <managed-property>
            <property-name>maximum</property-name>
            <property-class>int</property-class>
            <value>10</value>
        </managed-property>
    </managed-bean>
</faces-config>

The archive attached to this blog does not have the dependencies included for size and (possible) legal restrictions, so here’s a list of what my workspace has:

  • commons-el.jar

  • commons-logging-1.0.4.jar

  • el-api.jar

  • el-ri.jar

  • jsf-api-1.2.jar

  • jsf-facelets-1.1.jar

  • jsf-impl-1.2.jar

  • log4j-1.2.13.jar

  • winstone-0.8.1.jar

That’s all there is to. To run the sample application, simply run embed.WinstoneServer, point your browser at http://localhost:8080, and start guessing.

As I’ve hopefully made clear, embedding a JSF application can be quite simple, though that simplicitly is linked somewhat to the embedding environment. Please note that this sample is not meant to be a catalog of best practices for JSF, Facelets or Winstone embedding, but to be a simple introduction to the topic, and, to that end, I hope you’ll find it useful. (You can download the source archive here).

Search

    Quotes

    Sample quote

    Quote source

    About

    My name is Jason Lee. I am a software developer living in the middle of Oklahoma. I’ve been a professional developer since 1997, using a variety of languages, including Java, Javascript, PHP, Python, Delphi, and even a bit of C#. I currently work for Red Hat on the WildFly/EAP team, where, among other things, I maintain integrations for some MicroProfile specs, OpenTelemetry, Micrometer, Jakarta Faces, and Bean Validation. (Full resume here. LinkedIn profile)

    I am the president of the Oklahoma City JUG, and an occasional speaker at the JUG and a variety of technical conferences.

    On the personal side, I’m active in my church, and enjoy bass guitar, running, fishing, and a variety of martial arts. I’m also married to a beautiful woman, and have two boys, who, thankfully, look like their mother.

    My Links

    Publications