Posted by
edburns on September 2, 2009 at 9:53 AM PDT
This ultra-quick blog entry shows how to use the JSF runtime to
access metadata for a composite component. Note that most of the
metadata is optional when creating a composite component, therefore,
this entry will be of interest to tool vendors and those wishing to
write composite components that stand a chance of showing up nicely in
tools.
The Using Page
When showing a composite component demo, I always like to start out
with the using page.
Listing 1: the using page
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<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:my="http://java.sun.com/jsf/composite/cc"
xmlns:meta="http://mojarra.dev.java.net/cc-metadata">
<h:head>
.grayBox { padding: 8px; margin: 10px 0; border: 1px solid #CCC; background-color: #f9f9f9; }
</h:head>
<h:body>
<h:form>
<p>Composite Component usage:
</p>
<div id="cc" class="grayBox" style="border: 1px solid #090;">
<my:myComponent loginAction="#{userBean.loginAction}" />
<h2>Metada for This Composite Component
</h2>
<div id="foo" class="grayBox" style="border: 1px solid #090;">
<meta:printMetadata viewName="main.xhtml" libraryName="cc"
resourceName="myComponent.xhtml"/>
<p><h:commandButton value="submit" /></p>
</h:form>
</h:body>
Line 5 declares the namespace “my” using the handy
composite component namespace prefix
“http://java.sun.com/jsf/composite” followed by
“cc”. JSF 2 interprets this to mean: treat any .xhtml files
in the directory /resources/cc as a JSF composite
component.
Line 6 declares a Facelet custom tag library with the namespace
“http://mojarra.dev.java.net/cc-metadata”.
Line 20 uses the composite component named “myComponent”
in the “cc” library.
Line 27 uses the “printMetadata” component from the
Facelet custom tag library. We must pass in the name of a Facelet page,
the library name, and the resource name.
The Composite Component
Let's examine the composite component itself. As I mentioned
earlier, most of this metadata is optional and is included here just to
show what the system understands. This component has just about every
kind of metadata that is in the JSF2 spec.
Listing 2 the composite component
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<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:ui="http://java.sun.com/jsf/facelets"
xmlns:fmd="http://java.sun.com/xml/ns/javaee/faces/design-time-metadata"
xmlns:cc="http://java.sun.com/jsf/composite">
<cc:interface
name="component"
displayName="A really fancy composite component"
expert="true"
hidden="false"
preferred="true"
shortDescription="This component has all the supported metadata."
extraTopLevelAttribute="Whatever I want here">
<!-- start top level attributes -->
<cc:attribute
name="model"
required="true"
displayName="model"
expert="false"
hidden="false"
preferred="true"
shortDescription="The model for this component"
extraTopLevelAttribute="Whatever I want here">
<cc:extension>
<fmd:expert>true</fmd:expert>
</cc:extension>
<cc:attribute
name="userid"
default="guest"
displayName="User Id"
expert="false"
hidden="false"
preferred="true"
shortDescription="The model must have a userid property"/>
<cc:attribute
name="password"
default="guest"
displayName="Password"
expert="false"
hidden="false"
preferred="true"
shortDescription="The model must have a password property"/>
</cc:attribute>
<cc:attribute name="useridLabel" default="Userid:" />
<cc:attribute name="passwordLabel" default="Password:" />
<cc:attribute name="loginButtonLabel" default="Login" />
<cc:attribute name="loginAction" method-signature="java.lang.String f()"
required="true" />
<!-- end top level attributes -->
<!-- start facets -->
<cc:facet
name="header"
displayName="The header facet for the fancy component"
expert="true"
hidden="false"
preferred="false"
shortDescription="If you want a header, this is where you put it"
anotherExtraAttribute="Lots of metadata" />
<cc:facet
name="footer"
displayName="The footer facet for the fancy component"
expert="true"
hidden="false"
preferred="false"
shortDescription="If you want a footer, this is where you put it"
anotherExtraAttribute="Lots of metadata" />
<!-- end facets -->
<!-- start attached objects -->
<cc:editableValueHolder
name="userid"
displayName="Userid field"
expert="true"
hidden="false"
preferred="false"
shortDescription="Attach a converter or validator here, if you like"
someExtraMetadata="userid metadata" />
<cc:editableValueHolder
name="password"
displayName="Password field"
expert="true"
hidden="false"
preferred="false"
shortDescription="Attach a converter or validator here, if you like"
someExtraMetadata="password metadata" />
<cc:actionSource
name="login"
displayName="Login button"
expert="true"
hidden="false"
preferred="false"
shortDescription="Attach an actionListener here, if you like"
someExtraMetadata="login metadata" />
<!-- end attached objects -->
</cc:interface>
<cc:implementation>
<cc:renderFacet name="header" />
<p><h:outputLabel for="#{cc.clientId}:userid"
value="#{cc.attrs.useridLabel}" />
<h:inputText id="userid" /></p>
<p><h:outputLabel for="#{cc.clientId}:password"
value="#{cc.attrs.passwordLabel}" />
<h:inputText id="password" /></p>
<p><h:commandButton value="#{cc.attrs.loginButtonLabel}"
action="#{cc.attrs.loginAction}" /></p>
<cc:renderFacet name="footer" />
</cc:implementation>
Lines 16 - 23 declare the top level composite component metadata.
Lines 25 - 65 declare the attributes that may be passed as XML
elements on the composite component instance in the using page. Note
that the loginAction attribute is declared as required on
line 63. This is useful because Facelets will put up a helpful error if
the page author doesn't supply a value here.
Lines 68 - 88 declare the allowable facets. In this case, they're optional.
Lines 90 - 119 declare the attached objects within the implementation
section that are publically accessible from the using page.
Programmatically accessing the metadata
Up to this point, everything I've shown has been included in other
blog entries elsewhere. What hasn't been shown is how the JSF runtime
uses the metadata API to actually conjure up the composite component
instance. This API is also accessible to tools that want to embed a JSF
runtime into their tool environment. To show this off, I've created a
non-composite JSF component and exposed it in the usual way, with a
Facelet taglib. This still requires a descriptor, which is placed in
WEB-INF/classes/META-INF.
Listing 3 the cc-metadata.taglib.xml
<facelet-taglib xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance'
xmlns='http://java.sun.com/xml/ns/javaee'
xsi:schemaLocation='http://java.sun.com/xml/ns/javaee web-facelettaglibrary_2_0.xsd'
version="2.0">
<namespace>http://mojarra.dev.java.net/cc-metadata</namespace>
<tag>
<tag-name>printMetadata</tag-name>
<component>
<component-type>jsf2.PrintMetadata</component-type>
</component>
<!-- these are advisory and not enforced by the runtime -->
<attribute>
<name>viewName</name>
<required>true</required>
</attribute>
<attribute>
<name>libraryName</name>
<required>true</required>
</attribute>
<attribute>
<name>resourceName</name>
<required>true</required>
</attribute>
</tag>
</facelet-taglib>
Lines 11 - 23 are optional but I like to include them for explicit
documentation.
The JSF Component that shows how to access the metadata is in Listing
4 A. This listing covers obtaining a reference to the
BeanInfo that is the composite component metadata.
Listing 4 A, Accessing the composite component metadata programmatically
package jsf2;
import java.beans.BeanInfo;
import java.io.IOException;
import javax.faces.FacesException;
import javax.faces.application.Resource;
import javax.faces.component.FacesComponent;
import javax.faces.component.UIOutput;
import javax.faces.context.FacesContext;
import javax.faces.context.ResponseWriter;
import javax.faces.view.ViewDeclarationLanguage;
@FacesComponent(value="jsf2.PrintMetadata")
public class PrintMetadata extends UIOutput {
@Override
public void encodeAll
(FacesContext context
) throws IOException {
ResponseWriter writer = context.getResponseWriter();
viewName,
libraryName,
resourceName;
try {
viewName = this.getAttributes().get("viewName").toString();
libraryName = this.getAttributes().get("libraryName").toString();
resourceName = this.getAttributes().get("resourceName").toString();
throw new FacesException("Must supply viewName, libraryName and resourceName attributes");
}
ViewDeclarationLanguage vdl = context.getApplication().
getViewHandler().getViewDeclarationLanguage(context, viewName);
Resource compositeComponentResource = context.getApplication().getResourceHandler().createResource(resourceName, libraryName);
BeanInfo metadata
= vdl.
getComponentMetadata(context, compositeComponentResource
);
CompositeComponentMetadataUtils.writeMetadata(metadata, writer);
}
}
On lines 24 - 29 we extract the required attributes that have been
passed to us via the page author.
On line 32, we obtain a reference to the
ViewDeclarationLanguage instance for Facelets.
On line 34, we create a Resource instance using the
passed in composite component library name and resource name.
Line 36 features the call o getComponentMetadata(). It
is safe to call this method only during the context of the JSF request
processing lifecycle. Because we're doing it from inside the rendering
of a component, we know that this is such a context.
Finally, Listing 4 B shows a traversal of the metadata.
Listing 4 B, what you can do with the metadata
package jsf2;
import java.beans.BeanDescriptor;
import java.beans.BeanInfo;
import java.beans.FeatureDescriptor;
import java.beans.PropertyDescriptor;
import java.io.IOException;
import java.util.Enumeration;
import java.util.List;
import java.util.Map;
import javax.el.ValueExpression;
import javax.faces.component.UIComponent;
import javax.faces.context.FacesContext;
import javax.faces.context.ResponseWriter;
import javax.faces.view.AttachedObjectTarget;
public class CompositeComponentMetadataUtils {
/**
* <p>Use the composite component metadata specification
* in section JSF.3.6.2.1 to print out the metadata to
* the argument writer.</p>
* @throws IOException
*/
public static void writeMetadata
(BeanInfo metadata,
// Print out the top level BeanDescriptor stuff.
writeFeatureDescriptor("composite-component-BeanDescriptor", descriptor,
writer);
writeFeatureDescriptorValues(
"composite-component-BeanDescriptor", descriptor,
writer);
writeFeatureDescriptor("composite-component-attribute", cur,
writer);
writeFeatureDescriptorValues("composite-component-attribute", cur,
writer);
}
}
public static void writeFeatureDescriptor
(String prefix,
writer.write(prefix + "-name:" +
fd.getName() + "\n");
writer.write(prefix + "-displayName:" +
fd.getDisplayName() + "\n");
writer.write(prefix + "-shortDescription:" +
fd.getShortDescription() + "\n");
writer.write(prefix + "-expert:" +
fd.isExpert() + "\n");
writer.write(prefix + "-hidden:" +
fd.isHidden() + "\n");
writer.write(prefix + "-preferred:" +
fd.isPreferred() + "\n");
}
public static void writeFeatureDescriptorValues
(String prefix,
Enumeration<String> extraValues = fd.attributeNames();
while (extraValues.hasMoreElements()) {
curName = extraValues.nextElement();
if (curName.equals(AttachedObjectTarget.ATTACHED_OBJECT_TARGETS_KEY)) {
List<AttachedObjectTarget> attachedObjects =
(List<AttachedObjectTarget>) fd.getValue(curName);
for (AttachedObjectTarget curTarget : attachedObjects) {
writer.write(prefix + "-attached-object-" + curTarget.getName() + "\n");
}
} else if (curName.equals(UIComponent.FACETS_KEY)) {
Map<String, PropertyDescriptor> facets =
(Map<String, PropertyDescriptor>) fd.getValue(curName);
for (String cur
: facets.
keySet()) {
String facetPrefix
= prefix
+ "-facet-" + cur
;
writeFeatureDescriptor(facetPrefix, facets.get(cur),
writer);
writeFeatureDescriptorValues(facetPrefix,
facets.get(cur), writer);
}
} else {
ValueExpression ve = (ValueExpression) fd.getValue(curName);
writer.write(prefix + "-extra-attribute-" + curName + ": " +
ve.getValue(FacesContext.getCurrentInstance().getELContext())
+ "\n");
}
}
}
}
On line 30, we ask the argument BeanInfo for its
BeanDescriptor. We pass this to the helper method
writeFeatureDescriptor(). This simple method just prints
out the standard FeatureDescriptor metadata.
On line 33, we pass the same descriptor to the helper method
writeFeatureDescriptorValues(). This method steps through
the Enumeration returned from
attributeNames(), calls getValue() on each
entry, and takes appropriate action on each entry, based on the metadata
specification. Currently there are three kinds of entries.
Lines 70 - 75 deal with the attached objects. The type
for this entry is
List<AttachedObjectTarget>.
AttachedObjectTarget encapsulates how an attached
object, in this declared on lines 90 through 119 of Listing 1,
exposes an inner component to the page author.
Lines 76 - 85 deal with the exposed facets. The type
for this entry is Map<String,
PropertyDescriptor>, where the key is the facet name
and the PropertyDescriptor represents each
individual <cc:facet> element. These are
on lines 68 - 88 of Listing 1.
Lines 87 - 91 deal with the else case, which can be
characterized as “if it's not the attached objects, and
it's not the facets, then it'll just come to you as a
ValueExpression.
Back up on line 36, we ask the metadata for the
PropertyDescriptor [] using
getPropertyDescriptors(). This is specified to return a
PropertyDescritor for each of the attributes, in this case
show on lines 25 - 64 of Listing 1. For each attribute, we print out
the FeatureDescriptor and the values. However, note that
in the case of attributes, there will only be
ValueExpression instances in the getValue()
data structure. A screengrab of the running page is show here.

When the Expert Group designed this api, we wanted to make it as easy
as possible to provide tool support for composite components. I hope
this is helpful to tool authors for this purpose.
Technorati Tags: edburns
about layout