Monday, October 14, 2013

How to install CDI in Tomcat?

Introduction

JSF was since version 2.2 moving towards CDI for bean management. Since JSF 2.2, as part of Java EE 7, there's the new CDI compatible @ViewScoped and there's the CDI-only @FlowScoped which doesn't have an equivalent for @ManagedBean. Since JSF 2.3, as part of Java EE 8, the @ManagedBean and associated scopes from javax.faces.bean package are deprecated in favor of CDI.

Now, there are some JSF / Jakarta Faces users using Tomcat which does as being a barebones JSP/Servlet container not support CDI out the box (also not JSF, you know, you had to supply JSF JARs yourself). If you intend to use CDI on Tomcat, the most straightforward step would be to upgrade it to TomEE. It's exactly like Tomcat, but then with among others OpenWebBeans on top of it, which is Apache's CDI implementation. TomEE installs as easy as Tomcat: just download the ZIP and unzip it. TomEE integrates in Eclipse as easy as Tomcat: just use existing Tomcat server plugin. As a bonus, TomEE also comes with EJB and JPA, making services and DB interaction a breeze.

However, perhaps you just have no control over upgrading the server. In that case, you'd like to supply CDI along with the webapp itself then in flavor of some JARs and additional configuration entries/files. So far, there are 2 major CDI implementations: Weld (the reference implementation) and OpenWebBeans. We'll treat them both in this article.

Install Weld in Tomcat 10+ (last updated: 13 July 2024)

Tomcat 10.0.x is the first version to be "Jakartified", i.e. it's using jakarta.* package instead of javax.* package for the API classes. It is using Servlet 5.0 API of Jakarta EE 9. Tomcat 10.1.x is the second Jakartified version, using Servlet 6.0 API of Jakarta EE 10.

