Meta Class
A Meta class is the single location for defining most aspects of the component. It specifies the tag name, properties, fields, and facets, along with their documentation. It references the other related classes, and resource files, and enables animation support. It does not include actual methods with code, as that is what the Component class is intended for.
Why Use Code Generation?
There are many redundancies, and boiler-plate code in a typical JSF component. Getter and setter methods behave in a typical manner, state saving of fields is very predictable in its requirements, the faces-config.xml entries follow predictable patterns, and the JSP Tag classes and Facelets MetaHandler classes are quite uniform. In fact, hand coding of these dependencies has only proven error prone, with no real opportunity for hand optimisation. There tend not to be many ways of correctly implementing those dependencies, and their requirements can be quite nuanced.
It is possible to eliminate much of the boiler-plate code through the use of reflection, by having a component framework do common behaviours at runtime. This was investigated, and found non-performant. Alternatively, non-reflective, pure Java code, was the most performant option. As well, pure Java code and a simple faces-config.xml file is less intrusive, and more straight forward than a framework, which must integrate into JSF.
Why Use Meta Classes?
The typical solution for code generation, used by JSF implementations themselves, and most other component libraries, is to use XML files, which are parsed and processed to generate the outputs. It is possible to use DTD or Schema to provide a means for validating the XML component and property definitions, but XML editing tools vary in capability. There is mostly weak typing, no code completion, certain typos can only be discovered at runtime, if at all. The generator is more complex due to the XML to Java conversion, and the fact that every scenario has to be handled within the generator itself, as the inputs are dumb XML, and not actual objects with behaviours.
To overcome all of those deficiencies, ACE uses annotated Java classes. That means it's possible to leverage the existing Java compiler, Java language features, and Integrated Development Environments. This means that, as you're typing, your IDE is validating and correcting your input, suggesting only valid options, and showing you JavaDoc to explain what everything means.
Example Meta Class
@Component(
tagName = "tabSet",
componentClass = "org.icefaces.ace.component.tabset.TabSet",
rendererClass = "org.icefaces.ace.component.tabset.TabSetRenderer",
generatedClass = "org.icefaces.ace.component.tabset.TabSetBase",
extendsClass = "javax.faces.component.UIComponentBase",
componentType = "org.icefaces.ace.component.TabSet",
rendererType = "org.icefaces.ace.component.TabSetRenderer",
componentFamily = "org.icefaces.ace.TabSet",
tlddoc = "This is where the component is documented")
@ResourceDependencies({
@ResourceDependency(name="file.js",library="third_party_library"),
@ResourceDependency(name="tabset/tabset.js",library="icefaces.ace"),
@ResourceDependency(name="tabset/tabset.css",library="icefaces.ace")
})
@ClientBehaviorHolder(events = {
@ClientEvent(name="clientSideTabChange",
tlddoc="Fired when the tabSet has clientSide=true, and a tab " +
"change occurs. Use onstart=\"return false;\" to limit " +
"javascript execution.",
defaultExecute="@none", defaultRender="@none"),
@ClientEvent(name="serverSideTabChange",
tlddoc="Fired when the tabSet has clientSide=false, and a tab " +
"change occurs (default event).",
defaultExecute="@all", defaultRender="@all")
}, defaultEvent="serverSideTabChange")
public class TabSetMeta extends UIComponentBaseMeta {
@Property(tlddoc="The index of the current selected tab.",
defaultValue="0")
private int selectedIndex;
@Property(tlddoc="MethodExpression representing a method that will be " +
"invoked when the selected TabPane has changed. The expression " +
"must evaluate to a public method that takes a ValueChangeEvent " +
"parameter, with a return type of void.",
expression= Expression.METHOD_EXPRESSION,
methodExpressionArgument="javax.faces.event.ValueChangeEvent")
private MethodExpression tabChangeListener;
@Facets
class FacetsMeta{
@Facet
UIComponent header;
}
}
Class Relationships
From the @Component Annotation, there are four classes referenced:
- generatedClass = "org.icefaces.ace.component.tabset.TabSetBase"
- Where all the properties, fields, and facets, declared in the Meta class, will be generated
- Refered to as the Base class
- extendsClass = "javax.faces.component.UIComponentBase"
- The class which the generated Base class will be made to extend
- componentClass = "org.icefaces.ace.component.tabset.TabSet"
- The end resulting component class
- Hand-coded class, where any custom methods and behaviours may be implemented
- Extends the Base class (generatedClass), so that it's methods have access to the generated properties
- rendererClass = "org.icefaces.ace.component.tabset.TabSetRenderer"
- Hand-coded class, which is an instance of Renderer, and is responsible for rendering the component
Basically, the Component class (componentClass) extends the Base class (generatedClass) extends the Super-class (extendsClass).
Implicitly, the generated Tag and MetaHandler classes will have their package and class names derived from the Component class. Given the example above, they would be: org.icefaces.ace.component.tabset.TabSetTag and org.icefaces.ace.component.tabset.TabSetMetaHandler.
Another important class relationship is the baseMeta class that the Meta class itself extends. Just as @Component(extendsClass = "javax.faces.component.UIComponentBase"), there is the matching statement: public class TabSetMeta extends UIComponentBaseMeta. What this accomplishes, is it declares that this Meta class shall inherit the definitions from UIComponentBaseMeta, which contains the properties for UIComponentBase, when generating the TLD for the component. It is optional, since a component could potentially not want to expose it's super-class' properties as part of it's own public API. When used, it reduces a lot of boiler-plate property declarations, for standard properties such as: id, rendered, etc.
Common Annotations
The Meta class may contain the following annotations:
@Component Annotation
Used to describe the component as a whole, it mostly describes the class relationships, as well as some fields that the Base class and faces-config.xml need for wiring the component and renderer together. The tlddoc field is used for the TLD description of the component, which is useful for JSP and Facelets aware IDEs.
@ResourceDependency and @ResourceDependencies Annotations
A component may have a single @ResourceDependency annotation, or it may have a single @ResourceDependencies annotation, which itself may contain any number of @ResourceDependency annotations. Any of them specified on the Meta class are automatically duplicated in the generated Base class. The intention being that everything necessary for declaring the component may be placed within the Meta class, instead of being separately declared within the ending Component class.
The purpose of a @ResourceDependency annotation is to declare which Javascript and CSS resources that the component relies upon, so that JSF may automatically include them on the page, when this component is on the page.
@ClientBehaviorHolder Annotation
Any component that integrates with the Animation API must have this specified. The generator uses it for outputting code that enables the animation integration.
@Property Annotation
The most frequently used, and most important annotation. When a property is specified, as a regular field within the Meta class, the field type specifies the property type, and the field name specifies the property name. The @Property annotation describes other details of the property, which a Java field declaration can not encapsulate.
Properties fall into two basic categories: ValueExpression enabled properties, and MethodExpression properties. The default is a ValueExpression. To declare that a property is a Method Expression, which takes a ValueChangeEvent argument, one would put:
@Property(expression=Expression.METHOD_EXPRESSION, methodExpressionArgument="javax.faces.event.ValueChangeEvent")
A property has a default value. If none is specified, then it is null or 0 or 0.0 or false. In many cases the default value is a String literal, and in other cases it may be some sort of expression. Here are examples of both:
@Property (value="Car")
String type;
@Property (value="Manufacturer.DEFAULT", defaultValueType=DefaultValueType.EXPRESSION)
String manufacturer;
@Property (value="10", defaultValueType=DefaultValueType.EXPRESSION)
Integer count;
In some specific circumstances, it's necessary to override the property name, because the name collides with a Java reserved word. The typical example is for. This is how one would specify a property named "for", even though one can't create a field named "for", as it's a Java reserved word:
@Property(name="for")
String forVal;
The tlddoc field is used to specify the tag attribute description for this property.
The javadocGet field is used to specify the contents of the javadoc that will be put above the generated getter method for this property, in the Base class.
The javadocSet field is used to specify the contents of the javadoc that will be put above the generated setter method for this property, in the Base class.
If javadocGet or javadocSet are not specified, then they will default to the value given for the tlddoc field.
The required field specifies that this property is required, and must be specified when the component's tag is used. The default value is no, the property is not required.
Properties either already exist in the component class' super-class, or they are new and must be generated within the Base class. But sometimes an inherited property can benefit from documentation that is more specific to this component. For example a component extending from UIOutput would inherit the value property. But each sub-class may render or represent that value differently, and so, wish to override the documentation. With the inherited property, it might make sense to not generate any getter or setter methods, since they already exist in the super-class. Or, it might make sense to override those getter and setter methods with generated ones, since ACE code generation better handles when components are within a UIData container, like a dataTable. To provide guidance, the @Property annotation may specify implementation=Implementation.EXISTS_IN_SUPERCLASS, meaning that the property and code already exist in the super-class, and not to generate anything, and to only allow the property to be redocumented, if tlddoc, javadocGet or javadocSet have been specified. Otherwise, if code should be generated, specify: implementation=Implementation.GENERATE, which is the default.
@Field Annotation
A field is similar to a property, but is essentially non-public. When a component needs to maintain some private internal state, which should not appear in the TLD, and that does not have a corresponding tag attribute, then a field is appropriate.
@Facet and @Facets Annotations
A Meta class field may have a @Facet annotation on it. Since JSF component facets exist in a different namespace from properties, a means of separating Meta facets into a separate namespace was necessary. So, a Meta class may contain an inner class, with a @Facets annotation, and then within that class, fields may have a @Facet annotation on them.
The purpose of a @Facet annotation is to explicitly declare a component's facet API, and provide a means for documenting each facet. Technically it's not necessary, as facets may be used by passing in their String name into the facets Map, without the need for generated getter or setter methods, or any other supporting code. But, such an implicit, un-typed, and undocumented coding style does not fit with the ACE methodology.
@ClientBehaviorHolder and @ClientEvent Annotations
The @ClientBehaviorHolder annotation is placed at the class level in the Meta class. This annotation causes the generated component to implement the javax.faces.component.behavior.ClientBehaviorHolder interface, so that it can support the <ace:ajax /> tag. Its events property specifies a list of @ClientEvent annotations, and the defaultEvent property specifies the default event to trigger when an <ace:ajax /> tag is used without the event attribute.
With @ClientEvent annotations one can define the various events supported by the component, using the following properties:
name - The name of the event. We follow the convention of writing the names in camel case.
defaultExecute - The default value for the execute attribute in the <ace:ajax /> tag.
defaultRender - The default value for the render attribute in the <ace:ajax /> tag.
javadoc - Javadoc documentation for this event.
tlddoc - TLDdoc documentation for this event.
This is an example of the usage of these annotations:
@ClientBehaviorHolder(events = {
@ClientEvent(name="slideStart", javadoc="...", tlddoc="...", defaultRender="@all", defaultExecute="@all"),
@ClientEvent(name="slide", javadoc="...", tlddoc="...", defaultRender="@all", defaultExecute="@all"),
@ClientEvent(name="slideEnd", javadoc="...", tlddoc="...", defaultRender="@all", defaultExecute="@all")
}, defaultEvent="slideEnd")
@TagHandler Annotation
Some of the tags that developers can use when working with the ACE component suite are not components per se, but they enhance other components in customized ways. Examples of this are the <ace:ajax /> and the <ace:animation /> tags. For features like these, a @TagHandler annotation must be used instead of the @Component annotation in the Meta class. Some of the properties are the same as those of the @Component annotation. The following properties are NOT applicable to tag handlers: componentClass, rendererClass, componentType, rendererType, and componentFamily.
The following properties apply to this annotation:
tagHandlerType - Must have a value of the org.icefaces.ace.meta.annotation.TagHandlerType enum type, depending on which class of the javax.faces.view.facelets package is being extended.
tagHandlerClass - Specifies the fully qualified class name of the tag handler (which will extend the generated base class).
behaviorClass - If this tag handler is a JSF behavior, this property specifies the fully qualified class name of the behavior class (which extends javax.faces.component.behavior.ClientBehaviorBase in some way).
behaviorId - The id for this behavior in the faces-config.xml file.
Extending baseMeta Super-class
All of the standard JSF base classes have a corresponding baseMeta class. Importing those properties is as simple as having your Meta class extend the appropriate baseMeta class.