4

I know this seems to be a common one, but I'm lost with it. Occurs on clicking the Add button in assessment.jsf. Anyway, I've attached what I think are the relevant sections.

FWIW, AssessmentType.equals() isn't triggered when I debug.

Thanks in advance.

j_idt38:j_idt47:j_idt48: Validation Error: Value is not valid

assessment.xhtml:

        <h:form>
            <h:selectOneMenu value="#{assessmentBean.assessmentField}">
                <f:selectItems value="#{assessmentBean.assessment.type.fields}" />
            </h:selectOneMenu>

            <h:commandButton value="Add" action="#{assessmentBean.doAddField}">
                <f:param name="assessmentId"
                    value="#{assessmentBean.assessment.id}" />
            </h:commandButton>
        </h:form>

assessment.jsf:

<form id="j_idt38:j_idt47" name="j_idt38:j_idt47" method="post" action="/jsf-web/edit/assessment.jsf" enctype="application/x-www-form-urlencoded"> 
<input type="hidden" name="j_idt38:j_idt47" value="j_idt38:j_idt47" /> 
<select name="j_idt38:j_idt47:j_idt48" size="1">    <option value="1">Presenting Condition</option> 
    <option value="2">Problem Duration</option> 
</select> 
<script type="text/javascript" src="/jsf-web/javax.faces.resource/jsf.js.jsf?ln=javax.faces"></script>
<input type="submit" name="j_idt38:j_idt47:j_idt50" value="Add" onclick="mojarra.jsfcljs(document.getElementById('j_idt38:j_idt47'),{'j_idt38:j_idt47:j_idt50':'j_idt38:j_idt47:j_idt50','assessmentId':'1'},'');return false" /><input type="hidden" name="javax.faces.ViewState" id="javax.faces.ViewState" value="3431661972220941645:6952134510589038883" autocomplete="off" /> 
</form> 

AssessmentType.java:

import java.util.List;

import javax.persistence.CascadeType;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.OneToMany;
import javax.validation.constraints.NotNull;

import lombok.Data;

import org.hibernate.envers.Audited;

@Audited
@Data
@Entity
public class AssessmentType implements Comparable<AssessmentType> {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @NotNull
    private String name;

    @OneToMany( fetch=FetchType.EAGER, cascade = {CascadeType.PERSIST, CascadeType.MERGE}, targetEntity=AssessmentField.class )
    private List<AssessmentField> fields;

    @Override
    public int compareTo(final AssessmentType o) {
        return getId().compareTo(o.getId());
    }

    @Override
    public String toString() {
        return getName();
    }
}

AssessmentFieldConverter.java

import javax.faces.component.UIComponent;
import javax.faces.context.FacesContext;
import javax.faces.convert.Converter;
import javax.faces.convert.FacesConverter;
import javax.naming.InitialContext;
import javax.naming.NamingException;

import com.htu.fizio.api.AssessmentFieldManager;
import com.htu.fizio.domain.AssessmentField;

@FacesConverter(forClass = AssessmentField.class)
public class AssessmentFieldConverter implements Converter {

    AssessmentFieldManager<AssessmentField> assessmentFieldManager;

    @SuppressWarnings({ "unchecked", "rawtypes" })
    @Override
    public Object getAsObject(FacesContext ctx, UIComponent component, String value) {
        try {
            final InitialContext ic = new InitialContext();

            assessmentFieldManager = (AssessmentFieldManager) ic.lookup("fizio/AssessmentFieldManagerImpl/local");

            return assessmentFieldManager.find(Long.valueOf(value));
        } catch (NamingException e) {
            e.printStackTrace();
        }

        return null;
    }

    @Override
    public String getAsString(FacesContext ctx, UIComponent component, Object value) {
        return String.valueOf(((AssessmentField) value).getId());
    }
}

