Simple IRC Widget with <a4j:push>

Introduction

     One of the frequently asked questions from the community is about real-world usage of the <a4j:push> component. In this article you can find detailed information about creating simple IRC widget with <a4j:push>.

     It is a common and good use-case to place a widget on a page that shows a chatter on an IRC channel. There is a lot of widgets that provide full IRC experience, but you will discover how to output chatter on a channel without any possibility to post messages from the widget.

     Let's start!

     First of all you need to decide which Java framework for writing IRC clients you can use. I've chosen PircBot, that provides an easy and quick way to develop your own IRC bot. PircBot includes an event-driven architecture to handle common IRC events and allows you to perform a variety of tasks on IRC. Thus, in order to accomplish this tutorial you need to get PircBot.

 

Creating a prototype

     I prefer to create a widget prototype with a list of key features before writing a line of code:

prototype.png

   Key features:

  1. Ajaxed widget (reverse Ajax technique implementation)
  2. Customizable look-and-feel
  3. Easy way to define connection parameters

     Now we can start developing RichFaces IRC Widget.

 

Developing IRC Widget

 

     Basic Setup
    1. First of all you should create basic JSF project with RichFaces on board
    2. Add pircbot-1.4.2.jar to your project. Finally I've got the following structure:
      |-- WebContent
      |   |-- META-INF
      |   |   `-- MANIFEST.MF
      |   |-- WEB-INF
      |   |   |-- faces-config.xml
      |   |   |-- lib
      |   |   |   |-- commons-beanutils-1.7.0.jar
      |   |   |   |-- commons-collections-3.2.jar
      |   |   |   |-- commons-digester-1.8.jar
      |   |   |   |-- commons-logging-1.0.4.jar
      |   |   |   |-- jsf-api-1.2_09-BETA1.jar
      |   |   |   |-- jsf-facelets-1.1.14.jar
      |   |   |   |-- jsf-impl-1.2_09-BETA1.jar
      |   |   |   |-- jstl-1.0.jar
      |   |   |   |-- pircbot-1.4.2.jar
      |   |   |   |-- richfaces-api-3.3.1.GA.jar
      |   |   |   |-- richfaces-impl-3.3.1.GA.jar
      |   |   |   `-- richfaces-ui-3.3.1.GA.jar
      |   |   `-- web.xml
      |   |-- index.jsp
      |-- src
      |   `-- org
      |       `-- jboss
      |           `-- richfaces
      |               |-- Bean.java
      

 

    Bean for settings management

     In order to simplify connection parameters definition I've created settings.properties file placed in the WebContent folder:

host=irc.freenode.org
nick=ircwidget
realname=Online client
ircClient=RichFaces IRC Client
encoding=utf8
channel=#richfaces
autoNickMode=true
verboseMode=false

     Now you can easily change host, channel, nick or something else.

     Let's create a managed bean Settings that provides access to all these properties:

...

public class Settings {
     private String host = new String();
     private String nick = new String();
     private String encoding = new String();
     private String realname = new String();
     private String ircClient = new String();
     private String channel = new String();
     private boolean autoNickMode;
     private boolean verboseMode;

