Retrieving selectOneMenu complex object as selected item - jsf

I'm beginning with JSF (Mojarra 2.2 and Glassfish 4) and currently practicing with a web application which job is to store Clients and their Orders in DB.
When creating a new Order, one feature is to allow choosing an existing client from a JSF <h:selectOneMenu>. An Order entity stores a Client entity among other attributes...
I've followed BalusC's great answer about prepopulating a <h:selectOneMenu> from a DB (here), and have successfully populated mine from data stored in an eager ApplicationScoped ManagedBean, but I can't manage to retrieve the selected item in the backing bean as complex object. It is always null.
This is driving me mad and your help will be truly appreciated!
Here are the relevant code snippets:
#ManagedBean(eager = true)
#ApplicationScoped
public class Data implements Serializable {
private static final long serialVersionUID = 1L;
#EJB
private ClientDao clientDao;
private List<Client> clients;
#PostConstruct
private void init() {
clients = clientDao.lister();
}
public List<Client> getClients() {
return clients;
}
}
Order creation bean (note: 'commande' means order ;)
#ManagedBean
#RequestScoped
public class CreerCommandeBean implements Serializable {
private static final long serialVersionUID = 1L;
private Commande commande;
private String choixNouveauClient = "nouveauClient";
#EJB
private CommandeDao commandeDao;
public CreerCommandeBean() {
commande = new Commande();
}
public void inscrire() {
System.out.println("client : " + commande.getClient()); // prints **NULL**
// ... orderService to store in DB
}
... getters and setters
Client converter:
#FacesConverter(value = "clientConverter", forClass = Client.class)
public class ClientConverter implements Converter {
#Override
public Object getAsObject(FacesContext context, UIComponent component, String value) {
if (value == null) {
return null;
}
Data data = context.getApplication().evaluateExpressionGet(context, "#{data}", Data.class);
for (Client c : data.getClients()) {
if (c.getId().toString().equals(value)) {
return c;
}
}
throw new ConverterException(new FacesMessage(String.format("Cannot convert %s to Client", value)));
}
#Override
public String getAsString(FacesContext context, UIComponent component, Object value) {
return (value instanceof Client) ? String.valueOf(((Client) value).getId()) : null;
}
}
Facelet excerpt:
<p:outputPanel id="gridContainerAncienClient">
<p:selectOneMenu value="#{creerCommandeBean.commande.client}"
rendered="#{creerCommandeBean.choixNouveauClient == 'ancienClient'}">
<f:converter converterId="clientConverter" />
<f:selectItems value="#{data.clients}" var="cli"
itemValue="#{cli}" itemLabel="#{cli.prenom} #{cli.nom}" />
</p:selectOneMenu>
</p:outputPanel>

CreerCommandeBean is #RequestScoped. That means it will live only for one request.
When you select a client to be assigned to #{creerCommandeBean.commande.client} you do this by a request. #{creerCommandeBean.commande.client} is now the selected client. Then the request is over, the bean gets destroyed and your "changes" are lost.
When you try to retrieve that data, you do that by a request again: A new instance of CreerCommandeBean is created and the constructor assigns the property commande with a new instance of Commande whose property client again is probably null.
Solution:
Use a broader scope. e.g. #ViewScoped which makes the bean "live" as long as you stay in the same view - no matter how many requests you make.
Tip:
Read BalusC's Post on Communication is JSF 2.0. Parts might be slightly different in JSF 2.2 but it's still a good and comprehensive introduction.

I got stuck with similar problem, only to realize that I forgot to implement equals() and hashCode() method in my Object. Client Class in this case.
I should blame myself for skipping the instructions in BalusC's blog.
"...Please note the Object#equals() implementation. This is very important for JSF. After conversion, it will compare the selected item against the items in the list. As the Object#equals() also require Object#hashCode(), this is implemented as well...."

Related

How to keep entity up to date after saving changes in managed bean

Let's assume a simple Jsf example with a xhtml page, a ManagedBean, a service and an JPA entityClass. I have a lot of usecases with the following structure:
Hold an entity in my bean
Do actions on the entity
Do rendering on the updated entity
Some easy example, so everyone will understand
Entity:
public class Entity {
private long id;
private boolean value;
...
// Getter and Setter
}
Dao:
public class EntityService {
// Entity Manger em and other stuff
public void enableEntity(long id) {
Entity e = em.find(id);
e.value = true;
em.persist(e);
}
}
Managed Bean:
#ManagedBean
#RequestScoped/ViewScoped
public class EntityBean() {
#EJB
private EntityService entityService;
private Entity entity;
#PostConstruct
public void init() {
// here i fetch the data, to provide it for the getters and setters
entity = entityService.fetchEntity();
}
public void enableEntity() {
entityService.enableEntity(entity.getId);
}
// Getter and Setter
}
and finally a simple xhtml:
<html>
// bla bla bla
<h:panelGroup id="parent">
<h:panelGroup id="disabled" rendered="#{not EntityBean.entity.value}>
<p:commandButton value="action" action="#{EntityBean.enableEntity}" update="parent" />
</h:panelGroup>
<h:panelGroup id="enabled" rendered="#{EntityBean.entity.value}>
// other stuff that should become visible
</h:panelGroup>
</h:panelGroup>
</html>
What i want to achieve:
Always show the up to date entity in every request!
What i already tried
I tried with a dao-fetch in my getter. But you can read everywhere that this is bad practice, because jsf will call the getter more than once (but for now the only way i can keep them really up to date).
I tried RequestScoped Beans. But the Bean will be created before the action is done, and is not recreated on the update call and the value will be outdated (Makes sense, since this is one request, and the request starts with the click on the button).
I tried ViewScoped Beans and added an empty String return value to my method. My hope was, that this redirection will recreate the Bean after the action was processed. But this was not the case.
I tried to call the refetch function manually after every method i used. But I have some cross bean actions on the same entity (My real entities are way more complex than this example). So the different Beans do not always know, if and when the entity has changed.
My Questions:
Can this be done with any kind of Scope? Let's say that every request will fetch the data from my PostConstruct again.
There must be a better solution than the dao-fetch in the getter method
This seems to be a fundamental problem for me, because getting the up to date data is essential for my app (data is changed often).
Using Primefaces 6.1 and Wildfly 10.x
What do you think about this?
A request scoped bean which will be created for update, too and does only one fetchEntity() per request.
<f:metadata>
<f:viewAction action="#{entityBean.load()}" onPostback="true"/>
</f:metadata>
#ManagedBean
#RequestScoped
public class EntityBean() {
#EJB
private EntityService entityService;
private Entity entity = null;
public void load() {}
public Entity getEntity() {
if(entity == null) {
entity = entityService.fetchEntity();
}
return entity;
}
public void enableEntity() {
entityService.enableEntity(getEntity().getId);
}
// Getter and Setter
}

Why the SelectCheckboxMenu + Map<Integer,List<Integer>> just turn the value inside the variable? [duplicate]

I have used the HashMap method for binding a list of checkboxes to a Map<String, Boolean> with success. This is nice since it allows you to have a dynamic number of checkboxes.
I'm trying to extend that to a variable length list of selectManyMenu. Being that they are selectMany, I'd like to be able to bind to a Map<String, List<MyObject>>. I have a single example working where I can bind a single selectManyMenu to a List<MyObject> and everything works fine, but whey I put a dynamic number of selectManyMenus inside a ui:repeat and attempt to bind to the map, I end up with weird results. The values are stored correctly in the map, as verified by the debugger, and calling toString(), but the runtime thinks the map's values are of type Object and not List<MyObject> and throws ClassCastExceptions when I try to access the map's keys.
I'm guessing it has something to do with how JSF determines the runtime type of the target of your binding, and since I am binding to a value in a Map, it doesn't know to get the type from the value type parameter of the map. Is there any workaround to this, other than probably patching Mojarra?
In general, how can I have a page with a dynamic number of selectManyMenus? Without, of course using Primefaces' <p:solveThisProblemForMe> component. (In all seriousness, Primefaces is not an option here, due to factors outside of my control.)
The question UISelectMany on a List<T> causes java.lang.ClassCastException: java.lang.String cannot be cast to T had some good information that I wasn't aware of, but I'm still having issues with this SSCE:
JSF:
<ui:define name="content">
<h:form>
<ui:repeat value="#{testBean.itemCategories}" var="category">
<h:selectManyMenu value="#{testBean.selectedItemMap[category]}">
<f:selectItems value="#{testBean.availableItems}" var="item" itemValue="#{item}" itemLabel="#{item.name}"></f:selectItems>
<f:converter binding="#{itemConverter}"></f:converter>
<f:validator validatorId="test.itemValidator"></f:validator>
</h:selectManyMenu>
</ui:repeat>
<h:commandButton value="Submit">
<f:ajax listener="#{testBean.submitSelections}" execute="#form"></f:ajax>
</h:commandButton>
</h:form>
</ui:define>
Converter:
#Named
public class ItemConverter implements Converter {
#Inject
ItemStore itemStore;
#Override
public Object getAsObject(FacesContext context, UIComponent component, String value) {
return itemStore.getById(value);
}
#Override
public String getAsString(FacesContext context, UIComponent component, Object value) {
return Optional.of(value)
.filter(v -> Item.class.isInstance(v))
.map(v -> ((Item) v).getId())
.orElse(null);
}
}
Backing Bean:
#Data
#Slf4j
#Named
#ViewScoped
public class TestBean implements Serializable {
private static final long serialVersionUID = 1L;
#Inject
ItemStore itemStore;
List<Item> availableItems;
List<String> itemCategories;
Map<String, List<Item>> selectedItemMap = new HashMap<>();
public void initialize() {
log.debug("Initialized TestBean");
availableItems = itemStore.getAllItems();
itemCategories = new ArrayList<>();
itemCategories.add("First Category");
itemCategories.add("Second Category");
itemCategories.add("Third Category");
}
public void submitSelections(AjaxBehaviorEvent event) {
log.debug("Submitted Selections");
selectedItemMap.entrySet().forEach(entry -> {
String key = entry.getKey();
List<Item> items = entry.getValue();
log.debug("Key: {}", key);
items.forEach(item -> {
log.debug(" Value: {}", item);
});
});
}
}
ItemStore just contains a HashMap and delegate methods to access Items by their ID field.
Item:
#Data
#Builder
public class Item {
private String id;
private String name;
private String value;
}
ItemListValidator:
#FacesValidator("test.itemValidator")
public class ItemListValidator implements Validator {
#Override
public void validate(FacesContext context, UIComponent component, Object value) throws ValidatorException {
if (List.class.isInstance(value)) {
if (((List) value).size() < 1) {
throw new ValidatorException(new FacesMessage(FacesMessage.SEVERITY_FATAL, "You must select at least 1 Admin Area", "You must select at least 1 Admin Area"));
}
}
}
}
Error:
java.lang.ClassCastException: [Ljava.lang.Object; cannot be cast to java.util.List
Stacktrace snipped but occurs on this line:
List<Item> items = entry.getValue();
What am I missing here?
As hinted in the related question UISelectMany on a List<T> causes java.lang.ClassCastException: java.lang.String cannot be cast to T, generic type arguments are unavailable during runtime. In other words, EL doesn't know you have a Map<String, List<Item>>. All EL knows is that you have a Map, so unless you explicitly specify a converter for the selected values, and a collection type for the collection, JSF will default to String for selected values and an object array Object[] for the collection. Do note that the [ in [Ljava.lang.Object indicates an array.
Given that you want the collection type to be an instance of java.util.List, you need to specify the collectionType attribute with the FQN of the desired concrete implementation.
<h:selectManyMenu ... collectionType="java.util.ArrayList">
JSF will then make sure that the right collection type is being instantiated in order to fill the selected items and put in the model. Here's a related question where such a solution is being used but then for a different reason: org.hibernate.LazyInitializationException at com.sun.faces.renderkit.html_basic.MenuRenderer.convertSelectManyValuesForModel.
Update: I should have tested the above theory. This doesn't work in Mojarra when the collection behind collectionType is in turn wrapped in another generic collection/map. Mojarra only checks the collectionType if the UISelectMany value itself already represents an instance of java.util.Collection. However, due to it being wrapped in a Map, its (raw) type becomes java.lang.Object and then Mojarra will skip the check for any collectionType.
MyFaces did a better job in this in its UISelectMany renderer, it works over there.
As far as I inspected Mojarra's source code, there's no way to work around this other way than replacing Map<String, List<Long>> by a List<Category> where Category is a custom object having String name and List<MyObject> selectedItems properties. True, this really kills the advantage of Map of having dynamic keys in EL, but it is what it is.
Here's a MCVE using Long as item type (just substitute it with your MyObject):
private List<Category> categories;
private List<Long> availableItems;
#PostConstruct
public void init() {
categories = Arrays.asList(new Category("one"), new Category("two"), new Category("three"));
availableItems = Arrays.asList(1L, 2L, 3L, 4L, 5L);
}
public void submit() {
categories.forEach(c -> {
System.out.println("Name: " + c.getName());
for (Long selectedItem : c.getSelectedItems()) {
System.out.println("Selected item: " + selectedItem);
}
});
// ...
}
public class Category {
private String name;
private List<Long> selectedItems;
public Category(String name) {
this.name = name;
}
// ...
}
<h:form>
<ui:repeat value="#{bean.categories}" var="category">
<h:selectManyMenu value="#{category.selectedItems}" converter="javax.faces.Long">
<f:selectItems value="#{bean.availableItems}" />
</h:selectManyMenu>
</ui:repeat>
<h:commandButton value="submit" action="#{bean.submit}">
<f:ajax execute="#form" />
</h:commandButton>
</h:form>
Do note that collectionType is unnecessary here. Only the converter is still necessary.
Unrelated to the concrete problem, I'd like to point out that selectedItemMap.entrySet().forEach(entry -> { String key ...; List<Item> items ...;}) can be simplified to selectedItemMap.forEach((key, items) -> {}) and that ItemListValidator is unnecessary if you just use required="true" on the input component.

Cant access property of managed bean from another managed bean

I want to access the property of a #SessionScoped bean in another bean using #ManagedProperty. In short, I want to access the name property of firstBean in secondBean.
#ManagedBean
#SessionScoped
public class FirstBean implements Serializable{
private String name;
//...other attributes
//...constructor
public String getSelectedModel() {
return selectedModel;
}
public void setSelectedModel(String selectedModel) {
this.selectedModel = selectedModel;
}
//other getters&setters
}
And second bean:
#ManagedBean
#SessionScoped
public class SecondBean implements Serializable{
#ManagedProperty(value="#{firstBean}")
private FirstBean firstBean
public SecondBean() {
System.out.println(firstBean.getName());
}
public IndexBean getFirstBean() {
return firstBean;
}
public void setFirstBean(FirstBean firstBean) {
this.firstBean = firstBean;
}
When I run this, I always get NullPointerException on System.out.println(firstBean.getName()); in the constructor of second bean, which seems to mean that I need to create a new instance of firstBean.
But strangely, when I commented out this line, I can do something like this with no errors, which means that firstBean is actually a property of secondBean.
<h:outputText value="#{secondBean.firstBean.name}" />
What's the problem here?
It's not possible to access an injected dependency in the constructor. You're basically expecting that Java is able to do something like this:
SecondBean secondBean; // Declare.
secondBean.firstBean = new FirstBean(); // Inject.
secondBean = new SecondBean(); // Construct.
It's absolutely not possible to set an instance variable if the instance is not constructed yet. Instead, it works as follows:
SecondBean secondBean; // Declare.
secondBean = new SecondBean(); // Construct.
secondBean.firstBean = new FirstBean(); // Inject.
Then, in order to perform business actions based on injected dependencies, use a method annotated with #PostConstruct. It will be invoked by the dependency injection manager directly after construction and dependency injection.
So, just replace
public SecondBean() {
System.out.println(firstBean.getName());
}
by
#PostConstruct
public void init() { // Note: method name is fully to your choice.
System.out.println(firstBean.getName());
}

Initialization of List in a JSF Managed bean

I' have a question about initialization of List in the POJO as it follows the next code:
public class Person {
//other fields...
private List<String> friends=new ArrayList<>();
public List<String> getFriends() {
return friends;
}
public void setFriends(List<String> friends) {
this.friends = friends;
}
}
OR is it better like this and have initalization in other class(like for example Bean(JSF))
public class Person {
//other fields...
private List<String> friends;
public List<String> getFriends() {
return friends;
}
public void setFriends(List<String> friends) {
this.friends = friends;
}
}
So my question is what approach is better?
If it's a managed bean as you say, you should do this in a method annotated with #PostConstruct
public class Person {
private List<String> friends;
#PostConstruct
public void init(){
friends = new ArrayList<String>();
}
//getter and setter...
}
The practice of doing any initialization in the getter and setter is generally frowned upon within the context of JSF. See Why JSF calls getters multiple times
Also, per the API for #PostConstruct, the contract specifies safety features and guarantees that if an exception is thrown in a method annotated as such, the bean should not be put into service. There are no such guarantees on a plain constructor.
In a managed bean, injection happens immediately after construction. This means that any operations you're carrying out in the constructor cannot depend on any injected resources (via #ManagedProperty). Whereas in a #PostConstruct method, you'll have access to all the resources declared on the managed bean
EDIT: It's important to note that there can be only one #PostConstruct for any #ManagedBean, so all important initializations should happen in there.
It's also worthwhile to note that, while the #PostConstruct method is the ideal place to initialize a backing bean variable/List, there are implications regarding the scope of the managed bean
#RequestScoped: In a managed bean with this annotation, the method will be called per submit of the JSF view concerned. A #RequestScoped bean is destroyed and recreated with every request, The implication of this is that depending on your setup, the list initialized in the #PostConstruct may be reset to empty or default values during each request. Under certain circumstances, conversion errors may occur as a result of the re-initialization of the list mid-JSF request.
#ViewScoped: In a managed bean with this annotation, you're guaranteed to have the #PostConstruct method run once, if and only if you're dealing with the same instance of the #ViewScoped bean. If the viewscoped bean is destroyed and recreated, the #PostConstruct method will run again.
#SessionScoped: A bean with this annotation is created once and stays alive until the user's HTTP session ends. In this scenario, the #PostConstruct method is guaranteed to run once and only once until the bean is destroyed
See also
https://stackoverflow.com/a/3406631/1530938
I would suggest this:
public class Person {
//other fields...
private List<String> friends=new ArrayList<>();
// returns a copy to protect original list
public List<String> getFriends() {
Collections.unmodifiableList(new ArrayList<>(friends));
}
public void addFriend(String> friend) {
this.friends.add(friend);
}
public void addFriends(List<String> friends) {
this.friends.addAll(friends);
}
}
In my opinion it would be best to handle that in the constructors. If a default constructor is used, initialize the list in the constructor.
public Person() {
friends = new ArrayList<>();
}
If a constructor which accepts parameters is used, let the calling class pass in a list.
public Person(ArrayList<> friends) {
this.friends = friends;//friends
}
My suggestion, add a null check in the getter:
public class Person {
//other fields...
private List<String> friends;
public List<String> getFriends() {
if (this.friends == null) friends = new ArrayList<String>();
return friends;
}
}
But also notice I have omitted the setter. Instead, in any client code, call like this:
personInstance.getFriends().add("Some Item");
Or if you have a full list to add:
personInstance.getFriends().addAll(someStringCollection);
It depends. Usually first way preferable because you may want to add something to collection later. If you won't know was your collection initialized or not you must check it every time.

JSF converter Validation Error: value is not valid for SelectOneMenu UIComponent [duplicate]

This question already has answers here:
Validation Error: Value is not valid
(3 answers)
Closed 6 years ago.
I am using the managedBean userHome in requestScope, in which the entity 'user' is going to be persist.
The user has the leader column which is mapped in ManyToOne relation.My Code looks like this
#ManagedBean
#RequestScoped
public class UserHome {
private User user = new User();
// Getters and Setters
private List<SelectItem> selectItems = new ArrayList<SelectItem>();
public UserHome() {
for(User user: availableLeaders) {
selectItems.add(new SelectItem(user.getName(), user));
}
}
public void persis();
}
User.java
public class User {
#Id
#Column
private Integer id;
#Column
privat String name;
#ManyToOne
private User leader;
}
I am trying to get the value of this leader through h:selectOneMenu like this
<h:selectOneMenu value="#{userHome.user.leader}" converter="userConverter">
<f:selectItems value="#{userHome.selectItems}"/>
</h:selectOneMenu>
My converter looks like this
#FacesConverter(forClass = User.class, value="userConverter")
public class UserConverter implements Converter {
private Map<String, User> userValues = new HashMap<String, User>();
public UserConverter() {
init();
}
#Override
public Object getAsObject(FacesContext context, UIComponent component,
String value) {
return userValues.get(value);
}
#Override
public String getAsString(FacesContext context, UIComponent component, Object value) {
System.out.println("RAJASEKARAN "+value);
return ((User)value).getName();
}
public void init() {
UserHome userHome = new UserHome();
for(User user:userHome.availableLeaders()) {
userValues.put(user.getName(), user);
}
}
}
While try to save the user I am getting the error UserEdit:j_idt18: Validation Error: Value is not valid
Adding to BalusC's answer: after the postback, you need to make sure that the User instances are either exactly the same ones as you used for rendering the select items, or that you implement equals for your User class.
The code doesn't show where availableLeaders comes from, but if this is fetched from a DB on-demand, then the converter will not convert to the exact same object instance that's in the list that JSF resolves via #{userHome.selectItems}.
After the conversion, JSF will check whether the converted instance can be found in that list using the equals() method.
You've constructed the SelectItem the wrong way. As per the class' documentation, the 1st argument should represent the item value (which is to be converted and submitted) and the 2nd argument should represent the item label (which is to be displayed in list). But you specified them the other way round.
Fix it accordingly:
selectItems.add(new SelectItem(user, user.getName()));
If that still doesn't fix the problem, then it means that the equals() method of User class is not (properly) implemented. JSF will use it to validate the selected User against any of the item values of the list after conversion.
Unrelated to the concrete problem, it may be useful to know that <f:selectItems> in JSF2 offers you the possibility to build the list without the need to build a list of SelectItem manually. Here's an example which achieves exactly the same:
<f:selectItems value="#{userHome.availableLeaders}" var="user"
itemValue="#{user}" itemLabel="#{user.name}" />
This allows you to get rid of the additional selectItems property and the loop in the bean constructor.

Resources