DigitalJoel

2010/09/12

JSF graphicImage from database

Filed under: development, facelets, java, JSF, JSF2 — digitaljoel @ 8:45 pm

I’ve seen some questions on the webtier mailing list about loading image resources from a database, but no real implementation suggestions.  I ended up needing to figure this out for work, and got permission to post the solution here.  It’s not 100% complete as it doesn’t allow for caching at the client or in the JSF layer, but it’s a starting point.  You won’t be able to take this solution and simply plop a jar in your project and make it go.  You’ll have to implement your own solution, but can base it on this information if it helps you.

In my case, our software manages Programs and Brands.  What those entities represent isn’t important, but know that each program can have a logo and custom css, and each Brand can have a logo.  I wanted to be able to use the regular JSF tags to output these images so the designers wouldn’t have to learn a new tag.  Something like this:


    <h:outputStylesheet library="program_#{user.programName}" name="css" />
    <h:graphicImage library="program_#{user.programName}" name="logo" />

The first step is to implement a custom ResourceHandler.  This would be super awesome if we could just extend ResourceHandlerWrapper like the JSF developers intended.  They built that class precisely for extension, unfortunately, the implementation has what I consider a fatal flaw.  JSF calls handleResourceRequest in order to create a resource when a resource request comes in.  The resource is a representation of your image or css.  In handleResourceRequest, the ResourceHandlerWrapper calls in to the wrapped instance’s handleResourceRequest, and the ResourceHandlerImpl  handleResource calls its own createResource method.

Update: It looks like they have fixed the ResourceHandlerImpl in 2.0.3 so that it now delegates to the configured resource handler for createResource.  This is great in that it means you should be able to now extend ResourceHandlerWrapper instead of ResourceHandlerImpl and get the correct behavior.  You should view this thread http://forums.java.net/jive/thread.jspa?threadID=153490&tstart=0 for more info.

So, if my extension of ResourceHandlerWrapper implements its own createResource method in order to create my own custom resources, then I must also implement my own handleResourceRequest which, if you look at the linked javadoc would be really easy to mess up all resource handling in your application.

The point of all this rambling?  I had to do something I’m not proud of.  I extended ResourceHandlerImpl, which is a non-published API, but it was that or implement my own handleResourceRequest, which I really didn’t want to do.  So, here’s the source of my ResourceHandler.


package yourpackage.jsf;

import com.sun.faces.application.resource.ResourceHandlerImpl;
import javax.faces.application.Resource;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

/**
 *
 * @author Joel.Weight
 */
public class DelegatingResourceHandler extends ResourceHandlerImpl
{
    private ResourceHelper[] helpers = new ResourceHelper[] {
    new ProgramResourceHelper()
    , new BrandResourceHelper()
    };

    private static Log log = LogFactory.getLog( DelegatingResourceHandler.class );

    @Override
    public Resource createResource( String resourceName, String libraryName )
    {
        ResourceHelper helper = getHelper( libraryName );
        if ( helper != null )
        {
            return helper.createResource( resourceName, libraryName );
        }
        // otherwise delegate to the default implementation.
        if ( log.isDebugEnabled() )
        {
            log.debug( "Delegating resource creation to default implementation. name: " + resourceName +
            " and library: " + libraryName );
        }
        return super.createResource( resourceName, libraryName, null );
    }

    @Override
    public boolean libraryExists( String libraryName )
    {
        ResourceHelper helper = getHelper( libraryName );
        if ( helper != null )
        {
            return true;
        }
        return super.libraryExists( libraryName );
    }

    /**
    * Get the helper that handles a given library.
    * @param libraryName
    * @return
    */
    private ResourceHelper getHelper( String libraryName )
    {
        for ( ResourceHelper helper : helpers )
        {
            if ( helper.handlesLibrary( libraryName ))
            {
                return helper;
            }
        }
        return null;
    }
}

Alright, what’s going on here.  First, an array of ResourceHelpers.  Since I want to load images from different entities, I have a helper for each one, which will be responsible for instantiating the resource for the given entity.

Next, the createResource method asks each helper if it handles a given library.  Each helper has a prefix that signals to it that it should handle a given resource request.  For instance, program_ and brand_ are the prefixes I have.  If none of the helpers handle the library, then it delegates to the default handler functionality.

Finally libraryExists asks each helper if it handles the given library.  If none of them, then it delegates to the default handler functionality.

Next is the helper implementation.  I created an abstract class so the Program and Brand helpers could share common functionality.  Here it is.


package yourpackage.jsf;

import javax.faces.application.Resource;
import javax.faces.context.FacesContext;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

/**
 *
 * @author Joel.Weight
 */
public abstract class ResourceHelper
{

    protected String prefix;

    private static Log log = LogFactory.getLog( ProgramResourceHelper.class );

