View Part 2 - Server ClassesJ8 Home « View Part 2 - Server Classes

In this lesson we implement the code for when a user has entered the manufacturer application with a run mode of "server".

Create Server ClassesTop

In this part of the lesson we implement the code for when a user has entered the manufacturer application with a run mode of "server". This entails the graphical user interface the user sees when they start the Manufacturer application in "server" mode as well as providing the logic for displaying configurable objects and starting and exiting the server in a safe manner. The ManufacturerServerStartupWindow will display the server window where the user can enter the location of the manufacturer file and port number.

Creating The StartupRmi Class Top

The StartupRmi class starts the server so it can accept connections over RMI.

Create the StartupRmi class in the client package and cut and paste the following code into it.


package client;

import java.rmi.RemoteException;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.JLabel;
import model.StockImpl;

/**
 * Start server to accept connections over RMI.
 */
public class StartupRmi {
    /**
     * The Logger instance through which all log messages from this class are
     * routed. Logger namespace is J8CaseStudy.
     */
    private static Logger log = Logger.getLogger("J8CaseStudy"); // Log output

    /**
     * Error code to be passed back to the operating system if port number provided is invalid.
     */
    public static final int ERR_CODE_INVALID_PORT_NUMBER = -1;

    /**
     * Error code to be passed back to the operating system if registry cannot be started.
     */
    public static final int ERR_CODE_CANT_START_REGISTRY = -2;

    /*
     * Strings that appear in log messages and in the status bar.
     */
    private static final String CANT_START_REGISTRY = "Unable to start the RMI " +
            "registry, which is required in order to run the server.\nPerhaps" +
            " the port is already in use?";
    private static final String INVALID_PORT_NUMBER = "Invalid port number ";
    private static final String SERVER_STARTING = "Starting RMI Registry and " +
            "Registring Server";
    private static final String SERVER_RUNNING = "Server running.";

    /**
     * Our default port - the same as the standard RMI port.
     */
    private int port = java.rmi.registry.Registry.REGISTRY_PORT;

    private StockImpl stock;

    /**
     * Creates a new instance of StartupRMI.
     * 
     * @param fileLocation The location of the Manufacturer file.
     * @param port The port number the RMI registry will listen on.
     * @param status A label to update our status on the server GUI.
     */

    public StartupRmi(String fileLocation, String port, JLabel status) {
        log.entering("StartupRmi", "StartupRmi");
        try {
            // Validate port number.
            this.port = Integer.parseInt(port); 
            log.info("Starting RMI registry on port " + port);
            status.setText(SERVER_STARTING);
            stock = model.StockImpl.getStockImplInstance(fileLocation);
            remoteservices.RemoteServicesImpl.getServices(stock, this.port);
            log.info("Server started.");
            status.setText(SERVER_RUNNING);
            /*
             * Get our saved run mode options and update it now that port number 
             * has been verified.
             */
            SavedRunModeOptions savedRunModeOptions = 
                    SavedRunModeOptions.getSavedRunModeInstance();
            savedRunModeOptions.setParameter(
                    SavedRunModeOptions.FILE_LOCATION,fileLocation);
            savedRunModeOptions.setParameter(SavedRunModeOptions.SERVER_PORT, port);
        } catch (NumberFormatException e) {
            /*
             * The port number should have been verified and numeric from using the 
             * PositiveInteger class but catch here just in case something strange 
             * has happened.
             */
            log.log(Level.SEVERE, INVALID_PORT_NUMBER, e);
            ManufacturerApplicationStartup.handleException("Invalid Port number entered");
        } catch (RemoteException e) {
            // Registry can't be started so notify the user, and exit.
            log.log(Level.SEVERE, CANT_START_REGISTRY, e);
            ManufacturerApplicationStartup.handleException("Error Starting Rmi Registry");
        }
        log.exiting("StartupRmi", "StartupRmi");
    }
}

Creating The ManufacturerServerStartupWindow Class Top

The ManufacturerServerStartupWindow class renders the graphical user interface the user sees when they start the Manufacturer application with a run mode of "server". This classes functionality provides the logic for displaying configurable objects and starting and exiting the server in a safe manner. This is a view element of the MVC paradigm.

Create the ManufacturerServerStartupWindow class in the client package and cut and paste the following code into it.


package client;

import java.awt.*;
import java.awt.event.*;
import java.io.File;
import java.util.Observable;
import java.util.Observer;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.*;
import javax.swing.border.BevelBorder;

/**
 * The graphical user interface the user sees when they start the Manufacturer
 * application with a run mode of "server". This classes functionality provides the 
 * logic for displaying configurable objects and starting and exiting the server 
 * in a safe manner. This is a view element of the MVC paradigm.
 * 
 * The class is invoked via the following mode: 
 * "server" Start application in server mode.
 * 
 * @author Charlie 
 * @version 1.0
 */
public class ManufacturerServerStartupWindow extends JFrame implements Observer  {
    /**
     * The Logger instance through which all log messages from this class are
     * routed. Logger namespace is J8CaseStudy.
     */
    private Logger log = Logger.getLogger("J8CaseStudy"); // Log output