     public Settings() {
          Properties p = new Properties();
          try {
               p.load(FacesContext.getCurrentInstance().getExternalContext()
                         .getResourceAsStream("settings.properties"));
               host = p.getProperty("host");
               nick = p.getProperty("nick");
               encoding = p.getProperty("encoding");
               realname = p.getProperty("realname");
               ircClient = p.getProperty("ircClient");
               channel = p.getProperty("channel");
               autoNickMode = Boolean.parseBoolean(p.getProperty("autoNickMode"));
               verboseMode = Boolean.parseBoolean(p.getProperty("verboseMode"));
          } catch (IOException e) {
               FacesContext.getCurrentInstance().addMessage(
                         null,
                         new FacesMessage(FacesMessage.SEVERITY_ERROR, e
                                   .getMessage(), null));
          }

     }
     /* Getters for all fields */
}

 

 

     Main Bean

     The PircBot package contains an abstract class named PircBot. This is an abstract class, therefore you cannot create an instance of it, but you can extend it and inherit its functionality. Take the following steps to create PircBot implementation:

  1. In the constructor you need to set all necessary parameters to connect the client to the IRC server:
    public Bean() {
              try {
                   Settings settings = new Settings();
                   this.setName(settings.getNick());
                   this.setEncoding(settings.getEncoding());
                   if (settings.getRealname() != null
                             && settings.getRealname().length() != 0) {
                        this.setLogin(settings.getRealname());
                   }
                   this.setVersion(settings.getIrcClient());
                   this.setAutoNickChange(settings.isAutoNickMode());
                   this.setVerbose(settings.isVerboseMode());
    
                   this.connect(settings.getHost());
                   if (this.isConnected()) {
                        this.joinChannel(settings.getChannel());
                   }
              } catch (NickAlreadyInUseException e) {
                   FacesContext.getCurrentInstance().addMessage(
                             null,
                             new FacesMessage(FacesMessage.SEVERITY_ERROR, e
                                       .getMessage(), null));
              } catch (IOException e) {
                   FacesContext.getCurrentInstance().addMessage(
                             null,
                             new FacesMessage(FacesMessage.SEVERITY_ERROR, e
                                       .getMessage(), null));
              } catch (IrcException e) {
                   FacesContext.getCurrentInstance().addMessage(
                             null,
                             new FacesMessage(FacesMessage.SEVERITY_ERROR, e
                                       .getMessage(), null));
              }
         }
    
  2. After that you need to register PushEventListener in the Bean and add addListener() method that accepts a PushEventListener in order to register this listener and send EventObject to this listener on ready:
    PushEventListener listener;
    public void addListener(EventListener listener) {
         if (this.listener != listener) {
              this.listener = (PushEventListener) listener;
         }
    }
    
  3. Finally we need to implement two methods for IRC messages retrieving
    1. The onMessage() method that overrides a method of the same signature in the PircBot abstract class.  This method will be called whenever somebody sends a message to a channel. Notice, that our client runs in a single channel mode, so you need sender and message parameters only.
    2. The sendEvent() method called by onMessage() adds new message to the out property and informs PushEventListener about a new Event.
      private List<String[]> out = new ArrayList<String[]>();
      
      public void sendEvent(String user, String message) {
           out.add(new String[] { user, message });
           listener.onEvent(new EventObject(this));
      }
      
      @Override
      protected void onMessage(String channel, String sender, String login,
                String hostname, String message) {
           sendEvent(sender, message);
      }
      

 

     Here is a full code of Bean.java:

...

public class Bean extends PircBot {

     PushEventListener listener;
     private List<String[]> out = new ArrayList<String[]>();

     public Bean() {
          try {
               Settings settings = new Settings();
               this.setName(settings.getNick());
               this.setEncoding(settings.getEncoding());
               if (settings.getRealname() != null
                         && settings.getRealname().length() != 0) {
                    this.setLogin(settings.getRealname());
               }
               this.setVersion(settings.getIrcClient());
               this.setAutoNickChange(settings.isAutoNickMode());
               this.setVerbose(settings.isVerboseMode());

               this.connect(settings.getHost());
               if (this.isConnected()) {
                    this.joinChannel(settings.getChannel());
               }
          } catch (NickAlreadyInUseException e) {
               FacesContext.getCurrentInstance().addMessage(
                         null,
                         new FacesMessage(FacesMessage.SEVERITY_ERROR, e
                                   .getMessage(), null));
          } catch (IOException e) {
               FacesContext.getCurrentInstance().addMessage(
                         null,
                         new FacesMessage(FacesMessage.SEVERITY_ERROR, e
                                   .getMessage(), null));
          } catch (IrcException e) {
               FacesContext.getCurrentInstance().addMessage(
                         null,
                         new FacesMessage(FacesMessage.SEVERITY_ERROR, e
                                   .getMessage(), null));
          }
     }
     
     public void sendEvent(String user, String message) {
          out.add(new String[] { user, message });
          listener.onEvent(new EventObject(this));
     }
     
     @Override
     protected void onMessage(String channel, String sender, String login,
               String hostname, String message) {
          sendEvent(sender, message);
     }

     public void addListener(EventListener listener) {
          if (this.listener != listener) {
               this.listener = (PushEventListener) listener;
          }
     }

     /* Getters and Setters */

}

 

Front-end (page connection.xhtml)

     According to the sketch above you can use <rich:simpleTogglePanel>, <rich:dataList> to represent chatter data, and <a4j:push> to implement reverse Ajax technique. In the <a4j:push> component you should specify addListener() method:

<a4j:push reRender="irc-widget" eventProducer="#{bean.addListener}"
          interval="3000" />
<rich:messages />
<rich:simpleTogglePanel id="irc-widget" switchType="client">
     <f:facet name="header">
     <h:outputText value="IRC Widget | #{settings.host}, #{settings.channel}" />
     </f:facet>
     <rich:dataList var="out" value="#{bean.out}">
          <span class="ircwidget-user">
               <h:outputText value="#{out[0]}: " />
          </span>
          <span class="ircwidget-message">
               <h:outputText value="#{out[1]}" />
          </span>
     </rich:dataList>
</rich:simpleTogglePanel>

 

Here is a result:

Simple IRC Widget_1248876380839.png