Friday, August 24, 2012

Installation of packages on Integration Server through custom developed java program

Out of the box a package can be installed on webMethods Integration Server via the Administrator’s Package Management GUI or using WmDeployer.

Following code snippet with allow you to install packages on a webMethods Integration Server through the usage of a custom developed java program, leveraging the internal wM codebase for introducing a package.

Java code snippet :
/*
 * Usage: java -classpath  <path_to_client_classes> PackageInstaller \
 *                        host:<hostname> port:<port> user:<username> \
 *                        pswd:<password> pckg:<package1>,<package2>,...
 *
 * The .zip extension for package names should be omitted
 *
 * If using a secure connection (https) to connect to your Integration Server
 * uncomment the following line in the connect() method below:
 *     context.setSecure(true);
 */
import com.wm.app.b2b.client.*;
import com.wm.util.*;
import java.util.StringTokenizer;
public class PackageInstaller
{
  public static String hostName = null;
  public static String port = null;
  public static String userName = null;
  public static String password = null;
  public static String errorMsg = null;
  public static String packages = null;
  public static String tmpStr = null;
  Context context = null;
  public boolean connected = false;
  public static boolean secure = false;
  public static void main(String[] args)
  {
        /***********************************************************************/
        /* Parse command line. The hostname, port, username and password must  */
        /* ALL be specified.                                                   */
        /***********************************************************************/
        
        errorMsg = "";
      for (int iCtr = 0; iCtr < args.length; iCtr++)
      {
          if (args[iCtr].indexOf("host:") != -1) {
              hostName = args[iCtr].substring(5);
              if (hostName.length() < 1)
                  errorMsg = errorMsg + "\n    No host name specified";
          }
          else if (args[iCtr].indexOf("port:") != -1) {
                port = args[iCtr].substring(5);
                if (port.length() < 1)
                    errorMsg = errorMsg + "\n    No port specified";
          }
            else if (args[iCtr].indexOf("user:") != -1) {
                userName = args[iCtr].substring(5);
                if (userName.length() < 1)
                    errorMsg = errorMsg + "\n    No user name specified";
            }
            else if (args[iCtr].indexOf("pswd:") != -1) {
                password = args[iCtr].substring(5);
                if (password.length() < 1)
                    errorMsg = errorMsg + "\n    No password specified";
            }
            else if (args[iCtr].indexOf("pckg:") != -1) {
                packages = args[iCtr].substring(5);
                if (packages.length() < 1)
                    errorMsg = errorMsg + "\n    No packages specified";
            }
            else {
                errorMsg = errorMsg + "\n    Invalid parameter '" +
                                     args[iCtr] + "' specified.";
            }
      }
      
        /***********************************************************************/
        /* Display program usage instruction if there were any errors with the */
        /* command line arguments.                                             */
        /***********************************************************************/
    
    if ((errorMsg.length() > 0) || (hostName == null) || (port == null) ||
        (userName == null) || (password == null) || (packages == null)) {
      System.out.println("  Usage: java -classpath  " +
                         "<path_to_client_classes> PackageInstaller " +
                         "host:<hostname> port:<port> user:<username> " +
                         "pswd:<password> pckg:<package1>,<package2>,...\n");
      System.out.println("  Omit the .zip extension for package names");
    if (errorMsg.length() > 0)
          System.out.println("  Errors encountered:" + errorMsg);
      System.exit(1);
    }
    //Create an instance of this class
    PackageInstaller packageInstaller = new PackageInstaller();
    System.out.println("Instantiated installer");
    packageInstaller.connect(true);
    if(!packageInstaller.connected) {
        System.exit(1);
    }
    StringTokenizer st = new StringTokenizer(packages, ",");
    while (st.hasMoreTokens()) {
    String packageName = st.nextToken();
    packageInstaller.install(packageName + ".zip");
        packageInstaller.activate(packageName);
    }    
    System.exit(0);
  }
 
