I couldn't manage to find any documentation on how to manage the tag on my custom Converter. I read many examples / tutos pointing out this tag, but when I apply it the same way I see in examples, the default value is never set to my absent-property, leaving it instead with the "deafault" null value.. or any value I set it to in the constructor (which is obviously a duplicate and thus I cannot considerate it a proper solution ).
If anyone has any tip about this....
To show out, the converter -whose property' default value is never 'set'- and its tag look like this.
faces-config :
<converter>
<converter-id>com.converter.currency</converter-id>
<converter-class>com.somedomain.converters.CurrencyConverter</converter-lass>
<property>
<property-name>nbDecimal</property-name>
<property-class>java.lang.Integer</property-class>
<default-value>2</default-value>
</property>
</converter>
taglib:
<tag>
<tag-name>convertCurrency</tag-name>
<converter>
<converter-id>com.converter.currency</converter-id>
<handler-class>com.somedomain.converters.CurrencyHandler</handler-class>
</converter>
</tag>
CurrencyHandler :
private final TagAttribute nbDecimal;
public CurrencyHandler(ConverterConfig config) {
super(config);
this.nbDecimal= this.getAttribute("nbDecimal");
}
// ...
CurrencyConverter :
private Integer nbDecimal; // + getter / setter
public CurrencyConverter() {
super();
}
public void init() {
Integer nbDecimal = getNbDecimal();
if (nbDecimal != null) {
setMinFractionDigits(nbDecimal);
setMaxFractionDigits(nbDecimal);
} else {
// this is where I could set them to default, which I don't want since it would not use the <default-value> in converter description
// unless that is the point where my mistake comes from...
}
}
public Object getAsObject(FacesContext context, UIComponent component, String value) {//...
}
public String getAsString(FacesContext context, UIComponent component, Object value) {//...
}
/**
*
*/
public Integer getNbDecimal() {
ELContext elContext = FacesContext.getCurrentInstance().getELContext();
if (getExpression() != null) {
Integer nbDecimal = (Integer) getExpression().getValue(elContext);
return nbDecimal;
} else {
return null;
}
}
}
Related
I have a converter as follows to trim all leading and trailing white spaces and strip additional spaces between words.
#ManagedBean
#ApplicationScoped
#FacesConverter(forClass=String.class)
public final class StringTrimmer implements Converter
{
#Override
public Object getAsObject(FacesContext context, UIComponent component, String value)
{
return value != null ? value.trim().replaceAll("\\s+", " ") : null;
}
#Override
public String getAsString(FacesContext context, UIComponent component, Object value)
{
return value!=null ? ((String) value).trim().replaceAll("\\s+", " ") : null;
}
}
This converter is applied globally to all string type properties in associated backing beans.
Sometimes it is necessary to bypass this converter for certain properties like "password" in which no white spaces or additional spaces between words should be trimmed or striped respectively.
How can such string type properties be bypassed so that this converter is not applied to them?
Several ways.
Explicitly declare a converter which does effectively nothing with the value.
E.g.
<h:inputSecret ... converter="noConverter" />
with
#FacesConverter("noConverter")
public class NoConverter implements Converter {
#Override
public Object getAsObject(FacesContext context, UIComponent component, String value) {
return value;
}
#Override
public String getAsString(FacesContext context, UIComponent component, Object value) {
return (value != null) ? value.toString() : ""; // This is what EL would do "under the covers" when there's no converter.
}
}
Pass an additional component attribute and let the converter check that.
<h:inputSecret ...>
<f:attribute name="skipConverter" value="true" />
</h:inputSecret>
with
#Override
public Object getAsObject(FacesContext context, UIComponent component, String value) {
if (Boolean.valueOf(String.valueOf(component.getAttributes().get("skipConverter")))) {
return value;
}
// Original code here.
}
#Override
public String getAsString(FacesContext context, UIComponent component, Object value) {
if (Boolean.valueOf(String.valueOf(component.getAttributes().get("skipConverter")))) {
return (value != null) ? value.toString() : "";
}
// Original code here.
}
Let the converter check the component type. The UIComponent behind <h:inputSecret> is an instance of the HtmlInputSecret class.
#Override
public Object getAsObject(FacesContext context, UIComponent component, String value) {
if (component instanceof HtmlInputSecret) {
return value;
}
// Original code here.
}
#Override
public String getAsString(FacesContext context, UIComponent component, Object value) {
if (component instanceof HtmlInputSecret) {
return (value != null) ? value.toString() : "";
}
// Original code here.
}
Which way to use depends on business requirements and degree of reusability of the 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.
I want to pass a list of strings as hidden input from a jsf page to the backing bean.
The backing bean is request scoped and needs to be it.
Trying to do it this way, but it doesn't seem to work. Any ideas how this can be done in a better way?
<ui:repeat value="#{bean.strings}" var="#{string}">
<h:inputHidden value="#{string}"/>
</ui:repeat>
Just use a converter for the list value:
<h:inputHidden value="#{bean.strings}" converter="myStringListConverter" />
Here is a converter that converts to/from a String using ### as separator:
#FacesConverter("myStringListConverter")
public class StringListConverter implements Converter {
// this is used as a regex, so choose other separator carefully
private static final String MY_SEPARATOR = "###";
#Override
public Object getAsObject(FacesContext context, UIComponent component,
String value) {
if (value == null) {
return new ArrayList<String>();
}
return new ArrayList<String>(Arrays.asList(value.split(MY_SEPARATOR)));
}
#Override
public String getAsString(FacesContext context, UIComponent component,
Object value) {
if (value == null) {
return "";
}
return join((List<String>) value, MY_SEPARATOR);
}
/**
* Joins a String list, src: http://stackoverflow.com/q/1751844/149872
*
* #param list
* #param conjunction
* #return
*/
public static String join(List<String> list, String conjunction) {
StringBuilder sb = new StringBuilder();
boolean first = true;
for (String item : list) {
if (first) {
first = false;
} else {
sb.append(conjunction);
}
sb.append(item);
}
return sb.toString();
}
}
If you are using JSF 2, this should work for you already as it is.
In case you are using JSF 1.2, you just have to drop the #FacesConverter annotation and register the converter in the faces-config.xml like so:
<converter>
<description>Simple String List Converer</description>
<converter-id>myStringListConverter</converter-id>
<converter-class>com.your.package.StringListConverter</converter-class>
</converter>
This question already has answers here:
Conversion Error setting value for 'null Converter' - Why do I need a Converter in JSF?
(2 answers)
Closed 6 years ago.
one more time i'm in trouble here. My point is:
In my project i need a converter for (obviously) convert the items from the SelectOneMenu component to a list property in the respective bean. In my jsf page i have:
<p:selectOneMenu id="ddlPublicType" value="#{publicBean.selectedPublicType}" effect="fade" converter="#{publicBean.conversor}" >
<f:selectItems value="#{publicoBean.lstPublicTypes}" var="pt" itemLabel="#{pt.label}" itemValue="#{pt.value}"></f:selectItems>
</p:selectOneMenu>
And my bean is:
#ManagedBean(name = "publicBean")
#RequestScoped
public class PublicBean {
// Campos
private String name; // Nome do evento
private TdPublicType selectedPublicType = null;
private List<SelectItem> lstPublicTypes = null;
private static PublicTypeDAO publicTypeDao; // DAO
static {
publicTypeDao = new PublicTypeDAO();
}
// Construtor
public PublicoBean() {
lstPublicTypes = new ArrayList<SelectItem>();
List<TdPublicType> lst = publicTypeDao.consultarTodos();
ListIterator<TdPublicType> i = lst.listIterator();
lst.add(new SelectItem("-1","Select..."));
while (i.hasNext()) {
TdPublicType actual = (TdPublicType) i.next();
lstPublicTypes.add(new SelectItem(actual.getIdPublicType(), actual.getNamePublicType()));
}
}
// Getters e Setters
...
public Converter getConversor() {
return new Converter() {
#Override
public Object getAsObject(FacesContext context, UIComponent component, String value) {
// This value parameter seems to be the value i had passed into SelectItem constructor
TdPublicType publicType = null; // Retrieving the PublicType from Database based on ID in value parameter
try {
if (value.compareTo("-1") == 0 || value == null) {
return null;
}
publicType = publicTypeDao.findById(Integer.parseInt(value));
} catch (Exception e) {
FacesMessage msg = new FacesMessage("Error in data conversion.");
msg.setSeverity(FacesMessage.SEVERITY_ERROR);
FacesContext.getCurrentInstance().addMessage("info", msg);
}
return publicType;
}
#Override
public String getAsString(FacesContext context, UIComponent component, Object value) {
return value.toString(); // The value parameter is a TdPublicType object ?
}
};
}
...
}
In the getAsObject() method, the value parameter seems to be the value i had passed into SelectItem constructor. But in the getAsString() method, the value also seems to be a string representation of an Id. This parameter shouldn't be of type TdPublicType ? There is anything wrong in my code?
The getAsString() should convert the Object (which is in your case of type TdPublicType) to a String which uniquely identifies the instance, e.g. some ID, so that it can be inlined in HTML code and passed around as HTTP request parameters. The getAsObject() should convert exactly that unique String representation back to the concrete Object instance, so that the submitted HTTP request parameter can be converted back to the original object instance.
Basically (trivial prechecks and exception handling omitted):
#Override
public String getAsString(FacesContext context, UIComponent component, Object modelValue) throws ConverterException {
// Convert Object to unique String representation for display.
return String.valueOf(((TdPublicType) modelValue).getId());
}
#Override
public Object getAsObject(FacesContext context, UIComponent component, String submittedValue) throws ConverterException {
// Convert submitted unique String representation back to Object.
return tdPublicTypeService.find(Long.valueOf(submittedValue));
}
Update: you've another problem, you're specifying the value property of TdPublicType class as the item value instead of the TdPublicType instance itself. This way the converter will retrieve the value property instead of the TdPublicType instance in the getAsString(). Fix it accordingly:
<f:selectItems value="#{publicoBean.lstPublicTypes}" var="pt"
itemLabel="#{pt.label}" itemValue="#{pt}"/>
Now the code is working. My error was in the loading method. I was doing this:
// Loading menu
List<TdPublicType> l = daoPublicType.retrieveAll();
Iterator<TdPublicType> i = l.iterator();
while (i.hasNext()) {
TdPublicType actual = (TdPublicType) i.next();
lstMenuPublicType.add(new SelectItem(actual.getIdtPublicType(), actual.getNamePublicType()));
}
But the right way is:
// Loading menu
List<TdPublicType> l = daoPublicType.retrieveAll();
Iterator<TdPublicType> i = l.iterator();
while (i.hasNext()) {
TdPublicType actual = (TdPublicType) i.next();
lstMenuPublicType.add(new SelectItem(actual, actual.getNamePublicType())); // In the first parameter i passed the PublicType object itself not his id.
}
use can use generic converter which will convert the value in the backing bean.
You do not need any casting also.
#FacesConverter(value = "GConverter")
public class GConverter implements Converter{
private static Map<Object, String> entities = new WeakHashMap<Object, String>();
#Override
public String getAsString(FacesContext context, UIComponent component, Object entity) {
synchronized (entities) {
if (!entities.containsKey(entity)) {
String uuid = UUID.randomUUID().toString();
entities.put(entity, uuid);
return uuid;
} else {
return entities.get(entity);
}
}
}
#Override
public Object getAsObject(FacesContext context, UIComponent component, String uuid) {
for (Entry<Object, String> entry : entities.entrySet()) {
if (entry.getValue().equals(uuid)) {
return entry.getKey();
}
}
return null;
}
}
Example usage would be
<p:selectOneMenu id="ddlPublicType" value="#{publicBean.selectedPublicType}" effect="fade" converter="GConverter" >
<f:selectItems value="#{publicoBean.lstPublicTypes}" var="pt" itemLabel="#{pt.label}" itemValue="#{pt}"></f:selectItems>
</p:selectOneMenu>
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}">