From one of my previous questions on this site I realized I don't know anything about custom converter life cycle. I searched a bit on internet and found nothing useful.
I would like to understand if custom converters are created once for all and recycled any time they are needed or if they are created on the fly and destroyed.
I suppose their main purpose is to perform some easy and light tasks, so it would make no difference if the a new instance of the converter is created each time it is found inside a jsf page sent to the user.
But I would like to use a custom converter to solve a common task in what it is an unpaved way. My custom convert would have an heavy initialization logic, so I have to be sure about its life-cycle. It must be created once for all and not every time it is needed. Is it possible ?
Depending on the answers I will receive I can abort the idea of using custom converter or decide to move the heavy initialization logic in a singletone.
Converters are created once for each time you reference them when using #FacesConverter annotation. That means if you execute slow code there it'll bring you into problems.
Alternatively, you can annotate them as #ManagedBean with the scope you want and use them with an EL reference instead of raw converter id. If you want to initialize them in some way, the solution for you would be setting them the scope for the whole application and making them eagerly initialized, so they'll be created when application starts up:
Converter:
#ManagedBean(eager = true)
#ApplicationScoped
public class WorkerConverter implements Converter {
public WorkerConverter() {
System.out.println("Building converter...");
}
#Override
public Object getAsObject(FacesContext context, UIComponent component,
String value) {
Integer id = Integer.parseInt(value);
if (id == 1) {
return new Worker(1, "John");
} else {
return new Worker(1, "Larry");
}
}
#Override
public String getAsString(FacesContext context, UIComponent component,
Object value) {
return ((Worker) value).getId().toString();
}
}
Managed bean:
#ManagedBean
#ViewScoped
public class SelectWorkerBean {
public static class Worker {
Integer id;
String name;
public Worker(Integer id, String name) {
this.id = id;
this.name = name;
}
#Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Worker other = (Worker) obj;
if (id == null) {
if (other.id != null)
return false;
} else if (!id.equals(other.id))
return false;
return true;
}
public Integer getId() {
return id;
}
public String getName() {
return name;
}
#Override
public int hashCode() {
return id;
}
#Override
public String toString() {
return "Worker [name=" + name + "]";
}
}
private Worker selectedWorker;
private List<Worker> workers = Arrays.asList(new Worker(1, "John"),
new Worker(2, "Larry"));
public Worker getSelectedWorker() {
return selectedWorker;
}
public List<Worker> getWorkers() {
return workers;
}
public void send() {
System.out.println(selectedWorker + " selected");
}
public void setSelectedWorker(Worker selectedWorker) {
this.selectedWorker = selectedWorker;
}
}
Page:
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:ui="http://java.sun.com/jsf/facelets"
xmlns:f="http://java.sun.com/jsf/core">
<h:head>
<title>Test page</title>
</h:head>
<h:body>
<h:form>
<h:selectOneMenu value="#{selectWorkerBean.selectedWorker}"
converter="#{workerConverter}">
<f:selectItems value="#{selectWorkerBean.workers}" var="worker"
itemLabel="#{worker.name}" />
</h:selectOneMenu>
<h:commandButton value="send" action="#{selectWorkerBean.send}" />
</h:form>
</h:body>
</html>
Related
I wanted to create my own date converter.I have input field with f:converter id="MyConverter". When I type "today" value I want show today date. Method getAsObject is working good. I've got value "today" from input field and return new LocalDate(), but method getAsObject() have always null as a value. What should I do to fix it?
#FacesConverter(forClass = Date.class, value = "MyConverter")
public class MyConverter implements Converter {
#Override
public Object getAsObject(FacesContext context, UIComponent component, String value) throws ConverterException {
if (value == null) {
return null;
}
if (value.equals("today")) {
return new LocalDate();
}
//do something else
}
#Override
public String getAsString(FacesContext context, UIComponent component, Object value) throws ConverterException {
// value is always null!
if (value instanceof LocalDate) {
LocalDate date = (LocalDate) value;
return date.toString();
} else {
return "";
}
}
EDIT:
jsf tag (it is a little bit complicated) it's actually 2 date input fields (Date interval component), and I want to use MyDateConverter on it:
<cc:interface componentType="DateIntervalComponent">
<cc:attribute name="value" required="true" />
<cc:attribute name="placeholder" />
<cc:attribute name="labelFromDate" default="From Date" />
<cc:attribute name="labelToDate" default="To Date" />
</cc:interface>
<cc:implementation>
<div id="#{cc.userId}">
<b:input id="fromDate" binding="#{cc.FromDateComponent}"
label="#{cc.attrs.labelFromDate}">
<f:converter converterId="MyConverter" />
<f:ajax render="#{cc.userId}" />
</b:input>
<b:input id="toDate" binding="#{cc.ToDateComponent}"
label="#{cc.attrs.labelToDate}">
<f:converter converterId="MyConverter" />
<f:ajax render="#{cc.userId}" />
</b:input>
</div>
</cc:implementation>
#FacesComponent("DateIntervalComponent")
public class DateIntervalComponent extends UIInput implements NamingContainer {
private UIInput fromDateComponent;
private UIInput toDateComponent;
public DateIntervalComponent() {
setRendererType(null);
}
#Override
public String getFamily() {
return UINamingContainer.COMPONENT_FAMILY;
}
#Override
public DateInterval getValue() {
return (DateInterval) super.getValue();
}
#Override
public void processEvent(ComponentSystemEvent event) throws AbortProcessingException {
super.processEvent(event);
}
#Override
public void encodeBegin(FacesContext context) throws IOException {
if (fromDateComponent != null && toDateComponent != null) {
DateInterval value = getValue();
if (!fromDateComponent.isLocalValueSet()) {
fromDateComponent.setValue(value != null ? value.getStart() : null);
}
if (!toDateComponent.isLocalValueSet()) {
toDateComponent.setValue(value != null ? value.getEnd() : null);
}
}
super.encodeBegin(context);
}
#Override
public DateInterval getSubmittedValue() {
Object startValue = fromDateComponent.getValue();
Object endValue = toDateComponent.getValue();
return new DateInterval((Date) startValue, (Date) endValue);
}
#Override
public void validate(FacesContext context) {
super.validate(context);
if (!isValid()) {
fromDateComponent.setValid(false);
toDateComponent.setValid(false);
return;
}
}
#Override
public void resetValue() {
super.resetValue();
fromDateComponent.resetValue();
toDateComponent.resetValue();
}
public UIInput getFromDateComponent() {
return fromDateComponent;
}
public UIInput getToDateComponent() {
return toDateComponent;
}
public void setToDateComponent(UIInput toDateComponent) {
this.toDateComponent = toDateComponent;
}
public void setFromDateComponent(UIInput fromDateComponent) {
this.fromDateComponent = fromDateComponent;
}
}
regards
You need to keep track of the state of your attributes as soon as the FacesComponent itself is STATELESS.
I use the following strategy to keep state of FacesComponent attibutes
First of all, create a generic class based on UINamingContainer (if this is your type of component).
public class MyUINamingContainer extends UINamingContainer {
#SuppressWarnings("unchecked")
/**
* Searches for an attribute and return it.
*/
protected <T> T getAttribute(String attName) {
return (T) getStateHelper().eval(attName);
}
#SuppressWarnings("unchecked")
/**
* Evaluate an attribute and return it if found.
*/
protected <T> T getAttribute(String localName, String attName) {
return (T) getStateHelper().eval(localName, getAttributes().get(attName));
}
/**
* Refresh the attributes state
*
* #param <T>
* #param attName attribute's name. This name will identify the attribute into the map.
It must be different from the name used to the attribute on the view, otherwise
it will cause a infinite loop.
* #param value attribute's value.
*/
protected <T> void setAttribute(String attName, T value) {
getStateHelper().put(attName, value);
}
}
Then, make my FacesComponent class inherit from it, so it would know the stateful getters and setters:
#FacesComponent("myComponent")
public class MyComponent extends MyUINamingContainer {
public SomeType getMyAttributeOnStateMap() {
return getAttribute("myAttributeOnStateMap","myAttribute");
}
public void setMyAttributeOnStateMap(SomeType myAttribute) {
setAttribute("myAttributeOnStateMap", myAttribute);
}
}
Into the component xhtml code:
<cc:interface componentType="myComponent">
<cc:attribute name="myAttribute"/>
</cc:interface>
<cc:implementation>
<h:input value="#{cc.myAttributeOnStateMap}" />
<cc:implementation>
PS.: Note that the attribute has a different name into the map. This is because it will get stuck in a loop it you use the same name for the map attribute and the view one. The method getAttribute(String,String) would always look for the same attribute and never find anything, leading you to a overflow.
PS2.: Note that the name used as the value into the h:input was the name used to store it into the map.
This question already has answers here:
Conversion Error setting value for 'null Converter' - Why do I need a Converter in JSF?
(2 answers)
Closed 7 years ago.
Is a Converter necessary for a <h:selectOneMenu> or <p:selectOneMenu> with arbitrary user-created classes as its values? I mean, is the following code supposed to work without a converter?
<p:selectOneMenu value="#{bean.language}">
<f:selectItems value="#{bean.allLanguages}" />
</p:selectOneMenu>
and
#Named(value = "bean")
#ConversationScoped
public class Bean {
private Language language; // appropriate getter and setter are present
public List<SelectItem> getAllLanguages() {
// populates a list of select items with Strings as item labels and Languages as item values
}
}
I have a similar code with an enum as the type (Language) and it works perfectly. But when I replace the type with a normal java class, I get a conversion error.
You need a converter here, as JSF will assume strings by default, that is the way you coded it. JSF has no idea how to convert your pseudo entities to strings and vice versa.
Some notes:
1 . Your getAsString method defines your identifier for your entities/POJOs, not what the JSF (or whatever) select gets as itemLabel.
2 . Your converter can dig into the DB for real entities using this infamous article:
http://balusc.blogspot.de/2011/09/communication-in-jsf-20.html#ConvertingAndValidatingGETRequestParameters
You can also use CDI annotations with that "pattern".
3 . Your value = "bean"is redundant and the CDI scope of choice is usually #ViewScoped. However, you have to keep in mind that CDI #Named + JSF #ViewScoped isn't working together without using Seam 3 or Apache MyFaces CODI.
You do not need a converter, if you use this little class that I wrote :-) It can back selectOne and selecteMany components. It requires that your class's toString() provides a one-to-one unique representation of your object. If you like, you could substitute a method name other than toString(), like toIDString()
To use ListBacker in your ManagedBean, use ListBacker<Type> wherever you would have used List<Type>
#ManagedBean
#RequestScoped
public class BackingBean {
private ListBacker<User> users; // +getter +setter
#PostConstruct
public void init() {
// fill it up from your DAO
users = new ListBacker<User>(userDAO.find());
}
// Here's the payoff! When you want to use the selected object,
// it is just available to you, with no extra database hits:
User thisOneIsSelected = users.getSelectedItemAsObject();
// or for multi-select components:
List<User> theseAreSelected = users.getSelectedItemsAsObjects();
}
In your xhtml file:
<p:selectOneMenu value="#{backingBean.users.selectedItem}">
<f:selectItems value="#{backingBean.users.contents}" var="item" itemValue="#{item.value}" itemLabel="#{item.label}" />
</p:selectOneMenu>
The ListBacker class:
public class ListBacker<T extends AbstractEntityBase> {
// Contains the String representation of an Entity's ID (a.k.a.
// primary key) and the associated Entity object
Map<String, T> contents = new LinkedHashMap<String, T>(); // LinkedHashMap defaults to insertion-order iteration.
// These hold values (IDs), not labels (descriptions).
String selectedItem; // for SelectOne list
List<String> selectedItems; // for SelectMany list
public class ListItem {
private String value;
private String label;
public ListItem(String value, String label) {
this.value = value;
this.label = label;
}
public String getValue() {
return value;
}
public String getLabel() {
return label;
}
}
public ListBacker() {}
public ListBacker(List<T> lst) {
put(lst);
}
public void clear() {
contents.clear();
selectedItem = null;
if(selectedItems != null) {
selectedItems.clear();
}
}
public List<ListItem> getContents() {
return convert(contents);
}
public String getSelectedItem() {
return selectedItem;
}
public void setSelectedItem(String selectedItem) {
this.selectedItem = selectedItem;
}
public List<String> getSelectedItems() {
return selectedItems;
}
public void setSelectedItems(List<String> selectedItems) {
this.selectedItems = selectedItems;
}
public T getSelectedItemAsObject() {
return convert(selectedItem);
}
public List<T> getSelectedItemsAsObjects() {
return convert(selectedItems);
}
public void put(T newItem) {
contents.put(newItem.toString(), newItem);
}
public void put(List<T> newItems) {
for (T t : newItems) {
put(t);
}
}
// PROTECTED (UTILITY) METHODS
protected List<ListItem> convert(Map<String, T> maps) {
List<ListItem> lst = new ArrayList<ListItem>();
for (Entry<String, T> e : maps.entrySet()) {
lst.add(new ListItem(e.getKey(), e.getValue().desc()));
}
return lst;
}
protected List<T> convert(List<String> ids) {
List<T> lst = new ArrayList<T>();
for (String id : ids) {
lst.add(convert(id));
}
return lst;
}
protected T convert(String id) {
return contents.get(id);
}
}
I have two toString() implementations, one for JPA entities:
public abstract class AbstractEntityBase {
#Override
public final String toString() {
return String.format("%s[id=%s]", getClass().getSimpleName(), getIdForToString().toString());
}
/**
* Return the entity's ID, whether it is a field or an embedded ID class..
* #return ID Object
*/
protected abstract Object getIdForToString();
}
and one for JPA EmbeddedId's:
public abstract class CompositeKeyBase {
#Override
public final String toString() {
return String.format("%s[id=%s]", getClass().getSimpleName(), getIdForToString());
}
/**
* Supports the class's toString() method, which is required for ListBacker.
* Compile a string of all ID fields, with this format:
* fieldName=StringVALUE,field2=STRINGvAlUE2,...,fieldx=stringvalue <br />
* Recommended: start with Eclipse's "generate toString()" utility and move it to getIdForToString()
* #return a 1-to-1 String representation of the composite key
*/
public abstract String getIdForToString();
}
An example implementation of getIdForToString(), for an entity that has one Id field:
#Override
public Object getIdForToString() {
return userID;
}
An example implementation of getIdForToString(), for an EmbeddedId that has two fields:
#Override
public String getIdForToString() {
return "userID=" + userID + ",roleID=" + roleID;
}
I've got a simple composite component which has to render a inputText. When a put the value and press commandButton the follow exception is throw:
java.lang.IllegalArgumentException: Cannot convert 1 of type class java.lang.String to class sample.entity.Product
When i use h:inputText instead d:myInputText it's work fine.
Is possible use a FacesConverter and attribute forClass for composite component? I do not like to use converter attribute or converterId of tag f:converter.
Anybody help me?
Page code:
<h:form>
<h:messages />
Product Id: <h:myInputText value="#{productController.product}"/>
<h:commandButton value="Submit" action="#{productController.someAction()}" />
Product Description: <h:outputText value="#{productController.product.description}"/>
</h:form>
Composite code:
<composite:interface>
<composite:attribute name="value"/>
<composite:editableValueHolder name="value" targets="#{cc.clientId}:value"/>
</composite:interface>
<composite:implementation>
<div id="#{cc.clientId}">
<h:inputText id="value" value="#{cc.attrs.value}"/>
<h:message for="#{cc.clientId}:value" />
</div>
</composite:implementation>
ManagedBean code:
#Named("productController")
#RequestScoped
public class ProductController {
private Product product;
public Product getProduct() {
if (product == null) {
product = new Product();
}
return product;
}
public void setProduct(Product product) {
this.product = product;
}
public void someAction() {
System.out.println("Product " + product);
}
}
Converter code:
#FacesConverter(forClass = Product.class)
public class ProductConverter implements Converter {
#Override
public Object getAsObject(FacesContext fc, UIComponent uic, String value) {
System.out.println("[DEBUG] getAsObject: " + value);
if (value == null || "".equals(value)) {
return null;
}
//TODO: some logic to get entity from database.
return new Product(new Long(value));
}
#Override
public String getAsString(FacesContext fc, UIComponent uic, Object o) {
System.out.println("[DEBUG] getAsString: " + o);
if (o == null) {
return null;
}
return String.valueOf(((Product) o).getId());
}
}
Entity code:
public class Product {
private Long id;
private String description;
public Product() {
}
public Product(Long id) {
this.id = id;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
#Override
public int hashCode() {
int hash = 7;
hash = 29 * hash + (this.id != null ? this.id.hashCode() : 0);
return hash;
}
#Override
public boolean equals(Object obj) {
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
final Product other = (Product) obj;
if (this.id != other.id && (this.id == null || !this.id.equals(other.id))) {
return false;
}
return true;
}
#Override
public String toString() {
return "Product{" + "id=" + id + '}';
}
}
I use Mojarra 2.1.14, Glassfish 3.1 and CDI.
Best regards.
I was able to reproduce it in Mojarra 2.1.14. This is a bug in Mojarra. It works fine in MyFaces 2.1.9. I've reported it to Mojarra guys as issue 2568. In the meanwhile, there's not really another option than explicitly specifying a <f:converter for> in the client or moving to MyFaces (which has its own set of specific quirks/issues as well though).
In our JavaEE6 project (EJB3, JSF2) on JBoss 7.1.1, it seems we have a memory leak with SeamFaces #ViewScoped.
We made a little prototype to check the fact :
we use JMeter to call a page 200 times;
the page contains and calls a viewscoped bean which injects a stateful EJB;
we fix the session timeout at 1 minute.
At the end of the test, we check the content of the memory with VisualVM, and here what we got:
with a #ViewScoped bean, we still get 200 instances of the stateful MyController - and the #PreDestroy method is never called;
with a #ConversationScoped bean, #preDestroy method is called a the session end and then we got a clean memory.
Do we badly use the view scope, or is it truly a bug?
Here's the XHTML 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:f="http://java.sun.com/jsf/core"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:ui="http://java.sun.com/jsf/facelets"
xmlns:s="http://jboss.org/seam/faces">
<f:metadata>
<f:viewParam name="u" value="#{myBean.uselessParam}" />
<s:viewAction action="#{myBean.callService}" />
</f:metadata>
<h:body >
<f:view>
</f:view>
</h:body>
</html>
Now the included bean myBean. For the #ConversationScoped variant, all commented parts are uncommented.
#ViewScoped
// #ConversationScoped
#Named
public class MyBean implements Serializable
{
#Inject
MyController myController;
//#Inject
//Conversation conversation;
private String uselessParam;
public void callService()
{
//if(conversation.isTransient())
//{
// conversation.begin();
//}
myController.call();
}
public String getUselessParam()
{
return uselessParam;
}
public void setUselessParam(String uselessParam)
{
this.uselessParam = uselessParam;
}
}
And then the injected stateful bean MyController:
#Stateful
#LocalBean
public class MyController
{
public void call()
{
System.out.println("call ");
}
#PreDestroy
public void destroy()
{
System.out.println("Destroy");
}
}
I see many developers are satisfied with #ViewAccessScoped in Myface CODI.
Could you please give it a try and tell the feedback.
I have faced the above mentioned problem in JSF managed #ViewScoped bean. After referring to few blogs I understood that JSF saves view bean states in http session and gets destroyed only when session is invalidated. Whenever we click on the jsf page every time new view scope bean referred in page is created. I did a work around using Spring Custom View Scope. It works fine. Below is the detail code.
For JSF 2.1:
Step 1: Create a View Scope Bean Post Construct Listener as follows.
public class ViewScopeBeanConstructListener implements ViewMapListener {
#SuppressWarnings("unchecked")
#Override
public void processEvent(SystemEvent event) throws AbortProcessingException {
if (event instanceof PostConstructViewMapEvent) {
PostConstructViewMapEvent viewMapEvent = (PostConstructViewMapEvent) event;
UIViewRoot viewRoot = (UIViewRoot) viewMapEvent.getComponent();
List<Map<String, Object>> activeViews = (List<Map<String, Object>>)
FacesContext.getCurrentInstance().getExternalContext().getSessionMap(). get("com.org.jsf.activeViewMaps");
if (activeViews == null) {
activeViews = new ArrayList<Map<String, Object>>();
activeViews.add(viewRoot.getViewMap());
FacesContext.getCurrentInstance().getExternalContext().getSessionMap(). put("com.org.jsf.activeViewMaps", activeViews);
} else {
activeViews.add(viewRoot.getViewMap());
}
}
}
Step 2: Register event listener in faces-config.xml
<system-event-listener>
<system-event-listener-class>
com.org.framework.custom.scope.ViewScopeBeanConstructListener
</system-event-listener-class>
<system-event-class>javax.faces.event.PostConstructViewMapEvent</system-event-class>
<source-class>javax.faces.component.UIViewRoot</source-class>
</system-event-listener>
Step 3: Create a Custom View Scope bean as follows.
public class ViewScope implements Scope {
#Override
public Object get(String name, ObjectFactory objectFactory) {
Map<String, Object> viewMap = FacesContext.getCurrentInstance().getViewRoot().getViewMap();
if (viewMap.containsKey(name)) {
return viewMap.get(name);
} else {
List<Map<String, Object>> activeViewMaps = (List<Map<String, Object>>)
FacesContext.getCurrentInstance().getExternalContext().getSessionMap().get("com.org.jsf.activeViewMaps");
if (activeViewMaps != null && !activeViewMaps.isEmpty()
&& activeViewMaps.size() > 1) {
Iterator iterator = activeViewMaps.iterator();
if (iterator.hasNext()) {
Map<String, Object> oldViewMap = (Map<String, Object>)
iterator.next();
oldViewMap.clear();
iterator.remove();
}
}
Object object = objectFactory.getObject();
viewMap.put(name, object);
return object;
}
}
Note : Other overridden methods can be empty.
For JSF 2.2:
JSF 2.2 saves the navigated view maps in http session in 'com.Sun.faces.application.view.activeViewMaps' as key. So add the below code in Spring Custom View Scope. No need of listeners as in JSF 2.1
public class ViewScope implements Scope {
public Object get(String name, ObjectFactory objectFactory) {
Map<String, Object> viewMap =
FacesContext.getCurrentInstance().getViewRoot().getViewMap();
if (viewMap.containsKey(name)) {
return viewMap.get(name);
} else {
LRUMap lruMap = (LRUMap) FacesContext.getCurrentInstance().
getExternalContext().getSessionMap().get("com.sun.faces.application.view.activeViewMaps");
if (lruMap != null && !lruMap.isEmpty() && lruMap.size() > 1) {
Iterator itr = lruMap.entrySet().iterator();
while (itr.hasNext()) {//Not req
Entry entry = (Entry) itr.next();
Map<String, Object> map = (Map<String, Object>) entry.getValue();
map.clear();
itr.remove();
break;
}
}
Object object = objectFactory.getObject();
viewMap.put(name, object);
return object;
}
}
Chances are this is a bug. Honestly the Seam 3 implementation wasn't all that great and the CODI one (and also what will be in DeltaSpike) is much better.
I read on SO some QA about the same component, but I feel I'm missing something, because I am one step behind.
I can't even make the page open when using the primefaces autocomplete component in it.
The snippet for it is:
<p:autoComplete value="#{indirizzoCtrl.selectedCodiceNazione}"
completeMethod="#{indirizzoCtrl.completeNazione}"
var="nazione" itemLabel="#{nazione.nome}"
itemValue="#{nazione.codiceNazione}" />
Nazione is a Pojo class where CodiceNazione and Nome are two String field (with getter and setter for sure). completeNazione is a method on the ManagedBean that returns List<Nazione>.
Looking at BalusC explanation here, it seems to me that i don't need any converter involved, because both itemValue and value attributes are mapped to string property.
Anyway, when I just open the page containing this autocomplete snippet, it crashes with this error:
javax.el.PropertyNotFoundException: /Cliente/Indirizzo.xhtml #23,56 itemValue="#{nazione.codiceNazione}": itemValue="#{nazione.codiceNazione}": Property 'codiceNazione' not found on type java.lang.String
Why this is happening? I really can't get it. The method completeNazione hasn't even called yet, so it shouldn't know any Nazione yet.
What's wrong with it?
Edited:
Following the suggestion, I tried to add a converter, but I still get the same error.
Here's my converter:
public class NazioneConverter implements Converter {
final static Logger log = Logger.getLogger(NazioneConverter.class);
#Override
public Object getAsObject(FacesContext context, UIComponent component, String value) {
if (value.trim().equals("")) {
return null;
} else {
try {
IndirizzoRepository ir = new IndirizzoRepository();
List<Nazione> nazioni = ir.getNazioneByName(value);
if (nazioni.size()==1) return nazioni.get(0);
else throw new Exception();
} catch (Exception e) {
String msg = "Errore di conversione";
log.error(msg, e);
throw new ConverterException(new FacesMessage(FacesMessage.SEVERITY_ERROR, msg, "Non è una nazione conosciuta"));
}
}
}
#Override
public String getAsString(FacesContext context, UIComponent component, Object value) {
if (value == null || value.equals("")) {
return "";
} else {
return String.valueOf(((Nazione) value).getNome());
}
}
}
now the component in the view looks like:
<p:autoComplete value="#{indirizzoCtrl.indirizzo.nazione.codiceNazione}"
completeMethod="#{indirizzoCtrl.completeNazione}"
var="nazione" itemLabel="#{nazione.nome}" converter="#{nazioneConverter}"
itemValue="#{nazione.codiceNazione}" forceSelection="true" />
But still don't working. The converter is not even invoked: I registered it in my faces-config.xml file.
I also tried itemValue="#{nazione}" as in the primefaces showcase but the problem became the ItemLabel attribute, mapped to nazione.nome.
What am I doing wrong?
This worked for me:
//Converter
#FacesConverter(value="MarcaConverter")
public class MarcaConverter implements Converter{
MarcaDAO marcaDAO;
public Object getAsObject(FacesContext contet, UIComponent component, String value) {
if(value==null || value.equals(""))
return null;
try{
int id = Integer.parseInt(value);
return marcaDAO.findMarcaById(id);
}catch (Exception e) {
e.printStackTrace();
throw new ConverterException(new FacesMessage(FacesMessage.SEVERITY_ERROR, "Marca no válida", ""));
}
}
public String getAsString(FacesContext contet, UIComponent component, Object value) {
if(value==null || value.equals(""))
return null;
return String.valueOf(((Marca)value).getCodigoMarca());
}
}
//--------------------------------------
//Bean
#ManagedBean
#ViewScoped
public class MyBeans implements Serializable{
private Marca marca;
...
public Marca getMarca(){
return marca;
}
public void setMarca(Marca m){
marca=m;
}
...
public List<Marca> obtenerMarcasVehiculos(String s) {
List<Marca> marcas,smarcas=new ArrayList<Marca>();
try{
marcas= marcaDAO.findAllMarcas();
if(s.trim().equals("")) return marcas;
for(Marca m:marcas)
if (m.getNombreMarca().toString().contains(s) || m.getNombreMarca().toLowerCase().contains(s.toLowerCase())) {
smarcas.add(m);
}
return smarcas;
}catch(Exception e){
//JsfUtil.showFacesMsg(e,"Error al obtener las marcas de vehículos","",FacesMessage.SEVERITY_WARN);
e.printStackTrace();
JsfUtil.lanzarException(e);
return null;
}
}
//-----------------------------------------
//*.xhtml page
...
<p:autoComplete
id="cbxMarca" value="#{myBean.marca}" size="40"
converter="MarcaConverter"
completeMethod="#{myBean.obtenerMarcasVehiculos}"
var="m" itemLabel="#{m.nombreMarca}" itemValue="#{m}"
forceSelection="true" dropdown="true"
required="true" scrollHeight="200">
</p:autoComplete>
...
//-----------------------------------------
//Class Marca
public class Marca implements Serializable{
private static final long serialVersionUID = 1L;
private Integer codigoMarca;
private String nombreMarca;
...
Have you read the user guide? http://www.primefaces.org/documentation.html
I must say I have never used the autocomplete with pojo but from what I've read in the user guide, Çağatay Çivici says there:
Note that when working with pojos, you need to plug-in your own converter.
Here you can find out that a converter (PlayerConverter) is implemented even if player.name and of the other props are Strings.
I admit this is interesting and I'll do some research but I don't have the necessary time right now...
Change
converter="#{nazioneConverter}" to converter="nazioneConverter" in autocomplete
Change the itemValue from itemValue="#{nazione.codiceNazione}" to itemValue="#{nazione}" in autoComplete.