  public void connect(boolean dispMsg)
  {       
    context = new Context();
    try {
    /***********************************************/
    /* Uncomment the following line if using https */
    /***********************************************/
    // context.setSecure(true);
        context.connect(hostName + ":" + port, userName, password);
      connected = true;
    }
    catch(ServiceException e) {
        if (dispMsg) {
            System.out.println("Could not connect to " + hostName + ":" + port);
          System.out.println(e.toString());
        }
      connected = false;
    }
  }
  public void install(String packageName)
  {
      try {
        System.out.println("Installing package " + packageName + " ... ");
      Values input = new Values();
      input.put("file", packageName);
      Values data = context.invoke("wm.server.packages", "packageInstall", input);
    // System.out.println("IData:\n");
    // System.out.println(data.toString());
    }
    catch(ServiceException e) {
        System.out.println(e.toString());
      System.exit(1);
    }
  }
 
  public void activate(String packageName)
  {
      try {
        System.out.println("Activating package " + packageName + " ... ");
      Values input = new Values();
      input.put("package", packageName);
      Values data = context.invoke("wm.server.packages", "packageActivate", input);
    // System.out.println("IData:\n");
    // System.out.println(data.toString());
    }
    catch(ServiceException e) {
        System.out.println(e.toString());
      System.exit(1);
    }
  }
  public PackageInstaller()
  {       
      System.out.println();
    System.out.println("******PackageInstaller utility*******");
    System.out.println();
  }
}

Remarks
:

  1. When compiling and running the java program make sure to include files wm-isclient.jar and mail.jar in your class path. File wm-isclient.jar can found in the "common/lib" folder of an Integration Server whereas file mail.jar is localized in the "ext" sub folder of the "common/lib" folder.
  2.  Usage of the PackageInstaller program is PackageInstaller host:<hostname> port:<port> user:<username> pswd:<password> pckg:<package1>,<package2>,...
    The .zip extension for package names should be omitted
  3.  At this moment the java program is working with at least webMethods Integration Server v7.1.2. Specifications contained herein are potentially subject to change so use them at your own risk.

Author: Johan De Wulf

Tuesday, August 14, 2012

JXL parsing problems with apache POI generated xls


The problem

We are using apache poi 3.8 to generate excels. Our excel generation is based on templates where we duplicate rows and columns as necessary to fit the data that is fed into them. In this particular usecase we add 23 rows and one column. Our counterparty uses jxl 2.5.5 (released 2005-05-05) to parse the incoming xls files generated by our poi setup. The added rows do not pose a problem but the added column does. The problem is that the values in the added column are not parsed and instead jxl outputs warnings:
Warning: Cell D1 exceeds defined cell boundaries in Dimension record (3x42)
Warning: Cell D2 exceeds defined cell boundaries in Dimension record (3x42)
Warning: Cell D3 exceeds defined cell boundaries in Dimension record (3x42)
Warning: Cell D4 exceeds defined cell boundaries in Dimension record (3x42)
Warning: Cell D5 exceeds defined cell boundaries in Dimension record (3x42)

Analysis

If you google for the warning you quickly arrive at the jxl.read.biff.SheetReader class as the culprit, more specifically the "addCell()" method. As you can see it will generate warnings for out of bound cells:
private void addCell(Cell cell) {
      // Sometimes multiple cells (eg. MULBLANK) can exceed the
      // column/row boundaries. Ignore these
      if (cell.getRow() < numRows && cell.getColumn() < numCols) {
            if (cells[cell.getRow()][cell.getColumn()] != null) {
                  StringBuffer sb = new StringBuffer();
                  CellReferenceHelper.getCellReference(cell.getColumn(), cell.getRow(), sb);
                  logger.warn("Cell " + sb.toString() + " already contains data");
            }
            cells[cell.getRow()][cell.getColumn()] = cell;
      }
      else {
            logger.warn("Cell " +
                  CellReferenceHelper.getCellReference
                        (cell.getColumn(), cell.getRow()) +
                  " exceeds defined cell boundaries in Dimension record " +
                  "(" + numCols + "x" + numRows + ")");
      }
}

Reproducing the error

We set up a small testcase using jxl to parse the excel we sent the counterparty. At the core is this read() method:
public void read(File file) throws IOException, BiffException {
      Workbook w = Workbook.getWorkbook(file);
      
      // Get the second sheet
      Sheet sheet = w.getSheet(1);
      System.out.println(sheet);
      for (int i = 0; i < sheet.getRows(); i++) {
            for (int j = 0; j < sheet.getColumns(); j++) {
                  Cell cell = sheet.getCell(j, i);
                  System.out.print("\t" + cell.getContents());
            }
            System.out.println();
      }
}
I included the latest version of jxl available in the maven repository which is 2.6.12 (released 2009-12-26) at the time of writing. The warning did not occur and the additional column was parsed without a problem. I downgraded the jxl version to the oldest available in the repository: 2.5.7 (released 2005-07-30) and ran it again. This generated the warnings our counterparty was experiencing and resulted in a missing column.
Edit: once we received confirmation of the jxl version being used by our counterparty, we ran the test again with 2.5.5 which yielded the same results as 2.5.7

