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.
Related
This can be seen as a continuation of my other question.
In my backend view, I have a list of some POJO. The POJO is converted to JSON with its toString() method:
BackingView.java
#Getter #Setter private List<SomePOJO> pojoList = ...
SomePojo.java
#EqualsAndHashCode(callSuper = false, of = {"id"})
public class SomePOJO implements Serializable {
private static final long serialVersionUID = 1L;
#Getter #Setter private Long id;
#Getter #Setter private Long date;
#Getter #Setter private Date name;
....
#Override
public String toString() {
ObjectMapper mapper = new ObjectMapper();
try {
return mapper.writeValueAsString(this);
} catch (JsonProcessingException e) {
e.printStackTrace();
return null;
}
}
//making a readable string-representation of the object to display on the orderList
public String toStringOrderlistDisplay() {
return "Pojo with id " + id + "and so on... "
}
In my frontend, I want to allow the user to sort this pojo-list using Primefaces orderList :
<p:orderList id="ordList" widgetVar="ordList"
value="#{backingview.pojoList }" var="rfg"
controlsLocation="left" responsive="true" itemValue="#{rfg}"
itemLabel="#{rfg.toStringOrderlistDisplay()}" converter="#{pojoConverter}">
</p:orderList>
PojoConverter.java
#Named
#FacesConverter(value = "pojoConverter")
public class PojoConverter implements Converter {
#Override
public Pojo getAsObject(FacesContext context, UIComponent component, String value) {
if (value != null && value.trim().length() > 0) {
try {
ObjectMapper mapper = new ObjectMapper();
return mapper.readValue(value, RechtsaktFallgeschichte.class);
}
catch (NumberFormatException | IOException ex) {
ex.printStackTrace();
throw new ConverterException(new FacesMessage(FacesMessage.SEVERITY_ERROR, "Conversion Error", "Not a valid Pojo."));
}
}
else { return null; }
}
#Override
public String getAsString(FacesContext context, UIComponent component, Object value) {
SomePOJO r = (SomePOJO) value;
if (r != null) {
return r.toString();
}
else { return null; }
}
}
Converting to JSON seems to work just fine, if I print the output of the getAsString() method, everything looks as you would expect. However, when the getAsObject() method is called, the value-String-parameter always contains "[object Object]" instead of the POJOs JSON representation. What am I doing wrong here?
I don't know if it is possible to achieve what you are trying to do, but a better way (and the working one) is to pass the POJO id in the getAsString() method, and convert it back to Pojo using a cache map Map<Long, Pojo> pojoCache.
The converter should look like this:
PojoConverter.java
#Named
#FacesConverter(value = "pojoConverter")
public class PojoConverter implements Converter<Pojo> {
#Inject
private PojoService pojoService;
#Override
public Pojo getAsObject(FacesContext context, UIComponent component, String value) {
if (value != null && value.trim().length() > 0) {
return pojoService.getPojoCache().get(Long.valueOf(value));
} else {
return null;
}
}
#Override
public String getAsString(FacesContext context, UIComponent component, Pojo value) {
if (value != null) {
return String.valueOf(value.getId());
} else {
return null;
}}
}
For a more detailed explanation check this: p:orderList converter getAsObject() doesn't call Object.toString()
I'm working on a datatable in which a single column contains various fields like
Name
Salary
Date
Birth
So I took datatype as String in my bean for that column.
Now my concern is, can I use p:calendar for date of birth with datatype String in my bean?
If so then how?
Use a FacesConverter
Proof of concept:
#Named
public class DateStringConverter implements Converter {
#Override
public Object getAsObject(FacesContext arg0, UIComponent arg1, String arg2) {
SimpleDateFormat parser = new SimpleDateFormat("yyyy-MM-dd");
try {
return parser.parse(arg2);
} catch (ParseException e) {
e.printStackTrace();
return null;
}
}
#Override
public String getAsString(FacesContext arg0, UIComponent arg1, Object arg2) {
return arg2.toString();
}
}
xhthml
<p:calendar value="#{dateAsString}" pattern="yyyy-MM-dd" converter="#{dateStringConverter}" />
I have a <p:selectCheckboxMenu> and I want to get the selected values back in bean. But the value I receive
when I select an item from the menu it's a string, representing the type field from the CategorizationBean.
I just want when I select an item from the table, to get the whole CategorizationBean structure in the bean.
This is the snippet from the xhtml page:
<p:selectCheckboxMenu label="Categorization"
value="#alertMB.selectedCategories}"
converter="com.converter.CategoryConverter">
<f:selectItems value="#{alertMB.categoryDomainEntry}"
var="category"
itemLabel="#{category.type}"
itemValue="#{category}"/>
</p:selectCheckboxMenu>
Snippet from bean:
public List<CategorizationBean> getSelectedCategories() {
return selectedCategories;
}
public void setSelectedCategories(List<CategorizationBean> selectedCategories) {
this.selectedCategories = selectedCategories;
}
public class CategorizationBean implements Serializable{
private String type;
private long id;
I think that you have missed by using a list of beans, I use this example and it works:
<p:selectCheckboxMenu id="slctRdBtn"
value="#{yourBean.compLovDtgrid}"
converter="compLovDtgridConverter">
<f:selectItems
value="#{yourBean.listCompLovDtgrid}"
var="rdbtn" itemLabel="#{rdbtn.vjlrLibelleRep}"
itemValue="#{rdbtn}" />
</p:selectCheckboxMenu>
and for the converter:
#FacesConverter(forClass=CompLovDtgrid.class , value="compLovDtgridConverter")
public class CompLovDtgridConverter implements Converter{
#Override
public String getAsString(FacesContext context, UIComponent component, Object value)
{
return (value instanceof CompLovDtgrid) ? ((CompLovDtgrid) value).getVjlrCodeRep() : null;
}
#Override
public Object getAsObject(FacesContext context, UIComponent component,String value)
{
if(value == null)
return null;
YourBean data = context.getApplication().evaluateExpressionGet(context, "#{yourBean}", YourBean.class);
for(CompLovDtgrid compLovDtgrid : data.getListCompLovDtgrid())
{
if(compLovDtgrid.getVjlrCodeRep().equals(value))
return compLovDtgrid;
}
throw new ConverterException(new FacesMessage(String.format("Cannot convert %s to CompLovDtgrid", value)));
}
}
and for the list, I use:
public List<CompLovDtgrid> getListCompLovDtgrid()
{
return listCompLovDtgrid;
}
public void setListCompLovDtgrid(List<CompLovDtgrid> listCompLovDtgrid) {
this.listCompLovDtgrid = listCompLovDtgrid;
}
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}">