h:selectOneMenu generic converter for all entities without calling DB again and again - jsf

I want to get selected object from <h:selectOneMenu>, but the problem is I couldn't find any generic converter for all type of entities.
My first question is, is there a generic converter for all type of entities? I don't want to write another converter again for each other entity. My second question is, is there a way to get selected object without any converter? I don't want to call the DB again and again.
I have a Car entity with id and name properties.

My first question is, is there a generic converter for all type of entities?
This does indeed not exist in standard JSF. The JSF utility library OmniFaces has such a converter in its assortiment, the omnifaces.SelectItemsConverter. All you need to do is to declare it as converter of an UISelectOne or UISelectMany component as follows:
<h:selectOneMenu ... converter="omnifaces.SelectItemsConverter">
See also the SelectItemsConverter showcase. This converter relies on the toString() of the object item. There's also another one, the omnifaces.SelectItemsIndexConverter, which relies instead on the index of the object item in the options list, see also the SelectItemsIndexConverter showcase.
There are currently no other JSF component/utility libraries offering the same.
Second question is, is there a way to get selected object without any converter?
No. Just use the OmniFaces one so that you don't need to create a custom converter which hits the DB. Or if you want to go overboard, create a custom renderer for <h:selectOneMenu> which renders the item index as option value and is able to set it as model value, but that's a lot of more work than a simple converter and you'd still need to do some additional work in order to get the desired object from the list based on the index — which just doesn't make any sense.
See also:
How to populate options of h:selectOneMenu from database?
Conversion Error setting value for 'null Converter'
Implement converters for entities with Java Generics