Solutions

Solution 1: Upgrading jxl version

The reason the newer version of jxl parses the excel correctly is because they updated the addCell() method to:
private void addCell(Cell cell) {
      // Sometimes multiple cells (eg. MULBLANK) can exceed the
      // column/row boundaries. Ignore these
      if (cell.getRow() < numRows && cell.getColumn() < numCols) {
            if (cells[cell.getRow()][cell.getColumn()] != null) {
                  StringBuffer sb = new StringBuffer();
                  CellReferenceHelper.getCellReference(cell.getColumn(), cell.getRow(), sb);
                  logger.warn("Cell " + sb.toString() + " already contains data");
            }
            cells[cell.getRow()][cell.getColumn()] = cell;
      }
      else {
            outOfBoundsCells.add(cell);
            /*
            logger.warn("Cell " +
                  CellReferenceHelper.getCellReference
                        (cell.getColumn(), cell.getRow()) +
                  " exceeds defined cell boundaries in Dimension record " +
                  "(" + numCols + "x" + numRows + ")");
            */
      }
}
As you can see the excel generated by apache poi is still incorrect but jxl has learned to deal with it gracefully.

Solution 2: Fixing apache poi

Apache poi updates the dimensions of a sheet every time you add a row or a column. However there is a slight difference between how these additions are handled:
Row addition (org.apache.poi.hssf.model.InternalSheet.addRow():698):
if (row.getRowNumber() >= d.getLastRow()) {
      d.setLastRow(row.getRowNumber() + 1);
}
Column addition (org.apache.poi.hssf.model.InternalSheet.addValueRecord():633):
if (col.getColumn() > d.getLastCol()) {
      d.setLastCol(( short ) (col.getColumn() + 1));
}
We updated line 633 to:
if (col.getColumn() >= d.getLastCol()) {
And generated the excel again. This new excel was parsed correctly by both versions of jxl.
Note that to submit the bug to apache poi, it is best to have a simple code sample able to reproduce the problem. The following method will generate an excel that has the wrong dimensions:
public static void main(String...args) throws IOException {
      Workbook workbook = new HSSFWorkbook();
      Sheet sheet = workbook.createSheet("test");
      Row row = sheet.createRow(0);
      Cell cell = row.createCell(0);
      cell.setCellValue("cell1");
      cell = row.createCell(1);
      cell.setCellValue("cell2");
      OutputStream output = new FileOutputStream(new File("c:/generated.xls"));
      try {
            workbook.write(output);
      }
      finally {
            output.close();
      }
}
This bug and the suggested fix have been logged in the apache bug tracker.

Author: Alexander Verbruggen
Author: Alexander Verbruggen

Monday, August 6, 2012

using Spring Expression Language (SpEL) in a blueprint container

Spring Expression Language (SpEL) can be used to configure beans in a spring application context. For example the Spring-batch framework makes heavy use of it for what they call late binding of properties. To get a feel of what SpEL can do, some examples from the spring documentation:

"#{ T(java.lang.Math).random() * 100.0 }" --> return random value [0, 100)
"#{'5.00' matches '^-?\\d+(\\.\\d{2})?$'}" --> return true this regex matches
"#{8 / 5 % 2}" --> return 1

The prefix and suffix #{ and } in this case are just a convention and can be configured differently if you want or just leave them out


In this blog I will show how it can be used in an osgi-blueprint container. The idea is quite simple, we will use the type-converter feature from the blueprint spec to convert our SpEL expression we want to inject. If the expression is correct the result type should match the type expected by the bean. With the code given in this simple example it is not possible to access the beans from the blueprint container. That for a next post.
Here is the code:
package be.i8c.spel;


import org.osgi.service.blueprint.container.Converter;
import org.osgi.service.blueprint.container.ReifiedType;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.expression.ParseException;
import org.springframework.expression.ParserContext;
import org.springframework.expression.spel.standard.SpelExpressionParser;

/**
 * 
 * tries to 'convert' string to targetType by evaluating it as a SpEL expression
 *
 */
public class ExpressionLanguageConverter implements Converter {
 
 private static final String EXPRESSION_PREFIX = "#{";
 
 private static final String EXPRESSION_SUFFIX = "}";
 
 private final ParserContext parserContext;
 
 private final EvaluationContext evaluationContext;
 


 public ExpressionLanguageConverter(EvaluationContext evaluationContext){
  
  this.evaluationContext = evaluationContext;
  parserContext = new ParserContext() {
   
   public boolean isTemplate() {
    return true;
   }
   
   public String getExpressionSuffix() {
    return EXPRESSION_SUFFIX;
   }
   
   public String getExpressionPrefix() {
    return EXPRESSION_PREFIX;
   }
  };
 }
 
 /**
  * @return true if the sourceObject is a string expression 
  *      that can be parsed as a Spring Expression Language 
  *      this does not guarantee that the evaluating the expression gives a result of type targetType
  */
 public boolean canConvert(Object sourceObject, ReifiedType targetType) {
  
  if (String.class.isInstance(sourceObject) && ((String)sourceObject).startsWith(EXPRESSION_PREFIX)) {
   try {
    parse(sourceObject);
    return true;
   } catch (ParseException e) {
    return false;
   }
  }
  return false;
 }

 
 @SuppressWarnings("unchecked")
 public Object convert(Object sourceObject, ReifiedType targetType) throws Exception {
  if (String.class.isInstance(sourceObject)) {
   Expression expression = parse(sourceObject);
   return expression.getValue(evaluationContext, targetType.getRawClass());
  }
    
  throw new RuntimeException("could not convert/evaluate source:"+sourceObject +" to target:"+targetType);
 }

 private Expression parse(Object sourceObject) {
  SpelExpressionParser parser = new SpelExpressionParser();
  Expression expression = parser.parseExpression((String)sourceObject, parserContext);
  return expression;
 }

}
OSGI is all about class loading so we have to configure the EvaluationContext to use the classloader associated with this bundle, this is done with a custom TypeLocator:


package be.i8c.spel.impl;

import org.osgi.framework.Bundle;
import org.springframework.expression.EvaluationException;
import org.springframework.expression.TypeLocator;

public class BundleTypeLocator implements TypeLocator {
 
 private final Bundle bundle;

 public BundleTypeLocator(Bundle bundle){
  this.bundle = bundle;
 }

 public Class<?> findType(String typename) throws EvaluationException {
  Class<?> clazz;
  try {
   clazz = bundle.loadClass(typename);
   return clazz;
  } catch (ClassNotFoundException e) {
   throw new EvaluationException("could not find class in bundle:"+bundle.getBundleId(), e);
  }
  
 }

}
All this code is wired together in the blueprint.xml file:
<?xml version="1.0" encoding="UTF-8"?>
<blueprint xmlns="http://www.osgi.org/xmlns/blueprint/v1.0.0"
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xsi:schemaLocation="
    http://www.osgi.org/xmlns/blueprint/v1.0.0 http://www.osgi.org/xmlns/blueprint/v1.0.0/blueprint.xsd default-activation="lazy">
    
    <type-converters>
     <bean id="spelConverter" class="be.i8c.spel.ExpressionLanguageConverter">
      <argument ref="evaluationContext"/>
     </bean>
    </type-converters>
    
    <bean id="evaluationContext" class="org.springframework.expression.spel.support.StandardEvaluationContext">
     <property name="typeLocator">
      <bean class="be.i8c.spel.impl.BundleTypeLocator">
       <argument ref="blueprintBundle"/>
      </bean>
     </property>
    </bean>
    
    <bean id="numberGuess" class="be.i8c.spel.impl.DummyService" activation="eager" scope="singleton" >
     <argument type="java.lang.Double" value="#{ T(java.lang.Math).random() * 100.0 }"/>
 </bean>
</blueprint>
Note the type-converters tags to register our type converter, our custom typeLocator and the SpEL expression #{ T(java.lang.Math).random() *100.0}.

Last but not least don't forget to install the spring-expression and spring-core bundle in your osgi container when you want to deploy.

ref:
http://static.springsource.org/spring/docs/3.1.x/spring-framework-reference/html/expressions.html


author: Johan Huylebroeck

Tuesday, July 31, 2012

Skinning in JSF


Why Skinning?

CSS is the tool for webdevelopers (or more likely designers) to customize sites without having to alter code. This allows for visual variability without the need to compile/test/deploy/... things. It also allows user-specific theming where the user can decide which theme looks best.
However there is one thing notably lacking in css: variables. They may be introduced in CSS3 but it will be a ways before they are actually picked up by all major browsers. Until then we can look at other frameworks to see how they achieve skinning in JSF, most notably Richfaces. This document is written using richfaces as a guideline but does not actually require any richfaces components to be present so it can be used with any JSF framework.

Richfaces skinning

Richfaces supports skinning by using a simple properties file to represent the variables. The properties file must:
  • be located in META-INF/skins
  • have the extension "skin.properties"
For example you might create a skin "META-INF/skins/test.skin.properties". To tell richfaces to use this skin, you have to configure a context parameter in the web.xml file:
<context-param>
      <param-name>org.richfaces.skin</param-name>
      <param-value>test</param-value>
</context-param>
You can take an existing skin file to see which variables exist or you can browse the component pages to see which variables they reference. Additionally richfaces will by default (you can turn this off) style regular jsf components as well in order to create a single theme for the entire application.

Using the variables yourself

Of course you don't only want to skin existing richfaces components, you may want to use the variables in the css of your own components/pages. In order to do this, you need to create a file with an extension ".ecss" instead of ".css". This will be automatically picked up by richfaces and the variables inside it will be replaced with the properties available in the skin file. Each variable must be written like this:
'#{richSkin.myParam}'
The quotes are mandatory. There are however a number of caveats.

Library lookups are wonky

Richfaces will translate your ecss outputStylesheet to something like this:
<link type = "text/css" rel = "stylesheet"
      href = "/app/rfRes/myStyleSheet.ecss.xhtml?db=eAHb-CCOEQAGJQHx" />
Which is fine unless you add the style sheet to a library, then it will generate this (note the escaped ampersand):
<link type = "text/css" rel = "stylesheet"
      href = "/app/rfRes/myStyleSheet.ecss.xhtml?db=eAHb-CCOEQAGJQHx&amp;ln=style" />
I'm not entirely sure if the problem lies with richfaces (I don't really see an obvious bug in the code with regards to URI encoding) or with jsf in general but either way it is a bit off. This may work depending on the browser, not sure how well supported this is though. Anyway, you can easily sidestep this by not putting it in a resource library and simply adding stuff on the root.

