Version 3

    This article explains how you can easily extend JSF 2.x/Seam to support basic content management features.

     

    The usecase targeted is one where a website uses two content repositories one for unpublished content and the other for published/live content.

     

    A privileged user is able to switch between these two content views. They are also able to promote content.

     

    The latter two features are not described here.

     

    It relies on a feature of the Mojarra implementation which allows you to use a custom FaceletFactory.

     

    To use a custom facelet factory you add this entry to the web.xml file:

     

    <context-param>

            <param-name>com.sun.faces.faceletFactory</param-name>

            <param-value>com.company.ContentManagementFaceletFactory</param-value>

    </context-param>

     

    public class ContentManagementFaceletFactory extends FaceletFactory {

        private volatile ConcurrentMap<String, URL> unpublishedRelativeLocations;
       
        private volatile ConcurrentMap<String, URL> relativeLocations;

     

        private FaceletFactory faceletFactory;
       
        private final static CmsSignaller cmsSignaller = getCmsSignaller();
       
        private URL baseUrl = null;

     

        public CMISFaceletFactory( FaceletFactory faceletFactory )
        {
         this.faceletFactory = faceletFactory;
         this.relativeLocations = new ConcurrentHashMap<String, URL>();
         this.unpublishedRelativeLocations = new ConcurrentHashMap<String, URL>();
         this.baseUrl = getResourceResolver().resolveUrl("/");
        } 

     

        public URL resolveURL(String uri) throws IOException {
           boolean needToClearCaches = cmisSignaller.clearCaches();
     
           if( needToClearCaches )
           {

                      this.unpublishedRelativeLocations = new ConcurrentHashMap<String, URL>();

                      this.relativeLocations = new ConcurrentHashMap<String, URL>();

                    }
               }
           }
          cmisSignaller.setNeedToClearFaceletCache( false );
     
          boolean inTest = cmsSignaller.isInTest();
          URL url = null;
          if( inTest )
          {
              url = this.unpublishedRelativeLocations.get(uri);
          }
          else
          {
              url = this.relativeLocations.get(uri);
          }
          if (url == null) {
              url = this.resolveURL(this.baseUrl, uri);
              if (url != null) {
              if( inTest ){
                  this.unpublishedRelativeLocations.putIfAbsent(uri, url);
              }
             else{
                  this.relativeLocations.putIfAbsent(uri, url);
             }
          }

          else {
                    throw new IOException("'" + uri + "' not found.");
          }
       }
       return url;

    }
       
    public URL resolveURL( URL source, String path ) throws IOException
    {
        if (path.startsWith("/")) {
            URL url = getResourceResolver().resolveUrl(path);
            if (url == null) {
                    throw new FacesFileNotFoundException(path
                                                    + " Not Found in ExternalContext as a Resource");
            }
            return url;
        } else {
            return new URL(source, path);
        }    
    }

     

    private static CmsSignaller getCmsSignaller()
    {
      WebConfiguration webConfig = WebConfiguration.getInstance();
         String cmsSignallerClassName = webConfig.getServletContext().getInitParameter( "com.company.CmsSignaller" );
         try
      {
       return (CmsSignaller) Class.forName(cmsSignallerClassName).newInstance();
      }
      catch (Exception e)
      {
       throw new RuntimeException( e );
      }
    }

     

    @Override
    public Facelet getFacelet(String uri) throws IOException
    {
      return this.faceletFactory.getFacelet( resolveURL(uri) );
    }

     

    @Override
    public Facelet getMetadataFacelet(String uri) throws IOException
    {
      return this.faceletFactory.getMetadataFacelet( resolveURL(uri) );
    }

     

    The CmsSignaller is a simple interface that allows you to plugin the necessary content management system behaviour

     

    public interface CmsSignaller
    {

    //Simply whether the priviledged user is looking at the unpublished content.

    public abstract boolean isInTest();

    //Whether the unpublishedRelativeLocation and relativeLocation path to URL caches need to tbe cleared (i.e. has content been modified in the content management repository?)

    public abstract boolean clearCaches();

    //reset the application application specific to say that facelet caches no longer need to be cleared.

    public abstract void setNeedToClearFaceletCache(boolean clear);

    }

     

    The implementation class is specified in the web.xml file:

     

    <context-param>

            <param-name>com.company.CmsSignaller</param-name>

            <param-value>com.company.JcrCmsSignaller</param-value>

    </context-param

     

    Elements in the relativeLocations and unpublishedRelativeLocations are not evicted on an individual basis but rather the entire cache is cleared, this suited our particular use case.

     

    In JSF 2.2 the status of the FaceletFactory is being elevated out of the Mojarra implementation into the spec.

     

    http://jdevelopment.nl/jsf-22/#611

     

    Obviously you have to implement your ResourceResolver but there are number of existing articles on the web for this.

     

    http://javaserverfaces.java.net/nonav/docs/2.1/javadocs/index.html

     

    i.e.

     

    @Override

    public URL resolveUrl(String resource)

    {

    URL resourceUrl = null;

    try

    {

       /resolve against your content management system

       resourceUrl = resolveAgainstCMS(resource);

    }

    catch (Exception e)

    {

       log.trace("didn't override");

    }

     

    if( resourceUrl == null )

    {

                //didn't find anything in the content management system, resolve to the default version in the WAR file.

                resourceUrl = super.resolveUrl(resource);

    }

    return resourceUrl;

    }