Friday, March 2, 2012

JSF and Swing Part 1 - Overview

Overview
Typically, your web application runs as a n-tiered application, with the presentation tier being the web browser, the business tier as the application server, and the data tier as the database server.  A desktop application will run the presentation tier, possibly the business tier, and possibly the data tier.  To illustrate this, think of a game as a desktop application, and a web application.  There are, of course, other models, but we will focus on how we create a desktop application where the presentation tier is replaceable with a web based layer.

The Business Problem
Let us look at a common problem many business applications must solve, the Observer Design Pattern.  We have a process or business logic component that will report back to the client when some status has changed.

The Design Implications
First, we can see the need to decouple the presentation layer is a must.  In the real world, the separation is not always enforced or complete, but by adhering to a design to interface paradigm, we will achieve complete decoupling - well, almost complete, we still need the instance.  (Note: even this minor coupling will be decoupled when we move to an injection model.)

The application:
We have Start and End Monitor buttons that will initiate or stop the listening of some process that will send us messages.  We have Start and End Process buttons that will start or stop the process that will send us messages.  And we have the message area where the 'WARNING : 72' is changing based on the messages sent from the process.

The Basic Swing Solution:
We have a single frame application with a menu bar that has buttons to turn on and off the monitoring, and on and off the process we are going to monitor.  We could have used a toggle button to turn on and off the monitoring, but I chose to display distinct buttons for each function.  The process could be any process, including a remote process.  For demonstration purposes it is just a thread we start and stop with the Start and End Process buttons.

MainFrame.java

package com.sample.swing;
import com.sample.observe.ObservableInterface;
import com.sample.observe.ObservableSingleton;
import com.sample.swing.components.ObserverComponent;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JMenuBar;
import javax.swing.JPanel;
public class MainFrame extends JFrame {
    JButton startMonitorButton = new JButton("Start Monitor");
    JButton endMonitorButton = new JButton("End Monitor");
    JButton startProcessButton = new JButton("Start Process");
    JButton endProcessButton = new JButton("End Process");
    ObservableInterface processToMonitor = ObservableSingleton.MONITOR_PROCESS_ONE;
    ObserverComponent status = new ObserverComponent(startMonitorButton, endMonitorButton, processToMonitor);
    private javax.swing.JPanel viewPanel;
    private javax.swing.JPanel mainPanel;
    private javax.swing.JMenuBar menuBar;
    public MainFrame() {
        super("My Main Frame");
        initComponents();
    }
    private void initComponents() {
        startMonitorButton.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                MainFrame.this.startMonitorButton.setEnabled(false);
                MainFrame.this.endMonitorButton.setEnabled(true);
                processToMonitor.registerListener(MainFrame.this.status);
            }
        });
        endMonitorButton.setEnabled(false);
        endMonitorButton.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                MainFrame.this.startMonitorButton.setEnabled(true);
                MainFrame.this.endMonitorButton.setEnabled(false);
                processToMonitor.removeListener(MainFrame.this.status);
            }
        });
        startProcessButton.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                MainFrame.this.startProcessButton.setEnabled(false);
                MainFrame.this.endProcessButton.setEnabled(true);
                processToMonitor.startProcess();
            }
        });
        endProcessButton.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                MainFrame.this.startProcessButton.setEnabled(true);
                MainFrame.this.endProcessButton.setEnabled(false);
                processToMonitor.stopProcess();
            }
        });
        endProcessButton.setEnabled(false);
        mainPanel = new JPanel();
        mainPanel.setName("MainPanel");
        mainPanel.add(status);
        menuBar = new JMenuBar();
        menuBar.setName("menuBar");
        menuBar.add(startMonitorButton);
        menuBar.add(endMonitorButton);
        menuBar.add(startProcessButton);
        menuBar.add(endProcessButton);
        this.add(mainPanel);
        this.setJMenuBar(menuBar);
        this.setDefaultCloseOperation(EXIT_ON_CLOSE);
    }
}

SampleSwingApp.java