Eclipse does not like ecss files

Eclipse does not always like the variable format ecss uses. Depending on your level of bad luck you either get badly highlighted css or continuous parsing errors resulting in error popups.

Custom css properties are dropped

It is a long standing tradition to introduce new css features in browsers with a prefix for said browsers. For example take this bit of css which includes a chrome/firefox gradient and a firefox/general box-sizing declaration:
.menu .main a {
      background: -webkit-gradient(linear, left top, left bottom, from('#{richSkin.menuGradientLight}'), to('#{richSkin.menuGradientDark}'));
      background: -moz-linear-gradient(top, '#{richSkin.menuGradientLight}', '#{richSkin.menuGradientDark}');
      padding: 0px 10px;
      padding-top: 8px;
      -moz-box-sizing: border-box;
      box-sizing: border-box;
}
When I browsed the resulting compiled ecss, I got this:
*.menu *.main a {
      background: webkit-gradient(linear,lefttop,leftbottom,from(rgb(64,64,64)),to(rgb(43,43,43)));
      background: webkit-gradient(linear,lefttop,leftbottom,from(rgb(64,64,64)),to(rgb(43,43,43)));
      padding: 0px 10px;
      padding-top: 8px;
}
As you can see, it dropped the mozilla gradient and both the mozilla and default box-sizing properties. This is likely because richfaces actually interprets the css instead of merely replacing the variables. The colors were also defined as hexadecimal and converted to their rgb equivalent. Apart from validation, I'm not entirely sure why richfaces bothers parsing the css.

