ShowTable of Contents
Introduction
This article is part of the
XPages Extensibility API Developers Guide. It introduces complex types and describes step-by-step how to create and implement them using the XPages Extensibility API.
A complex type represents a tag that can be the value of a property. Examples of native complex types in XPages include:
- Simple Actions
- Converters
- Data Sources
- Resources
- Validators
A common usage case of a complex type would be adding a client side JavaScript resource to an XPage. In this case, the script tag is a value of the resources property associated with an XPage.
<?xml version="1.0" encoding="UTF-8"?>
<xp:view xmlns:xp="http://www.ibm.com/xsp/core">
<xp:this.resources>
<xp:script clientSide="true" src="/script.js"></xp:script>
</xp:this.resources>
</xp:view>
Every tag in XPages represents a definition element in the xsp-config file. These elements define tags, abstract tag superclasses or other named reference-able definitions. These are elements which can contain other elements; i.e. Property and Lower Level elements. For more information, see the "
XPages Configuration File Format" reference. The xsp-config file's definition elements include:
- complex-type
- component
- composite-component
- converter
- validator
Each definition element specifies their respective *-class element; except composite-component which specifies composite-file instead; this is the corresponding Java class associated with the element. In the case of
component,
converter and
validator; the Java class specified must extend / implement
javax.faces.component.UIComponent,
javax.faces.convert.Converter and
javax.faces.validator.Validator respectively or any subclass thereof. In contrast, by definition the Java class specified by
complex-class for the element
complex-type cannot extend from the
UIComponent class or any its subclasses.
Complex types were invented as an abstraction of converters, validators and any other objects in an XPage that did not correspond to a control (
component). However, the
converter and
validator definitions are declared differently from other complex type definitions for historical reasons; the JavaServer Faces (JSF) faces-config file format allowed for
converter and
validator definitions but it did not have the concept of a
complex-type.
To begin with, you should have an XPages extension like a control which exposes some properties. If not, see the article "
Creating a Basic UI Control for XPages" and in particular the section on "
Properties".
Complex Types
Given a component with a property that supports complex type values defined as follow:
<property>
<property-name>foo</property-name>
<property-class>com.example.complex.Foo</property-class>
</property>
When the component is added to an XPage, by default it is possible to compute the value of the property.
<eg:component foo="#{javascript: new com.example.complex.Foo()}"></eg:component>
However, to specify the property's value as a tag; a
complex-type must be defined with a
tag-name and
complex-class. In order for the
complex-type to be associated with the corresponding property, their respective
*-class elements (i.e.
property-class and
complex-class) must both refer to the same Java class. In contrast to computing the value of the property, the defined tag can now be assigned instead.
<eg:component>
<eg:this.foo>
<eg:foo></eg:foo>
</eg:this.foo>
</eg:component>
Creating a complex type involves the following steps:
- Create the complex type's Java class
- Register the complex type's tag
- Implement a property supporting the complex type
Create the Complex Class
Complex types are an abstraction of any object in an XPage that does not correspond to a control; so by definition their Java class cannot extend from
UIComponent or any of its subclasses. The complex class should usually be non-abstract and define a public constructor with no arguments. The simplest complex class does not need to implement any interfaces; however if the complex type defines properties then there is a choice about whether to support computed values on those properties.
If supporting computed values, then the complex class should extend from the abstract class
com.ibm.xsp.complex.ValueBindingObjectImpl. This provides a base implementation for complex types which need to support computed values. Without explicit handling, computed values give serialization errors when persistence is enabled on the server; the complex type base class provides for this support by implementing the interfaces
com.ibm.xsp.binding.ComponentBindingObject and
javax.faces.component.StateHolder.
package com.example.complex;
import javax.faces.context.FacesContext;
import javax.faces.el.ValueBinding;
import com.ibm.xsp.complex.ValueBindingObjectImpl;
public class ExampleComplex extends ValueBindingObjectImpl {
private String goo;
public ExampleComplex() {
}
public String getGoo() {
if (goo != null) {
return goo;
}
ValueBinding vb = getValueBinding("goo");
if (vb != null) {
return (String)vb.getValue(getFacesContext());
}
return null;
}
public void setGoo(String goo) {
this.goo = goo;
}
}
If the complex class exposes properties which support computed values, their corresponding getter and setter methods follow the same format as those defined on a component. Finally, the complex class should override the methods
saveState and
restoreState as necessary to support serialization.
public void restoreState(FacesContext context, Object value) {
Object[] state = (Object[])value;
super.restoreState(context, state[0]);
goo = (String)state[1];
}
public Object saveState(FacesContext context) {
Object[] state = new Object[2];
state[0] = super.saveState(context);
state[1] = goo;
return state;
}
Register the Complex Tag
All definition elements are registered in an
xsp-config file under the root element
faces-config. Their definitions have a similar format; each element specifies their ID or type, Java class, tag name and properties. In the case of this example, this is the
xsp-config entry for the complex type defined for the Java class created above.
<faces-config>
<faces-config-extension>
<namespace-uri>http://example.com/xsp/control</namespace-uri>
<default-prefix>eg</default-prefix>
</faces-config-extension>
<complex-type>
<description>Example Complex Type</description>
<display-name>Example Complex Type</display-name>
<complex-id>com.example.complex.examplecomplex</complex-id>
<complex-class>com.example.complex.ExampleComplex</complex-class>
<property>
<description>Goo</description>
<display-name>Goo</display-name>
<property-name>goo</property-name>
<property-class>java.lang.String</property-class>
</property>
<complex-extension>
<tag-name>cmplx</tag-name>
</complex-extension>
</complex-type>
</faces-config>
The
complex-id is a String identifier used to refer to this complex type definition. By convention, they are prefixed like Java package names to prevent conflicts with other complex types. It can be referenced by
base-complex-id to build inheritance trees. If doing so then the
tag-name can be omitted which allows the
complex-class defined to be either an interface or abstract class. Should the complex-type expose properties, if they do not support computed values then each property should set
allow-run-time-binding to false.
Despite being complex types, converters and validators are defined differently for historical reasons. The JSF faces-config file format did define
converter and
validator elements; however the concept of a complex type, an abstraction of any object on a page that doesn't correspond to a control, was introduced by XPages. Converters and validators are defined similar to complex type; however the
converter and
validator definition elements are used respectively.
<converter>
<converter-id>com.example.converter.exampleconverter</converter-id>
<converter-class>com.example.converter.ExampleConverter</converter-class>
<converter-extension>
<tag-name>exampleConverter</tag-name>
</converter-extension>
</converter>
<validator>
<validator-id>com.example.validator.examplevalidator</validator-id>
<validator-class>com.example.validator.ExampleValidator</validator-class>
<validator-extension>
<tag-name>exampleValidator</tag-name>
</validator-extension>
</validator>
Implement a Property that Allows a Complex Type Value
Properties that allow complex type values are implemented similar to any other property exposed by a control. To implement a property which supports complex types for a control, declare a variable using the complex class and define its respective getter and setter methods obeying the JavaBean model for properties and using the JSF get method template.
private ExampleComplex foo;
public ExampleComplex getFoo() {
if (foo != null) {
return foo;
}
ValueBinding vb = getValueBinding("foo");
if (vb != null) {
return (ExampleComplex) vb.getValue(getFacesContext());
} else {
return null;
}
}
public void setFoo(ExampleComplex foo) {
this.foo = foo;
}
Without explicit handling, computed values and non-serializable objects give serialization errors when persistence is enabled on the server, so the XPages-specific template for computed value serialization must be followed. To serialize a complex type, the static methods
saveObjectState and
restoreObjectState provided by the
StateHolderUtil class should be used in conjunction with the component's
saveState and
restoreState methods.
public void restoreState(FacesContext context, Object state) {
Object values[] = (Object[]) state;
super.restoreState(context, values[0]);
foo = (ExampleComplex) StateHolderUtil.restoreObjectState(context, this, values[1]);
}
public Object saveState(FacesContext context) {
Object values[] = new Object[2];
values[0] = super.saveState(context);
values[1] = StateHolderUtil.saveObjectState(context, foo);
return values;
}
Once the property has been implemented, it must be exposed by the control via the
xsp-config file as follows. The
property-class should be the same as the
complex-class, so that the
complex-type tag can be used for the foo property. Once the classes are the same, the Designer editor in the All Properties tab will detect that the tag can be set as a value of the property.
<property>
<description>Foo</description>
<display-name>Foo</display-name>
<property-name>foo</property-name>
<property-class>com.example.complex.ExampleComplex</property-class>
</property>
Finally, despite the property supporting complex types; it is still accessed from the renderer similar to any other property. However, instead of the control returning a primitive or Java class; it returns the defined complex class created previously.
ExampleControl tcomponent = component instanceof ExampleControl ? (ExampleControl) component : null;
ExampleComplex foo;
if (tcomponent != null) {
foo = tcomponent.getFoo();
} else {
foo = (ExampleComplex) component.getAttributes().get("foo");
}
if (foo != null) {
// Output markup
}
Example Files
Download the
example files archive. To use this example, create a new application.
In the Package Explorer view, right-click on the new application and select Import - General - Archive File - Next.
Browse to the example files and select Finish. When asked to overwrite existing files, select "Yes To All".