In order to install CDI on Tomcat 10.x, perform the following steps:

  1. For Tomcat 10.1.x, drop weld-servlet-shaded.jar of version 5.x in webapp's /WEB-INF/lib. In case you're using Maven, this is the coordinate:
    <dependency>
        <groupId>org.jboss.weld.servlet</groupId>
        <artifactId>weld-servlet-shaded</artifactId>
        <version>5.1.2.Final</version>
    </dependency>
    

    For Tomcat 10.0.x, use weld-servlet-shaded.jar of version 4.x instead:
    <dependency>
        <groupId>org.jboss.weld.servlet</groupId>
        <artifactId>weld-servlet-shaded</artifactId>
        <version>4.0.3.Final</version>
    </dependency>
    
  2. Create a /META-INF/context.xml file in webapp's web content with following content (or, if you already have one, add just the <Resource> entry to it):
    <Context>
        <Resource name="BeanManager" 
            auth="Container"
            type="jakarta.enterprise.inject.spi.BeanManager"
            factory="org.jboss.weld.resources.ManagerObjectFactory" />
    </Context>
    
    This will register Weld's BeanManager factory in Tomcat's JNDI. This cannot be performed programmatically by Weld because Tomcat's JNDI is strictly read-only. This step is not necessary for Mojarra and OmniFaces because both libraries are able to find it in ServletContext instead. However, there may be other libraries which still expect to find BeanManager in JNDI, so you'd then best keep this configuration file anyway for those libraries.
  3. Create a (empty) /WEB-INF/beans.xml file (no, not in /META-INF! that's only for inside JAR files such as OmniFaces).
  4. Optionally: if you also want to use JSR-303 Bean Validation (@NotNull and friends), then drop jakarta.validation-api.jar of version 3.0.x and hibernate-validator.jar of version 8.0.x in webapp's /WEB-INF/lib, or use below Maven coordinate:
    <dependency>
        <groupId>org.hibernate.validator</groupId>
        <artifactId>hibernate-validator</artifactId>
        <version>8.0.1.Final</version>
    </dependency>
    

    For Tomcat 10.0.x, use hibernate-validator.jar of version 7.0.x instead.

Now your webapp is ready for CDI in Tomcat 10+ via Weld!

Install Weld in Tomcat 9- (last updated: 1 Dec 2022)

The difference with Tomcat 10+ is that Tomcat 9- still uses the old javax.* package instead of the new jakarta.* package. This is not compatible with Weld 4+, you need Weld 3 instead. Perform the following steps:

  1. Drop weld-servlet-shaded.jar of version 3.x in webapp's /WEB-INF/lib. In case you're using Maven, this is the coordinate:
    <dependency>
        <groupId>org.jboss.weld.servlet</groupId>
        <artifactId>weld-servlet-shaded</artifactId>
        <version>3.1.9.Final</version>
    </dependency>
    
  2. Create /META-INF/context.xml file in webapp's web content with following content (or, if you already have one, add just the <Resource> entry to it):
    <Context>
        <Resource name="BeanManager" 
            auth="Container"
            type="javax.enterprise.inject.spi.BeanManager"
            factory="org.jboss.weld.resources.ManagerObjectFactory" />
    </Context>
    
    This will register Weld's BeanManager factory in Tomcat's JNDI. This cannot be performed programmatically by Weld because Tomcat's JNDI is strictly read-only. This step is not necessary if you're targeting at least Mojarra 2.2.11 and/or OmniFaces 2.4 or newer. Both are able to find it in ServletContext instead. However, there may be other libraries which still expect to find BeanManager in JNDI, you'd then best keep this configuration file anyway for those libraries.
  3. Create a (empty) /WEB-INF/beans.xml file (no, not in /META-INF! that's only for inside JAR files such as OmniFaces).
  4. Only if your web.xml is declared conform Servlet version 4.0 instead of 3.1, then you also need to put the @javax.faces.annotation.FacesConfig annotation on an arbitrary CDI managed bean somewhere in the project (usually the one representing the "application-wide config" would be OK).
    package com.example;
    
    import javax.enterprise.context.ApplicationScoped;
    import javax.faces.annotation.FacesConfig;
    
    @FacesConfig
    @ApplicationScoped
    public class Config {
    
    }
    It is indeed utterly unnecessary, but it is what it is.
  5. Optionally: if you also want to use JSR-303 Bean Validation (@NotNull and friends), then drop jakarta.validation-api.jar and hibernate-validator.jar in webapp's /WEB-INF/lib, or use below Maven coordinate:
    <dependency>
        <groupId>org.hibernate.validator</groupId>
        <artifactId>hibernate-validator</artifactId>
        <version>6.2.5.Final</version>
    </dependency>
    

Now your webapp is ready for CDI in Tomcat 9- via Weld! Note that in previous Weld versions you needed to register a <listener> in web.xml. This is not necessary anymore with at least Weld 2.2.0 on a "recent" Tomcat 9- version!

Install OpenWebBeans in Tomcat 10+ (last updated: 13 July 2024)

  1. This is easiest with Maven as OpenWebBeans has quite some sub-dependencies. Here are the coordinates:
    <dependency>
        <groupId>org.apache.openwebbeans</groupId>
        <artifactId>openwebbeans-jsf</artifactId>
        <version>4.0.2</version>
    </dependency>
    <dependency>
        <groupId>org.apache.openwebbeans</groupId>
        <artifactId>openwebbeans-el22</artifactId>
        <version>4.0.2</version>
    </dependency>
    
  2. Create /META-INF/context.xml file in webapp's web content with following content (or, if you already have one, add just the <Resource> entry to it):
    <Context>
        <Resource name="BeanManager" 
            auth="Container"
            type="jakarta.enterprise.inject.spi.BeanManager"
            factory="org.apache.webbeans.container.ManagerObjectFactory" />
    </Context>
    
    This will register OpenWebBeans' BeanManager factory in Tomcat's JNDI. This cannot be performed programmatically by OpenWebBeans because Tomcat's JNDI is strictly read-only.
  3. Add the below <listener> entry to webapp's web.xml:
    <listener>
        <listener-class>org.apache.webbeans.servlet.WebBeansConfigurationListener</listener-class>
    </listener>
    
    This will make sure that OpenWebBeans initializes before Jakarta Faces, otherwise you may face a java.lang.IllegalStateException: It's not allowed to call getBeans(Type, Annotation...) before AfterBeanDiscovery.
  4. Create a (empty) /WEB-INF/beans.xml file (no, not in /META-INF! that's only for JARs such as OmniFaces).
  5. Optionally: if you also want to use JSR-303 Bean Validation (@NotNull and friends), add the below Maven coordinate:
    <dependency>
        <groupId>org.hibernate.validator</groupId>
        <artifactId>hibernate-validator</artifactId>
        <version>8.0.1.Final</version>
    </dependency>
    

Now your webapp is ready for CDI in Tomcat 10+ via OpenWebBeans!

Install OpenWebBeans in Tomcat 9- (last updated: 3 January 2021)

The difference with Tomcat 10+ is that Tomcat 9- still uses the old javax.* package instead of the new jakarta.* package. Perform the following steps:

  1. This is easiest with Maven as OpenWebBeans has quite some sub-dependencies. Here are the coordinates (do note that it also includes JSR-303 Bean Validation API as without it OpenWebBeans would unexpectedly fail deployment with java.lang.TypeNotPresentException: Type javax.validation.ConstraintViolation not present caused by java.lang.ClassNotFoundException: javax.validation.ConstraintViolation):
    <dependency>
        <groupId>javax.enterprise</groupId>
        <artifactId>cdi-api</artifactId>
        <version>2.0</version>
    </dependency>
    <dependency>
        <groupId>org.apache.openwebbeans</groupId>
        <artifactId>openwebbeans-jsf</artifactId>
        <version>2.0.20</version>
    </dependency>
    <dependency>
        <groupId>jakarta.validation</groupId>
        <artifactId>validation-api</artifactId>
        <version>2.0.2</version>
    </dependency>
    
  2. Create /META-INF/context.xml file in webapp's web content with following content (or, if you already have one, add just the <Resource> entry to it):
    <Context>
        <Resource name="BeanManager" 
            auth="Container"
            type="javax.enterprise.inject.spi.BeanManager"
            factory="org.apache.webbeans.container.ManagerObjectFactory" />
    </Context>
    
    This will register OpenWebBeans' BeanManager factory in Tomcat's JNDI. This cannot be performed programmatically by OpenWebBeans because Tomcat's JNDI is strictly read-only.
  3. Add the below <listener> entry to webapp's web.xml:
    <listener>
        <listener-class>org.apache.webbeans.servlet.WebBeansConfigurationListener</listener-class>
    </listener>
    
    This will make sure that OpenWebBeans initializes before OmniFaces, otherwise you may face a java.lang.IllegalStateException: It's not allowed to call getBeans(Type, Annotation...) before AfterBeanDiscovery.
  4. Create a (empty) /WEB-INF/beans.xml file (no, not in /META-INF! that's only for JARs such as OmniFaces).
  5. Optionally: if you also want to use JSR-303 Bean Validation (@NotNull and friends), add the below Maven coordinate:
    <dependency>
        <groupId>org.hibernate.validator</groupId>
        <artifactId>hibernate-validator</artifactId>
        <version>6.2.0.Final</version>
    </dependency>
    

Now your webapp is ready for CDI in Tomcat 9- via OpenWebBeans!

Saturday, October 5, 2013

CDI behaved unexpectedly in EAR, so OmniFaces 1.6.3 released!

After the OmniFaces 1.6 release, some OmniFaces users reported an undeployable EAR when it contained multiple WARs with OmniFaces bundled. It threw the following exception:

java.lang.IllegalArgumentException: 
    Registering converter 'class org.omnifaces.converter.ListIndexConverter' failed,
    duplicates converter ID 'omnifaces.ListIndexConverter' of other converter 'class org.omnifaces.converter.ListIndexConverter'.
        at org.omnifaces.cdi.converter.ConverterExtension.processConverters(ConverterExtension.java:82)
        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
        ...

It completely blocked the deployment. Well, that was pretty awkward and astonishing. I could reproduce it myself as well by creating a very simple EAR with 2 WARs with each an own OmniFaces JAR file and beans.xml and no further additional code. In first instance, I had no idea how/why it was caused and didn't immediately bother to debug it ("will check it later on"), so I decided to just perform a quick fix by bypassing the exception if it concerns exactly the same converter (and validator) class. I.e. duplicate entries were allowed as long as it's exactly the same class.

WELD-001414 Bean name is ambiguous

However, when it's fixed, then it throws:

org.jboss.weld.exceptions.DeploymentException: 
    WELD-001414 Bean name is ambiguous. Name omnifaces_ViewScopeProvider resolves to beans 
    Managed Bean [class org.omnifaces.cdi.viewscope.ViewScopeManager] with qualifiers [@Any @Default @Named], 
    Managed Bean [class org.omnifaces.cdi.viewscope.ViewScopeManager] with qualifiers [@Any @Default @Named]
        at org.jboss.weld.bootstrap.Validator.validateBeanNames(Validator.java:476)
        at org.jboss.weld.bootstrap.Validator.validateDeployment(Validator.java:373)
        at org.jboss.weld.bootstrap.WeldBootstrap.validateBeans(WeldBootstrap.java:379)
        at org.jboss.as.weld.WeldStartService.start(WeldStartService.java:64)
        ...

It still completely blocked the deployment. My astonishment increased. When I remove the ViewScopeManager class for testing purposes, exactly the same exception is thrown for ConverterManager and ValidatorManager. All those beans have one common thing: a hardcoded @Named name:

@Named(ViewScopeProvider.NAME)
public class ViewScopeManager extends ViewScopeProvider {

}

CDI bean management context is by default EAR-wide

It turns out that the CDI is sharing the very same bean management context across all WARs in the same EAR. It is thus apparently not possible to have a @Named("somename") instance with the same name in multiple WARs in the same EAR, also not an implicit one (i.e. @Named without value). This problem was by Arjan T nailed down to the following SSCCE using an explicit name:

Deployment structure:

ear
    war1
        WEB-INF
            classes/test/Foo.class
            beans.xml
    war2
        WEB-INF
            classes/test/Bar.class
            beans.xml

Source codes:

package test;

import javax.enterprise.context.ApplicationScoped;
import javax.inject.Named;

@Named("test")
@ApplicationScoped
public class Foo {
    //
}
package test;

import javax.enterprise.context.ApplicationScoped;
import javax.inject.Named;

@Named("test")
@ApplicationScoped
public class Bar {
    //
}

The same problem manifests when you have e.g. a @Named public class Foo {} in both WARs without an explicit name (and thus the implicit name "foo" is being used). Giving the @Named a different explicit name in each WAR or removing the @Named annotation altogether (with CDI, each Javabean/POJO found in the classpath is still implicitly automatically registered as a CDI managed bean, but without the @Named it would get an autogenerated name) fixes the "WELD-001414 Bean name is ambiguous" deployment problem. This is a very strange and completely unexpected problem. JSF managed beans doesn't behave like that. Spring managed beans also not — as far as I know, I never actually used Spring, but I couldn't find any evidence on the Internet confirming that.

It's beyond me why CDI behaves by default like that and that CDI is apparently not configurable in such way that only one CDI context per WAR of an EAR is created. It'd perhaps make sense if the EAR-wide CDI context was configurable via some configuration setting in a /META-INF/beans.xml in the EAR (not in WAR!), or even implicitly enabled as EAR-wide when a /META-INF/beans.xml is present in the EAR, but it is not. There have been numerous discussions about this, among others in CDI spec issue 129.

Well, to fix this I had to remove the @Named annotation from all OmniFaces-packaged CDI beans ConverterManager, ValidatorManager and ViewScopeManager. They had previously a hardcoded @Named annotation, because it allowed an easy way to grab an instance of them by just programmatically EL-evaluating their name as follows without the need for a CDI dependency (so that OmniFaces also deploys successfully in e.g. Tomcat where CDI isn't supported out the box!):


ConverterProvider instance = Faces.evaluateExpressionGet("#{" + ConverterProvider.NAME + "}");

Note: ConverterProvider is an interface. The ConverterManager is the actual implementation.

Right now, after removing the @Named, they have to be resolved through CDI's own BeanManager as follows *cough*:


@Inject
private BeanManager manager;

// ...

Set<Bean<?>> beans = manager.getBeans(ConverterProvider.class);
Bean<ConverterProvider> bean = (Bean<ConverterProvider>) manager.resolve(beans);
CreationalContext<ConverterProvider> context = manager.createCreationalContext(bean);
ConverterProvider instance = manager.getReference(bean, ConverterProvider.class, context);

In normal CDI managed beans, the BeanManager is available by just @Injecting it. However, this made it undeployable to Tomcat, so I had to resort to grabbing it from JNDI by java:comp/BeanManager (actually, Tomcat required an extra env/ path) and manually performing nasty reflection trickery to grab the instance. You can see all the code in the new org.omnifaces.config.BeanManager enum singleton. Ultimately, with help of it I ended up with the following Tomcat-compatible approach without any direct CDI dependencies:


ConverterProvider instance = BeanManager.INSTANCE.getReference(ConverterProvider.class);

WELD-001408 Unsatisfied dependencies for type

Still, it didn't deploy successfully, causing yet more astonishment:

Caused by: org.jboss.weld.exceptions.DeploymentException: 
    WELD-001408 Unsatisfied dependencies for type [ValidatorExtension] with qualifiers [@Default] at injection point 
    [[BackedAnnotatedParameter] Parameter 1 of [BackedAnnotatedConstructor] @Inject public org.omnifaces.cdi.validator.ValidatorManager(ValidatorExtension)]
        at org.jboss.weld.bootstrap.Validator.validateInjectionPointForDeploymentProblems(Validator.java:405)
        at org.jboss.weld.bootstrap.Validator.validateInjectionPoint(Validator.java:327)
        at org.jboss.weld.bootstrap.Validator.validateGeneralBean(Validator.java:178)
        at org.jboss.weld.bootstrap.Validator.validateRIBean(Validator.java:209)
        at org.jboss.weld.bootstrap.Validator.validateBean(Validator.java:521)
        ...

This is regardless of whether I injected it as a property:


@Inject
private ValidatorExtension extension;

or in the constructor (as per Weld documentation):


private ValidatorExtension extension;

@Inject
public ValidatorManager(ValidatorExtension extension) {
    this.extension = extension;
}

When I remove the ValidatorExtension class for testing purposes, exactly the same exception is thrown for ConverterExtension.

CDI extensions from one WAR not injectable in another WAR of same EAR

After all, it turns out that there's only one CDI extension instance being created EAR-wide, even though the extension is loaded from a JAR in WAR and not from a JAR in EAR. This finally totally explains the initial IllegalArgumentException problem of converters and validators being added twice. The duplicate converter/validator class actually came from the other WAR.

However, as the CDI extension is loaded from a JAR in WAR, which has thus a different classloader than the other WARs, it's uninjectable in another WARs. In effect, the extension is successfully initialized and injected in one WAR, but not in another. The CDI extension loaded from one WAR couldn't be found in the other WARs of the same EAR (because of being loaded by a different .class file), causing exactly the aforementioned exception.

OmniFaces 1.6 has three extensions: ConverterExtension to proactively collect all @FacesConverter instances, ValidatorExtension to proactively collect all @FacesValidator instances and ViewScopeExtension to create the ViewScopeContext for @ViewScoped annotation. The extension wherein all converter/validator instances eligible for CDI injection are being collected, needs to be @Injected in an application scoped bean which implements an interface so that the application could create and resolve them via the dependency-free (Tomcat!) interface.

However, this peculiar CDI problem thus caused the whole CDI extension based approach to be completely unusable for proactively collecting CDI converter/validator instances. A workaround was found by lazily collecting them inside the application scoped bean itself, so that we could get rid of those CDI extensions ConverterExtension and ValidatorExtension.

With all those changes, the CDI converters/validators are finally fully usable throughout all WARs in the same EAR (only not in GlassFish 3.1.2.2 due to a bug in classloading, this is not related to OmniFaces, it works fine in GlassFish 4).

WELD-001303 No active contexts for scope type

The ViewScopeExtension fortunately doesn't need to be @Injected in the ViewScopeManager, so that part worked fine and the whole EAR finally deployed successfully. However, the ViewScopeContext, which is supposed to be registered by the extension, was available only in the very same WAR as where the CDI extension class is loaded from. The other WARs would throw the following exception:

org.jboss.weld.context.ContextNotActiveException:
    WELD-001303 No active contexts for scope type org.omnifaces.cdi.ViewScoped
        at org.jboss.weld.manager.BeanManagerImpl.getContext(BeanManagerImpl.java:578)
        at org.jboss.weld.bean.proxy.ContextBeanInstance.getInstance(ContextBeanInstance.java:71)
        at org.jboss.weld.bean.proxy.ProxyMethodHandler.invoke(ProxyMethodHandler.java:79)
        at com.example.CdiViewScopedBean$Proxy$_$$_WeldClientProxy.submit(CdiViewScopedBean$Proxy$_$$_WeldClientProxy.java)
        ...

Noted should be that it didn't block the deployment. It's thrown during runtime when an OmniFaces CDI @ViewScoped bean is referenced for the first time and thus needs to be constructed.

Custom scope context created by CDI extension from one WAR not available in another WAR of same EAR

This is very unfortunate as there doesn't seem to be any way to register a custom context programmatically other than via a CDI extension. There's thus no feasible solution to this problem. We would be all ears to implement something like a ServletContextListener to programmatically register the custom CDI scope context WAR-wide, but there seems to be no API-provided way for even something simple like that. All initialization has to take place in a CDI extension per se.

So far, the new OmniFaces CDI @ViewScoped works unfortunately in only one WAR of such an EAR. This all is also summarized along with a table of the tested application servers in Known issues of OmniFaces CDI features in combination with specific application servers. I didn't test it with JSF 2.2's @ViewScoped, but by looking at the codebase of Mojarra and MyFaces, it would expose exactly the same problem when JSF 2.2 JARs are being bundled in those WARs instead of provided by the appserver (as by default on a decent Java EE application server).

OmniFaces 1.6.1 1.6.2 1.6.3 released

Our important goal was to make OmniFaces at least deployable, i.e. it shouldn't be blocking the deployment of WAR or EAR in any way, even though some CDI features may work only half due to the way how CDI works in EARs. We succeed in this with 1.6.3. It's available in Maven on the following coordinates:


<dependency>
    <groupId>org.omnifaces</groupId>
    <artifactId>omnifaces</artifactId>
    <version>1.6.3</version>
</dependency>

WAR-only users are also recommended to upgrade, because during some intensive stress testing of zeef.com by our server admin with hundreds of sessions, the new version 1.4 of the ConcurrentLinkedHashMap, which is repackaged by OmniFaces specifically for usage in <o:cache> and @ViewScoped, was without any clear reason allocating hundreds of megabytes of memory in flavor of a lot of PaddedAtomicLong references. Downgrading the repackaged ConcurrentLinkedHashMap back to its previous version 1.2 fixed the problem of excessive memory allocation while the performance has not significantly changed. Note that older OmniFaces versions before 1.6 already used version 1.2 of ConcurrentLinkedHashMap, so OmniFaces version 1.6 is the only version having this problematic version 1.4 of ConcurrentLinkedHashMap.

Also, a new CDI extension was added which should bypass ("veto") the registration as CDI managed bean of all classes in org.omnifaces package other than org.omnifaces.cdi.* and org.omnifaces.showcase.*. This should eliminate exceptions about ambiguous injection points (some classes in OmniFaces implement Map and that may conflict with a @Producer returning Map) and warnings in server logs about unmanagable beans (like as produced by OpenWebBeans in TomEE 1.6 and reported as issue 894).

By the way, to the users who encountered those problems in their EARs after upgrading to OmniFaces 1.6: herewith we want to wholeheartedly say "Sorry for that!" to them and hopefully OmniFaces 1.6.3 will do the needful job for them. We should really have tested the EAR part as well although those problems were after all completely unexpected. Also, thank you very much for reporting back those problems.

UPDATE: well, it appears that 1.6.1 blocked deployment on non-CDI containers like Tomcat and friends. This was my own mistake. My "Tomcat-Without-CDI" project in Eclipse was misconfigured to have a physical 1.6 JAR in /WEB-INF/lib which got precedence over 1.6.1 in "Deployment Assembly". In effect, I actually never tested 1.6.1 in Tomcat, but always 1.6. Now, 1.6.2 has really been tested on Tomcat and it works fine over there (and of course all others which we tested before).

UPDATE 2: an user of a server which we never used, Google App Engine, reported that 1.6.1/1.6.2 blocked deployment because GAE doesn't support JNDI. This is in turn fixed in 1.6.3.

Conclusion: CDI spec is broken as to behavior in EAR

The CDI guys really need to work out this. What started as clean code in OmniFaces converter/validator injection ended up as ugly code with some reflection trickery and even then it still doesn't work 100% as intented in an EAR with OmniFaces in multiple WARs.

In my opinion, the right solution would be to make the EAR-wide CDI context configurable via some configuration setting in a /META-INF/beans.xml in the EAR (not in WAR!), or even implicitly enabled as EAR-wide when a /META-INF/beans.xml is present in the EAR and otherwise default to WAR-wide (as intuitively expected by developers).

Admittedly, the CDI spec is in Java EE 6 "only" the crisp first version 1.0, so some astonishing teething problems like this are understandable (Servlet 1.0 and JSF 1.0 were also tear jerking), but those issues are even not covered/fixed in 1.1, so all of above still fails the same way in Java EE 7 containers (GlassFish 4.0 and WildFly 8 alpha 4 were tested).

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):

Wednesday, July 31, 2013

Serving multiple images from database as a CSS sprite

Introduction

In the first public beta version of ZEEF which was somewhat thrown together (first get the minimum working using standard techniques, then review, refactor and improve it), all favicons were served individually. Although they were set to be agressively cached (1 year, whereby a reload is when necessary forced by the timestamp-in-query-string trick with the last-modified timestamp of the link), this resulted in case of an empty cache in a ridiculous amount of HTTP requests on a subject page with relatively a lot of links, such as Curaçao by Bauke Scholtz:

Yes, 209 image requests of which 10 are not for favicons, which nets as 199 favicon requests. Yes, that much links are currently on the Curaçao subject. The average modern webbrowser has only 6~8 simultaneous connections available on a specific domain. That's thus a huge queue. You can see it in the screenshot, it took on an empty cache nearly 5 seconds to get them all (on a primed cache, it's less than 1 second).

If you look closer, you'll see that there's another problem with this approach: links which doesn't have a favicon re-requests the very same default favicon again and again with a different last-modified timestamp of the link itself, ending up in copies of exactly same image in the browser cache. Also, links from the same domain which share the same favicon, have their favicons duplicated this way. In spite of the agressive cache, this was simply too inefficient.

Converting images to common format and size

The most straightforward solution would be to serve all those favicons as a single CSS sprite and make use of CSS background-position to reference the right favicon in the sprite. This however requires that all favicons are first parsed and converted to a common format and size which allows easy manipulation by standard Java 2D API (ImageIO and friends) and easy generation of the CSS sprite image. PNG was chosen as format as that's the most efficient and lossless format. 16x16 was chosen as default size.

As first step, a favicon parser was created which verifies and parses the scraped favicon file and saves every found image as PNG (the ICO format can store multiple images, usually each with a different dimension, e.g. 16x16, 32x32, 64x64, etc). For this, Image4J (a mavenized fork with bugfix) has been of a great help. The original Image4J had only a minor bug, it ran in an infinite loop on favicons with broken metadata, such as this one. This was fixed by vijedi/image4j. However, when an ICO file contained multiple images, this fix discarded all images, instead of only the broken one. So, another bugfix was done on top of that (which by the way just leniently returned the "broken" image — in fact, only the metadata was broken, not the image content itself). Every single favicon will now be parsed by ICODecoder and BMPDecoder of Image4J and then ImageIO#read() of standard Java SE API in this sequence. Whoever returned the first non-null BufferedImage(s) without exceptions, this will be used. This step also made us able to completely bypass the content-type check which we initially had, because we discovered that a lot of websites were doing a bad job in this, some favicons were even served as text/html which caused false negatives.

As second step, if the parsing of a favicon resulted in at least one BufferedImage, but no one was in 16x16 dimension, then it will be created based on the firstnext dimension which is resized back to 16x16 with help of thebuzzmedia/imgscalr which yielded high quality resizings.

Finally all formats are converted to PNG and saved in the DB (and cached in the local disk file system).

Serving images as CSS sprite

For this a simple servlet was been used which does basically ultimately the following in doGet() (error/cache checking omitted for simplicity):


Long pageId = Long.valueOf(request.getPathInfo().substring(1));
Page page = pageService.getById(pageId);
long lastModified = page.getLastModified();
byte[] content = faviconService.getSpriteById(pageId, lastModified);

if (content != null) { // Found same version in disk file system cache.
    response.getOutputStream().write(content);
    return;
}

Set<Long> faviconIds = new TreeSet<>();
faviconIds.add(0L); // Default favicon, appears as 1st image of sprite.
faviconIds.addAll(page.getFaviconIds());

int width = Favicon.DEFAULT_SIZE; // 16px.
int height = width * faviconIds.size();

BufferedImage sprite = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
Graphics2D graphics = sprite.createGraphics();
graphics.setBackground(new Color(0xff, 0xff, 0xff, 0)); // Transparent.
graphics.fillRect(0, 0, width, height);

int i = 0;

for (Long faviconId : faviconIds) {
    Favicon favicon = faviconService.getById(faviconId); // Loads from disk file system cache.
    byte[] content = favicon.getContent();
    BufferedImage image = ImageIO.read(new ByteArrayInputStream(content));
    graphics.drawImage(image, 0, width * i++, null);
}

ByteArrayOutputStream output = new ByteArrayOutputStream();
ImageIO.write(sprite, "png", output);
content = output.toByteArray();
faviconService.saveSprite(pageId, lastModified, content); // Store in disk file system cache.
response.getOutputStream().write(content);

To see it in action, you can get all favicons of the page Curaçao by Bauke Scholtz (which has page ID 18) as CSS sprite on the following URL: https://zeef.com/favicons/page/18.

Serving the CSS file containing sprite-image-specific selectors

In order to present the CSS sprite images at the right places, we should also have a simple servlet which generates the desired CSS stylesheet file containing sprite-image-specific selectors with the right background-position. The servlet should basically ultimately do the following in doGet() (error/cache checking omitted to keep it simple):


Long pageId = Long.valueOf(request.getPathInfo().substring(1));
Page page = pageService.getById(pageId);

Set<Long> faviconIds = new TreeSet<>();
faviconIds.add(0L); // Default favicon, appears as 1st image of sprite.
faviconIds.addAll(page.getFaviconIds());

long lastModified = page.getLastModified().getTime();
int height = Favicon.DEFAULT_SIZE; // 16px.

PrintWriter writer = response.getWriter();
writer.printf("[class^='favicon-']{background-image:url('../page/%d?%d')!important}", 
    pageId, lastModified);
int i = 0;

for (Long faviconId : faviconIds) {
    writer.printf(".favicon-%s{background-position:0 -%spx}", faviconId, height * i++);
}

To see it in action, you can get the CSS file of the page Curaçao by Bauke Scholtz (which has page ID 18) on the following URL: https://zeef.com/favicons/css/18.

Note that the background-image URL has the page's last modified timestamp in the query string which should force a browser reload of the sprite whenever a link has been added/removed in the page. The CSS file itself has also such a query string as you can see in HTML source code of the ZEEF page, which is basically generated as follows:


<link id="favicons" rel="stylesheet" 
    href="//zeef.com/favicons/css/#{zeef.page.id}?#{zeef.page.lastModified.time}" />

Also note that the !important is there to overrule the default favicon for the case the serving of the CSS sprite failed somehow. The default favicon is specified in general layout CSS file layout.css as follows:


#blocks .link.block li .favicon,
#blocks .link.block li [class^='favicon-'] {
    position: absolute;
    left: -7px;
    top: 4px;
    width: 16px;
    height: 16px;
}

#blocks .link.block li [class^='favicon-'] {
    background-image: url("#{resource['zeef:images/default_favicon.png']}");
}

Referencing images in HTML

It's rather simple, the links were just generated in a loop whereby the favicon image is represented via a plain HTML <span> element basically as follows:


<a id="link_#{linkPosition.id}" href="#{link.targetURL}" title="#{link.defaultTitle}">
    <span class="favicon-#{link.faviconId}" />
    <span class="text">#{linkPosition.displayTitle}</span>
</a>

The HTTP requests on image files have been reduced from 209 to 12 (note that 10 non-favicon requests have increased to 11 non-favicon requests due to changes in social buttons, but that's not further related to the matter):

It took on an empty cache on average only half a second to download the CSS file and another half a second to download the CSS sprite. Per saldo, that's thus 5 times faster with 197 connections less! On a primed cache it's even not requested at all. Noted should be that I'm here behind a relatively slow network and that the current ZEEF production server on a 3rd party host isn't using "state of the art" hardware yet. The hardware will be handpicked later on once we grow.

Reloading CSS sprite by JavaScript whenever necessary

When you're logged in as page owner, you can edit the page by adding/removing/drag'n'drop links and blocks. This all takes place by ajax without a full page reload. Whenever necessary, the CSS sprite can during ajax oncomplete be forced to be reloaded by the following script which references the <link id="favicons">:


function reloadFavicons() {
    var $favicons = $("#favicons");
    $favicons.attr("href", $favicons.attr("href").replace(/\?.*/, "?" + new Date().getTime()));
}

Basically, it just updates the timestamp in the query string of the <link href> which in turn forces the webbrowser to request it straight from the server instead of from the cache.

Note that in case of newly added links which do not exist in the system yet, favicons are resolved asynchronously in the background and pushed back via Server-Sent Events. In this case, the new favicon is still downloaded individually and explicitly set as CSS background image. You can find it in the global-push.js file:


function updateLink(data) {
    var $link = $("#link_" + data.id);
    $link.attr("title", data.title);
    $link.find(".text").text(data.text);
    $link.find("[class^='favicon-']").attr("class", "favicon")
        .css("background-image", "url(/favicons/link/" + data.icon + "?" + new Date().getTime() + ")");
    highlight($link);
}

But once the HTML DOM representation of the link or block is later ajax-updated after an edit or drag'n'drop, then it will re-reference the CSS sprite again.

The individual favicon request is also done in "Edit link" dialog. The servlet code for that is not exciting, but for the case you're interested, the URL is like https://zeef.com/favicons/link/354 and all the servlet basically does is (error/cache checking omitted for brevity):


Long linkId = Long.valueOf(request.getPathInfo().substring(1));
Link link = linkService.getById(linkId);
Favicon favicon = faviconService.getById(link.getFaviconId());
byte[] content = favicon.getContent();
response.getWriter().write(content);

Note that individual favicons are not downloaded by their own ID, but instead by the link ID, because a link doesn't necessarily have any favicon. This way the default favicon can easily be returned.

Tuesday, June 11, 2013

OmniFaces 1.5 is released!

OmniFaces 1.5 has been released!

Along a bunch new EL functions, new tag attributes, new utility methods and new converters there are 2 new major additions: 1) specifying JSF and HTML code as output format parameter; 2) escapable faces messages, targeting multiple components for messages and markupless rendering of messages.