Custom variable styling

To work around the issues mentioned above, I wrote my own stylesheet compiler. It requires a few things to function:
  • stylesheets must use the extension ".compiled.css" instead of ".css", so for example "mystyle.compiled.css" will be picked up by the stylesheet compiler
  • it currently uses the richfaces web.xml property mentioned above to determine which skin file you want and also scans for the "META-INF/skins/<name>.skin.properties" file, in that respect it is compatible with richfaces
The variable format is slightly more lightweight, it uses "$", the css fragment in the above would become:
.menu .main a {
      background: -webkit-gradient(linear, left top, left bottom, from($menuGradientLight), to($menuGradientDark));
      background: -moz-linear-gradient(top, $menuGradientLight, $menuGradientDark);
      padding: 0px 10px;
      padding-top: 8px;
      -moz-box-sizing: border-box;
      box-sizing: border-box;
}
The compiler does not actually parse the stylesheet, it simply replaces all the variables it finds in the skin properties file.

Bits and pieces

The stylesheet compiler is a standalone jar file which can be plugged into any war file. It contains the following things.

CSSResourceHandler.java

First off you need to provide a custom implementation of the jsf resource handler. Based on this tutorial and the richfaces implementation it looks like this:
public class CSSResourceHandler extends javax.faces.application.ResourceHandlerWrapper {
      private Logger logger = LoggerFactory.getLogger(getClass());
      