AssessmentBean.java

    import java.util.List;

    import javax.annotation.PostConstruct;
    import javax.ejb.EJB;
    import javax.faces.bean.ManagedBean;
    import javax.faces.bean.ManagedProperty;
    import javax.faces.bean.SessionScoped;

    import lombok.Getter;
    import lombok.Setter;

    import com.htu.fizio.api.AssessmentManager;
    import com.htu.fizio.domain.Assessment;
    import com.htu.fizio.domain.AssessmentField;
    import com.htu.fizio.domain.AssessmentFieldValue;
    import com.htu.fizio.jsf.faces.FacesUtil;

...    

@PostConstruct
public void init() {
    if (FacesUtil.containsKey("assessmentId")) {
        final Long id = Long.parseLong(FacesUtil.get("assessmentId"));

        assessment = assessmentManager.find(id);
    } else {
        assessment = new Assessment();
    }
}

    public String doAddField() {
        final AssessmentFieldValue value = new AssessmentFieldValue();

        value.setField(assessmentField);
        value.setValue("");

        assessment.getFieldValues().add(value);

        assessmentManager.save(assessment);

        return "/edit/assessment";
    }

Edit:

Just noticed this when debugging, is it a likely suspect?:

Daemon Thread [HandshakeCompletedNotify-Thread] (Suspended (exception ConcurrentModificationException)) 
    HashMap$EntryIterator(HashMap$HashIterator<E>).nextEntry() line: 793    
    HashMap$EntryIterator.next() line: 834  
    HashMap$EntryIterator.next() line: 832  
    SSLSocketImpl$NotifyHandshakeThread.run() line: 2214    
rich
  • 18,987
  • 11
  • 75
  • 101
  • 1
    The ConcurrentModificationException looks bad, but as it's in a Deamon Thread it's very unlikely to interfere with JSF at the level depicted in the original part of the question. – Arjan Tijms Jun 12 '11 at 21:12

3 Answers3

15

Validation Error: Value is not valid

To the point, this error means that the selected item does not match any of the items available in the list. I.e. the object represented by the selected item value has never returned true on its equals() call with any of the available select items.

There are only two causes for this problem:

  1. The equals() method of the object type in question is broken.
  2. The contents of the list of items is different during the validations phase of the form submit request than as it was during the render response phase of the initial request to display the form.

Since the first seems to be properly implemented -as per the comments-, the only cause left is the second. Assuming that you're nowhere doing business logic in a getter method, an easy test is to put the #{assessmentBean} in the session scope. If it works, then the data (pre)loading logic of the list of select items is definitely wrong.

BalusC
  • 1,082,665
  • 372
  • 3,610
  • 3,555
  • Thanks, but I couldn't get as far as the problem page with the bean session scoped - firstly it complained other beans needed to be session scoped too then I got this on an earlier page: org.hibernate.TransientObjectException: object references an unsaved transient instance - save the transient instance before flushing: com.htu.fizio.domain.Appointment – rich Jun 13 '11 at 18:20
  • @BalusC what kind of possible problems could happen to my `equals()` to make it not work? –  Jun 11 '12 at 17:47
  • @Daniel: If it contains a code logic error. – BalusC Jun 11 '12 at 17:57
  • @BalusC wel...so that is not my problem, I guess. I have a question on JSF, if possible, take a look please, its about converters –  Jun 11 '12 at 17:58
5

The validation is failing because after your converter converts the String representation of AssessmentType back to an object, JSF iterates over the existing values (assessmentBean.assessment.type.fields) and compares this recently converted object with all those existing ones.

Since you did not implement Object#equals for AssessmentType, it will default to an object identity comparison (roughly spoken, the memory address of your object) , which will of course fail.

The solution is thus to either implement Object#equals, or let the converter get the object from assessmentBean.assessment.type.fields instead of from AssessmentTypeManager.