    /**
     * A version number for the ManufacturerServerStartupWindow class so that serialisation 
     * can occur without worrying about the underlying class changing between
     * serialisation and deserialisation.
     */
    private static final long serialVersionUID = 2498052502L;
 
    private static final String START_BUTTON_TEXT = "Start Server";
    private static final String EXIT_BUTTON_TEXT = "Exit Server";
    private static final String START_BUTTON_TOOL_TIP = "Start server using this configuration";
    private static final String EXIT_BUTTON_TOOL_TIP = "Stop server safely";
    private static final String INITIAL_STATUS
            = "Enter server configuration parameters and click \""
            + START_BUTTON_TEXT + "\"";

    /*
     * Some values for possible port ranges so we can determine what sort of
     * port the user has specified.
     */
    private static final int LOWEST_PORT = 0;
    private static final int HIGHEST_PORT = 65535;
    private static final int SYSTEM_PORT_BOUNDARY = 1024;
 
    /*
     * Flags to show whether enough information has been provided for us to
     * start the application.
     */
    private boolean validFileLocation = false;
    private boolean validPort = true;

    /*
     * Details specified in the server window pane detailing where the database is.
     */
    private String databaseLocation = null;
    private String port = null;

    // User modifiable fields and buttons.
    private RunModeOptions RunModeOptionsPanel = new RunModeOptions(RunMode.SERVER);
    private JButton startServerButton = new JButton(START_BUTTON_TEXT);
    private JButton exitButton = new JButton(EXIT_BUTTON_TEXT);
    private JLabel status = new JLabel();

    /**
     * Constructor that creates the server window frame and loads up saved 
     * Manufacturer file location and port settings if they are set or a default port 
     * number if this is the first time we have used the program.
     *
     */
    public ManufacturerServerStartupWindow() {
		super("Stocking Goods Limited: Manufacturer File Server Startup");
		log.entering("ManufacturerServerStartupWindow", "ManufacturerServerStartupWindow");
        this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        this.setResizable(false);

        RunModeOptionsPanel.getObservable().addObserver(this);
        this.add(RunModeOptionsPanel, BorderLayout.NORTH);
        this.add(startOrExitServerPanel(), BorderLayout.CENTER);

        status.setBorder(BorderFactory.createBevelBorder(BevelBorder.LOWERED));
        JPanel statusPanel = new JPanel(new BorderLayout());
        statusPanel.add(status, BorderLayout.CENTER);
        this.add(statusPanel, BorderLayout.SOUTH);
        // Load saved configuration
        SavedRunModeOptions savedRunModeOptions = 
                SavedRunModeOptions.getSavedRunModeInstance();
        // If no default Manufacturer file location, validate before using the returned value.
        String databaseLocation =
                savedRunModeOptions.getParameter(SavedRunModeOptions.FILE_LOCATION);
        if (databaseLocation == null) {
            RunModeOptionsPanel.setLocationFieldText("");
        } else {
            RunModeOptionsPanel.setLocationFieldText(databaseLocation);
            startServerButton.setEnabled(true);
        }
        // There is always a default port number, so we don't have to validate this.
        RunModeOptionsPanel.setPortNumberText(
                savedRunModeOptions.getParameter(SavedRunModeOptions.SERVER_PORT));
        status.setText(INITIAL_STATUS);
        this.pack();
        // Centre on screen
        Dimension d = Toolkit.getDefaultToolkit().getScreenSize();
        int x = (int) ((d.getWidth() - this.getWidth()) / 2);
        int y = (int) ((d.getHeight() - this.getHeight()) / 2);
        this.setLocation(x, y);
        this.setVisible(true);
    }

    /**
     * Panel providing buttons that a user may click to start the server or 
     * exit the application.
     *
     * @return a panel containing the mode change buttons.
     */
    private JPanel startOrExitServerPanel() {
            log.entering("ManufacturerServerStartupWindow", "startOrExitServerPanel");
        JPanel startServerPanel = new JPanel();
        startServerPanel.setLayout(new FlowLayout(FlowLayout.CENTER));
        // Start server button action listener.
        startServerButton.addActionListener(new StartServer());
        startServerButton.setEnabled(false);
        startServerButton.setToolTipText(START_BUTTON_TOOL_TIP);
        startServerPanel.add(startServerButton);
        // Anonymous action listener for exit button.
        exitButton.addActionListener(new ExitServer());
        exitButton.setToolTipText(EXIT_BUTTON_TOOL_TIP);
        startServerPanel.add(exitButton);
        log.entering("ManufacturerServerStartupWindow", "startOrExitServerPanel", 
                startServerPanel);
        return startServerPanel;
    }

