I have a weird problem with a custom #FacesConverter with JBoss-7.1.0.CR1b during an AJAX call in p:selectOneMenu (Primefaces 3.0).
The simplified Converter looks like this, there are no NPE or other exceptions in this class
#FacesConverter("MyConverter")
public class MyConverter implements Converter
{
public Object getAsObject(FacesContext fc, UIComponent uic, String value)
{
logger.debug("getAsObject value: "+value);
if (submittedValue.trim().equals("")) {return null;}
else
{
MyEjb ejb = new MyEjb();
ejb.setId(Long.parseLong(value()));
return ejb; //**** alternative with return null; ****
}
}
public String getAsString(FacesContext fc, UIComponent uic, Object value)
{
if (value == null || value.equals("")) {return "";}
else
{
MyEjb ejb = (MyEjb)value;
return ""+ejb.getId();
}
}
}
The converter is used in a p:selectOneMenu:
<h:form>
<p:selectOneMenu value="#{clientBean.selected}" converter="MyConverter">
<f:selectItems value="#{clientBean.all}" var="my"
itemLabel="#{my.name}" itemValue="#{my}"/>
<p:ajax listener="#{clientBean.changed}" />
</p:selectOneMenu>
</h:form>
That's not rocket engineering, the changed method simply makes a debug:
public void changed()
{
logger.info("changed() "+selected);
}
But now the annoying part: The changed() is never called with the code like above, but I get the converter invoked three times:
12:37:51,500 DEBUG getAsObject value: 35
12:37:51,502 DEBUG getAsObject value:
12:37:51,503 DEBUG getAsObject value:
If I change the p:selectOneMenu value="#{clientBean.selectedId}" to a long selectedId and don't use the Converter the method is called once. Even if I return null in getAsObject()the changed() is called (once). I don't assume it's Primefaces related, because I have the same behavior if I use h:selectOneMenu and f:ajax.
You should have a <p:messages />, <p:growl /> or <h:messages /> which is been ajax-updated in your view. You should also pay attention to warnings in server logs about possible missing faces messages. The chance is big that you're seeing the infamous Validation error: Value not valid validation error.
After conversion, JSF will validate if the submitted object is one of the available items as part of safeguard against tampered/hacked requests. JSF will do that by submittedObject.equals(oneOfAvailableObjects) for each of the available objects as you have there in <f:selectItems>. If nothing matches, then JSF will display this validation error.
In your particular case, the MyEjb class has apparently no equals() method or its implementation is broken. See also Right way to implement equals contract.
Related
As I struggled for hours I finally found where those annoying ClassCastExceptions came from, which I thought were produced by Hibernate and it's enum-mapping.
But they came from my JSF view, where I passed a List from
<h:selectManyCheckbox value="#{createUserManager.user.roles}" ... >
<f:selectItems value="#{createUserManager.roles}"/>
</h:selectManyCheckbox>
back into my backing bean.
My data simply consists of the values of an enum:
public Role[] getRoles()
{
return Role.values();
} .
I was really shocked when I tested the setter of roles in the User-class and got this:
public void setRoles(List<Role> paramRoles) {
System.out.println(paramRoles.get(0) instanceof Role); //output: false
for(Role role : paramRoles){ ...} //crashes with ClassCastException
}
Changing List<Role> paramRoles to List<String> paramRoles worked perfectly.
How is this possible? Shouldn't those generics be type safe or is type erasure in connection with JSF killing the whole type safety thing?
Also shouldn't the return value of h:selectManyCheckbox be List<Role>, like I passed in via the f:selectItems?
The behaviour you are experiencing is fully expected. Moreover, it is related to java generics in the same way as to how HTTP works.
The problem
The HTTP part
The problem is that you don't fully understand how HTTP works. When you submit data by pressing the submit button, parameters of your generated by JSF <h:selectManyCheckbox> tag, as a bunch of <input type="checkbox" name="..." value="userRoleAsString"> checkboxes, will be sent as strings and retrived ultimately as request.getParameter("checkboxName"); also as strings. Of course, JSF has no idea how to construct you model object class, Role.
The generics part
As you know due to the fact that java chose type erasure for generics to provide for backwards compatibility, information about generic types is basically a compile-type artifact and is lost at runtime. So at runtime your List<Role> erases to a plain, good old List. And as far as EL is a runtime language that uses Java Reflection API to deal with your expressions / call methods, at runtime no such information is available. Taking into account the HTTP part, JSF does its best and assigns string objects to your list, as it's all it can implicitly do. If you are willing to tell JSF to do otherwise, you need to do that explicitly, i.e. by specifying a converter to know what type of object to expect in an HTTP request.
The JSF part: aftermath
JSF has a provided javax.faces.Enum converter and in indeed works, if EL knew of the compile-time generic type of your list, that is Role. But it doesn't know of it. It would be not necessary to provide for a converter in case your multiple selection would be done on a Role[] userRoles object, or if you used the unique selection like in <h:selectOneMenu> with a value bound to Role userRole. In these examples the built-in enum converter will be called automatically.
So, to get it work as expected you need to provide for a Converter that will 'explain' JSF what type of values does this list hold, and how to do the transformations from Role to String, and vice versa.
It is worth noting that this will be the case with any bound List<...> values within the multiple choice JSF components.
Points of reference on Stack Overflow
After the problem was examined and resolved I was wondering if no one faced it in the past and searched for some previous answers here. Not surprisingly, it was asked before, and of course the problem was solved by BalusC. Below are two most valuable point of reference:
JSF 2.0 use enum in selectMany menu;
How to make a dropdown menu of a enum in JSF;
How to create and use a generic bean for enums in f:selectItems?.
The test case and two examples of working converters
Below I provide for a test case entirely for your understanding: everything works as expected apart from the third <h:selectManyCheckbox> component. It's up to you to trace it fully to eliminate the issue altogether.
The view:
<h:form>
Many with enum converter
<!-- will be mapped correctly with Role object -->
<h:selectManyCheckbox value="#{q16433250Bean.userRoles}" converter="roleEnumConverter">
<f:selectItems value="#{q16433250Bean.allRoles}" var="role" itemLabel="#{role.name}" />
</h:selectManyCheckbox>
<br/>
Many with plain converter
<!-- will be mapped correctly with Role object -->
<h:selectManyCheckbox value="#{q16433250Bean.userRoles2}" converter="roleConverter">
<f:selectItems value="#{q16433250Bean.allRoles2}" var="role" itemLabel="#{role.name}" />
</h:selectManyCheckbox>
<br/>
Without any converter
<!-- will NOT be mapped correctly with Role object, but with a default String instead -->
<h:selectManyCheckbox value="#{q16433250Bean.userRoles3}">
<f:selectItems value="#{q16433250Bean.allRoles}" var="role" itemLabel="#{role.name}" />
</h:selectManyCheckbox>
<br/>
Without any converter + array
<!-- will be mapped correctly with Role object -->
<h:selectManyCheckbox value="#{q16433250Bean.userRoles4}">
<f:selectItems value="#{q16433250Bean.allRoles}" var="role" itemLabel="#{role.name}" />
</h:selectManyCheckbox>
<br/>
<h:commandButton value="Submit" action="#{q16433250Bean.action}"/>
</h:form>
The bean:
#ManagedBean
#RequestScoped
public class Q16433250Bean {
private List<Role> userRoles = new ArrayList<Role>();//getter + setter
private List<Role> userRoles2 = new ArrayList<Role>();//getter + setter
private List<Role> userRoles3 = new ArrayList<Role>();//getter + setter
private Role[] userRoles4;//getter + setter
public enum Role {
ADMIN("Admin"),
SUPER_USER("Super user"),
USER("User");
private final String name;
private Role(String name) {
this.name = name;
}
public String getName() {
return this.name;
}
}
public Role[] getAllRoles() {
return Role.values();
}
public String action() {
return null;
}
}
The converters:
#FacesConverter("roleEnumConverter")
public class RoleEnumConverter extends EnumConverter {
public RoleEnumConverter() {
super(Role.class);
}
}
and
#FacesConverter("roleConverter")
public class RoleConverter implements Converter {
public Object getAsObject(FacesContext context, UIComponent component, String value) {
if(value == null || value.equals("")) {
return null;
}
Role role = Role.valueOf(value);
return role;
}
public String getAsString(FacesContext context, UIComponent component, Object value) {
if (!(value instanceof Role) || (value == null)) {
return null;
}
return ((Role)value).toString();
}
}
When a page is loaded with an input which is bound to a value in the view scopped backing bean and if the value is is pre-initialized, then after required field validation the input is again populated with the previous value.
Here is the managed bean:
#ManagedBean(name = "adminController")
#ViewScoped
public class AdminController extends BaseWebController implements Serializable {
private static final long serialVersionUID = 1019716974557397635L;
private transient CustomerDTO customerDTO;
public AdminController() {
log.debug("Inside AdminController");
}
#PostConstruct
public void init() {
initCustomerDTO();
customerDTO.setCustomerName("Test");
}
public void saveCustomer(ActionEvent event) {
try {
getServiceProvider().getCustomerService().addNewCustomer(customerDTO);
getFacesContext().addMessage(null, FacesMessageUtils.getMessageForCustomerSaveSuccess(getFacesContext()));
} catch (Throwable throwable) {
getFacesContext().addMessage(null, FacesMessageUtils.getMessageForCustomerSaveError(getFacesContext()));
printStackTrace(throwable);
}
initCustomerDTO();
}
private void initCustomerDTO() {
customerDTO = new CustomerDTO();
}
public CustomerDTO getCustomerDTO() {
return customerDTO;
}
public void setCustomerDTO(CustomerDTO customerDTO) {
this.customerDTO = customerDTO;
}
}
CustomerDTO is a POJO.
The jsf contains:
<ui:composition xmlns="http://www.w3.org/1999/xhtml"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:ui="http://java.sun.com/jsf/facelets"
xmlns:p="http://primefaces.org/ui"
xmlns:pe="http://primefaces.org/ui/extensions">
<h:form id="customerForm">
<p:inputText id="customerName" styleClass="customerName"
autocomplete="off"
label="#{adbBundle['admin.addCustomerPanel.addCustomerTable.customerName']}"
value="#{adminController.customerDTO.customerName}"
required="true" />
<p:commandButton value="#{adbBundle['saveButton']}"
actionListener="#{adminController.saveCustomer}"
icon="ui-icon-check"
update=":growl, #form, :adminTabView:customerListForm:customerListPanel" />
</h:form>
</ui:composition>
I also have a custom converter class for trimming blank String.
#FacesConverter(forClass = String.class)
public class StringTrimmer implements Converter {
#Override
public Object getAsObject(FacesContext context, UIComponent component, String value) {
if (StringUtils.isBlank(value)) {
if (component instanceof EditableValueHolder) {
((EditableValueHolder) component).setSubmittedValue(null);
}
return null;
}
return value;
}
#Override
public String getAsString(FacesContext context, UIComponent component, Object value) {
return value.toString();
}
}
So when the page is first loaded the input field contains "Test". I cleaned out the test field and click on the save button. I received error message for required field validation, but the input is again get populated with the default value "Test" that I have set before.
How can I solve the issue?
Update:
I am running in JBoss AS7 with Mojarra 2.1.7, which is JBoss'es implementation and Primefaces 3.4.2.
Your problem is caused by the converter.
When JSF validation fails for a specific component, the submitted value is kept in the component, else it is nulled out and set as local value. When JSF is finished with validation of all components and no one has failed validation, then all local values are nulled out and set as model value (the one referenced as value attribute). When JSF needs to redisplay the form with values, it first checks if the submitted value is not null and then display it, else it checks if the local value is not null and then display it, else it displays the model value outright.
In your case, you're converting the empty string submitted value to null regardless of validation outcome. So the submitted value is always null and thus JSF will always redisplay the model value instead of the empty string submitted value.
I believe that you rather need the following context parameter instead of the converter, certainly if the sole goal is to prevent the model values being polluted with empty strings when empty inputs are submitted.
<context-param>
<param-name>javax.faces.INTERPRET_EMPTY_STRING_SUBMITTED_VALUES_AS_NULL</param-name>
<param-value>true</param-value>
</context-param>
Or if your sole goal is really to trim whitespace from string, as the converter class name suggests, then you should not be touching the submitted value at all. You can find a concrete example here: Why does <h:inputText required="true"> allow blank spaces? The getAsObject() won't be set as submitted value, but instead as local value (on validation success) and ultimately as model value.
When I run this the code below, Apache Tomcat Log says:
Property 'recuClientes' not found in type com.swc.rc.recu.UtilsRecu
The webPage returns No records found.
any suggestion?
This is the call
<p:outputPanel>
<h:form>
<p:dataTable var="rec" value="#{rc.recu}">
<p:column headerText="recu">
<h:outputText value="#{rec.deudor}" />
</p:column>
</p:dataTable>
</h:form>
</p:outputPanel>
This is the source,
i can use it, isnĀ“t it?
#XmlTransient
public Collection<Procs> getProcsCollection() {
return procsCollection;
}
public void setProcsCollection(Collection<Procs> procsCollection) {
this.procsCollection = procsCollection;
}
And this is the managedBean..
#ManagedBean(name = "rc")
#SessionScoped
public class UtilsRecu {
private Clientes cliente=new Clientes();
private List <Procs> recu=new LinkedList<Procs>();
public void recuClientes(){
recu=(List<Procs>) cliente.getProcsCollection();
}
public void setRecu(List<Procs> recu) {
this.recu= recu;
}
public List<Procs> getRecu() {
recuClientes();
return recu;
}
}
Thank you..
The exception which you're facing is not caused by the Facelets code shown so far. It's caused by incorrect usage of #{rc.recuClientes} somewhere else in the same page. Perhaps you have placed it plain vanilla in template text like so
#{rc.recuClientes}
and hoping that it would execute the method on load. But it doesn't work that way. It would be interpreted as a value expression and it would thus be looking for a getter method getRecuClientes() which returns something which can then be printed to the output. But as such a getter method does not exist, exactly the "property not found" exception would be thrown which you're facing.
Given the fact that this method performs some business logic (filling the list), it should be invoked by some action component such as <h:commandButton>.
<h:form>
<h:commandButton value="Submit" action="#{rc.recuClientes}" />
</h:form>
Or, if you intend to invoke it during the initial GET request already, then just annotate it with #PostConstruct without the need to reference it anywhere in the view.
#PostConstruct
public void recuClientes() {
recu = (List<Procs>) cliente.getProcsCollection();
}
This way it will be invoked directly after bean's construction.
By the way, that cast is a code smell. In well designed code, you shouldn't have the need for that cast in this particular construct.
I have this f:viewParam that I try to bind validate and convert a userId into Player, and I got an unexpected results.
<f:metadata>
<f:viewParam name="userId" value="#{myBean.selectedPlayer}" converter="pConverter"
converterMessage="Bad Request. Unknown User" required="true"
requiredMessage="Bad Request. Please use a link from within the system" />
</f:metadata>
<h:body>
<p:messages id="msgs"/>
<h:form>
<ul>
<li>Harry</li>
<li>Tom</li>
<li>Peter</li>
</ul>
</h:form>
<h:form>
<h:panelGrid columns="2" rendered="#{not empty myBean.selectedPlayer}">
<h:outputText value="Id: #{myBean.selectedPlayer.id}"/>
<h:outputText value="Name: #{myBean.selectedPlayer.name}"/>
</h:panelGrid>
</h:form>
<h:form id="testForm">
<h:inputText value="#{myBean.text}"/>
<p:commandButton value="Switch" update=":msgs testForm"/>
<h:outputText value="#{myBean.text}" rendered="#{not empty myBean.text}"/>
</h:form>
</h:body>
My Converter look like this
#FacesConverter(value="pConverter")
public class PConverter implements Converter {
private static final List<Player> playerList;
static{
playerList = new ArrayList<Player>();
playerList.add(new Player(1, "Harry"));
playerList.add(new Player(2, "Tom"));
playerList.add(new Player(3, "Peter"));
}
#Override
public Object getAsObject(FacesContext fc, UIComponent uic, String value) {
if(value == null || !value.matches("\\d+")){
return null;
}
long id = Long.parseLong(value);
for(Player p : playerList){
if(p.getId() == id){
return p;
}
}
throw new ConverterException(new FacesMessage("Unknown userId: " + value));
}
#Override
public String getAsString(FacesContext fc, UIComponent uic, Object value) {
if(!(value instanceof Player) || value == null){
return null;
}
return String.valueOf(((Player)value).getId());
}
}
As I click the three link (Harry, Tom, Peter), the converter work great. It converter the id and bind the player back to my managed bean. I then type something in the text box, then click Switch, the first time it work fine, what I typed appear next to the button, but then I change what I type, and click Switch again, then error message appear Bad Request. Please use a link from within the system, which is the error message for required for f:viewParam. If I took the f:viewParam out then everything work fine. Surprisingly, if I switch from f:viewParam to o:viewParam (OmniFaces), then it work great.
That's because the <f:viewParam> runs on every single HTTP request, also on postbacks. It works in your case fine for plain GET links, because you're passing exactly that parameter in the links. It fails in your case for POST forms, because you aren't passing that parameter in the button. So it becomes null in the request parameter map and the required validator kicks in and hence this validation error.
To keep the <f:viewParam required="true"> happy on POST forms as well, you basically need to retain the initial request parameter by <f:param> in the command buttons/links.
<p:commandButton value="Switch" update=":msgs testForm">
<f:param name="userId" value="#{param.userId}" />
</p:commandButton>
The OmniFaces <o:viewParam>, which is designed to be used in combination with view scoped beans, has an additional check in the isRequired() getter (source code here):
#Override
public boolean isRequired() {
// The request parameter get lost on postbacks, however it's already present in the view scoped bean.
// So we can safely skip the required validation on postbacks.
return !FacesContext.getCurrentInstance().isPostback() && super.isRequired();
}
So, this skips the required validator on every postback (and additionally, it also skips setting the model value on every postback due to its stateless nature). That's why you don't see the validation error and you still have the proper model value (which isn't reset on every postback).
Here is the scenario - I want to create a view which can be used from a number of other views to create and edit a specific type of object.
My application has an address entity which can be shared between other entities.
In the view which maintains an entity, I would like a button/link which navigates to the address edit view for the address linked to that entity.
Another view which handles a different entity also needs to be able to navigate to the address edit view with its address.
The address view would then navigate back to the calling view once editing is completed.
My problem is that I can't seem to find a way to pass the address entity from the first view into the address view.
I think I want some kind of conversation scope, but don't know how to get the address without knowing about the page bean that references it, but obviously my address view can only know about addresses.
I am using JSF2.1 (MyFaces/PrimeFaces) and CDI (OpenWebBeans) and CODI.
I'm sure I must be missing something simple. (Simple relative to JSF/CDI terms that is!)
Just pass the address ID as request parameter and have the target view to convert, validate and set it in the bean by <f:viewParam>.
E.g.
<h:link value="Edit address" outcome="addresses/edit">
<f:param name="id" value="#{address.id}" />
</h:link>
and then in addresses/edit.xhtml
<f:metadata>
<f:viewParam id="id" name="id" value="#{editAddressBacking.address}"
converter="#{addressConverter}" converterMessage="Bad request. Unknown address."
required="true" requiredMessage="Bad request. Please use a link from within the system." />
</f:metadata>
<h:message for="id" />
<h:form rendered="#{not empty editAddressBacking.address}">
<h:inputText value="#{editAddressBacking.address.street}" />
...
</h:form>
In order to navigate back to the original page, you could pass another request parameter.
<h:link value="Edit address" outcome="addresses/edit">
<f:param name="id" value="#{address.id}" />
<f:param name="from" value="#{view.viewId}" />
</h:link>
(where #{view} is the implicit object referring to the current UIViewRoot)
and set it by <f:viewParam> as well so that you can in the submit method of the edit address backing bean just return to it:
public String save() {
// ...
return from + "?faces-redirect=true";
}
See also:
Communication in JSF 2 - Processing GET request parameters
I think I have come up with a solution.
As I am using CODI I can leverage the ConversationGroup annotation.
I've created an emtpy interface AddressConversation, then added this to all the backing beans that need to show the address/addressEdit.xhtml view, as well as the backing bean for the addressEdit view.
I'm also using CODI view config so my action methods return ViewConfig derived class objects.
#Named
#ConversationScoped
#ConversationGroup(AddressConversation.class)
public class AddressView implements Serializable
{
private Class<? extends Views> fromView;
private Address editAddress;
private Address returnAddress;
// Getters/setters etc...
public Class<? extends Views> cancelEdit()
{
returnAddress = null;
return fromView;
}
}
So in a calling view I have (PrimeFaces commandLink)
<p:commandLink value="#{enquiryView.addressLinkText}" action="#{enquiryView.editAddress()}" immediate="true"/>
and in the backing bean EnquiryView I can #Inject an instance of AddressView in the correct conversation group, then set the address and return view properties when action method is called.
#Named
#ConversationScoped
#ConversationGroup(AddressConversation.class)
public class EnquiryView implements Serializable
{
#Inject #ConversationGroup(AddressConversation.class) private AddressView addrView;
public Class<? extends Views> editAddress()
{
addrView.setAddress(editEnq.getAddress());
addrView.setFromView(Views.Enquiry.EnquiryEdit.class);
return Views.Address.AddressEdit.class;
}
}
I can also observe the navigation in EnquiryView and update the enquiry entity when an address has been "saved" in the address edit view.
protected void onViewConfigNav(#Observes PreViewConfigNavigateEvent navigateEvent)
{
if (navigateEvent.getFromView() == Views.Address.AddressEdit.class &&
navigateEvent.getToView() == Views.Enquiry.EnquiryEdit.class)
{
onEditAddressReturn();
}
}
private void onEditAddressReturn()
{
if (addrView.getReturnAddress() != null) {
// Save pressed
editEnq.setAddress(addrView.getReturnAddress());
}
}
If you want another entity to be set when the address is OK, just give the adresss process the EL name of the bean you want to set :
<f:param name="targetBeanSetter" value="enquiryBean.adress" />
And in Java :
public String executeAndBack() {
int last = this.targetBeanSetter.lastIndexOf('.');
String base = this.targetBeanSetter.substring(0, last);
String property = this.targetBeanSetter.substring(last + 1);
Object yourEntityToSet = FacesContext.getCurrentInstance().getELContext().getELResolver().getValue(context.getELContext(), null, base);
try {
PropertyUtils.setSimpleProperty(yourEntityToSet, property, constructeurHolder.getConstructeur());
} catch (Throwable e) {
throw new RuntimeException(e.getMessage());
}
return from + "?faces-redirect=true";
}
If you only need access to the choosen Adress, without building linked objects, when you are back on the first page, just inject in EnquiryView using
#ManagedProperty(value="{address}")
Address adress;