Showing posts with label CDI. Show all posts
Showing posts with label CDI. Show all posts

Monday, November 24, 2014

OmniFaces 2.0 released!

OmniFaces 2.0 has been released!

The minimum requirements have been upgraded from Java 1.6, JSF 2.0, EL 2.1 and Servlet 2.5 to Java 1.7, JSF 2.2, EL 2.2, Servlet 3.0 and CDI 1.1. An important change is thus that the optional CDI dependency is now required. OmniFaces 2.0 won't deploy on an environment without CDI. This way we can keep things simple and move forward.

As of now, OmniFaces 2.0 is still fully backwards compatible with JSF 2.1, but not with JSF 2.0 anymore. OmniFaces 2.0 is also backwards compatible with CDI 1.0, as long as you don't use the Beans#destroy() utility method which works only in CDI 1.1. Noted should be that future 2.x versions may not be backwards compatible with either JSF 2.1 or CDI 1.0 anymore, so don't take the backwards compatibility of 2.0 as a guarantee for the same in future 2.x versions.

As usual, in the What's new page of the showcase site you can find an overview of all what's been added/changed/fixed for 2.0. The top three additions which will likely become popular are the <o:viewParamValidationFailed> taghandler, <o:graphicImage> component and @Param annotation. Do however check out the What's new page for more, with among others the NoAutoGeneratedIdViewHandler and the <o:validateBean> which Arjan Tijms blogged about.

Installation

Non-Maven users: download OmniFaces 2.0 JAR and drop it in /WEB-INF/lib the usual way, replacing the older version if any.

Maven users: use <version>2.0</version>.

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

Handle f:viewParam validation fail with a redirect or error status

You've perhaps stumbled upon at least one of the following functional requirements:

  • Send a redirect when a <f:viewParam> has failed validation (e.g. absent, invalid format, unknown value, etc)
  • Send a HTTP error when a <f:viewParam> has failed validation (e.g. absent, invalid format, unknown value, etc)

Generally, the answer was to fiddle around in @PostConstruct, <f:viewAction>, or preRenderView and explicitly check for FacesContext#isPostback() and/or #isValidationFailed() and then deal with it using ExternalContext#redirect() or #sendError(). And then you've perhaps also take messaging into account and/or ignore the "Faces message has been enqueued but is not displayed" warnings over all place in logs.

OmniFaces now comes with a <o:viewParamValidationFailed> taghandler for this very purpose which can be applied per <f|o:viewParam>, or for them all. Here's an example which sends a HTTP 400 error when the foo parameter is absent.

<o:viewParam name="foo" required="true" >
    <o:viewParamValidationFailed sendError="400" />
</o:viewParam>

Note that the <o:viewParam> is being used instead of <f:viewParam> to bypass a design mistake in <f:viewParam required="true">. Namely, it works only exactly like as <h:inputText required="true"> when an empty string is supplied as request parameter instead of null. So, when the request parameter is totally absent, i.e. it is null, then the <f:viewParam required="true"> behaves internally differently, causing among others postValidateEvent listeners and even JSR303 bean validation annotations such as @NotNull to be entirely skipped.

Stream images directly from backing bean, optionally as data URI

The <h:graphicImage> is relatively limited in usage. In JSF 1.x it only generates a HTML <img> element without any true JSF advantages. It didn't support e.g. directly referencing a byte[] or InputStream property representing the raw image content. You could just as well use plain HTML <img> directly. In JSF 2.x, it got additional support for referencing JSF resources via new name and library attributes. However, it still didn't support directly referencing the image's content as bean property.

PrimeFaces came with <p:graphicImage> which supported referencing a special wrapper class StreamedContent which in turn should contain among others the image via an InputStream. However, this caused much trouble and confusion among starters because the majority created and assigned it as a property of a request or view scoped bean during some postback bean action. This won't work because the webbrowser requests the image in a different and independent HTTP GET request, during which the initial request scoped bean won't exist anymore, and the JSF view state of the initial view (and thus also the view scoped bean) isn't available anywhere. Basically, you'd need to create the StreamedContent only in the getter method. This is fleshed out in the answer of among others the following questions:

