1

As you can see in the screenshot, I am having a JSF implicit value validation error for the property "member.city". The purpose is to -through ajax - populate the city select One Menu after selecting a country on the previous Country Select one menu. This is achieved by using selectOneMenu change listener and ajax to update the component displaying list of cities. However, I get a validation error and the form is not submitted.

Validation error screenshot

I tried with Entities with and without (hascode and equals methods), but in vain. I used a separated Managed Bean ViewScoped to retrive the filtered list, but in vain. **

If I replace the filtered list of cities by the list that retrieves all cities (in the xhtml form, replace <f:selectItems value="#{cityBean.citiesByCountry}" /> by <f:selectItems value="#{cityBean.all}" />, there's no validation error and the form successfully gets submitted.

** Here is the code:

Form:

<p:outputLabel for="country" value="#{i18n.country}" />
                    <p:selectOneMenu valueChangeListener="#{cityBean.selectCountry}" rendered="true" id="country" value="#{newMember.country}" converter="#{countryBean.converter}" 
                        style="width:160px;text-align:left;border:thin solid gray;"
                        required="true" requiredMessage="#{i18n.required}">                     
                        <f:selectItems value="#{countryBean.all}" var="countryname" itemLabel="#{countryname.name}"/>
                        <p:ajax event="change" update="city" />
                    </p:selectOneMenu>
                    <p:message for="country" />

                    <p:outputLabel for="city" value="#{i18n.city}" />
                    <p:selectOneMenu rendered="true" id="city" value="#{newMember.city}" converter="#{cityBean.converter}" converterMessage="Conversion error!"
                        style="width:160px;text-align:left;border:thin solid gray;"
                        required="true" requiredMessage="#{i18n.required}">                                         
                        <f:selectItems value="#{cityBean.citiesByCountry}" var="cityname" itemLabel="#{cityname.cityname}"/>                        
                    </p:selectOneMenu>
                    <p:message for="city" />

City Entity head:

@XmlRootElement
@Entity
@Table(name = "city", schema = "public")
@NamedQueries({
        @NamedQuery(name = "City.findAll", query = "SELECT c FROM City c"),
        @NamedQuery(name = "City.findById", query = "SELECT c FROM City c WHERE c.id = :id"),
        @NamedQuery(name = "City.findByCountry", query = "SELECT c FROM City c WHERE c.country = :country"),
        @NamedQuery(name = "City.findByCountryOrderedByName", query = "SELECT c FROM City c WHERE c.country = :country ORDER BY c.cityname ASC") })
@NamedNativeQueries({ @NamedNativeQuery(name = "City.findCountryNativeSQL", query = "select * from city c where c.countrybeta = :country.betacode", resultClass = City.class) })
public class City implements java.io.Serializable {

    private static final long serialVersionUID = 3364735938266980295L;
    private int id;
    private Integer version;
    private Country country;
    private String cityname;
    private String district;
    private int population;
    private Set<Member> members = new HashSet<Member>(0);

    public City() {
    }
// ...remainder of entity code (contructors and fields..)

The Bean:

@Named
@Stateful
@ConversationScoped
public class CityBean implements Serializable {

    private static final long serialVersionUID = 1L;
    private City city;
    private List<City> cityList;

    public City getCity() {
        return this.city;
    }

    private Country selectedCountry;

    @Inject
    private Conversation conversation;

    @PersistenceContext(type = PersistenceContextType.EXTENDED)
    private EntityManager entityManager;
// remainder of code....

// The List that works fine with Select one Menu
public List<City> getAll() {

        CriteriaQuery<City> criteria = this.entityManager.getCriteriaBuilder()
                .createQuery(City.class);
        return this.entityManager.createQuery(
                criteria.select(criteria.from(City.class))).getResultList();
    }

    @PostConstruct
    public void init() {
        setCityList(getCitiesByCountry());
    }

    public List<City> getCityList() {
        return cityList;
    }

    public void setCityList(List<City> cityList) {
        this.cityList = cityList;
    }

    /**
     * The List That triggers a jsf value validation error
     * @return List of Cities by Country ordered by city name
     */
    public List<City> getCitiesByCountry() {
        TypedQuery<City> query = this.entityManager.createNamedQuery(
                "City.findByCountryOrderedByName", City.class);
        query.setParameter("country", selectedCountry);
        return query.getResultList();
    }

    // The Select One Menu Listener
    public void selectCountry(ValueChangeEvent event) {

        if (event != null) {
            selectedCountry = (Country) event.getNewValue();
        }
        }

        // getters and setters....

ENV: - JSF 21 - Hibernate - JBoss AS 7.1 - PrimeFaces 3.5 - Linux 3.5 / Firefox 18.0.2

Hanynowsky
  • 2,880
  • 5
  • 29
  • 43
  • 2
    No logic in getters. If the contents of the backing list for the `` cannot be guaranteed to be constant thru a request lifecycle, you'll get what you're getting now. Move the logic for `getAll` out of there and into a `@PostConstructor` – kolossus Feb 15 '13 at 16:47
  • Sure thing to move logic from getters. It was the way it was for testing before refactoring the whole bean the correct way. Thanks for your useful comment anyway. – Hanynowsky Feb 15 '13 at 20:49

1 Answers1

4

Your concrete problem is caused because you aren't starting the conversation in @PostConstruct and thus the bean behaves like request scoped. This way the list of cities will incompatibly change during processing the form submit and become empty (and thus the selected item couldn't be found in the list of available items and hence this validation error).

You need to start the conversation in the @PostConstruct.

conversation.begin();

See also:


Unrelated to the concrete problem, using valueChangeListener and performing business logic in getter methods is the wrong approach. You should not be performing business logic in getter methods at all. This is plain inefficient. They should solely return already-prepared bean properties.

Rewrite your logic as follows:

<p:selectOneMenu value="#{bean.country}" ...>
    <f:selectItems value="#{bean.countries}" />
    <p:ajax listener="#{bean.updateCities}" update="city" />
</p:selectOneMenu>

<p:selectOneMenu id="city" value="#{bean.city}" ...>                                         
    <f:selectItems value="#{bean.cities}" />
</p:selectOneMenu>

with

private Country country;
private List<Country> countries;
private City city;
private List<City> cities;

@PostConstruct
public void init() {
    countries = service.listCountries();

public void updateCities() {
    cities = service.listCities(country);
}

// Add/generate normal!! getters/setters.

See also:

Community
  • 1
  • 1
BalusC
  • 992,635
  • 352
  • 3,478
  • 3,452
  • Still the Java EE prophet saves us from darkness :). Totally forgot about beginning the conversation. I was willing to move logic from getters onto proper logic methods as a final refactoring for the bean but I wanted to spot the issue behind the value validation error first. Thanks a Lot Sir b.Scholtz. It's all working fine now. – Hanynowsky Feb 15 '13 at 19:50