    public ResourceHelper( String prefix )
    {
        this.prefix = prefix;
    }

    /**
     * Create a resource with the given name for the given library
     * @param resourceName The resource name, as passed in the JSF tag
     * @param libraryName The library name, as passed in the JSF tag
     * @return The resource, or null if not found.
     */
    public abstract Resource createResource( String resourceName, String libraryName );

    /**
     * remove the prefix from a resourceName
     * @param name resourceName
     * @return the resource name without the prefix, or the original string if it doesn't start with the prefix.
     */
    public String stripPrefix( String name )
    {
        if ( name != null && name.startsWith( prefix ))
        {
            return name.substring( prefix.length() );
        }
        return name;
    }

    /**
     * Test whether this helper handles the given library name.
     * @return true if this helper handles the library, false otherwise.
     */
    public boolean handlesLibrary( String libraryName )
    {
        boolean result = ( libraryName != null
                                && libraryName.toLowerCase().startsWith( prefix )
                                && libraryName.length() > prefix.length());
        if ( log.isDebugEnabled() )
        {
            log.debug( String.format( "Helper with prefix %s handles library %s : %s", prefix, libraryName, result ));
        }
        return result;
    }

    /**
     * Gets a bean from the faces context.
     * @param name name of the bean to get
     * @return bean instance as returned from faces context.
     */
    public Object getBeanFromFacesContext( String name )
    {
        FacesContext context = FacesContext.getCurrentInstance();
        return context.getApplication().getELResolver().getValue( context.getELContext(), null, name );
    }
}

This is pretty simple.  It takes care of a lot of the prefix handling stuff.  The handlesLibrary method is the biggest shared functionality.  It looks at the prefix and determines if the library passed in the graphicImage or outputStylesheet tag should be handled by this helper.

Here is an implementation of the abstract class.  This is the one for Brand.


package yourpackage.jsf;

import awp.data.BrandService;
import awp.view.Brand;
import javax.faces.application.Resource;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

/**
 * ResourceHelper for getting images associated with a Brand.
 * @author Joel.Weight
 */
public class BrandResourceHelper extends ResourceHelper
{

    private BrandService brandService;

    private static Log log = LogFactory.getLog( BrandResourceHelper.class );

    public BrandResourceHelper()
    {
        super( "brand_" );
    }

    @Override
    public Resource createResource( String resourceName, String libraryName )
    {
        Brand brand = getBrandByLibraryName( libraryName );
        if ( brand != null )
        {
            return new BrandResource( resourceName, libraryName, brand );
        }
        return null;
    }

    private Brand getBrandByLibraryName( String libraryName )
    {
        Brand brand = null;
        String brandName = stripPrefix( libraryName );
        if ( brandName != null && brandName.length() > 0 )
        {
            brand = getBrandService().findBrandByName( brandName );
        }
        return brand;
    }

    private BrandService getBrandService()
    {
        if ( brandService == null )
        {
            brandService = (BrandService)getBeanFromFacesContext( "brandService" );
        }
        return brandService;
    }
}

As with the others, it’s quite simple, and the Program implementation looks very similar.  The only thing we really have to implement here is the constructor which sets up the prefix, and the createResource method.  The others are all helper methods.  createResource finds our entity in order to get the image from it.  Your implementation could query for just the image, or do whatever you want here.

Next up is our custom resource implementation.  This one is a little longer because we are extending a fair amount of the functionality in Resource


package yourpackage.jsf;

import yourpackage.DataObject;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import javax.faces.application.Resource;
import javax.faces.application.ResourceHandler;
import javax.faces.context.ExternalContext;
import javax.faces.context.FacesContext;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

/**
 * base representation of a database backed resource.
 */
public abstract class DataObjectResource<T extends DataObject> extends Resource
{
    protected String name;
    protected String libraryName;
    protected T dataObject;

    private static Log log = LogFactory.getLog( DataObjectResource.class );

    // HTTP Date format required by the HTTP/1.1 RFC
    private static final String LAST_MODIFIED_PATTERN = "EEE, dd MMM yyyy HH:mm:ss zzz";

    public DataObjectResource( String name, String libraryName, T object )
    {
        this.name = name;
        this.libraryName = libraryName;
        this.dataObject = object;
        if ( log.isTraceEnabled() )
        {
            log.trace( "Created new DataObjectResource: libraryName: " + libraryName + " name: " + name );
        }
    }

    /**
    * Get the input stream for this resource.
    * @return
    * @throws IOException
    */
    @Override
    public abstract InputStream getInputStream() throws IOException;

    @Override
    public Map<String, String> getResponseHeaders()
    {
        Map<String, String> result = new HashMap<String, String>( 6, 1.0f );
        SimpleDateFormat format = new SimpleDateFormat( LAST_MODIFIED_PATTERN );
        // make it modified so they always request the whole resource.
        // TODO: make this smarter when going into production.
        result.put( "Last-Modified", format.format( new Date()));
        return result;
    }

