1

Mojarra 2.1

I need to write a validator for the h:inputText which performs some logic only if the value for that input is changed. I.e.

public class MyValidator implements Validator{

    public void validate(FacesContext context,
                         UIComponent component,
                         Object value) throws ValidatorException;
         if(valueChanged(UIComponent component)){ //The method checks if the value's changed
               //do some piece of logic
         }
         return;
     }
}

I dug into the queuing events of the UIInput and found this:

validateValue(context, newValue);

// If our value is valid, store the new value, erase the
// "submitted" value, and emit a ValueChangeEvent if appropriate
if (isValid()) {
        Object previous = getValue();
        setValue(newValue);
        setSubmittedValue(null);
        if (compareValues(previous, newValue)) {
            queueEvent(new ValueChangeEvent(this, previous, newValue));
        }
    }

This piece of code is from the method, executed by the Validation phase callback. The first thought that popped into my head was queriyng all events fired during handling the request. The method queueEvent(FacesEvent) is implemented as follows:

 public void queueEvent(FacesEvent event) {

    if (event == null) {
        throw new NullPointerException();
    }
    UIComponent parent = getParent();
    if (parent == null) {
        throw new IllegalStateException();
    } else {
        parent.queueEvent(event);
    }

}

Therefore every such invokation will end up in UIViewRoot.queueEvent(FacesEvent) which is implemented as:

public void queueEvent(FacesEvent event) {

    if (event == null) {
        throw new NullPointerException();
    }
    // We are a UIViewRoot, so no need to check for the ISE
    if (events == null) {
        int len = PhaseId.VALUES.size();
        List<List<FacesEvent>> events = new ArrayList<List<FacesEvent>>(len);
        for (int i = 0; i < len; i++) {
            events.add(new ArrayList<FacesEvent>(5));
        }
        this.events = events;
    }
    events.get(event.getPhaseId().getOrdinal()).add(event);
}

Which means, all events is actually stored as a List<List<FacesEvent>> for each phase. But the List<List<FacesEvent>> events is a private field, so it's impossible to get direct acces to it.

Another thing is that the actual validation is being perfromed before the quingEvent, so implemting valueChangeListener doesn't seem useful as well.

Question: Is it possible to implements such validator in JSF in a natural way?

St.Antario
  • 26,175
  • 41
  • 130
  • 318

1 Answers1

1

Just do the value comparison yourself. In the validator, the old value is just readily available via UIComponent argument.

@Override
public void validate(FacesContext context, UIComponent component, Object submittedValue) {
    if (component instanceof EditableValueHolder) {
        Object newValue = submittedValue;
        Object oldValue = ((EditableValueHolder) component).getValue();

        if (newValue == null ? oldValue == null : newValue.equals(oldValue)) {
            return; // Not changed, so skip validation.
        }
    }

    // Do actual validation here.
}

If you happen to use JSF utility library OmniFaces, it has a ValueChangeValidator which does exactly this.

BalusC
  • 1,082,665
  • 372
  • 3,610
  • 3,555
  • One more clarification: The `getValue()` method takes the value directly from the decoded `ViewState`? – St.Antario Aug 25 '15 at 09:08
  • This part is essentially answered in http://stackoverflow.com/questions/31955955/understanding-jsf-components-class-lifetime/31958332#31958332 So, only if the local value is set (i.e. the previously submitted value didn't made it to update model values phase), it's basically grabbed from the view state. – BalusC Aug 25 '15 at 09:13