      private Properties skinProperties;
      
      private ResourceHandler wrapped;
      
      public CSSResourceHandler(ResourceHandler wrapped) {
            logger.debug("Creating custom resource handler with parent {}", wrapped);
            this.wrapped = wrapped;
      }
      
      @Override
      public ResourceHandler getWrapped() {
            return wrapped;
      }
      @Override
      public Resource createResource(String resourceName, String library, String contentType) {
            logger.trace("createResource(" + resourceName + ", " + library + ", " + contentType + ")");
            // get the resource in the conventional way
            Resource resource = super.createResource(resourceName, library, contentType);
            // if the resource is a ".compiled.css" file, compile it
            if (resourceName.endsWith(".compiled.css"))
                  return new CompiledCSS(resource, getSkinProperties());
            else
                  return resource;
      }
      @Override
      public Resource createResource(String resourceName, String library) {
            return createResource(resourceName, library, null);
      }
      @Override
      public Resource createResource(String resourceName) {
            return createResource(resourceName, null, null);
      }
      
      @Override
public String getRendererTypeForResourceName(String resourceName) {
            if (resourceName.endsWith(".compiled.css"))
                  return "javax.faces.resource.Stylesheet";
            else
                  return super.getRendererTypeForResourceName(resourceName);
      }
      
      private Properties getSkinProperties() {
            if (skinProperties == null) {
                  // we need to figure out which skin is configured
                  FacesContext context = FacesContext.getCurrentInstance();
                  // get the richfaces skin parameter
                  String skin = context.getExternalContext().getInitParameter("org.richfaces.skin");
                  // the default skin
                  if (skin == null)
                        skin = "DEFAULT";
                  // in richfaces, the context class loader is used, not the faces context method of resource discovery
                  // note that the path must follow the below convention as per richfaces documentation & implementation
                  InputStream input = Thread.currentThread().getContextClassLoader().getResourceAsStream("META-INF/skins/" + skin + ".skin.properties");
                  if (input != null) {
                        logger.debug("Loading skin " + skin);
                        skinProperties = new Properties();
                        try {
                              try {
                                    skinProperties.load(input);
                              }
                              finally {
                                    input.close();
                              }
                        }
                        catch (IOException e) {
                              throw new RuntimeException(e);
                        }
                  }
                  else
                        logger.error("Could not find skin " + skin);
            }
            return skinProperties;
      }
}
As you can see, it simply delegates most resource calls to the wrapper that it was initiated with. Only when the extension ".compiled.css" is found does it kick in. If the web.xml context parameter is not set, it will fall back to the richfaces default.

CompiledCSS.java

The compiled css class extends resource and much like the handler, delegates nearly all calls. It intercepts the request for content though and performs a replace on the original content before sending it back:
public class CompiledCSS extends Resource {
      private Resource original;
      private String cached;
      
      private Properties skinProperties;
      
      public CompiledCSS(Resource original, Properties skinProperties) {
            this.original = original;
            this.skinProperties = skinProperties;
      }
      