Arjan Tijms
  • 37,782
  • 12
  • 108
  • 140
  • I'm using project lombok (@Data), which generates an equals method for me, but even if I implement my own explicitly the method breakpoint on it isn't triggered. The same applies for hashCode(). I'm not sure why the object from AssessmentFieldManager would be a different type to that in assessmentBean.assessment.type.fields which is a List? Apologies - just realised I've pasted the AssessmentType converter instead of the AssessmentField one. – rich Jun 12 '11 at 20:46
  • It would not be a different type, but with a different *object identity*. For a random class without an explicit equals method `new Foo().equals(new Foo())` is always false. The converter would return a new instance, which is compared with every instance present in the `List` that is returned by `assessmentBean.assessment.type.fields`. – Arjan Tijms Jun 12 '11 at 21:16
  • >but even if I implement my own explicitly the method breakpoint on it isn't triggered - this does surprises me. Your case is very typical, and I've seen it happening a dozen of times in my own team. What you could maybe do is set an exception break-point on `javax.faces.validator.ValidatorException` to discover what code in JSF is throwing the validation exception (there most likely is one). – Arjan Tijms Jun 12 '11 at 21:22
  • I've added breakpoints in the four constructors and two methods I can see in javax.faces.validator.ValidatorException, none of which are triggered. – rich Jun 12 '11 at 21:33
  • 2
    My bad, sorry. This particular message is not normally set via the ValidatorException but in `javax.faces.component.UISelectMany#validateValue`. It's very likely the message you're seeing is `javax.faces.component.UISelectMany.INVALID` from the std JSF resource bundle (it has exactly this text). To the best of my knowledge, this bundle key is only used in UISelectMany. – Arjan Tijms Jun 12 '11 at 21:48
  • My breakpoint on that method isn't triggered either... – rich Jun 13 '11 at 18:27
  • 2
    Ok, but we're zeroing in on the problem I think ;) You can also try `javax.faces.component.UISelectOne#validateValue` as the text can also be `javax.faces.component.UISelectOne.INVALID` (this one is actually more likely as you're using an h:selectOneMenu on your Facelet). – Arjan Tijms Jun 13 '11 at 19:33
  • >AssessmentType.equals() isn't triggered when I debug - this could also mean the list is completely empty after a postback. Otherwise it really should call the equals method. – Arjan Tijms Jun 13 '11 at 20:10
  • javax.faces.component.UISelectOne#validateValue _does_ trigger a breakpoint! It provides the selected AssessmentField as an argument. I can't see whether it's using UISelectOne.INVALID - I can't find a source jar for javaee-6? – rich Jun 13 '11 at 20:37
  • 1
    For JBoss AS 6 you don't need the source for javaee-6, but for [jboss home]/server/default/deployers/jsf.deployer/Mojarra-2.0/jsf-libs/jsf-impl-2.0.3-b05.jar. Unfortunately JBoss is a little sloppy with providing full source archives, so you'd best download Mojarra 2.0.3 separately (http://java.net/downloads/javaserverfaces/release/2.0.3/mojarra-2.0.3-FCS-source.zip). If you use Eclipse with JBoss tools, the above mentioned jar is put on your class path and source attachment is relatively easy. – Arjan Tijms Jun 13 '11 at 21:02
  • Great, so it looks like the message is UISelectOne.INVALID. The found variable is being assigned as false. It looks as though getConverter() returns null and in SelectUtils.matchValue items.hasNext() is immediately false. – rich Jun 13 '11 at 21:19
  • 1
    If hasNext() immediately returns null, it is very likely that `assessmentBean.assessment.type.fields` indeed returns null. At some point JSF will find your f:selectItems component and evaluate its value. The quickest way to find this would be to set a breakpoint in assesmentBean.getAssesment(), then in getType() and then getFields. – Arjan Tijms Jun 13 '11 at 21:30
  • Alternatively, you can also set a breakpoint in `SelectItemsIterator#initializeItems`. You should see why items isn't being assigned a value there. – Arjan Tijms Jun 13 '11 at 21:34
2

I think I've resolved this, at least, I've moved on to the next error!

Despite posting reams of code I'd not posted the full xhtml in which there were multiple and nested form tags. Just the one form seems to allow passing the assessmentId parameter which in turn allows the AssessmentBean to then populate the List of AssessmentFields for the assessment type correctly.

Thanks for all the help.

rich
  • 18,987
  • 11
  • 75
  • 101