Thursday, December 13, 2012

Setting JSF ProjectStage with JNDI

One nice feature of JSF2 is the ProjectStage setting.  It lets the JSF implementation and the application developer optimize and customize behavior based on whether JSF is running in Development, Production, SystemTest, or UnitTest.  The usual way to tell JSF about which ProjectStage to use is through a context param in web.xml.
<context-param>
        <param-name>javax.faces.PROJECT_STAGE</param-name>
        <param-value>Development</param-value>
</context-param>
But you would rather not muck with web.xml for something like this.  It's much cleaner to define your ProjectStage based on what kind of server environment you are running.  In other words, wouldn't it be nice if JSF could know whether your server is running in Development, Test, Production, etc?  Then you wouldn't need to change your application at all.

A little-known but handy feature of JSF2 is the ability to use JNDI to set the JSF ProjectStage.  Here is how you do that in JBoss AS7.

First, add a resource reference in web.xml.
<resource-ref>
   <res-ref-name>jsf/ProjectStage</res-ref-name>
   <res-type>java.lang.String</res-type>
</resource-ref>
Then, create a jboss-web.xml file and place it beside web.xml in your WEB-INF directory. The file should bind the resource reference to JNDI like this:
<?xml version="1.0" encoding="ISO-8859-1"?>
<jboss-web>
    <resource-ref>
        <res-ref-name>jsf/ProjectStage</res-ref-name>
        <res-type>java.lang.String</res-type>
        <jndi-name>java:/env/jsf/ProjectStage</jndi-name>
    </resource-ref>
</jboss-web> 
Next, add the JNDI value to JBoss AS7 in the naming subsystem of your configuration file, such as standalone.xml:
      <subsystem xmlns="urn:jboss:domain:naming:1.1">
            <bindings>
                <simple name="java:/env/jsf/ProjectStage" value="Development"/>
            </bindings>
        </subsystem>

You can also add the JNDI value using CLI:
/subsystem=naming/binding=java\:\/env\/jsf\/ProjectStage/:add(binding-type=simple,value=Development,class=java.lang.String)
But I like to use CLI GUI for this because it automatically handles the escape characters:

Now we are done, but you might also want to change the value of ProjectStage on your server. Again, you can do that in XML or you can use CLI: 
/subsystem=naming/binding=java\:\/env\/jsf\/ProjectStage/:write-attribute(name=value,value=UnitTest)

6 comments:

Jaikiran said...

IMO, a much simpler *AS7 specific* way would perhaps be to take advantage of the AS7 system property expansion support in deployment descriptors. So your web.xml would then just have:

<context-param>
<param-name>javax.faces.PROJECT_STAGE</param-name>
<param-value>${jsf.project.stage:Production}</param-value>
</context-param>

where the jsf.project.stage system property (which can be set during server startup via -Djsf.project.stage) can then control the value. By default, we set it to Production, so that the absence of that system property can be treated as Production environment. Of course, this isn't standardized.

Stan Silvert said...

Good tip Jaikiran. I agree. That's a little easier, but not portable to other containers.

For those who don't know, you don't even have to use -D for that. System props can also be set in standalone.xml and/or CLI.

<system-properties>
<property name="jsf.project.stage" value="Production"/>
</system-properties>

/system-property=jsf.project.stage/:add(value=Production)

Anonymous said...

I was unseccessful with the property expansion trick on JBoss AS 7.1.1.

I tried param-values like ${jsf.project.stage:UnitTest} and ${jsf.project.stage} but PROJECT_STAGE was always set to the default value of Production. Do I have to take additional steps in order to activate system parameter expansion in web.xml?

Anonymous said...

I just noticed that deploying a webapp that uses property expansion causes a console message like

13:08:42,535 INFO [javax.enterprise.resource.webcontainer.jsf.application] (MSC service thread 1-8) Unable to discern ProjectStage for value ${jsf.project.stage:Production}.

Anonymous said...

If you use the system-property approach do not forget to set true in the configuration.
Oh, and the replacemet in the added example goes with ${jsf.project.stage}, not with ${jsf.project.stage:Production}.

Anonymous said...

Sorry, I forgot the limitations for xml tags: The "true" has to be set in spec-descriptor-property-replacement in the standalone.xml