    @Override
    public String getRequestPath()
    {
        StringBuilder buf = new StringBuilder(
        FacesContext.getCurrentInstance().getExternalContext().getRequestContextPath() );
        buf.append( ResourceHandler.RESOURCE_IDENTIFIER );
        buf.append( "/" ).append( name ).append( ".faces?ln=" ).append( libraryName );
        if ( log.isDebugEnabled() )
        {
            log.debug( "Request path for program resource " + this.toString() + " : '" + buf.toString() + "'" );
        }
        return buf.toString();
    }

    @Override
    public URL getURL()
    {
        ExternalContext context = FacesContext.getCurrentInstance().getExternalContext();
        StringBuilder buf = new StringBuilder( context.getRequestScheme() );
        buf.append( context.getRequestServerName() );
        if ( context.getRequestServerPort() != 80 && context.getRequestServerPort() != 443 )
        {
            buf.append( ":" ).append( context.getRequestServerPort());
        }
        buf.append( getRequestPath());
        URL url = null;
        try
        {
            url = new URL( buf.toString());
            if ( log.isDebugEnabled() )
            {
                log.debug( "Created new URL " + buf.toString() + " for ProgramResource " + this.toString() );
            }
        }
        catch( java.net.MalformedURLException e )
        {
            log.error( "Unable to create URL for ProgramResource " + this.toString(), e);
        }
        return url;
    }

    @Override
    public boolean userAgentNeedsUpdate( FacesContext fc )
    {
        // TODO: always updates the user agent.  fix this to allow for caching.
        return true;
    }

    @Override
    public String toString()
    {
        return String.format( "%s { name=%s libraryName=%s }"
        , this.getClass().getName()
        , name
        , libraryName );
    }
}

This class is pretty dumb, and needs the most help to make it usable in a production system.  It provides default implementations for all of the methods of the Resource, except the getInputStream method.  The good news is that when I get time to come back to this class, it’ll be smarter for every implementation of this abstract class.

Now, to a concrete implementation of our custom Resource class.  This time we’ll look at ProgramResource since it handles css and an image.  I also have an implementation for BrandResource.


package yourpackage.jsf;

import yourpackage.Program;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

/**
 * JSF Resource implementation used to get information from a Program in the database.
 */
public class ProgramResource extends DataObjectResource<Program>
{
    /**
    * Use this resource name in JSF to get the program logo.
    */
    public static final String LOGO_RESOURCE = "logo";

    /**
    * Use this resource name in JSF to get the program css.
    */
    public static final String CSS_RESOURCE = "css";

    private static Log log = LogFactory.getLog( ProgramResource.class );

    public ProgramResource( String name, String libraryName, Program program )
    {
        super( name, libraryName, program );
    }

    @Override
    public InputStream getInputStream() throws IOException
    {
        if ( LOGO_RESOURCE.equals( this.name ))
        {
            return new ByteArrayInputStream( dataObject.getLogo() );
        }
        else if ( CSS_RESOURCE.equals( this.name ))
        {
            return new ByteArrayInputStream( dataObject.getCss().getBytes());
        }
        else
        {
            if ( log.isWarnEnabled() )
            {
                log.warn( "Attempted to get input stream for ProgramResource, but no correct name specified. " +
                this.toString() );
            }
            return null;
        }
    }
}

First, notice the final static Strings that identify logo and css.  Those are the names of the resources you will use in the graphicImage and outputStylesheet tags.  They tell me where to get the data from for the getInputStream method.  The implementation of getInputStream is quite simple.  We just look at the name of the resource being requested, and use that as a key to figure out which field of our DataObject.

The last part of this puzzle is to tell JSF to use our DelegatingResourceHandler that we implemented at the start.  This is done with the following tag in your faces-config.xml file.  It should be within the application tag.


<resource-handler>yourpackage.jsf.DelegatingResourceHandler</resource-handler>

With all of this in place, I can show a different image in the same place in my application depending on the current context, and pull that image from the database.  For instance, if one user is in Program A, and another is in Program B, I simply use the user’s context to give the program to the graphicImage tag, and the outputStylesheet tag, and I have different styling and images for each of those users, without changing anything in my xhtml.

As I said before, this implementation is far from perfect.  If you attempt to reference a resource library and the helper can’t find it in the database then you will have a problem.  Also if you attempt to reference a resource name that doesn’t tie in to one of your identifiers in your Resource implementation, then you are toast.  Finally, there is no caching currently, so every request will load the image or css from the database.  That’s ugly.  Perhaps when I get back to this code in my current project, I’ll update the source in this blog post.  In the meantime, it ought to be a decent starting point for anyone looking to solve the same problem.

Create a free website or blog at WordPress.com.