      /**
       * This is where we actually convert the css
       */
      @Override
      public InputStream getInputStream() throws IOException {
            if (cached == null) {
                  // copy to string
                  ByteArrayOutputStream output = new ByteArrayOutputStream();
                  byte [] buffer = new byte[102400];
                  int read;
                  InputStream source = original.getInputStream();
                  try {
                        while ((read = source.read(buffer)) != -1)
                              output.write(buffer, 0, read);
                  }
                  finally {
                        source.close();
                  }
                  cached = new String(output.toByteArray());
                  for (Object key : skinProperties.keySet())
                        cached = cached.replaceAll("\\$" + key.toString(), skinProperties.get(key).toString());
            }
            return new ByteArrayInputStream(cached.getBytes());
      }
      
      @Override
      public String getRequestPath() {
            return original.getRequestPath();
      }
      @Override
      public Map<String, String> getResponseHeaders() {
            return original.getResponseHeaders();
      }
      @Override
      public URL getURL() {
            return original.getURL();
      }
      @Override
      public boolean userAgentNeedsUpdate(FacesContext context) {
            return original.userAgentNeedsUpdate(context);
      }
      @Override
      public String getContentType() {
            return "text/css";
      }
}

META-INF/faces-config.xml

You need to tell JSF to use your custom resource handler, you can do this in the faces-config file:
<faces-config
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/web-facesconfig_2_0.xsd"
version = "2.0">
      <application>
            <resource-handler>com.example.web.common.CSSResourceHandler</resource-handler>
      </application>
</faces-config>
At this point I'm not entirely sure how this works. Apart from the mandatory faces-config.xml file in your main war, you can apparantly have faces-config.xml files in your libraries as well. Richfaces registers its own resource handler and since both frameworks still work, JSF must register both instead of having a "the last setting wins" policy. How this plays out at runtime (are all the handlers stacked in order of appearance? Is it a flat list of handlers that is looped through?...) is unclear at this point.

Putting it together

Suppose you have a separate jar which defines some templates/components and most notably: some styling. The jar layout is as follows:
  • META-INF: everything must be in meta-inf for jsf to pick it up
    • resources: all jsf-related resources including templates must be available in the resources folder
      • style: the library "style"
        • default.compiled.css
      • templates
        • layout.xhtml
    • skins
      • custom.skin.properties
Suppose we define a very simple layout.xhtml:
<html xmlns = "http://www.w3.org/1999/xhtml"
      xmlns:h = "http://java.sun.com/jsf/html"
      xmlns:f = "http://java.sun.com/jsf/core"
      xmlns:rich = "http://richfaces.org/rich"
      xmlns:a4j = "http://richfaces.org/a4j"
      xmlns:ui = "http://java.sun.com/jsf/facelets">
      <h:head>
            <title>
                  <ui:insert name = "title"/>
            </title>
            <h:outputStylesheet name = "default.compiled.css" library = "style"/>
      </h:head>
      <h:body><ui:insert name = "main"/></h:body>
</html>
As you can see, we can reference the compiled stylesheet as we would a normal stylesheet. Now suppose we have this bit in the stylesheet:
.menu .main a {
      background: -webkit-gradient(linear, left top, left bottom, from($menuGradientLight), to($menuGradientDark));
      background: -moz-linear-gradient(top, $menuGradientLight, $menuGradientDark);
      padding: 0px 10px;
      padding-top: 8px;
      -moz-box-sizing: border-box;
      box-sizing: border-box;
}
Then we need to add the properties to the custom.skin.properties file:
menuBorderColor=#666666
menuGradientLight=#404040
menuGradientDark=#2b2b2b
When you open the css as it is retrieved by the browser, you will see:
.menu .main a {
      background: -webkit-gradient(linear, left top, left bottom, from(#404040), to(#2b2b2b));
      background: -moz-linear-gradient(top, #404040, #2b2b2b);
      padding: 0px 10px;
      padding-top: 8px;
      -moz-box-sizing: border-box;
      box-sizing: border-box;
}
If you package this as a "theme" library for your jsf applications, you can create an index.xhtml in your main war file that contains:
<ui:composition xmlns = "http://www.w3.org/1999/xhtml"
      xmlns:h = "http://java.sun.com/jsf/html"
      xmlns:f = "http://java.sun.com/jsf/core"
      xmlns:rich = "http://richfaces.org/rich"
      xmlns:a4j = "http://richfaces.org/a4j"
      xmlns:ui = "http://java.sun.com/jsf/facelets"
      template = "templates/layout.xhtml">
      <ui:define name = "main">
            <p>the main content comes here!</p>
      </ui:define>
</ui:composition>

Author: Alexander Verbruggen