Specifying JSF and HTML code as output format parameter

You'll probably recognize yourself in this:


general.contact = contact us
some.paragraph = Please <a href="{0}">{1}</a> for more information.

with


<h:outputFormat value="#{text['some.paragraph']}" escape="false">
    <f:param value="#{request.contextPath}/contact.xhtml" />
    <f:param value="#{text['general.contact']}" />
</h:outputFormat>

The resource bundle file is polluted with HTML code. This is not very maintenance friendly and the translator responsible for translating and maintaining the bundle files probably doesn't know anything about HTML. Also, you as developer don't have the possibility to use the JSF <h:link>, so you're forced to use a plain HTML <a> element (which doesn't understand JSF implicit navigation nor context root, so you've to specify the full URL as well).

With OmniFaces 1.5, the in OmniFaces 1.4 introduced <o:param>, will have new support for JSF and HTML code as children. This way it's possible to achieve the following:


general.contact = contact us
some.paragraph = Please {0} for more information.

with


<h:outputFormat value="#{text['some.paragraph']}" escape="false">
    <o:param><h:link outcome="contact" value="#{text['general.contact']}" /></o:param>
</h:outputFormat>

Escapable faces messages, targeting multiple components for messages and markupless rendering of messages

The standard JSF <h:messages> has been extended into <o:messages> with the following new features:

  • Possibility to specify multiple client IDs space separated in the for attribute.
  • Control HTML escaping by the new escape attribute.
  • Control iteration markup fully by the new var attribute which sets the current FacesMessage in the request scope and disables the default table/list rendering.