It seems like there should be a generic converter so that you can easily select the object from the drop down list without having to write a converter for every object type and without having to call the database (as most examples show). But there isn't that I know of, so I wrote my own converter to do this. Note that the converter expects the object to have a getId() method which returns a unique ID of some kind. If it doesn't it will fail. You can add logic to the getMethodName() if you need to determine the name of the method to use as a key programmatically. Note that we use Seam in our project. If you don't use Seam, the NO_SELECTION_VALUE parts can probably be removed as well as the three annotations on the class.
This code was inspired by: http://arjan-tijms.omnifaces.org/2011/12/automatic-to-object-conversion-in-jsf.html
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collection;
import javax.faces.component.UIComponent;
import javax.faces.component.UISelectItem;
import javax.faces.component.UISelectItems;
import javax.faces.context.FacesContext;
import javax.faces.convert.ConverterException;
import javax.faces.model.SelectItem;
import org.jboss.seam.annotations.Name;
import org.jboss.seam.annotations.faces.Converter;
import org.jboss.seam.annotations.intercept.BypassInterceptors;
/**
* #author: Jason Wheeler
* #description Converter for lists (SelectOneMenu, SelectManyMenu, etc)
* #created: 09/05/2013
*/
#Name("listConverter")
#BypassInterceptors
#Converter
public class ListConverter implements javax.faces.convert.Converter {
private String NO_SELECTION_VALUE = "org.jboss.seam.ui.NoSelectionConverter.noSelectionValue";
#Override
public String getAsString(FacesContext facesContext, UIComponent component, Object obj) {
if (obj == null) {
return NO_SELECTION_VALUE;
} else {
try {
Method method = obj.getClass().getMethod(getMethodName(obj));
return String.valueOf(method.invoke(obj));
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
public String getMethodName(Object obj) {
return "getId";
}
#Override
public Object getAsObject(FacesContext facesContext, UIComponent component, String val) throws ConverterException {
if (val == null) {
return null;
} else if (val.equals(NO_SELECTION_VALUE)) {
return null;
} else {
for (SelectItem item : getSelectItems(component)) {
if (val.equals(getAsString(facesContext, component, item.getValue()))) {
return item.getValue();
}
}
return null;
}
}
protected Collection<SelectItem> getSelectItems(UIComponent component) {
Collection<SelectItem> collection = new ArrayList<SelectItem>();
for (UIComponent child : component.getChildren()) {
if (child instanceof UISelectItem) {
UISelectItem ui = (UISelectItem) child;
SelectItem item = (SelectItem) ui.getValue();
collection.add(item);
} else if (child instanceof UISelectItems) {
UISelectItems ui = (UISelectItems) child;
Object value = ui.getValue();
collection.addAll((Collection<SelectItem>) value);
}
}
return collection;
}
}

I've just took #Bigwheels code, made some changes to work with JSF 2.0 and it fixed my problem:
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import javax.faces.component.UIComponent;
import javax.faces.component.UISelectItem;
import javax.faces.component.UISelectItems;
import javax.faces.context.FacesContext;
import javax.faces.convert.ConverterException;
import javax.faces.convert.FacesConverter;
import javax.faces.model.SelectItem;
import javax.persistence.Id;
#FacesConverter("selectItemConverter")
public class SelectItemConverter implements javax.faces.convert.Converter {
private String NO_SELECTION_VALUE = "SELECIONE";
#Override
public String getAsString(FacesContext facesContext, UIComponent component, Object obj) {
if (obj == null) {
return NO_SELECTION_VALUE;
} else {
try {
Method method = obj.getClass().getMethod(getIdMethodName(obj));
return String.valueOf(method.invoke(obj));
} catch (Exception e) {
throw new ConverterException(e);
}
}
}
public String getIdMethodName(Object obj) {
try {
Field[] fieldList = obj.getClass().getDeclaredFields();
Field id = null;
for (Field field : fieldList) {
if(field.isAnnotationPresent(Id.class)) {
id = field;
break;
}
}
return "get" + capitalize(id.getName());
} catch(Exception ex) {
throw new ConverterException(ex);
}
}
private String capitalize(final String line) {
return Character.toUpperCase(line.charAt(0)) + line.substring(1);
}
#Override
public Object getAsObject(FacesContext facesContext, UIComponent component, String val) throws ConverterException {
if (val == null) {
return null;
} else if (val.equals(NO_SELECTION_VALUE)) {
return null;
} else {
for (Object item : getSelectItems(component)) {
if (val.equals(getAsString(facesContext, component, item))) {
return item;
}
}
return null;
}
}
protected List getSelectItems(UIComponent component) {
List list = new ArrayList();
for (UIComponent child : component.getChildren()) {
if (child instanceof UISelectItem) {
UISelectItem ui = (UISelectItem) child;
SelectItem item = (SelectItem) ui.getValue();
list.add(item);
} else if (child instanceof UISelectItems) {
UISelectItems ui = (UISelectItems) child;
Object value = ui.getValue();
list.addAll((Collection<SelectItem>) value);
}
}
return list;
}
}

Related

Cannot add object(s) in an arrayList of object using selectManyCheckbox

I would like to add one or more object from a list of object into an other empty list. I am sure the first list is already initialized as I am able to print it in the p:selectManyCheckbox.
I think it does not copy the object Comite into the list but just the string as I get the error : java.lang.ClassCastException: java.lang.String cannot be cast to com.primasatis.jsf.model.Comite
Thus how can I copy it into the list ?
public class Comite {
private int idComite;
private int idPerimetre;
private String comite;
public Comite(int idComite, int idPerimetre, String comite) {
super();
this.idComite = idComite;
this.idPerimetre = idPerimetre;
this.comite = comite;
}
}
public static boolean addUserComiteRelation(int idUser, List<Comite> comites) {
System.out.println(comites.size()); // print the good number
for(int i=0; i<comites.size();i++) {
System.out.println(comites.get(i).comite); //error here
}
}
<h:form>
<p:outputLabel for="Comites" value="Comités" />
<p:selectManyCheckbox id="Comites" value="#{utilisateur.comites}"
required="true"
requiredMessage="L'utilisateur doit appartenir à au moins un comité">
<f:selectItems value="#{sessionScope.utilisateur.comites}" />
</p:selectManyCheckbox>
<h:message for="Comites"></h:message>
</h:form>
really quick view of my class utilisateur (#Named and #SessionScoped)
public class Utilisateur implements Serializable {
private Perimetre perimetreUtilisateur = null;
private List<Comite> comites = null;
#PostConstruct
private void init() {
perimetreUtilisateur = new Perimetre();
comites = new ArrayList<>();
}
}
The usual way to use p:selectManyCheckbox is to provide a List of javax.faces.model.SelectItem objects.
If you want to use your own Pojos as options in p:selectManyCheckbox, then you would have to write a Converter ( javax.faces.convert.FacesConverter ).
For Example, your converter would look like :
In My ManagedBean(Example: ComiteManagedBean) class I would keep all Comite instances in a MAP(Example: comiteMap) with Comite.idComite as Key.
import javax.faces.component.UIComponent;
import javax.faces.context.FacesContext;
import javax.faces.convert.Converter;
import javax.faces.convert.FacesConverter;
#FacesConverter("comiteConverter")
public class ComiteConverter implements Converter {
public Object getAsObject(FacesContext fc, UIComponent uic, String value) {
if(value != null && value.trim().length() > 0) {
try {
Comite comite = (ComiteManagedBean) fc.getExternalContext().getApplicationMap().get("ComiteManagedBean");
return service.getComiteMap().get(Integer.parseInt(value));
} catch(NumberFormatException e) {
throw new ConverterException(new FacesMessage(FacesMessage.SEVERITY_ERROR, "Conversion Error", "Not a valid theme."));
}
}
else {
return null;
}
}
public String getAsString(FacesContext fc, UIComponent uic, Object object) {
if(object != null) {
return ((Comite) object).getComite();
}
else {
return null;
}
}
}

Creating a ToolTip Managed bean

This is a followup to my previous post at ToolTip Performance in XPages I have got the code to do it written (not tested) so I can't seem to get my Managed Bean to get called properly. My config contians the following:
<managed-bean id="ToolTip">
<managed-bean-name>WFSToolTip</managed-bean-name>
<managed-bean-class>ca.workflo.wfsToolTip.ToolTipText</managed-bean-class>
<managed-bean-scope>session</managed-bean-scope>
</managed-bean>
and I have stripped my code down to the bare minimum:
package ca.workflo.wfsToolTip;
public class ToolTipText {
public String getToolTipText(String key){
return key;
}
}
My class is in the build path. I have a simple XPage with one filed on it and a tool tip for that field. The code for the tooltip is:
<xe:tooltip id="tooltip1" for="inputText1">
<xe:this.label>
<![CDATA[#{javascript:WFSToolTip.getToolTipText("More Stuff");}]]>
</xe:this.label>
</xe:tooltip>
When I load the test XPage in the browser I get an error that:
Error while executing JavaScript computed expression
Script interpreter error, line=1, col=12: Error calling method 'getToolTipText(string)' on java class 'ca.workflo.wfsToolTip.ToolTipText'
JavaScript code
1: WFSToolTip.getToolTipText("More Stuff");
I can't figure out why the call to getToolTipText would fail.
Can anyone see where I'm going wrong. This is my first Managed Bean and at the moment it is managing me rather than the other way around.
Thanks.
You need to:
- implement Serializable which boils down to state it and provide a version
- implement Map ... a little more work
Then you use Expression Language instead of SSJS. It would look like #{WFSToolTip["More Stuff"]}
This is how such a class would look like. You need to:
adjust the view name to reflect the name you want
the view needs to be flat, column 1 = tooltip name, column 2 = tooltip text
somewhere (on an admin/config page) you need to call WFSToolTip.clear(); (in SSJS) after you update the values in the configuration.
The example doesn't lazyload since running though a view navigator once is really fast. No point to do all these lookups.
Here you go:
package com.notessensei.xpages;
import java.io.Serializable;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.Vector;
import lotus.domino.Base;
import lotus.domino.Database;
import lotus.domino.NotesException;
import lotus.domino.View;
import lotus.domino.ViewEntry;
import lotus.domino.ViewEntryCollection;
import com.ibm.xsp.extlib.util.ExtLibUtil;
public class Parameters implements Serializable, Map<String, String> {
private final static String CONFIG_VIEW = "keywords";
private static final long serialVersionUID = 1L;
private final Map<String, String> internalMap = new HashMap<String, String>();
public Parameters() {
this.populateParameters(internalMap);
}
private void populateParameters(Map<String, String> theMap) {
Database d = ExtLibUtil.getCurrentDatabase();
try {
View v = d.getView(CONFIG_VIEW);
ViewEntryCollection vec = v.getAllEntries();
ViewEntry ve = vec.getFirstEntry();
ViewEntry nextVe = null;
while (ve != null) {
nextVe = vec.getNextEntry(ve);
// Load the parameters, column 0 is the key, column 0 the value
Vector colVal = ve.getColumnValues();
theMap.put(colVal.get(0).toString(), colVal.get(1).toString());
// Cleanup
this.shred(ve);
ve = nextVe;
}
// recycle, but not the current database!!!
this.shred(ve, nextVe, vec, v);
} catch (NotesException e) {
e.printStackTrace();
}
}
public void clear() {
this.internalMap.clear();
this.populateParameters(this.internalMap);
}
public boolean containsKey(Object key) {
return this.internalMap.containsKey(key);
}
public boolean containsValue(Object value) {
return this.internalMap.containsValue(value);
}
public Set<java.util.Map.Entry<String, String>> entrySet() {
return this.internalMap.entrySet();
}
public String get(Object key) {
return this.internalMap.get(key);
}
public boolean isEmpty() {
return this.internalMap.isEmpty();
}
public Set<String> keySet() {
return this.internalMap.keySet();
}
public String put(String key, String value) {
return this.internalMap.put(key, value);
}
public void putAll(Map<? extends String, ? extends String> m) {
this.internalMap.putAll(m);
}
public String remove(Object key) {
return this.internalMap.remove(key);
}
public int size() {
return this.internalMap.size();
}
public Collection<String> values() {
return this.internalMap.values();
}
private void shred(Base... morituri) {
for (Base obsoleteObject : morituri) {
if (obsoleteObject != null) {
try {
obsoleteObject.recycle();
} catch (NotesException e) {
// We don't care we want go get
// rid of it anyway
} finally {
obsoleteObject = null;
}
}
}
}
}
The difference to a regular HashMap is only the constructor that populates it. Hope that clarifies it.
I've never seen that id property.. My beans in faces-config look like this:
<managed-bean>
<managed-bean-name>CurrentJob</managed-bean-name>
<managed-bean-class>com.domain.inventory.Job</managed-bean-class>
<managed-bean-scope>session</managed-bean-scope>
</managed-bean>
Technically managed beans should implement Serializable and have a blank constructor. So you should have something like this inside :
public ToolTipText() {
}
I THINK you can get away without the Serializable for somethings... I always implement though but I'm sure you need the no argument constructor.
Thanks to all that have responded and helped out here especially Stephan Wissel. I thought I would post my version of Stephan's code, pretty much the same. There are issues with making the class an ApplicationScope because you need to shut down the HTTP task to refresh and reload the Class. What I did was added a button to the custom control where I to the view of the tooltips where I do the CRUD stuff and in the button do WFSToolTip().clear() and it rebuilds the map. Pretty neat. My next task for this is try to do the CRUD using JAVA and update the map directly. At the moment though I need to move on to my next task.
My next task revolves around a very similar Class. I have a master database that contains all the basic design and code. Then I have one or more applications that use that code and store the documents in their own database that contains the forms and views for that specific application. In the master I have created one or more application documents. Each of these documents contains the AppName (the key value) then the Map value is an array (Vector) containing the ReplicaID of the Application Database and a few other pieces of information. My class the loads a Map entry for each Application and collects a bunch of other information about the application from several places and stores that in the Map Value. At this point then I can set Database db = thisClass.getDatabase("App Name"). so a single custom control can be used for any/all of the applications. Pretty cool. I think I could get to like this.
Anyway here is the code I'm using for the ToolTips - BTW It has taken an XPage with about 175 fields and 100+ tooltips from being painfully slow to being acceptable. The good thing about it is that the XPage is creating a process profile document and once created it is not frequently modified as an admin action - not an everyday user action.
Please feel free point out error, omitions or suggestions to the code:
package ca.workflo.wfsToolTip;
import lotus.domino.Base;
import lotus.domino.Session;
import lotus.domino.Database;
import lotus.domino.View;
import lotus.domino.NotesException;
import lotus.domino.ViewEntry;
import lotus.domino.ViewEntryCollection;
import java.io.Serializable;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.Vector;
import com.ibm.xsp.extlib.util.ExtLibUtil;
public class ToolTipText implements Serializable, Map<String, String> {
private static final long serialVersionUID = 1L;
private Session s;
private String repID;
private Database db;
private Database helpDB;
private View helpView;
private ViewEntry ve;
private ViewEntry tVE;
private ViewEntryCollection veCol;
private final Map<String, String> internalMap = new HashMap<String, String>();
public ToolTipText() {
this.populateMap(internalMap);
}
private void populateMap(Map<String, String> theMap) {
try {
s = ExtLibUtil.getCurrentSession();
db = s.getCurrentDatabase();
repID = db.getProfileDocument("frmConfigProfile", "").getItemValue(
"WFSHelpRepID").firstElement().toString();
helpDB = s.getDbDirectory(null).openDatabaseByReplicaID(repID);
helpView = helpDB.getView("vwWFSToolTipHelp");
veCol = helpView.getAllEntries();
ve = veCol.getFirstEntry();
ViewEntry tVE = null;
while (ve != null) {
tVE = veCol.getNextEntry(ve);
Vector colVal = ve.getColumnValues();
theMap.put(colVal.get(0).toString(), colVal.get(1).toString());
recycleObjects(ve);
ve = tVE;
}
} catch (NotesException e) {
System.out.println(e.toString());
}finally{
recycleObjects(ve, tVE, veCol, helpView, helpDB);
}
}
public void clear() {
this.internalMap.clear();
this.populateMap(this.internalMap);
}
public boolean containsKey(Object key) {
return this.internalMap.containsKey(key);
}
public boolean containsValue(Object value) {
return this.internalMap.containsValue(value);
}
public Set<java.util.Map.Entry<String, String>> entrySet() {
return this.internalMap.entrySet();
}
public String get(Object key) {
try {
if (this.internalMap.containsKey(key)) {
return this.internalMap.get(key);
} else {
return "There is no Tooltip Help for " + key;
}
} catch (Exception e) {
return "error in tooltip get Object ";
}
}
public boolean isEmpty() {
return this.internalMap.isEmpty();
}
public Set<String> keySet() {
return this.internalMap.keySet();
}
public String put(String key, String value) {
return this.internalMap.put(key, value);
}
public void putAll(Map<? extends String, ? extends String> m) {
this.internalMap.putAll(m);
}
public String remove(Object key) {
return this.internalMap.remove(key);
}
public int size() {
return this.internalMap.size();
}
public Collection<String> values() {
return this.internalMap.values();
}
public static void recycleObjects(Object... args) {
for (Object o : args) {
if (o != null) {
if (o instanceof Base) {
try {
((Base) o).recycle();
} catch (Throwable t) {
// who cares?
}
}
}
}
}
}

How to use detached entitys + version in a JSF Converter

i have a problem with conversion of entitys with a version. I made a simple example to explain my problem because the "real" application is to big and contains many unnecessary things.
Situation: I have a web application with primefaces and openjpa. I have 20 components (autocompletes + selectedmenues) that needs a converter and they use persistence entitys.
Informations: I only want use jsf,primefaces for it! (Nothing special like omnifaces or something else.) The Question is at bottom. This is only test-code. It is NOT complete and there are some strange things. But this explain my problem at best.
Example entity: (Only fields and hashcode + equals)
#Entity
public class Person {
#Id
private Long id;
private String name;
#Version
private Long version;
#Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((name == null) ? 0 : name.hashCode());
return result;
}
#Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Person other = (Person) obj;
if (name == null) {
if (other.name != null)
return false;
} else if (!name.equals(other.name))
return false;
return true;
}
}
First solution: My first solution was that i make a own converter for every component.I inject my managed bean there and use the getter from the "value" of the component.
Bean
#ManagedBean(name = "personBean")
#ViewScoped
public class PersonBean implements Serializable {
private List<Person> persons;
/** unnecessary things **/
xhtml:
<p:selectOneMenu >
<f:selectItems value="#{personBean.persons}"/>
</p:selectOneMenu>
converter:
#ManagedBean
#RequestScoped
public class PersonConverter implements Converter{
#ManagedProperty(value = "personBean")
private PersonBean personBean;
#Override
public Object getAsObject(FacesContext context, UIComponent component,
String value) {
//null & empty checks
Long id = Long.valueOf(value);
for(Person person : personBean.getPersons()){
if(person.getId().equals(id)){
return person;
}
}
throw new ConverterException("some text");
}
#Override
public String getAsString(FacesContext context, UIComponent component,
Object value) {
//null & Instanceof checks
return String.valueOf(((Person)value).getId());
}
}
Summary: This solution works good. But i found that there must be a better solution as an converter for every component.
Second solution: I found here on stackoverflow the Global Entity Converter. One converter for all, i thought that was a good solution. ("p:autocomplete for a global Entity Converter"). I use it and i thought it works fine. BUT after a few tests i found another big problem, the version of the entity.
Problem1 with entity converter:
I have the version field not in my hashcode or equals (i found nothing about it). I only read this (The JPA hashCode() / equals() dilemma) about it. The problem is that the entity will not replaced in the hashmap and in some cases i get an optimistic locking exception because the "old" entity stays in the hashmap.
if (!entities.containsKey(entity)) {
String uuid = UUID.randomUUID().toString();
entities.put(entity, uuid);
return uuid;
} else {
return entities.get(entity);
}
Solution: I thought that i can resolve this problem by adding an interface to my entitys that checks whether a version exists.
Interface:
public interface EntityVersionCheck {
public boolean hasVersion();
}
Implementation:
#Override
public boolean hasVersion() {
return true;
}
Converter:
#Override
public String getAsString(FacesContext context, UIComponent component, Object entity) {
synchronized (entities) {
if(entity instanceof EntityVersionCheck && ((EntityVersionCheck)entity).hasVersion()){
entities.remove(entity);
}
if (!entities.containsKey(entity)) {
String uuid = UUID.randomUUID().toString();
entities.put(entity, uuid);
return uuid;
} else {
return entities.get(entity);
}
}
}
This solution works for the optimistic locking exception but brings another problem!
Problem2 with entity converter:
<p:selectOneMenu value="#{organisation.leader}">
<f:selectItems value="#{personBean.persons}"/>
</p:selectOneMenu>
If a organisation has already a leader. It will be replaced with a new uuid if the leader is in the persons - list, too. The leader will be set to null or convert exception because the uuid does not exists anymore in the hashmap. It means he use the converter for the organisation.leader and add the leader to the hashmap. Than comes the persons- List and add all other persons in a hashmap and override the uuid from the organisation.leader if he exists in persons, too.
Here are two cases now:
When i select a other leader, it works normally.
If i dont change the "current" selection and submit the organisation.leader tries to find his "old" uuid but the other person from the person list has override it and the uuid does not exists and the organisation.leader is null.
I found another solution for it and this is my final solution BUT i find, that is a very very strange solution and i will do this better but i found nothing about it.
Final Solution
I add the "old" uuid to the "new" object.
#Override
public String getAsString(FacesContext context, UIComponent component,
Object entity) {
synchronized (entities) {
String currentuuid = null;
if (entity instanceof EntityVersionCheck
&& ((EntityVersionCheck) entity).hasVersion()) {
currentuuid = entities.get(entity);
entities.remove(entity);
}
if (!entities.containsKey(entity)) {
if (currentuuid == null) {
currentuuid = UUID.randomUUID().toString();
}
entities.put(entity, currentuuid);
return currentuuid;
} else {
return entities.get(entity);
}
}
}
Question: How i make this better and right?
If Solution 1 worked and you just want it more generic:
Holding your instances within scope of a bean you can use a more generic converter by removing the managed-bean lookup from it. Your entities should inherit from a base entity with a identifier property. You can instantiate this converter in your bean where you retrieved the entities.
Or use a guid map or public identifier if id should not be exposed in html source.
#ManagedBean(name = "personBean")
#ViewScoped
public class PersonBean implements Serializable {
private List<Person> persons;
private EntityConverter<Person> converter;
// this.converter = new EntityConverter<>(persons);
}
<p:selectOneMenu converter="#{personBean.converter}">
<f:selectItems value="#{personBean.persons}"/>
</p:selectOneMenu>
Abstract Base Entity converter:
/**
* Abstract Entity Object JSF Converter which by default converts by {#link Entity#getId()}
*/
public abstract class AEntityConverter<T extends Entity> implements Converter
{
#Override
public String getAsString(final FacesContext context, final UIComponent component, final Object value)
{
if (value instanceof Entity)
{
final Entity entity = (Entity) value;
if (entity.getId() != null)
return String.valueOf(entity.getId());
}
return null;
}
}
Cached collection:
/**
* Entity JSF Converter which holds a Collection of Entities
*/
public class EntityConverter<T extends Entity> extends AEntityConverter<T>
{
/**
* Collection of Entity Objects
*/
protected Collection<T> entities;
/**
* Creates a new Entity Converter with the given Entity Object's
*
* #param entities Collection of Entity's
*/
public EntityConverter(final Collection<T> entities)
{
this.entities = entities;
}
#Override
public Object getAsObject(final FacesContext context, final UIComponent component, final String value)
{
if (value == null || value.trim().equals(""))
return null;
try
{
final int id = Integer.parseInt(value);
for (final Entity entity : this.entities)
if (entity.getId().intValue() == id)
return entity;
}
catch (final RuntimeException e)
{
// do something --> redirect to exception site
}
return null;
}
#Override
public void setEntities(final Collection<T> entities)
{
this.entities = entities;
}
#Override
public Collection<T> getEntities()
{
return this.entities;
}
}
Remote or database lookup Converter:
/**
* Entity Object JSF Converter
*/
public class EntityRemoteConverter<T extends Entity> extends AEntityConverter<T>
{
/**
* Dao
*/
protected final Dao<T> dao;
public EntityRemoteConverter(final EntityDao<T> dao)
{
this.dao = dao;
}
#Override
public Object getAsObject(final FacesContext context, final UIComponent component, final String value)
{
// check for changed value
// no need to hit database or remote server if value did not changed!
if (value == null || value.trim().equals(""))
return null;
try
{
final int id = Integer.parseInt(value);
return this.dao.getEntity(id);
}
catch (final RuntimeException e)
{
// do someting
}
return null;
}
}
I use dao approach whenever I have to convert view-parameters and bean was not constructed yet.
Avoid expensive lookups
In dao approach you should check if the value has changed before doing potential expensive lookups, since converters could be called multiple times within different phases.
Have a look at source of: http://showcase.omnifaces.org/converters/ValueChangeConverter
This basic approach is very flexible and can be extended easily for many use cases.

Strange ClassCastException on ADF Mobile App

I use JDev 11.1.2.4
I have a custom Supplier class which is being load some items by invoking applicationScope bean method.
I am trying to transform my object to appropriate selectItems. I could obtain right object list essentially, but suddenly faced ClassCastException. Unfortunatelly, i could not find any solution on internet.
I know those classes are exactly same. (additionaly i see on debug time that package and classeses has no difference as seen)
Where is the problem?? I read on internet something about different classloaders but i couldnt reach root cause or solution.
please helpme
brgds
package com.accmee.mobile.supplier;
import com.accmee.mobile.pojo.ServiceCategory;
import com.acme.structure.util.datalist.SimpleListSupplier;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import javax.el.MethodExpression;
import oracle.adfmf.framework.api.AdfmfJavaUtilities;
import oracle.adfmf.javax.faces.model.SelectItem;
public class ServiceCategorySupplier extends SimpleListSupplier
{
public ServiceCategorySupplier(boolean blankItemApplied)
{
super(blankItemApplied);
}
public ServiceCategorySupplier()
{
super();
}
public void loadList()
{
try
{
MethodExpression me = AdfmfJavaUtilities.getMethodExpression("#{applicationScope.loginBean.loadCategories}", List.class, new Class[] { }); /* this applicationScope bean method loads through webservice consume via JavaAPI, and works properly returns list with elements**/
List categories = (List)me.invoke(AdfmfJavaUtilities.getAdfELContext(), new Object[] { });
itemList.addAll(getConvertedToSelectItemList(categories, true)); // here passes parameter into method which faced exception
}
catch (Exception e)
{
e.printStackTrace();
}
}
public String getListName()
{
return "categories";
}
public static Collection getConvertedToSelectItemList(List list, boolean blankItemApplied)
{
Collection convertedCollection = new ArrayList();
SelectItem selectItem = null;
if (blankItemApplied)
{
selectItem = new SelectItem();
convertedCollection.add(selectItem);
}
for(int i=0;i<list.size();i++)
{
ServiceCategory superEntity = (ServiceCategory)list.get(i); // here is the ClassCastException, this line throws exception
selectItem = getConvertedToSelectItem(superEntity);
convertedCollection.add(selectItem);
}
return convertedCollection;
}
public static SelectItem getConvertedToSelectItem(ServiceCategory superEntity)
{
SelectItem selectItem = new SelectItem();
selectItem.setLabel(superEntity.getName());
selectItem.setValue(superEntity);
return selectItem;
}
}
The same class loaded by two different classloaders is considered at runtime as two different classes. Probably that's what's happening to you.
Watch this page: http://www.ibm.com/developerworks/java/library/j-dyn0429/
I had to change my approach. So, i changed returnType of loadCategories method to GenericType instead of my custom class.
Then it worked like that.
public class ServiceCategorySupplier extends SimpleListSupplier
{
public ServiceCategorySupplier(boolean blankItemApplied)
{
super(blankItemApplied);
}
public ServiceCategorySupplier()
{
super();
}
public void loadList()
{
try
{
MethodExpression me = AdfmfJavaUtilities.getMethodExpression("#{applicationScope.loginBean.loadCategories}", List.class, new Class[] { });
List categories = (List)me.invoke(AdfmfJavaUtilities.getAdfELContext(), new Object[] { });
list.addAll(categories);
loadItemList();
}
catch (Exception e)
{
e.printStackTrace();
throw new AdfException(e.getMessage(), AdfException.ERROR);
}
}
public void loadItemList()
{
SelectItem selectItem = null;
itemList=new SelectItem[list.size()];
ServiceCategory serviceCategory=null;
for(int i=0;i<list.size();i++)
{
GenericType serviceCategoryType = (GenericType)list.get(i);
serviceCategory = (ServiceCategory)GenericTypeBeanSerializationHelper.fromGenericType(ServiceCategory.class, serviceCategoryType);
selectItem = getConvertedToSelectItem(serviceCategory);
itemList[i]=selectItem;
}
}
public static SelectItem getConvertedToSelectItem(ServiceCategory superEntity)
{
SelectItem selectItem = new SelectItem();
selectItem.setLabel(superEntity.getName());
selectItem.setValue(superEntity.getId());
return selectItem;
}
public String getListName()
{
return "categories";
}
}

Avoid extra DB reads in the getAsObject method of converter class by caching data client side?

I'm showing a list of suggested items in an autocomplete input element. For that I need to implement a converter to convert the entity<entityName, entityId> to entityName & vice versa. However while implementing that I realized that I had to read the DB more than 1 time to find the corresponding entityId for the chosen entityName(while getAsObject()), I am wondering why isn't this stored somewhere client side so that the entityId could be passed when the entityname is selected.
Is there any way I could avoid this extra read?
This is indeed "by design" and perhaps a little oversight in the JSF spec. You can in theory perfectly avoid it by extracting the items from the UIComponent argument and comparing against them instead. It's however a bit of work. My colleague Arjan Tijms has written a blog about this: Automatic to-Object conversion in JSF selectOneMenu & Co.
Here's an extract of relevance; the below is the base converter which you'd need to extend instead:
public abstract class SelectItemsBaseConverter implements Converter {
#Override
public Object getAsObject(FacesContext context, UIComponent component, String value) {
return SelectItemsUtils.findValueByStringConversion(context, component, value, this);
}
}
Here's the SelectItemsUtils class which is partly copied from Mojarra's source:
public final class SelectItemsUtils {
private SelectItemsUtils() {}
public static Object findValueByStringConversion(FacesContext context, UIComponent component, String value, Converter converter) {
return findValueByStringConversion(context, component, new SelectItemsIterator(context, component), value, converter);
}
private static Object findValueByStringConversion(FacesContext context, UIComponent component, Iterator<SelectItem> items, String value, Converter converter) {
while (items.hasNext()) {
SelectItem item = items.next();
if (item instanceof SelectItemGroup) {
SelectItem subitems[] = ((SelectItemGroup) item).getSelectItems();
if (!isEmpty(subitems)) {
Object object = findValueByStringConversion(context, component, new ArrayIterator(subitems), value, converter);
if (object != null) {
return object;
}
}
} else if (!item.isNoSelectionOption() && value.equals(converter.getAsString(context, component, item.getValue()))) {
return item.getValue();
}
}
return null;
}
public static boolean isEmpty(Object[] array) {
return array == null || array.length == 0;
}
/**
* This class is based on Mojarra version
*/
static class ArrayIterator implements Iterator<SelectItem> {
public ArrayIterator(SelectItem items[]) {
this.items = items;
}
private SelectItem items[];
private int index = 0;
public boolean hasNext() {
return (index < items.length);
}
public SelectItem next() {
try {
return (items[index++]);
}
catch (IndexOutOfBoundsException e) {
throw new NoSuchElementException();
}
}
public void remove() {
throw new UnsupportedOperationException();
}
}
}
Here's how you should use it for your own converter, you only have to implement getAsString() (the getAsObject() is already handled):
#FacesConverter("someEntitySelectItemsConverter")
public class SomeEntitySelectItemsConverter extends SelectItemsBaseConverter {
#Override
public String getAsString(FacesContext context, UIComponent component, Object value) {
return ((SomeEntity) value).getId().toString();
}
}
Update the above concept has ended up in JSF utility library OmniFaces in flavor of the following converters:
SelectItemsConverter - for <f:selectItem(s)> based on Object#toString().
SelectItemsIndexConverter - for <f:selectItem(s)> based on item's index.
ListConverter - for e.g. <p:autoComplete> based on Object#toString()
ListIndexConverter - for e.g. <p:autoComplete> based on item's index.
The only way I have found to do this so that your converter does not need to access the DB, is to make the Converter a managed bean so that it can access some other bean which stores the list of suggested values of the AutoComplete component.
Something like this:
#ManagedBean
#RequestScoped
public class EntityConverter implements Converter
{
#ManagedProperty(value = "#{autoCompleteBean}")
private AutoCompleteBean autoCompleteBean;
public void setAutoCompleteBean(AutoCompleteBean autoCompleteBean)
{
this.autoCompleteBean = autoCompleteBean;
}
#Override
public Object getAsObject(FacesContext context, UIComponent component,
String value)
{
final List<Entity> entities = autoCompleteBean.getCachedSuggestions();
for (final Enity entity : entities)
{
if (entity.getIdAsString().equals(value))
{
return entity;
}
}
throw new IllegalStateException("Entity was not found!");
}
#Override
public String getAsString(FacesContext context, UIComponent component,
Object value)
{ ... }
In your jsf page, make sure you reference the converter as a bean. ie:
<p:autoComplete value="#{autoCompleteBean.selectedEntity}"
completeMethod="#{autoCompleteBean.getSuggestions}" var="theEntity"
itemValue="#{theEntity}" itemLabel=#{theEntity.someValue}
converter="#{entityConverter}">

Resources