Under The Hood: The Liferay Faces Generator

Under The Hood: The Liferay Faces Generator

The Liferay Faces Generator is a code generation tool used to create custom JavaServerTM Faces (JSF) components (such as the new Liferay Faces Alloy components). The purpose of the generator is to automatically write the boilerplate code necessary to create each custom JSF component. The Liferay Faces team uses the generator to produce taglib.xml declarations (including component configuration and documentation), attribute getters and setters, rendering code for AlloyUI attributes, and many minor aspects of custom components.

The Generator's tags.xml File

JSF component developers are intimately familiar with the JSF taglib.xml file, which is used to declare custom components, and each taglib.xml file already contains much of the information necessary to generate components. In order to take advantage of the familiar syntax and helpful information in taglib.xml files, the Liferay Faces Generator takes as input a tags.xml file which uses a purposefully similar syntax to a taglib.xml file to declare components for the generator. The tags.xml file also adds some helpful extensions to the taglib.xml schema to make generation easier.

Here's an example tags.xml entry:

<tag>
    <description><![CDATA[This is an <code>exampleTag</code>.]]></description>
    <tag-name>exampleTag</tag-name>
    <attribute>
        <attribute-extension>
            <default-value>"exampleDefault"</default-value>
        </attribute-extension>
        <description>
   <![CDATA[This is an <code>exampleAttribute</code>.]]>
  </description>
        <name>exampleAttribute</name>
        <type>java.lang.String</type>
    </attribute>
    </tag-extension>
        <extends-tags>styleable altInherited</extends-tags>
        <parent-class>
   <![CDATA[javax.faces.component.UIComponentBase]]>
  </parent-class>
        <renderer-parent-class>
   <![CDATA[javax.faces. render.Renderer]]>
  </renderer-parent-class>
        <since>2.0</since>
    </tag-extension>
</tag>

The above code will generate the following output in the taglib.xml file:

<tag>
    <description><![CDATA[This is an <code>exampleTag</code>.]]></description>
    <tag-name>exampleTag</tag-name>
    <component>
        <component-type>
   com.liferay.faces.example.exampletag.ExampleTag
  </component-type>
        <renderer-type>
   com.liferay.faces.example.exampletag.ExampleTagRenderer
  </renderer-type>
    </component>
    <attribute>
        <description>
   <![CDATA[HTML passthrough attribute specifying alternative
   information about the rendered HTML element.]]>
  </description>
        <name>alt</name>
        <required>false</required>
        <type><![CDATA[java.lang.String]]></type>
    </attribute>
    <attribute>
        <description>
   <![CDATA[This is an <code>exampleAttribute</code>. The default
   value is <code>"exampleDefault"</code>.]]>
  </description>
        <name>exampleAttribute</name>
        <required>true</required>
        <type>java.lang.String</type>
    </attribute>
    <attribute>
        <description>
   <![CDATA[HTML passthrough attribute specifying the
   css style of the element.]]>
  </description>
        <name>style</name>
        <required>false</required>
        <type><![CDATA[java.lang.String]]></type>
    </attribute>
    <attribute>
        <description>
   <![CDATA[List of CSS class names (separated by spaces) that are
   to be rendered within the class attribute.]]>
  </description>
        <name>styleClass</name>
        <required>false</required>
        <type><![CDATA[java.lang.String]]></type>
    </attribute>
    </tag-extension>
     <vdldoc:since>2.0</vdldoc:since>
    </tag-extension>
</tag>

The tags.xml entry and generated taglib.xml declaration are nearly identical. However, thanks to the generator, several elements appear in the taglib.xml entry which are not in the tags.xml file. The generator writes the <component> element and its children, the exampleAttribute's <required> element, and information about default values in the attribute descriptions (look closely at the <description> of exampleAttribute in both files). However, the most helpful elements generated are the additional <attribute> elements. The component developer can specify tags in the <extends-tags> element, and the generator will apply the extended tags' attributes to the current component. This feature eliminates the need for duplicate attribute declarations which occur in normal taglib.xml files.

Generated Java

The generator produces much of the Java code required for creating JSF custom components including attribute getters and setters and rendering code for AlloyUI attributes.

For JSF, each attribute must have a valid Java Bean getter and setter, so the generator creates these getters and setters in the ComponentBase.java file. Take for example, the following declaration of alloy:inputDates panes attribute:

<attribute>
    <attribute-extension>
        <alloy-ui>true</alloy-ui>
    </attribute-extension>
    <description>
  <![CDATA[The number of month panes shown in the popup calendar.
  Valid values are <code>1</code> (the default), <code>2</code>,
  and <code>3</code>.]]>
 </description>
    <name>panes</name>
    <type><![CDATA[java.lang.Integer]]></type>
</attribute>

The above attribute declaration will cause the generator to create the following getter and setter in InputDateBase.java:

public Integer getPanes() {
    return (Integer) getStateHelper().eval(InputDatePropertyKeys.panes,
        null);
}

public void setPanes(Integer panes) {
    getStateHelper().put(InputDatePropertyKeys.panes, panes);
}

Similarly, since the panes attribute simply passes its value through to the client-side AlloyUI JavaScript object, we set the <alloy-ui> element to true. Because this is an AlloyUI attribute, the generator writes helpful rendering code in the ComponentRenderBase.java class. In the case of the panes attribute, the generator writes the following rendering code for us in the InputDateRendererBase.java file:

protected static final String PANES = "panes";

/* ... */

@Override
public void encodeAlloyAttributes(FacesContext facesContext,
 ResponseWriter responseWriter, UIComponent uiComponent) throws
 IOException {

/* ... */

    Integer panes = inputDate.getPanes();

    if (panes != null) {

        encodePanes(responseWriter, inputDate, panes, first);
        first = false;
    }

/* ... */

}

/* ... */

protected void encodePanes(ResponseWriter responseWriter,
 InputDate inputDate, Integer panes, boolean first) throws IOException {

    encodeInteger(responseWriter, PANES, panes, first);
}

The Liferay Faces Generator has helped the Liferay Faces Team to more rapidly develop JSF custom components. Because the generator produces the tedious and boilerplate aspects of JSF components, we can focus on the more challenging, interesting, and important facets of component development. Although I've highlighted the major features of the generator's taglib.xml generation, getter and setter generation, and rendering code generation, the generator produces other helpful code as well. The Generator's wiki page is a great place to get information on all of the generator's features and see examples of the generator's usage.

Finally, the generator is currently designed with only the Liferay Faces project in mind, but I would love to make it more generic. If the you are interested, send me a pull request.