Particularly being able to use HTML in faces messages is a rather commonly seen/heard requirement. With the escape="false" it's now possible to use HTML in faces messages.


<o:messages escape="false" />

Beware of XSS attack implications when you're about to inline user-controlled input in such a message!

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

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

Added in OmniFaces 1.5

  • Decode UIComponent children in <o:param> when no value attribute is specified (so that you can specify JSF/HTML code as outputFormat parameter)
  • Allow endusers to specify custom passthrough attributes for Html5RenderKit
  • Added new includeRequestParams attribute to <o:form>
  • <o:messages> which extends <h:messages> with support for multiple client IDs in for attribute, ability to disable HTML escaping and ability to perform markupless rendering like <ui:repeat>
  • Components#includeCompositeComponent() to programmatically include a composite component in given parent component
  • Add list based alternatives for the converters that automatically convert based on select items: omnifaces.ListIndexConverter and omnifaces.ListConverter
  • Message interpolator for Bean Validation that allows a component's label to be inserted in the middle of a message
  • New Faces#getRequestURLWithQueryString() which returns full request URL with query string
  • Added reset attribute to <o:cache> as an alternative for the programmatic resetting of a cache entry
  • Added converters for an Iterable to List and DataModel, so these can be used in <ui:repeat> and <h:dataTable>
  • New protected methods FullAjaxExceptionHandler#findExceptionRootCause() and #findErrorPageLocation() so that it can easier be subclassed
  • EL functions of:toJson(), of:replaceAll() and of:matches()

Changed in OmniFaces 1.5

  • Obtain current component via UIComponent#getCurrentComponent() when it's null in Html5RenderKit (so that it also works with PrimeFaces input components which for some reason send null as component argument)
  • Improved CallbackPhaseListener to support multiple callbacks instead of only one; the old Events#setCallbackXxx() methods are now deprecated
  • Support @all, @form and @this in Ajax#update()
  • Moved Faces#includeFacelet() to Components#includeFacelet(); the old one is now deprecated

