Thursday, September 19, 2013

OmniFaces goes CDI with release of 1.6!

OmniFaces goes CDI

Finally, OmniFaces 1.6 has been released! With this release, OmniFaces features for the first time CDI-related features:

  • Fully transparent support for @Inject and @EJB in @FacesConverter and @FacesValidator. No additional configuration or annotations are required. Exactly like as how it would have been in JSF 2.2 until they removed it at the last moment and postponed for JSF 2.3.
  • CDI compatible @ViewScoped specifically for JSF 2.0 and 2.1. Exactly like it works in new JSF 2.2, including proper handling of @PreDestroy (JSF's own @ViewScoped does in 2.0/2.1 not invoke the @PreDestroy when the session is expired).
  • CDI alternative to <f:viewParam> which also supports JSF converters and validators, via an @Param annotation. One big advantage as compared to <f:viewParam> is that you can finally do the initialization thing based on those request parameters in a @PostConstruct instead of a preRenderView listener workaround.

Our main project zeef.com has run in production without any problems for almost a month using those new features from 1.6 snapshots.

CDI is as of now becoming more and more important in Java EE world. JSF is slowly moving to CDI as to bean management. As of JSF 2.2, @FlowScoped is CDI-only and a new CDI compatible @ViewScoped has been introduced. EJB is slowly moving to CDI as to injection. As of Java EE 6, you can already @Inject an EJB instead of using @EJB. Only self-injection (in order to properly invoke an @Asynchronous method from within another method in same EJB) is still not possible with CDI. Java EE is slowly unifying all management annotations towards CDI and will slowly deprecate the old bean management annotations including the original JSF ones. Java EE 8 will even be more CDI.

Last but not least, a big thanks goes out to OmniFaces user Radu Creanga for providing kickoff code snippets for CDI @FacesConverter, @FacesValidator and @ViewScoped. As we (Arjan and me) never really worked closely with CDI before, we wouldn't immediately have a clue where to start.

Delayed release

First of all, sorry for a somewhat delayed release. It has now already been over 3 months since 1.5 is released. Even though 1.5 was itself also 3 months after 1.4, we initially intented to release about every 2 months ("6 to 8 weeks"). As to the new features, the 1.6 was functionally ready for release almost one month ago. We were very exicted on that. However, intensive testing on a broad range of application servers taught that the new CDI features initially didn't work flawlessly on some of them. The new CDI features were thoroughly fixed, polished and enhanced to the max in order to work on as much application servers as possible. Also, possible workarounds were investigated in order to get the new CDI features to work anyway. After all, the delay was just because we wanted to get it all right instead of releasing a library which didn't work on half of servers.

Compatibility of OmniFaces 1.6 CDI features with application servers

So far, the following containers have been tested on new CDI features:

  • Tomcat 7.0.42: importantly, it deploys the webapplication successfully (on the initial 1.6 snapshot versions it didn't due to unresolved CDI dependencies and that was fixed by abstracting away the CDI providers). Only the CDI features are obviously not usable as plain Tomcat doesn't support CDI out the box.
  • Tomcat 7.0.42 + Weld 1.1.14: when manually installing Weld (and Hibernate Validator! there seems here to be an undocumented dependency on JSR303) on Tomcat, all CDI features start to work flawlessly.
  • JBoss EAP 6.0.1: it has always worked flawlessly from the beginning on.
  • JBoss EAP 6.1: it has always worked flawlessly from the beginning on.
  • TomEE 1.5.2: it has always worked flawlessly from the beginning on.
  • TomEE 1.6.0 SNAPSHOT: snapshot versions from before 11 september 2013 had problems with injecting the @Param. This was reported as OpenWebBeans issue 893. The OpenWebBeans guys were very supportive in this. It turned out that the CDI 1.1 spec was broken on this area (injecting a generic parameterized value holder). After all, OpenWebBeans guys decided to rollback the changes back to the way how CDI 1.0 work. The snapshot versions from after 11 september 2013 work flawlessly.
  • GlassFish 3.1.2.2 - 4.0.0: a web application with OmniFaces 1.6 bundled but without /WEB-INF/beans.xml will throw the following exception during deployment and end up with an unsuccessful deploy:
    Caused by: java.lang.NullPointerException
       at java.util.concurrent.ConcurrentHashMap.hash(ConcurrentHashMap.java:333)
       at java.util.concurrent.ConcurrentHashMap.get(ConcurrentHashMap.java:988)
       at org.jboss.weld.manager.BeanManagerImpl.getBean(BeanManagerImpl.java:1076)
       at org.jboss.weld.manager.BeanManagerImpl.getBean(BeanManagerImpl.java:148)
       at org.glassfish.weld.services.JCDIServiceImpl._createJCDIInjectionContext(JCDIServiceImpl.java:169)
       at org.glassfish.weld.services.JCDIServiceImpl.createJCDIInjectionContext(JCDIServiceImpl.java:146)
       at com.sun.ejb.containers.BaseContainer.createEjbInstanceAndContext(BaseContainer.java:1639)
       at com.sun.ejb.containers.StatelessSessionContainer.createStatelessEJB(StatelessSessionContainer.java:475)
       ...
    
    This is related to GlassFish issue 20566 and already fixed in GlassFish 4.0.1. There are 2 workarounds for ones who can't upgrade:
    • Enable CDI anyway in your web application by creating/generating the /WEB-INF/beans.xml file.
    • Disable implicit CDI artifact checking by executing the following asadmin command:
      $GFHOME/bin/asadmin set configs.config.server-config.cdi-service.enable-implicit-cdi=false
    After that, all CDI features work flawlessly.
  • Liberty 8.5.5: only the @FacesConverter and @FacesValidator failed because of an issue in state saving management of the MyFaces 2.0.5 implementation which is being used under the covers. This is related to MyFaces issue 3257. This is fixed in MyFaces 2.0.8 and 2.1.2. As there's no obvious way to upgrade the Liberty-bundled MyFaces, the workaround is to let those @FacesConverter and @FacesValidator annotated classes implement Serializable whenever you need @EJB and @Inject support in them. Note that they thus don't need to be Serializable when you don't need the CDI features on them. It's by the way somewhat astonishing to see a relatively new application server to be shipped with a de dato more than 2 year old JSF implementation. Registered Liberty users can request IBM here for a newer MyFaces version.
  • WebLogic 12.1.2: a web application with OmniFaces 1.6 bundled will throw the following exceptions during deployment and end up with an unsuccessful deploy:
    org.jboss.weld.exceptions.DeploymentException: WELD-001408 Unsatisfied dependencies for type [ConverterExtension] with qualifiers [@Default] at injection point [[field] @Inject private org.omnifaces.cdi.converter.ConverterManager.extension]
       at org.jboss.weld.bootstrap.Validator.validateInjectionPoint(Validator.java:311)
       at org.jboss.weld.bootstrap.Validator.validateInjectionPoint(Validator.java:280)
       at org.jboss.weld.bootstrap.Validator.validateBean(Validator.java:143)
       at org.jboss.weld.bootstrap.Validator.validateRIBean(Validator.java:163)
       at org.jboss.weld.bootstrap.Validator.validateBeans(Validator.java:382)
       at org.jboss.weld.bootstrap.Validator.validateDeployment(Validator.java:367)
       at org.jboss.weld.bootstrap.WeldBootstrap.validateBeans(WeldBootstrap.java:379)
       ...
    org.jboss.weld.exceptions.DeploymentException: WELD-001408 Unsatisfied dependencies for type [ValidatorExtension] with qualifiers [@Default] at injection point [[field] @Inject private org.omnifaces.cdi.validator.ValidatorManager.extension]
       at org.jboss.weld.bootstrap.Validator.validateInjectionPoint(Validator.java:311)
       at org.jboss.weld.bootstrap.Validator.validateInjectionPoint(Validator.java:280)
       at org.jboss.weld.bootstrap.Validator.validateBean(Validator.java:143)
       at org.jboss.weld.bootstrap.Validator.validateRIBean(Validator.java:163)
       at org.jboss.weld.bootstrap.Validator.validateBeans(Validator.java:382)
       at org.jboss.weld.bootstrap.Validator.validateDeployment(Validator.java:367)
       at org.jboss.weld.bootstrap.WeldBootstrap.validateBeans(WeldBootstrap.java:379)
       ...
    
    The WebLogic guys were however very supportive and mentioned that this problem is already identified and fixed in upcoming 12.1.3. Commercial WebLogic 12.1.2 users can currently patch their server with patch ID 16785005 (which they can download by going to https://support.oracle.com and then go to the patches section). After that, all CDI features work flawlessly.
  • Geronimo 3.0.0: injection in validators and converters doesn't work. We didn't investigate this further as this version is deprecated and users can upgrade easily to 3.0.1.
  • Geronimo 3.0.0.4 (packaged by IBM as WASCE): it has always worked flawlessly from the beginning on.
  • Geronimo 3.0.1: it has always worked flawlessly from the beginning on.
  • Resin 4.0.36: its CDI implementation CanDI didn't support the generic parameterized injection point of @Param nor the generic parameterized @Observes of the extensions for @FacesConverter and @FacesValidator annotations. The web application deploys fine, but once the @Param is used, the following exception is thrown:
     {resin-port-8080-43} classpath:META-INF/caucho/app-default.xml:55:
    javax.enterprise.inject.InjectionException: 'public
    org.omnifaces.cdi.param.ParamValue
    org.omnifaces.cdi.param.RequestParameterProducer.produce(javax.enterprise.inject.spi.InjectionPoint)'
    is an invalid @Produces method because it returns a generic type class
    org.omnifaces.cdi.param.ParamValue
    
    Resin issue 5522 and this mailing list message was posted about this problem. As of now it's still unresolved. There was no feedback for a rather long time. We decided to not wait any longer and proceed with OmniFaces 1.6 release anyway. Injection in validators/converters simply doesn't work. We have found a workaround for this, but this has performance implications, so we decided to not implement it for now. In our opinion, CanDI is simply broken here. Resin users should report/vote the issue to CanDI. Noted should be that the @ViewScoped works flawlessly on Resin.

FacesLocal

OmniFaces 1.6 is not only CDI. The Faces utility class got a new brother, the FacesLocal. You probably already know that the Faces utility class is very handy for oneliner usages. However, once you need to invoke it multiple times in the same method scope, or when you happen to have the FacesContext already available as method argument (e.g., inside a custom component or renderer), then the repeated FacesContext#getCurrentInstance() calls which are been done under the covers by Faces utility class may become a bit too expensive. Whilst the access to a ThreadLocal variable is blazing fast on modern hardware, it is under the covers still blocking all other running threads for some nanoseconds or what.

Therefore, all methods of the Faces utility class which are internally using FacesContext#getCurrentInstance() have been split to a new FacesLocal utility class where those methods now take FacesContext as an argument.

So, instead of for example the following piece of code which blocks all other running threads for no less than 4 times,


User user = Faces.getSessionAttribute("user");
Item item = Faces.evaluateExpressionGet("#{item}");
String cookieValue = Faces.getRequestCookie("cookieName");
List<Locale> supportedLocales = Faces.getSupportedLocales();

you can do the following which does that only once:


FacesContext context = Faces.getContext();
User user = FacesLocal.getSessionAttribute(context, "user");
Item item = FacesLocal.evaluateExpressionGet(context, "#{item}");
String cookieValue = FacesLocal.getRequestCookie(context, "cookieName");
List<Locale> supportedLocales = FacesLocal.getSupportedLocales(context);

Noted should be that all methods of Faces utility class still remain their functionality. It's only under the covers delegating further to FacesLocal. So, the new OmniFaces 1.6 Faces class is still fully backwards compatible and shouldn't break any existing code.

Mojarra 2.2

We briefly tested the OmniFaces 1.6 showcase application on Mojarra 2.2 as well. It works flawlessly on Tomcat 7.0.42 + Mojarra 2.2.3. CDI features also work flawlessly when Weld 1.1.14 was added on top of that. When we removed all references to #{now} and #{startup} from the showcase application, it also works flawlessly on GlassFish 4.0.0 + Mojarra 2.2.2. Those java.util.Date beans failed to initialize due to this GlassFish bug which we hope to be fixed by them soon enough (please vote for the issue if you can).

Noted should be that the OmniFaces CDI @ViewScoped also works flawlessly on Mojarra 2.2, but it's recommended to just use JSF 2.2's own CDI javax.faces.view.ViewScoped annotation for this. Not because OmniFaces one is so bad, on the contrary, but just because standard solutions should be preferred over alternative solutions if they achieve exactly the same.

An overview of all additions/changes/bugfixes in OmniFaces 1.6

Taken over from the What's new? page on showcase:

Added in OmniFaces 1.6

  • Injecting, converting and validating HTTP request parameters via CDI @Param (say, the CDI alternative to <f:viewParam>)
  • Transparent support for dependency injection (CDI and EJB) inside @FacesConverter and @FacesValidator
  • ValueChangeConverter, a base class which converts only when the submitted value is really changed as compared to the model value
  • Components#getCurrentCommand() which returns the currently invoked command component (useful for logging)
  • Components#createValueExpression(), #createMethodExpression(), #createVoidMethodExpression(), #createActionListenerMethodExpression() and #createAjaxBehavior()
  • New JNDI utility class
  • Faces#getBookmarkableURL(), #getRequestHostname() and #setResponseStatus()
  • message attribute for <o:messages> to display a global message in case any of the in for specified components has a faces message
  • Added logException method to FullAjaxExceptionHandler so that it can be overridden for more fine grained control of logging
  • New FacesLocal utility class which extends Faces utility class with methods which take FacesContext as argument
  • CDI compatible @ViewScoped annotation specifically for JSF 2.0/2.1
  • useRequestURI attribute for <o:form> to submit to exactly the same URL as in browser's address bar, with query string
  • of:splitArray() and of:splitList() to split an array or list in subarrays or sublists
  • of:formatPercent() to format a number as a percentage

Changed in OmniFaces 1.6

  • showMessageFor attribute of multi field validators now supports a space separated collection of client IDs where the message should be shown
  • CDN resource handler now supports: 1) always being enabled, also during development stage, 2) wildcard configuration, and 3) EL resolving in CDN URL
  • Faces#redirect() will not use String#format() anymore when no params are supplied, this enables developers to pass already-encoded URLs to Faces#redirect()

Fixed in OmniFaces 1.6

  • Json#encode() incorrectly escaped singlequotes (was causing validation error in jQuery.parseJSON())
  • GzipResponseFilter returned gzipped content as plain text in WebSphere 8.5 / Liberty
  • <o:onloadScript> rendered its body as plain text during an @all render and didn't work anymore during a postback since Mojarra 2.1.24
  • <o:highlight> threw duplicate component ID error in TomEE 1.6.0

Maven download stats

Here are the Maven download stats which are not counted on the Google Code downloads page:

  • June 2013: 2739
  • July 2013: 2432
  • August 2013: 2266

Here's a screenshot of the graph at Maven central (click to enlarge):

2 comments:

rdcrng said...

Kudos to you guys for the extensive testing!

Oleg Varaksin said...

Congrats guys, a good release!

The useRequestURI attribute for <o:form< is very handy for us. Before I had to pass current URL parameters via hidden fiels.