Whilst the getter method approach worked, this ultimately ended up in ugly and confusing backing bean code, because the getter method is called twice and you only needed to populate the StreamedContent with the image's content in the second (stateless) call. You'd be tempted to just switch to a plain vanilla @WebServlet for the sole purpose of serving images, or to use the data URI scheme to immediately render the image content directly embedded in the HTML output.

OmniFaces now comes with a <o:graphicImage> component which solves all of those problems. It supports directly referencing a byte[] or InputStream property representing the raw image content. It also immediately validates if the managed bean holding the property is @ApplicationScoped, so that the developer is automatically forced to "do things the right way" (i.e. don't accidentally deal with request/view scoped state by declaring the byte[] or InputStream as an instance variable). And, it supports a new dataURI="true" attribute which will immediately embed the image in the HTML output, and hereby thus supporting the image as property of a request or view scoped bean which is set during some postback bean action (hereby providing a perfect solution for a "preview uploaded image" case without polluting the data store).

Here's an example which shows images from a DB in a loop. Note that the getter method basically immediately invokes the service call and that it's also really only invoked when the browser actually requests the image, and thus not during rendering the HTML output. The application scoped bean is basically acting like an image servlet/service, but then with less boilerplate code than e.g. the @WebServlet approach.

@Named
@RequestScoped
public class Bean {

    private List<Image> images; // Image class should NOT have "content" property, or at least it be lazy loaded.

    @Inject
    private ImageService service;

    @PostConstruct
    public void init() {
        images = service.list();
    }

    public List<Image> getImages() {
        return images;
    }

}
@Named
@ApplicationScoped
public class ImageStreamer {

    @Inject
    private ImageService service;

    public byte[] getById(Long id) {
        return service.getContent(id);
    }

}
<ui:repeat value="#{bean.images}" var="image">
    <o:graphicImage value="#{imageStreamer.getById(image.id)}" />
</ui:repeat>

See the showcase for live examples, with among others an upload-preview example.

Inject, convert and validate HTTP request parameters via CDI and have them ready in @PostConstruct

The @Param has been improved to support directly injecting the desired target type. So instead of

@Inject @Param private ParamValue<String> paramName;

you can now finally use

@Inject @Param private String paramName;

, lowering the burden to actually use it. It even supports injecting (auto)converted values like so

@Inject @Param private Product product;

with a @FacesConverter(forClass=Product.class).

The major advantage as compared to <f:viewParam> is that the injected value is readily available in @PostConstruct and you don't need an additional <f:viewAction> (or the JSF 2.0/2.1 <f:event type="preRenderView"> workaround) to perform the desired bean initialization based on those params. The disadvantage is that it's fully decoupled from the JSF lifecycle. I.e. conversion and validation is performed immediately during bean's construction, which is not necessarily during validations phase. Also, you would still need an additional Faces#isValidationFailed() check inside the @PostConstruct if you'd like to conditionally perform bean initialization depending on the validation outcome.

It doesn't make <f:viewParam> entirely superfluous, but depending on the business requirements it's a pretty good alternative as it makes the model (the backing bean) more declarative and the view (the XHTML) more clean. For example, one of the disadvantages of <f:viewParam> is that you can't declare it in a common template file and thus you'd have to copypaste it over all template clients. This can be solved by replacing it by using @Param on the common backing bean.

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

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

Added in OmniFaces 2.0 (does NOT apply to 1.10)

  • NoAutoGeneratedIdViewHandler which throws a runtime exception when an autogenerated JSF client ID is being rendered
  • <o:viewParamValidationFailed> which enables sending either a redirect or error status on validation failure of view parameters
  • <o:graphicImage> which is capable of referencing a byte[] or InputStream property with optional support for data URI format
  • @Param now supports directly injecting the (auto-converted) value
  • <o:moveComponent> via which components, facets and behaviors can be moved at runtime to a target component in various ways
  • <o:resolveComponent> via which a component can be looked up by its ID and a reference to it put in various scopes
  • <o:validateBean> now supports validating beans at the class level
  • Servlets utility class got a bunch of new methods related to cookies and JSF
  • New BeansLocal utility class next to Beans class with the same philosophy as Faces/FacesLocal

Changed in OmniFaces 2.0 (also applies to 1.10)

  • Default validation message of ValidateMultipleFields components can now be supplied via <message-bundle>
  • <o:viewParam> enables support for @NotNull and Pre/PostValidateEvent even when parameter is not specified, on contrary to <f:viewParam>
  • Html5RenderKit is now not forgiving anymore on broken renderers that don't pass the current component to the write methods (fixes "plain HTML" messup)
  • Skip null/empty values in <o:validateOrder> as that's actually the responsibility of required="true"
  • CDNResourceHandler and CombinedResourceHandler can now be disabled via a web.xml context param supporting a request based EL expression

Fixed in OmniFaces 2.0 (also applies to 1.10)

  • Set system default timezone in CDI @FacesConverter when web.xml context param says so
  • Components#hasInvokedSubmit() failed in iterating components such as UIData and UIRepeat
  • Fixed <o:massAttribute> to only consider own children instead of those of parent
  • GzipResponseFilter is made better compatible with Servlet 3.1 containers

Maven download stats

Here are the Maven download stats:

  • June 2014: 4429
  • July 2014: 4243
  • August 2014: 4169
  • September 2014: 4531
  • October 2014: 3651

Below is the version pie of October 2014:

But ... I don't want / can't use CDI at all!

Don't panic! OmniFaces 1.10 is the new release from the 1.x branch and is the same as 1.8.1, but with the bugfixes from 2.0 (but no additions!) and without any CDI dependency. This still depends on Java 1.6, JSF 2.0, EL 2.1 and Servlet 2.5 like the previous 1.x releases. The CDI part, which was optional since 1.6, has completely been removed (except for the org.omnifaces.config.BeanManager singleton enum, which uses reflection anyway). The reason for this is that some 1.x users were unable to upgrade to OmniFaces 1.6 or newer in an outdated environment due to bugs or conflicts in the ancient CDI library used.

I can't say if this will be the last OmniFaces 1.x release. If there's a bug report which applies to both 1.x and 2.x, then it will be fixed for both versions. The 1.x release won't contain any new features. This is not only to push users towards CDI, but also because any newer features will most likely also depend on the existence of new APIs. For example, the <o:viewParamValidationFailed> depends on JSF 2.1 and <o:graphicImage> depends on EL 2.2.

Non-Maven users: download OmniFaces 1.10 JAR and drop it in /WEB-INF/lib the usual way, replacing the older version if any.

Maven users: use <version>1.10</version>.

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

Vdldoc 2.0

Vdldoc, our Facelets .taglib.xml documentation generator, has also had its 2.0 release. The Java 7 javadoc look'n'feel has been upgraded to Java 8 javadoc look'n'feel. It now also supports Java EE 7's http://xmlns.jcp.org/* XML namespace. There are no other technical/functional changes.

Monday, June 2, 2014

OmniFaces 1.8.3 released!

OmniFaces 1.8.3 has finally been released!

Also this release had some unscheduled delay for various reasons. Great programmers are also just humans with a "life" next to all the development work. I personally had after all a lot more time needed to acclimatize myself to the Netherlands after having lived in CuraƧao for almost 6 years (still don't really feel home here outside the working hours, still want to go back once the kids grow out the house). Arjan had among others also some unforeseen issues with his new home.

As usual, in the What's new page of the showcase site you can find an overview of all what's been added/changed/fixed for 1.8. The three most useful additions are the <o:deferredScript>, <o:massAttribute> and @Eager.

Installation

Non-Maven users: download OmniFaces 1.8.3 JAR and drop it in /WEB-INF/lib the usual way, replacing the older version if any.

Maven users: use <version>1.8.3</version>.

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

Defer loading and parsing of JavaScript files

If you've ever analyzed the performance of your website using a tool like Google PageSpeed, then you'll probably recognize the recommendation to defer loading and parsing of JavaScript files. Basically, the recommendation is to load JavaScript files only when the browser is finished with rendering of the page. This is to be achieved by dynamically creating a <script> element via document.createElement() during window.onload. Please note that this is not the same as just moving the scripts to the bottom of the page using <h:outputScript target="body">! That would speed up downloading of other resources, but still block the rendering of the HTML in most browsers (read: everything expect IE).

OmniFaces now comes with a <o:deferredScript> component for this very purpose which works just like <h:outputScript> with a library and name attribute.

<h:head>
    ...
    <o:deferredScript library="libraryname" name="resourcename.js" />
</h:head>

You can also use it on for example PrimeFaces scripts, but some additional work needs to be done. For detail, refer this Stack Overflow Question and Answer: Defer loading and parsing of PrimeFaces JavaScript files. This approach has been in production at zeef.com since March 20 (more than 2 months already thus) and has decreased the time to "DOM content loaded" from ~3s to ~1s on a modern client machine.

Set a common attribute on multiple components

The new <o:massAttribute> taghandler allows you to set a common attribute on multiple components. So, instead of for example:

<h:inputText ... disabled="#{someBean.disabled}" />
<h:inputText ... disabled="#{someBean.disabled}" />
<h:inputText ... disabled="#{someBean.disabled}" />
<h:inputText ... disabled="#{someBean.disabled}" />
<h:inputText ... disabled="#{someBean.disabled}" />

You can just do:

<o:massAttribute name="disabled" value="#{someBean.disabled}">
    <h:inputText ... />
    <h:inputText ... />
    <h:inputText ... />
    <h:inputText ... />
    <h:inputText ... />
</o:massAttribute>

The advantage speaks for itself.

Eagerly instantiate a CDI managed bean

When using the standard JSF managed bean facility via @ManagedBean which is since JSF 2.2 semi-official deprecated (not documented as such, but the JSF team is clearly pushing toward it given the total absence of new features around JSF managed bean facility, instead they are all in CDI managed bean facility), it was possible to declare an application scoped JSF managed bean to be eagerly instantiated during application's startup like so:

import javax.faces.bean.ApplicationScoped;
import javax.faces.bean.ManagedBean;

@ManagedBean(eager=true)
@ApplicationScoped
public class Bean {
    // ...
}

However, this isn't possible with standard CDI, not even with the one as available in Java EE 7. So OmniFaces has added the @Eager and @Startup annotations for the very purpose. The @Startup is just a stereotype for @Eager @ApplicationScoped.

So, both beans below are equivalent:

import javax.enterprise.context.ApplicationScoped;
import javax.inject.Named;
import org.omnifaces.cdi.Eager;

@Named
@Eager
@ApplicationScoped
public class Bean {
    // ...
}
import javax.inject.Named;
import org.omnifaces.cdi.Startup;

@Named
@Startup
public class Bean {
    // ...
}

An additional bonus of OmniFaces @Eager is that it not only works on application scoped CDI managed beans, but also on request and session scoped CDI managed beans and on beans annotated with OmniFaces @ViewScoped (thus not the JSF 2.2 one yet, that will come in the upcoming OmniFaces 2.0 which will target JSF 2.2, OmniFaces 1.x is namely JSF 2.0 targeted).

Having @Eager on a request scoped bean may look somewhat strange, but this makes an interesting use case possible: eagerly and asynchronously fetch some data from a DB in the very beginning of the request, long before the FacesServlet is invoked, it runs even before the servlet filters are hit (it's initiated via a ServletRequestListener). Depending on the server hardware used, the available server resources, all code running between the invocation of the first servlet filter and entering the JSF render response, this may give you a time space of 10ms ~ 500ms (or perhaps more if you've some inefficient code in the pipeline ;) ) to fetch some data from DB in a different thread parallel with the HTTP request and thus a speed improvement equivalent to the time the DB needs to fetch the data. Below is an example of how such an approach can look like:

The asynchronous service (this silly example fetches the entire table; just do whatever DB or any other relatively long-lasting service task you want to do as long as the method is annotated with @Asynchronous and you return an AsyncResult as Future; the container will all by itself worry about managing the threads):

package com.example;

import java.util.List;
import java.util.concurrent.Future;
import javax.ejb.AsyncResult;
import javax.ejb.Asynchronous;
import javax.ejb.Stateless;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;

@Stateless
public class MyEntityService {

    @PersistenceContext
    private EntityManager em;

    @Asynchronous
    public Future<List<MyEntity>> asyncList() {
        List<MyEntity> entities = em
            .createQuery("SELECT e FROM MyEntity e", MyEntity.class)
            .getResultList();
        return new AsyncResult<>(entities);
    }

}

The @Eager request scoped bean (note the requestURI attribute, this must exactly match the context-relative request URI without any path fragments and query strings, this example assumes a /test.xhtml page (with a FacesServlet mapping of *.xhtml); wildcards like * are not supported yet, this may come in the future if there's demand)

package com.example;

import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import javax.annotation.PostConstruct;
import javax.enterprise.context.RequestScoped;
import javax.inject.Inject;
import javax.inject.Named;
import org.omnifaces.cdi.Eager;

@Named
@Eager(requestURI="/test.xhtml")
@RequestScoped
public class MyEagerRequestBean {

    private Future<List<MyEntity>> entities;

    @Inject
    private MyEntityService service;

    @PostConstruct
    public void init() {
        entities = service.asyncList();
    }

    public List<MyEntity> getEntities() {
        try {
            return entities.get();
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            throw new FacesException(e);
        } catch (ExecutionException e) {
            throw new FacesException(e);
        }
    }

}

This way, when you request /test.xhtml with something like this:

<h:dataTable value="#{myEagerRequestBean.entities}" var="entity">
    <h:column>#{entity.property}</h:column>
</h:dataTable>

... then the above bean will be constructed and initialized far before the FacesServlet is invoked. Note that this thus also means that the FacesContext is not available inside the @PostConstruct! From that point on, both JSF and JPA will do their jobs simultaneously in separate threads until JSF calls the getter for the first time. The JSF thread (the HTTP request thread) will then block until JPA has returned the result, or perhaps it's already returned at that moment and then JSF can just advance immediately without waiting for JPA.

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

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

Added in OmniFaces 1.8

  • WebXml#getFormErrorPage() to get web.xml configured location of the FORM authentication error page
  • <o:deferredScript> which is capable of deferring JavaScript resources to window.onload
  • Faces#addResponseCookie() got 2 new overloaded methods whereby domain and path defaults to current request domain and current path
  • Components#isRendered() which also checks the rendered attribute of all parents of the given component
  • <o:massAttribute> which sets the given attribute on all nested components
  • FacesMessageExceptionHandler which sets any caught exception as a global FATAL faces message
  • <o:cache> has new disabled attribute to temporarily disable the cache and pass-through children directly
  • @Eager annotation to eagerly instantiate request-, view-, session- and application scoped beans
  • <o:viewParam> skips converter for null model values so that query string doesn't get polluted with an empty string
  • Small amount of utility methods and classes, e.g. method to check CDI annotations recursively in stereotypes, shortcut method to obtain VDL, etc

Changed in OmniFaces 1.8

  • CombinedResourceHandler now also recognizes and combines <o:deferredScript>
  • UnmappedResourceHandler now also recognizes PrimeFaces dynamic resources using StreamedContent

Fixed in OmniFaces 1.8

  • Assume RuntimeException in BeanManager#init() as CDI not available (fixes deployment error on WAS 8.5 without CDI enabled)
  • Use "-" (hyphen) instead of null as default option value to workaround noSelectionOption fail with GenericEnumConverter
  • <o:param> shouldn't silently convert the value to String (fixes e.g. java.util.Date formatting fail in <o:outputFormat>)
  • Fixed javax.enterprise.inject.AmbiguousResolutionException in subclassed @FacesConverter and @FacesValidator
  • <o:messages> failed to find the for component when it's not in the same parent
  • <o:conditionalComment> shouldn't XML-escape the if value, enabling usage of & character
  • UnmappedResourceHandler broke state saving when partial state saving is turned off
  • CombinedResourceHandler didn't properly deal with MyFaces-managed resources

Maven download stats

Here are the Maven download stats:

  • January 2014: 3537
  • February 2014: 3580
  • March 2014: 3892
  • April 2014: 3572
  • May 2014: 3971

Below is the version pie of May 2014:

Last but not least

For the case you missed it: OmniFaces repo, wiki and issue tracking (basically: everything) has moved from Google Code to GitHub, along with a "brand new" homepage in GitHub style at omnifaces.org. The downloads will from now just point directly to Maven via links at the homepage.

Monday, October 14, 2013

How to install CDI in Tomcat?

Introduction

JSF is 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 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 March 2021)

Tomcat 10 is the first version to be "Jakartified", i.e. it's using jakarta.* package instead of javax.* package for the API classes. Weld 4 was the first version to also be Jakartified. Perform the following steps:

  1. Drop weld-servlet-shaded.jar 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>4.0.0.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="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 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>7.0.1.Final</version>
    </dependency>
    

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

Install Weld in Tomcat 9- (last updated: 13 March 2021)

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 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.6.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.0.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 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, January 23, 2013

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

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