How to iterate over java.util.Set in JSF
By bozhobgI spent quite some time trying to find a solution for the following JSF issue: it is not possible to iterate over a java.util.Set.
- ui:repeat (facelets) doesn’t work
- a4j:repeat (richfaces) doesn’t work
- c:forEach works..only in case it does not rely on a variable defined by a parent component (rich:dataTable for instance)
All above are pretty logical phenomena, as UIData relies on ordered data, and generally a Set is not ordered.
In my case I had to use a Set defined in the Hibernate (JPA) object (PersistentSet).
An important note: you should use a set in case the view order is of no matter to you.
The solution..is pretty simple. And I’ll suggest it to be a part of facelets/richfaces for the next version, unless of course there is some valid specific
reason for it not to be.
1. Define your own UI component extending an existing repeater component. I used a4j:repeat (HtmlAjaxRepeat)
2. Override the metohd getDataModel
3. Define your component in your faces-config
4. create a custom facelets tag definition
5. Define a context-variable in web.xml pointing to the facelet tag definition.
Note: for use with JSP instead of Facelets, you should define a .tld and a Tag handler, which is not an ojbect of this post.
Now let’s see the steps in detail:
1,2. Here some code:
- package com.myproject.components;
- import java.util.ArrayList;
- import java.util.Set;
- import javax.faces.model.DataModel;
- import javax.faces.model.ListDataModel;
- import org.ajax4jsf.component.html.HtmlAjaxRepeat;
- import org.ajax4jsf.model.SequenceDataModel;
- public class UIIterator extends HtmlAjaxRepeat {
- @SuppressWarnings("unchecked")
- @Override
- protected DataModel getDataModel() {
- Object current = getValue();
- if(current instanceof Set){
- return new SequenceDataModel(new ListDataModel(
- new ArrayList((Set) current)));
- }
- return super.getDataModel();
- }
- }
package com.myproject.components; import java.util.ArrayList; import java.util.Set; import javax.faces.model.DataModel; import javax.faces.model.ListDataModel; import org.ajax4jsf.component.html.HtmlAjaxRepeat; import org.ajax4jsf.model.SequenceDataModel; public class UIIterator extends HtmlAjaxRepeat { @SuppressWarnings("unchecked") @Override protected DataModel getDataModel() { Object current = getValue(); if(current instanceof Set){ return new SequenceDataModel(new ListDataModel( new ArrayList((Set) current))); } return super.getDataModel(); } }
So, as we don’t care about the order of the elements, we just create a new ArrayList out of the Set. And we can now easily return the appropirate DataModel.
3. Add this to your faces-config. (I copied it from the a4j definition)
- <component>
- <description />
- <display-name>Iterator</display-name>
- <component-type>com.myproject.Iterator</component-type>
- <component-class>com.myproject.components.UIIterator</component-class>
- <component-extension>
- <component-family>javax.faces.Data</component-family>
- <renderer-type>org.ajax4jsf.components.RepeatRenderera</renderer-type>
- </component-extension>
- </component>
<component> <description /> <display-name>Iterator</display-name> <component-type>com.myproject.Iterator</component-type> <component-class>com.myproject.components.UIIterator</component-class> <component-extension> <component-family>javax.faces.Data</component-family> <renderer-type>org.ajax4jsf.components.RepeatRenderera</renderer-type> </component-extension> </component>
4. Here is the tag definition for facelets
- <?xml version="1.0" encoding="UTF-8"?>
- <!DOCTYPE facelet-taglib PUBLIC
- "-//Sun Microsystems, Inc.//DTD Facelet Taglib 1.0//EN"
- "http://java.sun.com/dtd/facelet-taglib_1_0.dtd">
- <facelet-taglib xmlns="http://java.sun.com/JSF/Facelet">
- <namespace>http://myproject.com/cust</namespace>
- <tag>
- <tag-name>repeat</tag-name>
- <component>
- <component-type>com.myproject.Iterator</component-type>
- <renderer-type>org.ajax4jsf.components.RepeatRenderer</renderer-type>
- </component>
- </tag>
- </facelet-taglib>
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE facelet-taglib PUBLIC "-//Sun Microsystems, Inc.//DTD Facelet Taglib 1.0//EN" "http://java.sun.com/dtd/facelet-taglib_1_0.dtd"> <facelet-taglib xmlns="http://java.sun.com/JSF/Facelet"> <namespace>http://myproject.com/cust</namespace> <tag> <tag-name>repeat</tag-name> <component> <component-type>com.myproject.Iterator</component-type> <renderer-type>org.ajax4jsf.components.RepeatRenderer</renderer-type> </component> </tag> </facelet-taglib>
Save this file as /WEB-INF/facelets/custom.taglib.xml
5. Add to your web.xml
- <context-param>
- <param-name>facelets.LIBRARIES</param-name>
- <param-value>/WEB-INF/facelets/custom.taglib.xml</param-value>
- </context-param>
<context-param> <param-name>facelets.LIBRARIES</param-name> <param-value>/WEB-INF/facelets/custom.taglib.xml</param-value> </context-param>
6. It is now ready to use
…
xmlns:cust=”http://myproject.com/cust”
…
<cust:repeat var=”myVar” value=”${aSet}”>
…
</cust:repeat>
I think it is way neater than other workarounds, like defining a custom EL Resolver.