    /**
     * Class that gets called if the "Start Server" button has been clicked. 
     * It will extract the Manufacturer file location and port number
     * information from the run mode options panel. Any buttons that should 
     * no longer be clicked upon will be disabled and StartupRmi invoked to 
     * set up the RMI server stub. 
     * 
     */
    private class StartServer implements ActionListener {
        /** {@inheritDoc} */
        public void actionPerformed(ActionEvent ae) {
            boolean validPortLocation = validFileLocation && validPort;
            if (validPortLocation == false) {
            	return;
            }
        	RunModeOptionsPanel.setLocationFieldEnabled(false);
        	RunModeOptionsPanel.setBrowseButtonEnabled(false);
        	RunModeOptionsPanel.setPortNumberEnabled(false);

            startServerButton.setEnabled(false);

            String databaseLocation = RunModeOptionsPanel.getLocationFieldText();
            String port = RunModeOptionsPanel.getPortNumberText();
  
            Thread exitRoutine = new ExitManufacturerFileCleanly(databaseLocation);
            Runtime.getRuntime().addShutdownHook(exitRoutine);
 
            new StartupRmi(databaseLocation, port, status);
        }
    }

    /**
     * Class that gets called if the "Exit Server" has been clicked. 
     * It will do a system exit. 
     * 
     */
    private class ExitServer implements ActionListener {
        /** {@inheritDoc} */
        public void actionPerformed(ActionEvent ae) {
            System.exit(0);
        }
    }

    /**
     * Callback method to process modifications in the RunModeOptions panel. 
     * RunModeOptions sends updates to registered Observers whenever
     * anything changes. When something changes we validate here and if
     * changes are valid we enable the "Connect" button of the dialog box.
     */
    public void update(Observable o, Object arg) {
        log.entering("ManufacturerServerStartupWindow", "update", arg);
        /*
         * we are going to ignore the Observable object, since we are only
         * observing one object. All we are interested in is the argument.
         */  
        if (!(arg instanceof RunModeOptionsUpdate)) {
            log.log(Level.WARNING, "Run Mode Dialog received update type: " 
            		+ arg, new IllegalArgumentException());
            return;
        }
        RunModeOptionsUpdate runModeOptionsUpdate = (RunModeOptionsUpdate) arg;
        // Load saved configuration
        SavedRunModeOptions savedRunModeOptions = 
                SavedRunModeOptions.getSavedRunModeInstance();

        // Process changes
        switch (runModeOptionsUpdate.getUpdateType()) {
            case FILE_LOCATION_MODIFIED:
            	databaseLocation = (String) runModeOptionsUpdate.getPayload();
                File f = new File(databaseLocation);
                if (f.exists() && f.canRead() && f.canWrite()) {
                    validFileLocation = true;
                    log.info("File chosen " + databaseLocation);
                    savedRunModeOptions.setParameter
                      		(SavedRunModeOptions.FILE_LOCATION, 
                      				databaseLocation);
                } else {
                    log.warning("Invalid file " + databaseLocation);
                } 
                break;
            case PORT_MODIFIED:
                port = (String) runModeOptionsUpdate.getPayload();
                int p = Integer.parseInt(port);
                // Log an info message for port numbers
                if (p >= LOWEST_PORT && p <= HIGHEST_PORT) {
                    if (p < SYSTEM_PORT_BOUNDARY) {
                        log.info("User chose System port " + port);
                    } else {
                        log.info("User chose dynamic port " + port);
                    }
                    validPort = true;
                    savedRunModeOptions.setParameter(SavedRunModeOptions.SERVER_PORT, port);
                } else {
                    validPort = false;
                    log.warning("Port number: " + port + " is invalid. Please " +
                    		"choose a port in the range 0 - 65535.");
                }
                break;
            default:
                log.warning("Unknown update: " + runModeOptionsUpdate);
                break;
        }
        startServerButton.setEnabled(validFileLocation);
        log.exiting("ManufacturerServerStartupWindow", "update");
    }
}

The following screenshot shows the client package structure after adding the StartupRmi and ManufacturerServerStartupWindow classes.

Server Startup
Screenshot 1. The client package after adding the ServicesImpl class.

Lesson 16 Complete

In this lesson we coded the View elements of the MVC pattern that relate to the server.

Related Java Tutorials

Fundamentals - Primitive Variables
Fundamentals - if Construct
Fundamentals - switch Construct
Objects & Classes - Arrays
Objects & Classes - Class Structure and Syntax
Objects & Classes - Reference Variables
Objects & Classes - Methods
Objects & Classes - Instance Variables & Scope
Objects & Classes - Constructors
Objects & Classes - Static Members
Objects & Classes - Enumerations
OO Concepts - Encapsulation
OO Concepts - Inheritance Concepts - Using the super keyword
Exceptions - Handling Exceptions
API Contents - Inheritance - Using the package keyword
API Contents - Inheritance - Using the import keyword
API Contents - Java I/O Overview - The java.io.File Class
Swing - Containers - The javax.swing.JFrame Class
Swing - Containers - The javax.swing.JPanel Class
Swing - Event Handling - Using the ActionListener Interface
Swing - Dialogs
Swing - Components

Swing - RMI - Serialization

What's Next?

In the next section we finish coding the View elements of the MVC pattern for our case study by creating the manufacturer window.