Fixed in OmniFaces 1.5

  • Add missing createResource() overrides to all ResourceHandlers (so that it also works in combination with other resource handlers which don't use createResource(library, name))
  • Normalize view ID in RestorableViewHandler (so that it also works when another suffix mapping is used than *.xhtml, this worked in 1.3 but broke in 1.4 (sorry!))
  • Add missing support for noSelectionValue attribute in SelectItemsConverter
  • Action URL generation for FacesViews didn't take other view handlers into account, now it does (so that it also works with PrimeFaces 4.0 Dialog framework).

Maven download stats

Here are the Maven download stats:

  • March 2013: 2137
  • April 2013: 1995
  • May 2013: 2089

Here's a screenshot of the graph at Maven central:

Tuesday, March 12, 2013

OmniFaces 1.4 is released! UPDATE: it's 1.4.1!

OmniFaces 1.4 has just been released! A little later than planned due to all the needed remaining work on FacesViews by Arjan Tijms which he blogged further about at Easy extensionless URLs in JSF with OmniFaces 1.4. Note that the OmniFaces showcase is now by default also configured to make use of it, and that it also got a new domain name, showcase.omnifaces.org, which also references an OpenShift application, but now with the HAProxy cartridge.


UPDATE: on March 12, one day later, the GzipResponseFilter of 1.4 was been reported to have an issue in Glassfish 3. This is tracked down to an if (!committed) block which was introduced during FacesViews testing. This change has been reverted and an 1.4.1 release is available at homepage and Maven. Please do not use the 1.4 release, but instead the 1.4.1!


Among the new things are:

Import functions

The handy <o:importConstants> now got a new brother: the <o:importFunctions> which allows you to import all public static non-void methods of a given class (usually an utility class) into the EL scope without the need to register them individually via a custom .taglib.xml file.


<o:importFunctions type="java.lang.Math" var="m" />

#{m:abs(-10)}
#{m:max(bean.number1, bean.number2)}

A (minor) disadvantage is that you'll miss intellisense/autocomplete in the average IDE.

Remove FacesServlet URL pattern from CSS/JS/image resources

The new UnmappedResourceHandler allows the developer to reference CSS background images in CSS resources without the need to clutter the CSS file with #{resource} expressions to reference CSS background images, which turned out to be a general maintenance pain when dealing with 3rd party libraries/plugins providing CSS/images (e.g. Twitter Bootstrap and several jQuery plugins).

Next to the <resource-handler> entry in faces-config.xml, you only also need to manually add the standard JSF resource URL prefix pattern /javax.faces.resource/* to the FacesServlet mapping in web.xml.

f:param with converter

Further, the standard <f:param> is been extended to implement ValueHolder interface in flavor of <o:param> and thus support a Converter. This is helpful in keeping the code DRY when you want to pass complex Java objects as a request parameter. I.e. you do not need


<h:link value="Edit Foo" outcome="/foo/edit">
    <f:param name="id" value="#{foo.id}" />
</h:link

anymore, but just


<h:link value="Edit Foo" outcome="/foo/edit">
    <o:param name="id" value="#{foo}" />
</h:link

provided that you've a @FacesConverter(forClass=Foo.class) which returns its id in getAsString().

An overview of what's new in OmniFaces 1.4

The below is basically taken over from the "What's new" page in the showcase.

Added in OmniFaces 1.4

  • Faces#getMetadataAttribute() and getMetadataAttributes() methods
  • New 'for' attribute in <o:validator> so that it can target inputs in composites
  • New TreeModel#getNextSibling() and getPreviousSibling() methods
  • of:coalesce() function
  • <o:importFunctions> taghandler to import public static non-void methods of a given type as EL functions
  • of:getMonth(), of:getShortMonth(), of:getDayOfWeek() and of:getShortDayOfWeek() functions
  • <o:param> which extends <f:param> with support for a Converter
  • UnmappedResourceHandler which removes the JSF prefix/suffix mapping from resource paths
  • Major overhaul of FacesViews; support for custom locations (including root) and extension, plus configurable actions when resource with extension or resource on configured path is requested
  • Programmatically getting list of welcome pages and checking if access is allowed to a URL according to security constraints
  • OmniFaces version will now be logged to webapp log in this format: INFO: Using OmniFaces version 1.4

Changed in OmniFaces 1.4

  • Support for Javabeans in Json#encode() as used by Ajax#data()
  • Recognize and workaround for JUEL 2.2.5 and before bug in <o:methodParam> (not needed for JUEL 2.2.6 and later)
  • Support spellcheck attribute on HtmlInputText and HtmlInputTextarea by Html5RenderKit
  • Support autofocus, pattern and placeholder attributes on HtmlInputSecret by Html5RenderKit
  • Delegate rendering to JSF when currently not in render response phase in FullAjaxExceptionHandler
  • Skip AbortProcessingException in FullAjaxExceptionHandler
  • Support java.util.TimeZone argument in of:formatDateWithTimezone()
  • Support com.example.SomeClass.SomeEnum on top of com.example.SomeClass$SomeEnum notation for inner enums in <o:importConstants>
  • Skip unrendered components during component tree visit of ResetInputAjaxActionListener
  • ELException will now also be unwrapped by FacesExceptionFilter and FullAjaxExceptionFilter in order to improve ajax exception handling in MyFaces

Bugfixed in OmniFaces 1.4

  • GzipResponseFilter incorrectly skipped gzip when the written data was not within servletcontainer's and Facelets buffer size
  • Fix initializaiton ordering problem of FacesViews in among others Glassfish server and removed accidentally introduced Servlet 3.0 dependency

Maven download stats

Here are the Maven download stats:

  • December 2012: 911
  • January 2013: 1427
  • February 2013: 1315

The months January and February average currently thus on nearly 46 Maven downloads per day, rounded down.

Tuesday, February 12, 2013

Stateless JSF

Just wanted to spread the word: as per Mojarra 2.1.19 / 2.2.0 (both currently only available as snapshot) it's very easy to make the JSF view completely stateless by just setting the transient attribute of the <f:view> to true:


    <f:view transient="true">
        Your regular content
    </f:view>

That's all. Really. It works for me. The view state isn't been created and thus the session also won't be created when not created yet. This is very useful for e.g. a JSF based login form on the public/stateless side of a website.

Remember to put the associated managed bean in the request scope instead of view/session scope, otherwise you're simply defeating the meaning of the word "stateless".

A lot of people have been waiting very long for this. See also issue 2731 and this blog of Mojarra developer Manfred Riem.

Wednesday, January 23, 2013

Apache Shiro, is it ready for Java EE 6? (a JSF2-Shiro Tutorial)

WARNING - OUTDATED CONTENT!

Currently Shiro has fine Jakarta Faces integration.

Introduction

After having used Java EE container managed authentication and even having homegrown JSF based authentication for a good amount of years and getting a bit tired of it, I wanted to review how well the current 3rd party Java EE authentication frameworks integrate in Java EE 6 with JSF 2, CDI and EJB 3. Apache Shiro (formerly known as JSecurity) is one of them. I also briefly looked at Spring Security, but it's not usable in JSF/CDI/EJB beans, but only in Spring beans. You'd almost be forced to migrate from Java EE to Spring altogether which just doesn't make sense anymore these non-J2EE days.

Back to top

What container managed authentication can (not)

First we need to understand how container managed authentication is insufficient for a bit more than just a public website with an "admin backend".

  • No builtin "Remember Me" functionality. Servlet 3.0 made this however easy to homegrow.
  • No straightforward way to redisplay login page with login errors in case you've specified the same page as both login page and error page. There are however tricks.

  • No remembering of POST request data when a form is submitted while the session is expired. There is no way to retain this data for resubmission other than homebrewing an authentication filter or going for a 3rd party one.
  • No permission based restriction. A "permission" basically checks a specific task/action depending on currently logged-in user which may be shared across multiple roles. Role based restriction is sometimes too rough, requiring you to create ridiculous meta-roles like "SUPER_USER", "POWER_USER", "SUPER_ADMIN", "ALMOST_ADMIN", "GOD" and so on.
  • No container-independent way of configuring the datasource containing users/roles. Not all containers offer the same granularity of configuring the datasource, making the datasource or even the datamodel potentially unportable across containers. JASPIC is intented to solve that, but it has as of now still container-specific problems.

Noted should be that the "Remember Me" functionality has become a breeze since the new Servlet 3.0 (Java EE 6) programmatic login facility in flavor of HttpServletRequest#login(). Before that, it was not possible without hacking around with container specific classes or fiddling with JASPIC. Also noted should be that since Java EE 5 the container managed security also offers annotation based restriction like @RolesAllowed in EJB methods which is very useful.

As to JSF, you can get the login username of the currently logged-in user in the view by #{request.remoteUser}. You can perform role based checks by #{request.isUserInRole('SOME')} in e.g. the rendered attribute of a JSF component. In the backing bean, the same methods are also available via ExternalContext. However, usually it are the EJBs who do the business actions and should thus do the security checks. But they are not allowed to grab the FacesContext, let alone the ExternalContext. For the role checks, the aforementioned annotations should be used instead.

Well, let's look if Shiro can do it better/easier.

Back to top

Preparing

The below article/tutorial assumes in Eclipse terms that you've created a "Dynamic Web Project" with JSF, CDI and JPA facets and that you've configured a Java EE 6 web profile compatible container like Glassfish or JBoss AS as target container. It also assumes that you know how to create a Hello World JSF application. So you know where/how to put the necessary Facelets code (I don't like repeating all the <html>, <h:body>, etc boilerplate in all Facelets examples).

Back to top

Getting Shiro to work

We need to have at least the shiro-core and shiro-web JARs in our webapp's /WEB-INF/lib. Then, the documentation for configuring Shiro on web applications is available here. Okay, we thus need a servlet context listener so that Shiro can do its global initialization thing and a servlet filter for the actual authentication/authorization works whereby Shiro also wraps the request/response by Shiro-controlled ones (so that e.g. #{request.remoteUser} and so on still keep its functionality). Add them to the webapp's /WEB-INF/web.xml as follows:


    <listener>
        <listener-class>org.apache.shiro.web.env.EnvironmentLoaderListener</listener-class>
    </listener>

    <filter>
        <filter-name>shiroFilter</filter-name>
        <filter-class>org.apache.shiro.web.servlet.ShiroFilter</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>shiroFilter</filter-name>
        <url-pattern>/*</url-pattern>
        <dispatcher>REQUEST</dispatcher>
        <dispatcher>FORWARD</dispatcher>
        <dispatcher>INCLUDE</dispatcher>
        <dispatcher>ERROR</dispatcher>
    </filter-mapping>

Noted should be that FORWARD and INCLUDE dispatchers are never internally used by JSF on Facelets. They are in any way kept there in web.xml for the sake of completeness (perhaps you want to ever use JSP in the same webapp? you never know). If you intend to navigate to a JSF resource which possibly requires re-checking authentication, then you'd better make it a fullworthy GET request by either a plain GET link/button or a POST-Redirect-GET action. See also when should I use <h:outputLink> instead of <h:commandLink>?

As to configuring Shiro, it uses the INI file syntax. There are several default filters available. If you need BASIC authentication, use the authcBasic filter. If you need FORM authentication, use authc filter.

Let's start with the simplest possible configuration, a BASIC authentication on everything inside the /app/ subfolder of the webapp and a single admin user. First create a /WEB-INF/shiro.ini file with the following contents (yes, the example username is "admin" and the example password is just like that, "password"):

[users]
admin = password

[urls]
/app/** = authcBasic

Let's test it ... Hey, that worked quite good!

Back to top

Form based authentication

Turning it into form based authentication is just a matter of changing authBasic to authc (again, see the list of default filters for the filter name). Only, the login page path defaults to /login.jsp. As JSP is a deprecated view technology since JSF 2.0 at December 2009, we obviously don't want to keep this setting as such. We want to change it to Facelets as /login.xhtml. This can be done by setting the authc.loginUrl entry. Note that you also need to include it in the [urls] list. Also note that I of course assume that you've mapped the FacesServlet on an URL pattern of *.xhtml. If you're using a different URL pattern, then you should also change it as such in the INI file.

[main]
authc.loginUrl = /login.xhtml

[users]
admin = password

[urls]
/login.xhtml = authc
/app/** = authc

The shiro.ini file syntax follows Javabean/EL look-a-like configuration of properties. If you look closer at the javadoc of FormAuthenticationFilter, then you'll see that loginUrl is actually a property of FormAuthenticationFilter! The shiro.ini basically interprets the entry key as a setter operation and uses the entry value as the set value. This configuration style however requires Apache Commons BeanUtils in the webapp. So, drop its JAR in /WEB-INF/lib as well.

The HTML form syntax of /login.xhtml as shown below is also rather straightforward. Remember, you can without problems use "plain HTML" in a JSF/Facelets page. Note that Shiro sets the login failure error message as a request attribute with the default name shiroLoginFailure. This was nowhere mentioned in the Shiro web documentation! I figured it by looking at the Javadoc of FormAuthenticationFilter.


    <h2>Login</h2>
    <form method="post">
        <label for="username">Username:</label>
        <input type="text" id="username" name="username" />
        <br/>
        <label for="password">Password:</label>
        <input type="password" id="password" name="password" />
        <br/>
        <label for="rememberMe">Remember me:</label>
        <input type="checkbox" id="rememberMe" name="rememberMe" value="true" />
        <br/>
        <input type="submit" value="Login" />
        <span class="error">#{shiroLoginFailure}</span>
    </form>

Let's test it ... Yes, that works also quite good.

Noted should be that the failure message just represents the fully qualified classname of the thrown exception. E.g. org.apache.shiro.authc.UnknownAccountException. This is intented to be further used as key of some i18n resource bundle like so #{bundle[shiroLoginFailure]} or perhaps a Map property.

Back to top

Remember Me

The form based authentication example as shown in the previous chapter has also a "Remember Me" checkbox. It indeed sets a rememberMe cookie on the response with some long and encrypted value. However, after I deleted the JSESSIONID cookie in the browser, it still brought me back to the login page as if the "Remember Me" was never ticked.

A little research with help of Google brought me to Matt Raible's blog from May 2011 (almost 2 years ago) wherein he initially also complained that the Shiro Remember Me didn't work. It turns out that you've to use the UserFilter instead of the FormAuthenticationFilter! As per the default filters listing, it has the name user. So, fix the shiro.ini accordingly.

[main]
authc.loginUrl = /login.xhtml
user.loginUrl = /login.xhtml

[users]
admin = password

[urls]
/login.xhtml = authc
/app/** = user

Finally, it works.

In hindsight, this approach of Shiro makes sense. You would at times of course also like to distinguish "remembered" users from really authenticated users, so that you can if necessary re-ask authentication on really sensitive submits. I am however surprised that this was nowhere mentioned in the Remember Me section of the Shiro web documentation, let alone in the rest of the Shiro web documentation, even though I initially expected that this was a classic RTFM case. I wonder, have the Shiro guys ever reviewed the documentation on that point after the complaint of Matt Raible?

Note that I had to duplicate authc.loginUrl and user.loginUrl to prevent authentication failures on authc from being still redirected to the default login URL /index.jsp. It would make more sense if the UserFilter basically extends from FormAuthenticationFilter so that both cases are covered. Maybe Shiro has its own reasons for this separation of the both filters, but I don't see it for now.

By the way, the cookie value turns out to be a Base64 encoded representation of an AES encrypted representation of a serialized representation of a collection of principals. Basically, it contains the necessary username information which is decryptable with the right key (which a hacker can in case of a default key easily figure by just looking at Shiro's source code). Not my favorite approach, but the AES encryption is really strong and you can (must) specify a custom AES cipher key or even a complete custom manager which can deal with the cookie value format you need. The custom AES chiper key can if necessary be specified as follows in shiro.ini according to this example in the Shiro INI documentation:


securityManager.rememberMeManager.cipherKey = 0x3707344A4093822299F31D008

The value is obviously fully to your choice. You can generate your own as follows using Shiro's own cryptographic API:


String key = Hex.encodeToString(new AesCipherService().generateNewKey().getEncoded());
System.out.println("0x" + key);

Back to top

Behavior on session expiration

The behavior on session expiration is rather straightforward on GET requests. When not remembered, it shows the login page and on successful login, it redirects you to the initially requested page, complete with the original query string. Exactly as expected.

The behavior is however not consistent on POST requests. I've observed the behavior on session expiration in 4 different cases:

  1. Synchronous POST without Remember Me

    This discarded all the POST data and the user was after the login redirected to the application root instead of the initial page. You'd have to navigate to the initial page and re-enter all the POST data yourself. As to the wrong redirect to the root, this turns out to be the default fallback URL for the case there's no saved request. However, there's definitely a saved request, so I peeked around in the source code and it turns out that the saved request is only valid on a GET request and thus for POST the fallback URL is instead been used.

    Well, this makes perhaps sense in some cases, but this should really have been better documented. In case of JSF, it doesn't harm if you use the POST URL for a GET request as JSF by default submits the <h:form> to exactly the same URL as the page is been requested with by GET (also known as "postback"). A concrete solution to the problem of being redirected to the wrong URL is discussed later in chapter Programmatic login.

    Further, it would have been be nice if Shiro remembered the POST request request body as well and upon a successful login via POST, replace the current request body with it and perform a 307 redirect. Or, perhaps, at least offer a way to obtain the saved POST request parameters by programmatic means, so that the developer can choose for setting the initial POST request URL as login form URL and all POST request parameters as hidden input fields of the login form. When the login is successful, then Shiro should not perform a redirect, but just let the request continue to the application. Theoretically, this is possible with a custom Shiro filter.

  2. Synchronous POST with Remember Me

    On a default JSF setup, this failed with a ViewExpiredException. This is not Shiro's fault. The actual login was successfully performed. However, as the session is expired, the JSF view state is also expired. You can solve this by either setting the javax.faces.STATE_SAVING_METHOD to client, or by using OmniFaces <o:enableRestorableView>. Once fixed that, the login went smoothly. All the POST data was successfully submitted. Of course, the login happens within the very same request already and effectively no redirect has taken place.

  3. Asynchronous POST without Remember Me

    This failed without any feedback. Shiro forced a synchronous redirect to the login URL which resulted in an ajax response which is effectively empty, leaving the enduser with no form of feedback. The enduser is facing the same page as if the form submit did nothing. In JSF ajax, redirects are not instructed by a HTTP 302 response, but by a special XML response. See also among others this stackoverflow.com answer.

    It'd be nice if Shiro performed a if ("partial/ajax".equals(request.getHeader("Faces-Request"))) check and returned the appropriate XML response. Fortunately, it's possible to extend Shiro's authentication filter to take this into account. A concrete solution is discussed later in chapter Make Shiro JSF ajax aware.

  4. Asynchronous POST with Remember Me

    This behaved exactly the same as the synchronous one described at point 2. The principle is also not much different though. You're however dependent on having a decent ajax exception handler if you would get feedback about the ViewExpiredException or not.

Back to top

Using a JSF form

Instead of a plain HTML form, you can of course also use a JSF form so that you can benefit of JSF builtin required="true" validation and/or to get look'n'feel in line with the rest of the site (e.g. by PrimeFaces). You, as a JSF developer, should probably already know for long that JSF prepends the ID of the parent form in the ID (and also name) attribute of the input components. However, Shiro checks by default the request parameters with the exact name username, password and rememberMe only. Fortunately, this is configurable in shiro.ini. Look in the javadoc of FormAuthenticationFilter, there are setters for the properties usernameParam, passwordParam and rememberMeParam.

So, given the following JSF form in /login.xhtml,


    <h2>Login</h2>
    <h:form id="login">
        <h:panelGrid columns="3">
            <h:outputLabel for="username" value="Username:" />
            <h:inputText id="username" required="true" />
            <h:message for="username" />

            <h:outputLabel for="password" value="Password" />
            <h:inputSecret id="password" required="true" />
            <h:message for="password" />

            <h:outputLabel for="rememberMe" value="Remember Me" />
            <h:selectBooleanCheckbox id="rememberMe" />
            <h:panelGroup />

            <h:panelGroup />
            <h:commandButton value="Login" />
            <h:panelGroup styleClass="error" rendered="#{not facesContext.validationFailed}">
                #{shiroLoginFailure}
            </h:panelGroup>
        </h:panelGrid>
    </h:form>

and the following shiro.ini,

[main]
authc.loginUrl = /login.xhtml
authc.usernameParam = login:username
authc.passwordParam = login:password
authc.rememberMeParam = login:rememberMe
user.loginUrl = /login.xhtml

[users]
admin = password

[urls]
/login.xhtml = authc
/app/** = user

you're already set. It works fine. Note that there's no means of a backing bean. That's also not necessary given that Shiro is performing the business logic by itself based on the request parameters.

Note that it's not possible to login by ajax this way. The submit itself would work fine and you would be logged in, but the navigation does not work. You won't be navigated to the initially requested page at all. So, if you're using JSF component libraries with builtin ajax facilities like PrimeFaces, then you'd need to set ajax="false" on the command button. Also, input validation should not be done via ajax. It will work, but those requests will trigger Shiro's "remember the last accessed restricted page" mechanism and cause Shiro to redirect to the wrong URL after successful login, namely the one on which the ajax validation request is fired.

If you really need to login or validate via ajax, then you can always consider programmatic login.

Back to top

Programmatic login

Shiro also offers a programmatic login possibility. This is more useful if you want to be able to utilize for example ajax based validation on the required input fields and/or want to be able to perform the login by ajax. The programmatic login API is rather simple, as documented in Shiro web documentation:


SecurityUtils.getSubject().login(new UsernamePasswordToken(username, password, remember));

However, the documentation doesn't seem to explain in any way how to obtain the saved request URL in order to perform a redirect to that URL. Some Googling brought me at this Shiro mailing list discussion which shows that the actual redirect can be done as follows:


WebUtils.redirectToSavedRequest(request, response, fallbackURL);

This would however not work when the current request concerns a JSF ajax request. As explained before, it has to return a special XML response instructing the JSF ajax engine to perform a redirect by itself. This functionality is provided by JSF's own ExternalContext#redirect() method which transparently distinghuishes ajax from non-ajax requests. So, we really need to have just the saved request URL so that we could perform the redirect ourselves. After peeking around in the Shiro source code how it deals with the saved request URL, I figured that the saved request URL is available as follows:


String savedRequestURL = WebUtils.getAndClearSavedRequest(request).getRequestUrl();

Okay, let's put the pieces together. First create a backing bean (for practical reasons we're using CDI managed bean annotations instead of JSF managed bean annotations, further in the article annotation based restriction will be discussed and that works only in managed beans when using CDI; feel however free to use JSF managed bean annotations instead for programmatic login):

package com.example.controller;

import java.io.IOException;

import javax.enterprise.context.RequestScoped;
import javax.inject.Named;

import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.web.util.SavedRequest;
import org.apache.shiro.web.util.WebUtils;
import org.omnifaces.util.Faces;
import org.omnifaces.util.Messages;

@Named
@RequestScoped
public class Login {

    public static final String HOME_URL = "app/index.xhtml";

    private String username;
    private String password;
    private boolean remember;

    public void submit() throws IOException {
        try {
            SecurityUtils.getSubject().login(new UsernamePasswordToken(username, password, remember));
            SavedRequest savedRequest = WebUtils.getAndClearSavedRequest(Faces.getRequest());
            Faces.redirect(savedRequest != null ? savedRequest.getRequestUrl() : HOME_URL);
        }
        catch (AuthenticationException e) {
            Messages.addGlobalError("Unknown user, please try again");
            e.printStackTrace(); // TODO: logger.
        }
    }

    // Add/generate getters+setters.
}

Note that, as you're reading this blog, I'll for simplicity also assume that you're familiar with OmniFaces which minimizes some FacesContext boilerplate. The Faces and Messages utility classes are from OmniFaces.

Now change the login form in /login.xhtml accordingly to submit to that and perform the necessary ajax magic:


    <h2>Login</h2>
    <h:form id="login">
        <h:panelGrid columns="3">
            <h:outputLabel for="username" value="Username:" />
            <h:inputText id="username" value="#{login.username}" required="true">
                <f:ajax event="blur" render="m_username" />
            </h:inputText>
            <h:message id="m_username" for="username" />

            <h:outputLabel for="password" value="Password:" />
            <h:inputSecret id="password" value="#{login.password}" required="true">
                <f:ajax event="blur" render="m_password" />
            </h:inputSecret>
            <h:message id="m_password" for="password" />

            <h:outputLabel for="rememberMe" value="Remember Me:" />
            <h:selectBooleanCheckbox id="rememberMe" value="#{login.remember}" />
            <h:panelGroup />

            <h:panelGroup />
            <h:commandButton value="Login" action="#{login.submit}" >
                <f:ajax execute="@form" render="@form" />
            </h:commandButton>
            <h:messages globalOnly="true" layout="table" />
        </h:panelGrid>
    </h:form>

Now edit the shiro.ini accordingly to get rid of authc filter:

[main]
user.loginUrl = /login.xhtml

[users]
admin = password

[urls]
/login.xhtml = user
/app/** = user

Let's test it ... Yes, it works again quite good. Additional bonus is that this approach also fixes the problem that Shiro by default redirects to a fallback URL when the saved request concerns a POST request. See also point 1 of chapter Behavior on session expiration.

Noted should be that when the page with the POST form is been requested by GET with a request parameter like so /customers/edit.xhtml?id=42 in order to set the Customer via <f:viewParam> and so on, then you would after successful login be redirected to /customers/edit.xhtml. This is not exactly Shiro's fault, it's JSF itself who is by default submitting to an URL without the query string in the <h:form>. If you have those parameters definied as <f:viewParam>, then you can just replace the form by the OmniFaces <o:form> as follows to include the view parameters in the form action URL:


    <o:form includeViewParams="true">
        ...
    </o:form>

This way JSF will submit to the URL with the view parameters in the query string and thus give you the opportunity to redirect to exactly that URL after successful login. See also this stackoverflow.com question and answer.

Back to top

Programmatic logout

The programmic logout API is also simple, but it is nowhere mentioned in the Shiro web documentation. It was however easily found with common sense and IDE autocomplete on the Subject instance. So, here is the oneliner:


SecurityUtils.getSubject().logout();

You can also just invalidate the HTTP session, however that doesn't trash the "Remember Me" cookie in case you're using it and the user would be auto-logged in again on the subsequent request when "Remember Me" was ticked. So, invalidating the session should merely be done to cleanup any other user-related state in the session, not to perform the actual logout.

Here's how the logout backing bean could look like:

package com.example.controller;

import java.io.IOException;

import javax.enterprise.context.RequestScoped;
import javax.inject.Named;

import org.apache.shiro.SecurityUtils;
import org.omnifaces.util.Faces;

@Named
@RequestScoped
public class Logout {

    public static final String HOME_URL = "login.xhtml";

    public void submit() throws IOException {
        SecurityUtils.getSubject().logout();
        Faces.invalidateSession();
        Faces.redirect(HOME_URL);
    }

}

Note that the redirect is really mandatory as the invalidated session is still available in the response of the current request. It's only not available anymore in the subsequent request. Also note that this issue is not specific to Shiro/JSF, just to HTTP in general.

In the view, just provide a command link/button which invokes #{logout.submit}.


    <h:form>
        <h:commandButton value="logout" action="#{logout.submit}" />
    </h:form>

Back to top

Make Shiro JSF ajax aware

As per point 3 of chapter Behavior on session expiration, the redirect to login page in case of session expiration wasn't properly dealt with in case of JSF ajax requests. The enduser basically ends up with no single form of feedback.

Fortunately, the Shiro API is designed in such way that this is fairly easy overridable with the following filter:

package com.example.filter;

import java.io.IOException;

import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;

import org.apache.shiro.web.filter.authc.UserFilter;

public class FacesAjaxAwareUserFilter extends UserFilter {

    private static final String FACES_REDIRECT_XML = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
            + "<partial-response><redirect url=\"%s\"></redirect></partial-response>";

    @Override
    protected void redirectToLogin(ServletRequest req, ServletResponse res) throws IOException {
        HttpServletRequest request = (HttpServletRequest) req;

        if ("partial/ajax".equals(request.getHeader("Faces-Request"))) {
            res.setContentType("text/xml");
            res.setCharacterEncoding("UTF-8");
            res.getWriter().printf(FACES_REDIRECT_XML, request.getContextPath() + getLoginUrl());
        }
        else {
            super.redirectToLogin(req, res);
        }
    }

}

To get it to run, just set it as user filter in shiro.ini (no, do not use the @WebFilter annotation nor the web.xml!):

[main]
user = com.example.filter.FacesAjaxAwareUserFilter
user.loginUrl = /login.xhtml

[users]
admin = password

[urls]
/login.xhtml = user
/app/** = user

Let's test it ... Yes, session expiration on ajax requests is now also properly handled.

Back to top

Configuring JDBC realm

In a bit sane Java EE web application wherein the container managed authentication is insufficient, the users are more than often not stored in some text file, but instead in a SQL database, along with their roles. In Shiro, you can use a Realm to configure it to obtain the users and roles (and permissions) from a SQL database. One of ready-to-use realms is the JdbcRealm which is relatively easy to setup via shiro.ini. Note also that this way the Realm is fully portable across different containers, which is definitely a big plus.

In this article we'll setup a test database with help of the embedded database engine H2 (formerly known as Hypersonic) and create a JPA model and an EJB service. Noted should be that the H2/JPA/EJB part is not necessary for functioning of Shiro. You're free in the choice of database vendor and the way how you model it and how you interact with it. In any way, the JPA/EJB examples are concretely used in the Register user and Hashing the password cases later on.

For your information only (you don't need to create them yourself at this point), the (Hibernate-generated) DDLs of the tables look basically like this:

create table User (
    id bigint generated by default as identity (start with 1), 
    password varchar(255) not null, 
    username varchar(255) not null, 
    primary key (id), 
    unique (username)
)

create table UserRoles (
    userId bigint not null, 
    role varchar(255)
)

Noted should be that the role column could better have been an enumerated type (enum, set, etc) depending on DB make/version. This is however beyond the scope of this article. Let's try to keep it simple for now.

First download the H2 JAR file (no, not the Windows installer nor the zip file, just the JAR file from Maven or Sourceforge!) and drop it in /WEB-INF/lib folder. Then edit shiro.ini accordingly to get rid of the [users] entry and utilize the users database via a JdbcRealm:

[main]
# Create and setup user filter.
user = com.example.filter.FacesAjaxAwareUserFilter
user.loginUrl = /login.xhtml

# Create JDBC realm.
jdbcRealm = org.apache.shiro.realm.jdbc.JdbcRealm

# Configure JDBC realm datasource.
dataSource = org.h2.jdbcx.JdbcDataSource
dataSource.URL = jdbc:h2:~/test
dataSource.user = sa
dataSource.password = sa
jdbcRealm.dataSource = $dataSource

# Configure JDBC realm SQL queries.
jdbcRealm.authenticationQuery = SELECT password FROM User WHERE username = ?
jdbcRealm.userRolesQuery = SELECT role FROM UserRoles WHERE userId = (SELECT id FROM User WHERE username = ?)

[urls]
/login.xhtml = user
/app/** = user

Note that the Javabean/EL-style properties of the data source in shiro.ini should actually match the properties of the real data source instance.

At this point, it isn't possible to test the login thing as the database is basically empty :) Continue to the next chapters to create the model and the service, so that we can create users.

Back to top

JPA model and EJB service

Now the model and service. First create the following user role enum, com.example.model.Role:

package com.example.model;

public enum Role {

    EMPLOYEE, MANAGER, ADMIN;

}

Then create the following user entity, com.example.model.User:

package com.example.model;

import java.util.List;

import javax.persistence.CollectionTable;
import javax.persistence.Column;
import javax.persistence.ElementCollection;
import javax.persistence.Entity;
import javax.persistence.EnumType;
import javax.persistence.Enumerated;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.validation.constraints.NotNull;

@Entity
@NamedQueries({
    @NamedQuery(
        name = "User.find",
        query = "SELECT u FROM User u WHERE u.username = :username AND u.password = :password"),
    @NamedQuery(
        name = "User.list",
        query = "SELECT u FROM User u")
})
public class User {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @NotNull
    @Column(unique = true)
    private String username;

    @NotNull
    private String password;

    @ElementCollection(targetClass = Role.class, fetch = FetchType.EAGER)
    @Enumerated(EnumType.STRING)
    @CollectionTable(name = "UserRoles", joinColumns = { @JoinColumn(name = "userId") })
    @Column(name = "role")
    private List<Role> roles;

    // Add/generate getters+setters and hashCode+equals.
}

Then create the following service class, com.example.service.UserService:

package com.example.service;

import java.util.List;

import javax.ejb.Stateless;
import javax.enterprise.context.RequestScoped;
import javax.enterprise.inject.Produces;
import javax.inject.Named;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;

import org.omnifaces.cdi.ViewScoped;

import com.example.model.User;

@Stateless
public class UserService {

    @PersistenceContext
    private EntityManager em;

    public User find(Long id) {
        return em.find(User.class, id);
    }

    public User find(String username, String password) {
        List<User> found = em.createNamedQuery("User.find", User.class)
            .setParameter("username", username)
            .setParameter("password", password)
            .getResultList();
        return found.isEmpty() ? null : found.get(0);
    }

    @Produces
    @Named("users")
    @RequestScoped
    public List<User> list() {
        return em.createNamedQuery("User.list", User.class).getResultList();
    }

    public Long create(User user) {
        em.persist(user);
        return user.getId();
    }

    public void update(User user) {
        em.merge(user);
    }

    public void delete(User user) {
        em.remove(em.contains(user) ? user : em.merge(user));
    }

}

Then create the following datasource in /WEB-INF/web.xml:


    <data-source>
        <name>java:app/H2/test</name>
        <class-name>org.h2.jdbcx.JdbcDataSource</class-name>
        <url>jdbc:h2:~/test</url>
        <user>sa</user>
        <password>sa</password>
        <transactional>false</transactional> <!-- https://community.jboss.org/message/730085 -->
        <max-pool-size>10</max-pool-size>
        <min-pool-size>5</min-pool-size>
        <max-statements>0</max-statements>
    </data-source>

Finally create the following persistence unit in /META-INF/persistence.xml (please note that this configuration indeed drops the DB tables on every server restart, again, it's just for testing purposes):


    <persistence-unit name="test-jsf-shiro">
        <jta-data-source>java:app/H2/test</jta-data-source>
        <class>com.example.model.User</class>

        <properties>
            <!-- Hibernate specific properties. -->
            <property name="hibernate.dialect" value="org.hibernate.dialect.HSQLDialect" />
            <property name="hibernate.hbm2ddl.auto" value="create-drop" />
            <property name="hibernate.show_sql" value="true" />
            
            <!-- EclipseLink specific properties. -->
            <property name="eclipselink.ddl-generation" value="create-tables" />
            <property name="eclipselink.ddl-generation.output-mode" value="database" />
        </properties>
    </persistence-unit>

Again, noted should be that all of the above boilerplate is not mandatory for the functioning of Shiro. It's merely to create and find users as demonstrated in the following chapter.

Back to top

Register user

In order to create users via JSF, we need the following backing bean:

package com.example.controller;

import javax.annotation.PostConstruct;
import javax.ejb.EJB;
import javax.enterprise.context.RequestScoped;
import javax.inject.Named;

import org.omnifaces.util.Messages;

import com.example.model.User;
import com.example.service.UserService;

@Named
@RequestScoped
public class Register {

    private User user;

    @EJB
    private UserService service;

    @PostConstruct
    public void init() {
        user = new User();
    }

    public void submit() {
        try {
            service.create(user);
            Messages.addGlobalInfo("Registration suceed, new user ID is: {0}", user.getId());
        }
        catch (RuntimeException e) {
            Messages.addGlobalError("Registration failed: {0}", e.getMessage());
            e.printStackTrace(); // TODO: logger.
        }
    }

    public User getUser() {
        return user;
    }

}

And this view, /register.xhtml, using among others OmniFaces <o:importConstants> to ease importing enums into <f:selectItems> and OmniFaces omnifaces.GenericEnumConverter in order to convert the selected roles to a proper List<Role> instead of a List<String>:


    <o:importConstants type="com.example.model.Role" />

    <h2>Register</h2>
    <h:form id="register">
        <h:panelGrid columns="3">
            <h:outputLabel for="username" value="Username:" />
            <h:inputText id="username" value="#{register.user.username}" required="true">
                <f:ajax event="blur" render="m_username" />
            </h:inputText>
            <h:message id="m_username" for="username" />

            <h:outputLabel for="password" value="Password:" />
            <h:inputSecret id="password" value="#{register.user.password}" required="true">
                <f:ajax event="blur" render="m_password" />
            </h:inputSecret>
            <h:message id="m_password" for="password" />

            <h:outputLabel for="roles" value="Roles:" />
            <h:selectManyCheckbox id="roles" value="#{register.user.roles}" required="true"
                layout="pageDirection" converter="omnifaces.GenericEnumConverter">
                <f:selectItems value="#{Role}" />
            </h:selectManyCheckbox>
            <h:message id="m_roles" for="roles" />

            <h:panelGroup />
            <h:commandButton value="Register" action="#{register.submit}" >
                <f:ajax execute="@form" render="@form" />
            </h:commandButton>
            <h:messages globalOnly="true" layout="table" />
        </h:panelGrid>
    </h:form>

Finally, at this point we should be able to create users via database and login them programmatically via a JDBC realm!

If you want to have an overview of all users, just start off with this table (note that this effectively retrieves the list via @Produces annotation of UserService#list()):


    <h2>Users</h2>
    <h:dataTable value="#{users}" var="user">
        <h:column>#{user.id}</h:column>
        <h:column>#{user.username}</h:column>
        <h:column>#{user.password}</h:column>
        <h:column>#{user.roles}</h:column>
    </h:dataTable>

Don't forget to ajax-update it on register, if necessary.

Back to top

Hashing the password

Storing password plaintext in the DB is not exactly secure. We'd of course like to hash them, if necessary along with a salt. Shiro offers several helper classes for hashing which allows you to do the job with a minimum of effort. Let's pick the most strongest hash algorithm: SHA256.

First edit Register#submit() method accordingly to hash like that (note: don't use redisplay="true" in the JSF password field! otherwise the hashed value would be reflected in the UI):


    user.setPassword(new Sha256Hash(user.getPassword()).toHex());
    service.create(user);
    // ...

Then tell Shiro's JDBC realm to hash like that as well. Add the following lines to the end of the [main] section of shiro.ini:


# Configure JDBC realm password hashing.
credentialsMatcher = org.apache.shiro.authc.credential.HashedCredentialsMatcher
credentialsMatcher.hashAlgorithmName = SHA-256
jdbcRealm.credentialsMatcher = $credentialsMatcher

That's it!

Noted should be that the HashedCredentialsMatcher javadoc strongly recommends salting passwords. Here's a cite of relevance:

ALWAYS, ALWAYS, ALWAYS SALT USER PASSWORDS!

That makes completely sense. However, configuring a salted password hash on a JDBC realm is in the current Shiro 1.2.1 version surprisingly not as easy as configuring the password hash. You basically need to homebrew a custom salt aware realm as described in this blog. In other words, in spite of their own strong recommendation, Shiro does not offer any ready-to-use realms for this. They seem to be working on that for version 1.3.

In any case, we're now at the point that all the essential stuff is properly configured and working smoothly: register, login, remember me, logout, users database and password hashing. Now, let's go a step further with user and role based restriction in Facelets rendering, HTTP requests and even bean methods.

Back to top

Restriction in Facelets rendering

With Shiro you can just continue using #{request.remoteUser} and #{request.isUserInRole('SOME')} the usual way as if you were using container managed authentication.


    <h:panelGroup rendered="#{empty request.remoteUser}">
        Welcome! Well, it seems that you are not logged in! 
        Please <h:link value="login" outcome="login" /> to see more awesomeness on this site!
    </h:panelGroup>

    <h:panelGroup rendered="#{not empty request.remoteUser}">
        Welcome! You're logged in as #{request.remoteUser}. Enjoy the site!

        <h:panelGroup rendered="#{request.isUserInRole('ADMIN')}">
            You're an ADMIN user! Wow, we'll render some more cool buttons for you soon.
        </h:panelGroup>

        <h:panelGroup rendered="#{not request.isUserInRole('ADMIN')}">
            You're not an ADMIN user. You probably will never become one.
        </h:panelGroup>
    </h:panelGroup>

This is absolutely a big plus. Not only makes this the views fully portable across container managed authentication and Shiro, but there are no other usable Shiro-offered ways, even no Facelets tags!

Shiro has a JSP/GSP based tag library, but JSP tags are unfortunately unusable in Facelets. It surprises me somewhat that they don't have a Facelets tag library even though it was born in the end of 2006 and has become the default view technology since Java EE 6 in the end of 2009. Even more, JSP is since then officially deprecated as the default view technology of JSF. If you're using the old Facelets 1.x (for JSF 1.x, which is over 6 years old already), then you can go for this Shiro Facelets taglib which was created by a power user of Shiro. Unfortunately, Facelets 1.x tags are unusable in Facelets 2.x (although it can easily be converted with a relative minimum of effort). All with all, it gives me somewhat the impression that Shiro is still hanging in the ancient J2EE/Spring era instead of moving forward along with the new Java EE 5/6 technologies.

Back to top

Restriction in HTTP requests

You can use the [urls] section of shiro.ini for this, as explained in Shiro web documentation. You can use the RolesAuthorizationFilter to restrict access on a per-role basis. This filter is available by the name roles which requires a commaseparated string of required roles as value. Here's an example:


[urls]
/login.xhtml = user
/app/admin/** = user, roles[ADMIN]
/app/manager/** = user, roles[MANAGER]
/app/employee/** = user, roles[EMPLOYEE]
/app/hr/** = user, roles[MANAGER,EMPLOYEE]
/app/** = user
/public/** = anon

Note that multiple roles are treated as an AND condition, so the resource /app/hr/** requires the user to have both the roles MANAGER and EMPLOYEE. Also note that there's an anon filter allowing access by anonymous users (guests, non-logged-in users).

Also note that when you intend to restrict users applicationwide by /**, that you should not forget to explicitly allow access to JSF resources (CSS/JS/image files), otherwise your login page would appear without any styles/scripts/images. You can achieve that by mapping the JSF resource URL pattern to the anon filter:


[urls]
/login.xhtml = user
/javax.faces.resource/** = anon
/** = user

If a rule is violated, then Shiro returns a HTTP 401 error, which is customizable by the following error page entry in web.xml as follows, which can be a fullworthy JSF page:


    <error-page>
        <error-code>401</error-code>
        <location>/WEB-INF/errorpages/unauthorized.xhtml</location>
    </error-page>

Everything with regard to HTTP request restriction seems to work fine. The configuraiton is quite simple, too.

Back to top

Programmatic restriction in bean methods

In JSF/CDI/EJB beans, the currently logged-in user is available by SecurityUtils.getSubject() which returns a Subject which has in turn several checkXxx(), hasXxx() and isXxx() methods to check the roles and permissions. The checkXxx() ones will throw an exception in case of a mismatch.

package com.example.controller;

import javax.enterprise.context.RequestScoped;
import javax.inject.Named;

import org.apache.shiro.SecurityUtils;

@Named
@RequestScoped
public class SomeBean {

    public void doSomethingWhichIsOnlyAllowedByADMIN() {
        SecurityUtils.getSubject().checkRole("ADMIN");

        // ...
    }

}

This throws a org.apache.shiro.authz.AuthorizationException, which is customizable by the following error page entry in web.xml as follows, which can be a fullworthy JSF page:


    <error-page>
        <exception-type>org.apache.shiro.authz.AuthorizationException</exception-type>
        <location>/WEB-INF/errorpages/unauthorized.xhtml</location>
    </error-page>

Note that this exception may be wrapped in a FacesException if it's been caught by JSF and delegated to the container. In that case, you'd like to use OmniFaces FacesExceptionFilter to unwrap it, so that the container properly receives a org.apache.shiro.authz.AuthorizationException instead of a FacesException.

Back to top

Declarative restriction in bean methods

Shiro has several annotations like @RequiresRoles. Its documentation mentions that it only requires AOP like AspectJ or Spring. Well, as we're running Java EE 6, we'd like to utilize a Java EE interceptor to check the invoked CDI/EJB class/method for Shiro annotations. No need for AspectJ/Spring. Java EE 5 has already standardized it for long.

While looking around if someone else didn't already invent such an interceptor, I stumbled via list of Shiro Articles on among others this blog. The code at that blog was however not really useable as it didn't check for Shiro-specific annotations, but instead a homebrewed one. It also performed a permission check on certain method name patterns which is perhaps smart (convention over configuration), but not really declarative. There's another blog which has basically the same idea as I'm looking for, but the overall code quality is pretty poor (e.g. catching NPE...), it was clearly written by a starter. It must be doable with less than half of the provided code (and even with full coverage of annotations instead of only three).

Well, let's write it myself. First create an annotation which the interceptor has to intercept on:

package com.example.interceptor;

import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

import javax.interceptor.InterceptorBinding;

@Inherited
@InterceptorBinding
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
public @interface ShiroSecured {
    //
}

Then create the interceptor itself:

package com.example.interceptor;

import java.io.Serializable;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.Arrays;

import javax.interceptor.AroundInvoke;
import javax.interceptor.Interceptor;
import javax.interceptor.InvocationContext;

import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authz.UnauthenticatedException;
import org.apache.shiro.authz.annotation.RequiresAuthentication;
import org.apache.shiro.authz.annotation.RequiresGuest;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.apache.shiro.authz.annotation.RequiresRoles;
import org.apache.shiro.authz.annotation.RequiresUser;
import org.apache.shiro.subject.Subject;

@Interceptor
@ShiroSecured
public class ShiroSecuredInterceptor implements Serializable {

    private static final long serialVersionUID = 1L;

    @AroundInvoke
    public Object interceptShiroSecurity(InvocationContext context) throws Exception {
        Subject subject = SecurityUtils.getSubject();
        Class<?> c = context.getTarget().getClass();
        Method m = context.getMethod();

        if (!subject.isAuthenticated() && hasAnnotation(c, m, RequiresAuthentication.class)) {
            throw new UnauthenticatedException("Authentication required");
        }

        if (subject.getPrincipal() != null && hasAnnotation(c, m, RequiresGuest.class)) {
            throw new UnauthenticatedException("Guest required");
        }

        if (subject.getPrincipal() == null && hasAnnotation(c, m, RequiresUser.class)) {
            throw new UnauthenticatedException("User required");
        }

        RequiresRoles roles = getAnnotation(c, m, RequiresRoles.class);

        if (roles != null) {
            subject.checkRoles(Arrays.asList(roles.value()));
        }

        RequiresPermissions permissions = getAnnotation(c, m, RequiresPermissions.class);

        if (permissions != null) {
             subject.checkPermissions(permissions.value());
        }

        return context.proceed();
    }

    private static boolean hasAnnotation(Class<?> c, Method m, Class<? extends Annotation> a) {
        return m.isAnnotationPresent(a)
            || c.isAnnotationPresent(a)
            || c.getSuperclass().isAnnotationPresent(a);
    }

    private static <A extends Annotation> A getAnnotation(Class<?> c, Method m, Class<A> a) {
        return m.isAnnotationPresent(a) ? m.getAnnotation(a)
            : c.isAnnotationPresent(a) ? c.getAnnotation(a)
            : c.getSuperclass().getAnnotation(a);
    }

}

Note: the abbreviated c, m and a variablenames are not my style, it's just to get the code to fit in 100 char max length of this blog — I have my editor set at 120 chars. Also note that the annotations are checked on the target class' superclass as well as the target class may in case of CDI actually be a proxy and Shiro's annotations don't have @Inherited set.

In order to get it to work on CDI managed beans, first register the interceptor in /WEB-INF/beans.xml as follows:

<?xml version="1.0" encoding="UTF-8"?>
<beans
    xmlns="http://java.sun.com/xml/ns/javaee"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://docs.jboss.org/cdi/beans_1_0.xsd"
>
    <interceptors>
        <class>com.example.interceptor.ShiroSecuredInterceptor</class>
    </interceptors>
</beans>

Similarly, in order to get it to work on EJBs, first register the interceptor in /WEB-INF/ejb-jar.xml as follows (or in /META-INF/ejb-jar.xml if you've a separate EJB project in EAR):

<?xml version="1.0" encoding="UTF-8"?>
<ejb-jar
    xmlns="http://java.sun.com/xml/ns/javaee"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
        http://java.sun.com/xml/ns/javaee/ejb-jar_3_1.xsd"
    version="3.1"
>
    <interceptors>
        <interceptor>
            <interceptor-class>com.example.interceptor.ShiroSecuredInterceptor</interceptor-class>
        </interceptor>
    </interceptors>
    <assembly-descriptor>
        <interceptor-binding>
            <ejb-name>*</ejb-name>
            <interceptor-class>com.example.interceptor.ShiroSecuredInterceptor</interceptor-class>
        </interceptor-binding>
    </assembly-descriptor>
</ejb-jar>

On a CDI managed bean, you need to set the custom @ShiroSecured annotation in order to get the interceptor to run.

package com.example.controller;

import javax.enterprise.context.RequestScoped;
import javax.inject.Named;

import org.apache.shiro.authz.annotation.RequiresRoles;

import com.example.interceptor.ShiroSecured;

@Named
@RequestScoped
@ShiroSecured
public class SomeBean {

    @RequiresRoles("ADMIN")
    public void doSomethingWhichIsOnlyAllowedByADMIN() {
        // ...
    }

}

This is not necessary on an EJB, the ejb-jar.xml has already registered it on all EJBs.

Noted should be that this interceptor feature is in no way supported on JSF managed beans. That's exactly the reason why this article is using CDI managed beans from the beginning on. If your business requirements however allow to have those security restriction annotations on EJBs only, then you can also just keep them over there and get away with JSF managed beans.

As an alternative, if you don't want to spend some XML code for some reason, then you can also explicitly set the desired interceptor on the CDI managed bean or EJB using the @Interceptors annotation. So, instead of the custom @ShiroSecured annotation and all the XML configuration, you could also use this annotation on both CDI managed beans and EJBs:


@Interceptors(ShiroSecuredInterceptor.class)

This is however considered tight coupling and thus poor design.

In any way, reagardless of how the interceptor is registered, the annotation based restriction works really nice! In case of ajax requests, you may want to register the OmniFaces FullAjaxExceptionHandler to prevent the no-feedback problem on exceptions in ajax requests.

Back to top

Summary

No, Shiro is not ready for Java EE 6. It didn't work out the box. It lacks the following essential Java EE 6 features:

Another point of concern is the state and documentation of the project. I have the impression that Shiro is somewhat hanging in the ancient J2EE/Spring era and also that documentation needs some more attention and love. Further, it would be really cool if Shiro also remembers the POST request which failed authentication, so that it can immediately be re-executed after successful login. Also, having a salt aware JDBC realm out the box of Shiro would be very useful.

Other than that, the configuration is a breeze and fully portable across containers. Also, the security API is awesome, it has quite handy cryptographic helper classes and furthermore it even supports impersonating out the box. I would forgive its current Java EE 6 shortcomings, which are fortunately relatively easy to fix, but they should really work on that soon :)

Back to top