jsfannotationsfaces-configsystemeventlistener

How to register a (Component)SystemEventListener for all UIInputs


I am trying to have a custom SystemEventListener that registers for all instances of type UIInput and reacts to their postValidate-Events. Based on an example I found on the web I managed to get one running for HtmlInputText by registering it in the faces-config.xml as follows:

<system-event-listener>
   <source-class>javax.faces.component.html.HtmlInputText</source-class>
   <system-event-class>javax.faces.event.PostValidateEvent</system-event-class>
   <system-event-listener-class>com.ourcompany.ourproduct.validators.inputPostValidationListener</system-event-listener-class>
 </system-event-listener>

I then tried to 1) broaden this to work for UIInputs in general and to 2) use the @ListenerForannotation, but I just can't seem to get either of it to work.

for 1) I couldn't really find any examples or documentation, so i just tried around by a) defining multiple source-class-tags or by b) using javax.faces.component.UIInput as source-class. Neither worked.

for 2) I tried

@ListenerFor(systemEventClass = PostValidateEvent.class, sourceClass = UIInput.class)

which worked neither for UIInput nor for html.HtmlInputText.

Now, when I duplicate the same XML-configuration for all the other types of HTML-Inputs, this does the trick, but it just clutters up the xml and altogether seems quite annoying to me.

So the question is: Am I generally doing something wrong with the @ListenerFor annotation? Is there a restriction on which source-classes are possible, i.e. why can't I use the more generic UIInput? Is there a more efficient way to register the listener for all those different inputs than repeating the XML? And finally: I'd rather like to implement ComponentSystemEventListener. Assuming that the above problem was resolved, I'd just change the implements-Statement and implement the abstract processEvent accordingly, right? Would that work just the same or is the registration/xml-config different in this case (e.g. maybe <component-system-event-listener> instead of just <system-event-listener>?

(and as an after-note: is it just me or is it kind of hard to find any non-trivial examples for this kind of stuff on the web?)


Solution

  • The @ListenerFor is supposed to be set on an UIComponent or Renderer implementation, not on a standalone SystemEventListener implementation. See also the javadoc (emphasis mine):

    The default implementation must support attaching this annotation to UIComponent or Renderer classes. In both cases, the annotation processing described herein must commence during the implementation of any variant of Application.createComponent() and must complete before the UIComponent instance is returned from createComponent(). The annotation processing must proceed according to an algorithm semantically equivalent to the following.

    ...

    In order to have a global listener, not specific to an UIComponent or Renderer, your best bet is creating and registering a PhaseListener which subscribes the listener to the view root.

    public class PostValidateListener implements PhaseListener {
    
        @Override
        public PhaseId getPhaseId() {
            return PhaseId.PROCESS_VALIDATIONS;
        }
    
        @Override
        public void beforePhase(PhaseEvent event) {
            event.getFacesContext().getViewRoot()
                .subscribeToViewEvent(PostValidateEvent.class, new InputPostValidationListener()); // Capitalize class name?
        }
    
        @Override
        public void afterPhase(PhaseEvent event) {
            // NOOP.
        }
    
    }
    

    To get it to run, register it as follows in faces-config.xml:

    <lifecycle>
        <phase-listener>com.example.PostValidateListener</phase-listener>
    </lifecycle>
    

    You can even make your InputPostValidationListener itself a PhaseListener.

    public class InputPostValidationListener implements PhaseListener, SystemEventListener {
    
        @Override
        public void beforePhase(PhaseEvent event) {
            event.getFacesContext().getViewRoot().subscribeToViewEvent(PostValidateEvent.class, this);
        }
    
        // ...
    }