package com.sample.swing;
import javax.swing.JFrame;
import org.jdesktop.application.SingleFrameApplication;
public class SampleSwingApp extends SingleFrameApplication {
    /**
     * At startup create and show the main frame of the application.
     */
    @Override protected void startup() {
        JFrame mainFrame = new MainFrame();
        this.setMainFrame(mainFrame);
        this.show(mainFrame);
    }
    /**
     * Main method launching the application.
     */
    public static void main(String[] args) {
        launch(SampleSwingApp.class, args);
    }
}

ObserverComponent.java

package com.sample.swing.components;
import com.sample.observe.ObservableInterface;
import com.sample.observe.ObserverInterface;
import javax.swing.JLabel;
import javax.swing.SwingUtilities;
public class ObserverComponent extends JLabel implements ObserverInterface {
    ObservableInterface processToMonitor;
    public ObserverComponent(ObservableInterface processToMonitor) {
        super("Invalid Status");
        this.processToMonitor = processToMonitor;
        setText(message(processToMonitor));
    }
    private String message(ObservableInterface status) {
        StringBuilder value = new StringBuilder(status.getStatus().toString());
        value.append(" : ");
        value.append(status.getMessage());
        return value.toString();
    }
    public synchronized void updateListener(ObservableInterface status) {
        final String message = message(status);
        SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                setText(message);
            }
        });
    }
}

The interfaces:
ObserverInterface.java

package com.sample.observe;
public interface ObserverInterface {
    void updateListener(ObservableInterface status); // called when a change has occurred on the monitored item/process
}

ObservableInterface.java

package com.sample.observe;
public interface ObservableInterface {
    public void setMessage(ObservedStatus status, String message); // sets the current status and message
    public String getMessage(); // returns a message sent with the status
    public ObservedStatus getStatus(); // returns the current status
    public void registerListener(ObserverInterface listener); // adds a listener
    public void removeListener(ObserverInterface listener); // removes a listener
    public void notifyListeners(); // notifies all listeners of a status change
    public void startProcess(); // starts the process to be monitored
    public void stopProcess(); // stops the process to be monitored
}

We can always add additional features like isProcessRunning, countObservers, etc., but here we have a basic observer/observable pattern.

Here we have our presentation layer.  Note: the only tight coupling from our presentation layer to any other layer is the assignment  processToMonitor = ObservableSingleton.MONITOR_PROCESS_ONE;.  Although this is a simple app, I chose the observer pattern to demonstrate a complex issue.  From this issue, you can see how you can decouple your entire view, no matter how complex, from the other layers.

We have 3 basic classes here.  The first class is the application SampleSwingApp, where main resides.  This simply launches the app.  The second class MainFrame sets up the view by creating a menu bar with 4 buttons, and the display panel for watching the process.  The third class ObserverComponent extends JLabel and is the component put in MainFrame's panel to report the changes coming from the monitored process.  Note: this is an event driven model, we do not pull the status of the process, we register a listener, and when a change occurs, we are notified.

There are distinct events that occur in the observer design pattern:
   1:  The process to monitor is started.
   2:  The process to monitor is stopped.
   3:  A listener or listeners are registered.
   4:  A listener is deactivated and removed from listening
   5:  Listeners are notified of status changes.

Because multiple threads are running, the order and timing of the above events are undefined.  A listener can be registered before or after a process is started.  A listener can be deactivated before, after, or during a process running.  A process can be started or stopped at any time.  And so, we need to ensure thread safety, i.e. we do not notify a listener that no longer wants to be notified, we receive a proper status, we notify all processes appropriately, etc.

The MainFrame sets up an action event for each button.  These actions register or unregister the listener, start or stop the process, and enable and disable the appropriate buttons.

The ObserverComponent simply implements the updateListener method that the Observable class will call.  Now this method has two notable items: it is not on the Swing thread, therefor, it needs to use the SwingUtilities.invokeLater.  And we don't want to be called twice whereby we update the status with the 1st status after we have updated the status with the 2nd status, therefor, we synchronize the method.

1 comment: