com.lfv.lanzius.server.LanziusServer.java Source code

Java tutorial

Introduction

Here is the source code for com.lfv.lanzius.server.LanziusServer.java

Source

/** 
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package com.lfv.lanzius.server;

import java.awt.GraphicsEnvironment;
import java.awt.Rectangle;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.Writer;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.Timer;
import java.util.TimerTask;
import java.util.TreeMap;
import java.util.TreeSet;
import java.util.Vector;

import javax.swing.JCheckBoxMenuItem;
import javax.swing.JDialog;
import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.filechooser.FileFilter;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.velocity.app.Velocity;
import org.jdom.Attribute;
import org.jdom.DataConversionException;
import org.jdom.Document;
import org.jdom.Element;
import org.jdom.input.SAXBuilder;
import org.jdom.output.Format;
import org.jdom.output.XMLOutputter;
import org.mortbay.jetty.Connector;
import org.mortbay.jetty.Handler;
import org.mortbay.jetty.Server;
import org.mortbay.jetty.handler.ContextHandler;
import org.mortbay.jetty.nio.SelectChannelConnector;

import com.lfv.lanzius.Config;
import com.lfv.lanzius.Constants;
import com.lfv.lanzius.DomTools;
import com.lfv.yada.data.server.ServerBundle;
import com.lfv.yada.data.server.ServerChannel;
import com.lfv.yada.data.server.ServerTerminal;
import com.lfv.yada.net.Packet;
import com.lfv.yada.net.server.ServerLogger;
import com.lfv.yada.net.server.ServerNetworkHandler;
import com.lfv.yada.net.server.ServerNetworkManager;
import com.lfv.yada.net.server.ServerTranslator;

public class LanziusServer implements ActionListener, ServerNetworkHandler, ServerTranslator, Constants {

    private Log log;

    // Graphical
    private JFrame frame;
    private WorkspacePanel panel;

    // Document
    private Document doc;
    private int docVersion;

    // Control
    private boolean isConfigLoaded;
    private Date serverStartedDate;
    private boolean isSwapping;
    private Element swapPlayer1;
    private Element swapPlayer2;

    // Monitor
    private int monitoredTerminalId;
    private int monitoringTerminalId;
    private int monitorChannelId;

    // Logging
    private boolean logEvents;
    private String logPath;
    private Map<Integer, ServerLogger> loggerMap;

    // Network
    private Server httpServer;
    private Connector httpConnector;
    private ServerNetworkManager networkManager;

    // Date
    private ServerBundle bundle;

    // Menu items
    private JMenuItem itemLoadConfig;
    private JMenuItem itemLoadExercise;
    private JMenuItem itemExit;

    private JMenuItem itemServerStart;
    private JMenuItem itemServerStop;
    private JMenuItem itemServerRestart;
    private JCheckBoxMenuItem itemServerMonitor;

    private JMenuItem itemTerminalLink;
    private JMenuItem itemTerminalUnlink;
    private JMenuItem itemTerminalUnlinkAll;
    private JMenuItem itemTerminalSwap;

    private JMenuItem itemGroupStart;
    private JMenuItem itemGroupPause;
    private JMenuItem itemGroupStop;

    private final int ALL = -1;

    private final int ATTR_ID = 0x01;

    private final int RESULT_LINK_OK = 0;
    private final int RESULT_LINK_ERROR_PLAYER_LINKED = 1 << ID_BITSHIFT;
    private final int RESULT_LINK_ERROR_ROLE_LINKED = 2 << ID_BITSHIFT;
    private final int RESULT_LINK_ERROR_TERMINAL_LINKED = -1;
    private final int RESULT_LINK_ERROR_NO_PLAYERS = -2;
    private final int RESULT_LINK_ERROR_NOT_ENOUGH_TERMINALS = -3;

    // ISA
    private long isaStartTime;
    private String isaTracePainter = "line";
    private HashSet<Integer> isaClients;
    private int isaPeriod;
    private int isaNumChoices = 6;
    private String[] isakeytext = { "1", "2", "3", "4", "5", "6", "7", "8", "9" };
    private boolean isaExtendedMode;

    private Timer networkStatusTimer = null;

    private File exerciseFile;

    private Properties serverProperties;

    public LanziusServer() {
        // Create a logger for the server
        log = LogFactory.getLog(getClass());
    }

    public void init() {

        log.info(Config.VERSION + "\n");

        docVersion = 0;

        frame = new JFrame(Config.TITLE + " - Server Control Panel");

        frame.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
        frame.addWindowListener(new WindowAdapter() {
            @Override
            public void windowClosing(WindowEvent e) {
                actionPerformed(new ActionEvent(itemExit, 0, null));
            }
        });

        // Create graphical terminal view
        panel = new WorkspacePanel(this);
        frame.getContentPane().add(panel);

        // Create a menu bar
        JMenuBar menuBar = new JMenuBar();

        // FILE
        JMenu fileMenu = new JMenu("File");
        fileMenu.setMnemonic(KeyEvent.VK_F);
        // Load configuration
        itemLoadConfig = new JMenuItem("Load configuration...");
        itemLoadConfig.addActionListener(this);
        fileMenu.add(itemLoadConfig);
        // Load terminal setup
        itemLoadExercise = new JMenuItem("Load exercise...");
        itemLoadExercise.addActionListener(this);
        fileMenu.add(itemLoadExercise);
        fileMenu.addSeparator();
        // Exit
        itemExit = new JMenuItem("Exit");
        itemExit.addActionListener(this);
        fileMenu.add(itemExit);
        menuBar.add(fileMenu);

        // SERVER
        JMenu serverMenu = new JMenu("Server");
        serverMenu.setMnemonic(KeyEvent.VK_S);
        // Start
        itemServerStart = new JMenuItem("Start");
        itemServerStart.addActionListener(this);
        serverMenu.add(itemServerStart);
        // Stop
        itemServerStop = new JMenuItem("Stop");
        itemServerStop.addActionListener(this);
        serverMenu.add(itemServerStop);
        // Restart
        itemServerRestart = new JMenuItem("Restart");
        itemServerRestart.addActionListener(this);
        itemServerRestart.setEnabled(false);
        serverMenu.add(itemServerRestart);
        // Monitor network connection
        itemServerMonitor = new JCheckBoxMenuItem("Monitor network");
        itemServerMonitor.addActionListener(this);
        itemServerMonitor.setState(false);
        serverMenu.add(itemServerMonitor);
        menuBar.add(serverMenu);

        // TERMINAL
        JMenu terminalMenu = new JMenu("Terminal");
        terminalMenu.setMnemonic(KeyEvent.VK_T);
        itemTerminalLink = new JMenuItem("Link...");
        itemTerminalLink.addActionListener(this);
        terminalMenu.add(itemTerminalLink);
        itemTerminalUnlink = new JMenuItem("Unlink...");
        itemTerminalUnlink.addActionListener(this);
        terminalMenu.add(itemTerminalUnlink);
        itemTerminalUnlinkAll = new JMenuItem("Unlink All");
        itemTerminalUnlinkAll.addActionListener(this);
        terminalMenu.add(itemTerminalUnlinkAll);
        itemTerminalSwap = new JMenuItem("Swap...");
        itemTerminalSwap.addActionListener(this);
        terminalMenu.add(itemTerminalSwap);
        menuBar.add(terminalMenu);

        // GROUP
        JMenu groupMenu = new JMenu("Group");
        groupMenu.setMnemonic(KeyEvent.VK_G);
        itemGroupStart = new JMenuItem("Start...");
        itemGroupStart.addActionListener(this);
        groupMenu.add(itemGroupStart);
        itemGroupPause = new JMenuItem("Pause...");
        itemGroupPause.addActionListener(this);
        groupMenu.add(itemGroupPause);
        itemGroupStop = new JMenuItem("Stop...");
        itemGroupStop.addActionListener(this);
        groupMenu.add(itemGroupStop);
        menuBar.add(groupMenu);

        frame.setJMenuBar(menuBar);

        GraphicsEnvironment graphicsEnvironment = GraphicsEnvironment.getLocalGraphicsEnvironment();
        Rectangle maximumWindowBounds = graphicsEnvironment.getMaximumWindowBounds();

        if (Config.SERVER_SIZE_FULLSCREEN) {
            maximumWindowBounds.setLocation(0, 0);
            maximumWindowBounds.setSize(Toolkit.getDefaultToolkit().getScreenSize());
            frame.setResizable(false);
            frame.setUndecorated(true);
        } else if (Config.SERVER_SIZE_100P_WINDOW) {
            // Fixes a bug in linux using gnome. With the line below the upper and
            // lower bars are respected
            maximumWindowBounds.height -= 1;
        } else if (Config.SERVER_SIZE_75P_WINDOW) {
            maximumWindowBounds.width *= 0.75;
            maximumWindowBounds.height *= 0.75;
        } else if (Config.SERVER_SIZE_50P_WINDOW) {
            maximumWindowBounds.width /= 2;
            maximumWindowBounds.height /= 2;
        }

        frame.setBounds(maximumWindowBounds);
        frame.setVisible(true);

        log.info("Starting control panel");

        // Autostart for debugging
        if (Config.SERVER_AUTOLOAD_CONFIGURATION != null)
            actionPerformed(new ActionEvent(itemLoadConfig, 0, null));

        if (Config.SERVER_AUTOSTART_SERVER)
            actionPerformed(new ActionEvent(itemServerStart, 0, null));

        if (Config.SERVER_AUTOLOAD_EXERCISE != null)
            actionPerformed(new ActionEvent(itemLoadExercise, 0, null));

        if (Config.SERVER_AUTOSTART_GROUP > 0)
            actionPerformed(new ActionEvent(itemGroupStart, 0, null));

        try {
            // Read the property files
            serverProperties = new Properties();
            serverProperties.loadFromXML(new FileInputStream("data/properties/serverproperties.xml"));
            int rcPort = Integer.parseInt(serverProperties.getProperty("RemoteControlPort", "0"));
            if (rcPort > 0) {
                groupRemoteControlListener(rcPort);
            }
            isaPeriod = Integer.parseInt(serverProperties.getProperty("ISAPeriod", "60"));
            isaNumChoices = Integer.parseInt(serverProperties.getProperty("ISANumChoices", "6"));
            for (int i = 0; i < 9; i++) {
                String tag = "ISAKeyText" + Integer.toString(i);
                String def_val = Integer.toString(i + 1);
                isakeytext[i] = serverProperties.getProperty(tag, def_val);
            }
            isaExtendedMode = serverProperties.getProperty("ISAExtendedMode", "false").equalsIgnoreCase("true");
        } catch (Exception e) {
            log.error("Unable to start remote control listener");
            log.error(e.getMessage());
        }
        isaClients = new HashSet<Integer>();
    }

    private void groupRemoteControlListener(final int port) {
        new Thread("RemoteControlThread") {
            public void run() {
                try {
                    ServerSocket serverSocket = new ServerSocket(port);
                    Socket socket = null;
                    boolean listening = true;
                    String inputLine;
                    while (listening) {
                        socket = serverSocket.accept();
                        BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
                        inputLine = in.readLine();
                        log.debug("Remote control thread received: " + inputLine);
                        try {
                            String arglist[];
                            arglist = inputLine.split(" ");
                            String command = arglist[0];

                            if (command.equals("play")) {
                                String newtime;
                                String comment;

                                if (arglist.length >= 2)
                                    newtime = arglist[1];
                                else
                                    newtime = null;
                                menuChoiceGroupStart(1, false, newtime);
                                if (arglist.length >= 3)
                                    comment = arglist[2];
                                else
                                    comment = null;

                                if (comment != null) {
                                    ServerLogger logger = loggerMap.get(1);
                                    if (logger != null) {
                                        logger.print(comment);
                                    }
                                }
                            } else if (command.equals("stop")) {
                                menuChoiceGroupStop(1, false);
                            } else if (command.equals("pause")) {
                                String comment;
                                if (arglist.length >= 2)
                                    comment = arglist[1];
                                else
                                    comment = null;
                                if (comment != null) {
                                    ServerLogger logger = loggerMap.get(1);
                                    if (logger != null) {
                                        logger.print(comment);
                                    }
                                }
                                menuChoiceGroupPause(1);
                            } else if (inputLine.startsWith("logpath")) {
                                String newlogPath;
                                newlogPath = inputLine.substring(inputLine.indexOf(' ') + 1);
                                ServerLogger logger = loggerMap.get(1);
                                if (logger != null) {
                                    log.debug("ServerLogger.logpath=" + logger.getLogPath() + " new path="
                                            + newlogPath);
                                    if (newlogPath != logger.getLogPath()) {
                                        log.debug("ServerLogger.logpath changed!!!!");

                                        loggerMap.remove(1);
                                        logger.print("GROUP STOP id[1]");
                                        logger.close();
                                        ServerLogger newlogger = new ServerLogger(1, newlogPath);
                                        loggerMap.put(1, newlogger);
                                        newlogger.print("GROUP START id[1]");
                                    }
                                } else {
                                    log.debug("No active logger for group 1");
                                }

                                if ((logPath != null) && (!(newlogPath.equals(logPath)))) {
                                    log.debug("logPath changed, stopping Groups");
                                }
                                logPath = newlogPath;
                                log.debug("logPath set to " + logPath);
                            }
                        } catch (Exception e) {
                            log.debug("Remote control action failed");
                        }
                        socket.close();
                    }
                    serverSocket.close();
                } catch (IOException e) {
                    log.error("Failed to start Remote Control listener thread!");
                    log.error(e.getMessage());
                }
            }
        }.start();
        log.info("Group remote control listening on port " + port + ".");
    }

    public void updateView() {
        if (doc != null) {
            synchronized (doc) {
                docVersion++;
            }
        } else {
            docVersion++;
        }
        panel.repaint();
    }

    private void updateTerminals(int terminalId, int groupId) {
        if (doc == null)
            return;
        synchronized (doc) {
            // Go through all terminals
            Iterator iter = doc.getRootElement().getChild("TerminalDefs").getChildren().iterator();
            while (iter.hasNext()) {
                Element et = (Element) iter.next();

                // Send messages only if terminal is online
                if (DomTools.getAttributeBoolean(et, "online", false, false)) {
                    int tid = DomTools.getAttributeInt(et, "id", 0, true);

                    // Bother about this terminal?
                    if (terminalId == ALL || terminalId == tid) {

                        // Get linked player
                        Element eps = DomTools.getElementFromSection(doc, "PlayerSetup", "terminalid",
                                String.valueOf(tid));

                        // Player is not linked to the terminal, send stop packet
                        if (eps == null) {
                            log.info("Sending stop to terminal " + tid);
                            networkManager.sendSessionRequestStop(tid);
                        } else {
                            // Get group id
                            int gid = DomTools.getAttributeInt(eps, "groupid", 0, true);

                            // Bother about this group?
                            if (groupId == ALL || groupId == gid) {
                                // Get group state
                                Element eg = DomTools.getElementFromSection(doc, "GroupDefs", "id",
                                        String.valueOf(gid));
                                String state = DomTools.getAttributeString(eg, "state", "stopped", false);
                                if (state.equals("started")) {
                                    // send start packet
                                    log.info("Sending start to terminal " + tid);
                                    networkManager.sendSessionRequestStart(tid);
                                } else if (state.equals("stopped")) {
                                    // send stop packet
                                    log.info("Sending stop to terminal " + tid);
                                    networkManager.sendSessionRequestStop(tid);
                                }
                            }
                        }
                    }
                }
            }
        }
    }

    private void setTerminalFlag(int terminalId, String flag, boolean value) {
        synchronized (doc) {
            Element et = DomTools.getElementFromSection(doc, "TerminalDefs", "id", String.valueOf(terminalId));
            if (et != null)
                et.setAttribute(flag, String.valueOf(value));
            else
                log.error("Terminal " + terminalId + " does not exist!");
        }
    }

    private boolean getTerminalFlag(int terminalId, String flag) {
        boolean value = false;
        synchronized (doc) {
            Element et = DomTools.getElementFromSection(doc, "TerminalDefs", "id", String.valueOf(terminalId));
            if (et != null)
                value = DomTools.getAttributeBoolean(et, flag, false, false);
            else
                log.error("Terminal " + terminalId + " does not exist!");
        }
        return value;
    }

    private void sendMonitorPacket(int attribute) {

        ServerTerminal monitoredTerminal = bundle.getTerminal(monitoredTerminalId);
        ServerTerminal monitoringTerminal = bundle.getTerminal(monitoringTerminalId);

        if ((monitoredTerminal == null) || (monitoringTerminal == null)) {
            log.error("Unable to send monitor packet, terminal does not exist!");
            return;
        }

        Packet p = null;

        // Send from monitored to monitoring
        if ((attribute == Packet.ATTR_MONITOR_SINK_START) || (attribute == Packet.ATTR_MONITOR_SINK_STOP)) {
            // Check online status
            if (getTerminalFlag(monitoringTerminalId, "online")) {
                log.debug("Monitor - Sending from monitored to monitoring");
                p = networkManager.prepareInfoPacket(monitoringTerminal, monitoredTerminal);
            }
        }

        // Send from monitoring to monitored
        else {
            // Check online status
            if (getTerminalFlag(monitoredTerminalId, "online")) {
                log.debug("Monitor - Sending from monitoring to monitored");
                p = networkManager.prepareInfoPacket(monitoredTerminal, monitoringTerminal);
            }
        }

        // Send packet
        if (p != null) {
            p.addAttributeBool(attribute);
            p.addAttributeInt(Packet.ATTR_CHANNEL, monitorChannelId);
            networkManager.sendInfoPacket(p);
        }
    }

    private void monitorStart() {
        if (monitoredTerminalId == 0 || monitoringTerminalId == 0)
            return;
        if (log.isDebugEnabled())
            log.debug("Monitor - Start  monitored=" + monitoredTerminalId + ", monitoring=" + monitoringTerminalId
                    + ", channel=" + monitorChannelId);
        sendMonitorPacket(Packet.ATTR_MONITOR_SINK_START);
        sendMonitorPacket(Packet.ATTR_MONITOR_SOURCE_START);
    }

    private void monitorStop() {
        if (monitoredTerminalId == 0 || monitoringTerminalId == 0)
            return;
        if (log.isDebugEnabled())
            log.debug("Monitor - Stop   monitored=" + monitoredTerminalId + ", monitoring=" + monitoringTerminalId
                    + ", channel=" + monitorChannelId);
        sendMonitorPacket(Packet.ATTR_MONITOR_SOURCE_STOP);
        sendMonitorPacket(Packet.ATTR_MONITOR_SINK_STOP);
    }

    private boolean buildConfigurationDocument(File file) {
        try {
            SAXBuilder saxb = new SAXBuilder();
            Document newdoc = saxb.build(file);
            if (newdoc == null)
                return false;

            // Quick validate
            Element r = newdoc.getRootElement();
            if (r == null)
                return false;
            if (!r.getName().equals("Configuration"))
                return false;
            if (!validateSection(r.getChild("TerminalDefs"), "Terminal", ATTR_ID))
                return false;
            if (!validateSection(r.getChild("GroupDefs"), "Group", ATTR_ID))
                return false;
            if (!validateSection(r.getChild("ChannelDefs"), "Channel", ATTR_ID))
                return false;
            if (!validateSection(r.getChild("RoleDefs"), "Role", ATTR_ID))
                return false;
            if (!validateSection(r.getChild("PlayerDefs"), "Player", ATTR_ID))
                return false;

            // Document is valid, add PlayerSetup node
            Element eps = new Element("PlayerSetup");
            Element epst = new Element("RecycleBin");
            newdoc.getRootElement().addContent(eps);
            newdoc.getRootElement().addContent(epst);

            doc = newdoc;
            synchronized (doc) {
                docVersion++;
            }

        } catch (Exception ex) {
            return false;
        }
        return true;
    }

    private boolean validateSection(Element sectionElement, String expectedChildName, int attributes) {

        // Check that section exists
        if (sectionElement == null)
            return false;

        // Go through children
        Iterator iter = sectionElement.getChildren().iterator();
        while (iter.hasNext()) {
            Element e = (Element) iter.next();
            // Check that child is expected
            if (!e.getName().equals(expectedChildName))
                return false;
            // Check id attribute for existence, isnumber and range (1-4095)
            if ((attributes | ATTR_ID) != 0) {
                Attribute a = e.getAttribute("id");
                if (a == null)
                    return false;
                try {
                    int value = a.getIntValue();
                    if (value <= 0 || value >= (1 << ID_BITSHIFT))
                        return false;
                } catch (DataConversionException ex) {
                    return false;
                }
            }
        }
        return true;
    }

    private boolean hasStateOr(Element node1, Element node2, String attributeName) {
        if (node1 == null || node2 == null)
            return false;
        return (node1.getAttribute(attributeName) != null) || (node2.getAttribute(attributeName) != null);
    }

    public Document getDocument() {
        return doc;
    }

    public int getDocumentVersion() {
        if (doc != null) {
            synchronized (doc) {
                return docVersion;
            }
        } else {
            return docVersion;
        }
    }

    public JFrame getFrame() {
        return frame;
    }

    public boolean isServerStarted() {
        return (serverStartedDate != null);
    }

    private int linkTerminal(int terminalId, int groupId, int[] playerIdArray, boolean autoRelocate) {

        synchronized (doc) {
            if (playerIdArray == null || playerIdArray.length == 0)
                return RESULT_LINK_ERROR_NO_PLAYERS;

            // Terminal already linked?
            // Find the next free terminal and set relocated flag
            boolean isRelocated = false;
            Element epd = null;
            if (terminalId > 0)
                epd = DomTools.getElementFromSection(doc, "PlayerSetup", "terminalid", String.valueOf(terminalId));
            if (epd != null || terminalId == 0) {
                if (autoRelocate) {
                    // Indicate that no terminal has yet been found
                    terminalId = 0;

                    // Loop through all terminals
                    Iterator iter = doc.getRootElement().getChild("TerminalDefs").getChildren().iterator();
                    while (iter.hasNext()) {
                        Element et = (Element) iter.next();
                        String tids = et.getAttributeValue("id");

                        // Is this terminal available?
                        if (DomTools.getElementFromSection(doc, "PlayerSetup", "terminalid", tids) == null) {
                            // The string tids is valid, it is a number!
                            terminalId = Integer.parseInt(tids);
                            isRelocated = true;
                            break;
                        }
                    }

                    if (terminalId == 0)
                        return RESULT_LINK_ERROR_NOT_ENOUGH_TERMINALS;
                } else {
                    if (terminalId == 0)
                        log.error("Method linkTerminal called with invalid parameters!");
                    return RESULT_LINK_ERROR_TERMINAL_LINKED;
                }
            }

            // Player(s) already linked to other terminal?
            // Also collect all roles already assigned
            Set<Integer> roles = new TreeSet<Integer>();
            Iterator iter = doc.getRootElement().getChild("PlayerSetup").getChildren().iterator();
            while (iter.hasNext()) {
                Element ep = (Element) iter.next();
                try {
                    if (groupId == ep.getAttribute("groupid").getIntValue()) {
                        Element epid = ep.getChild("PlayerIds");
                        Iterator iter2 = epid.getChildren().iterator();
                        while (iter2.hasNext()) {
                            Element epr = (Element) iter2.next();
                            try {
                                int id = epr.getAttribute("id").getIntValue();
                                for (int i = 0; i < playerIdArray.length; i++) {
                                    if (id == playerIdArray[i]) {
                                        return RESULT_LINK_ERROR_PLAYER_LINKED | id;
                                    }
                                }
                            } catch (DataConversionException ex) {
                                log.error("Attribute id on PlayerIds is not a number", ex);
                            }
                        }
                        Element eprs = ep.getChild("RoleSetup");
                        iter2 = eprs.getChildren().iterator();
                        while (iter2.hasNext()) {
                            Element eprr = (Element) iter2.next();
                            int rid = DomTools.getAttributeInt(eprr, "id", 0, false);
                            if (rid > 0)
                                roles.add(rid);
                        }
                    }
                } catch (DataConversionException ex) {
                    log.error("Attribute groupid on Player is not a number", ex);
                }
            }

            // Get all player elements in PlayerDefs section
            int nbrPlayers = playerIdArray.length;
            Element[] playerElementArray = new Element[nbrPlayers];
            for (int i = 0; i < playerIdArray.length; i++) {
                playerElementArray[i] = DomTools.getElementFromSection(doc, "PlayerDefs", "id",
                        String.valueOf(playerIdArray[i]));
                if (playerElementArray[i] == null) {
                    log.error("Player element in PlayerDefs does not exist: " + playerIdArray[i]);
                    nbrPlayers--;
                }
            }

            // Return if no players to link
            if (nbrPlayers <= 0) {
                log.error("No players to link to terminal " + terminalId + "!");
                return RESULT_LINK_ERROR_NO_PLAYERS;
            }

            // Remove old player(s) from recycle bin
            while (true) {
                Element ep = DomTools.getElementFromSection(doc, "RecycleBin", "terminalid",
                        String.valueOf(terminalId));
                if (ep == null)
                    break;
                doc.getRootElement().getChild("RecycleBin").removeContent(ep);
            }

            // Create new player node
            Element ep = new Element("Player");
            ep.setAttribute("terminalid", String.valueOf(terminalId));
            ep.setAttribute("groupid", String.valueOf(groupId));
            ep.setAttribute("relocated", String.valueOf(isRelocated));

            // Create merged name
            Element epn = new Element("Name");
            StringBuffer sb = new StringBuffer();
            for (int i = 0; i < playerElementArray.length; i++) {
                String s = "###";
                if (playerElementArray[i] != null)
                    s = DomTools.getChildText(playerElementArray[i], "Name", "P/" + playerIdArray[i], false);
                if (s != null) {
                    sb.append(s);
                    if (i < playerElementArray.length - 1)
                        sb.append(", ");
                }
            }
            epn.setText(sb.toString());
            ep.addContent(epn);

            // Create channels
            Element ecsd = new Element("ChannelSetup");
            for (int i = 0; i < playerElementArray.length; i++) {
                if (playerElementArray[i] != null) {
                    Element ecss = playerElementArray[i].getChild("ChannelSetup");
                    if (ecss != null) {
                        Iterator iters = ecss.getChildren("ChannelRef").iterator();
                        while (iters.hasNext()) {
                            Element ecrs = (Element) iters.next();

                            // First, check if this element already exists
                            int ids = DomTools.getAttributeInt(ecrs, "id", 0, true);
                            int gids = DomTools.getAttributeInt(ecrs, "groupid", groupId, false);

                            Element ecrd = null;
                            Iterator iterd = ecsd.getChildren().iterator();
                            while (iterd.hasNext()) {
                                Element e = (Element) iterd.next();
                                int idd = DomTools.getAttributeInt(e, "id", 0, true);
                                int gidd = DomTools.getAttributeInt(e, "groupid", groupId, false);
                                if ((ids == idd) && (gids == gidd)) {
                                    ecrd = e;
                                    break;
                                }
                            }

                            // Element exists, merge attributes
                            if (ecrd != null) {

                                // Merge state
                                if (hasStateOr(ecrs, ecrd, "state")) {
                                    String states = DomTools.getAttributeString(ecrs, "state", "off", false);
                                    String stated = DomTools.getAttributeString(ecrd, "state", "off", false);
                                    if (states.equals("rxtx"))
                                        ecrd.setAttribute("state", "rxtx");
                                    else if (states.equals("rx") && stated.equals("off"))
                                        ecrd.setAttribute("state", "rx");
                                    else
                                        ecrd.setAttribute("state", "off");
                                }

                                // Merge locked
                                if (hasStateOr(ecrs, ecrd, "locked")) {
                                    String lockeds = DomTools.getAttributeString(ecrs, "locked", "false", false);
                                    String lockedd = DomTools.getAttributeString(ecrd, "locked", "false", false);
                                    if (lockeds.equals("false") || lockedd.equals("false"))
                                        ecrd.setAttribute("locked", "false");
                                    else
                                        ecrd.setAttribute("locked", "true");
                                }

                                // Merge hidden
                                if (hasStateOr(ecrs, ecrd, "hidden")) {
                                    String hiddens = DomTools.getAttributeString(ecrs, "hidden", "false", false);
                                    String hiddend = DomTools.getAttributeString(ecrd, "hidden", "false", false);
                                    if (hiddens.equals("false") || hiddend.equals("false"))
                                        ecrd.setAttribute("hidden", "false");
                                    else
                                        ecrd.setAttribute("hidden", "true");
                                }

                                // Merge recordable
                                if (hasStateOr(ecrs, ecrd, "recordable")) {
                                    String recs = DomTools.getAttributeString(ecrs, "recordable", "false", false);
                                    String recd = DomTools.getAttributeString(ecrd, "recordable", "false", false);
                                    if (recs.equals("false") && recd.equals("false"))
                                        ecrd.setAttribute("recordable", "false");
                                    else
                                        ecrd.setAttribute("recordable", "true");
                                }

                                // Merge monitor
                                if (hasStateOr(ecrs, ecrd, "monitor")) {
                                    boolean mons = DomTools.getAttributeBoolean(ecrs, "monitor", false, false);
                                    boolean mond = DomTools.getAttributeBoolean(ecrd, "monitor", false, false);
                                    ecrd.setAttribute("monitor", String.valueOf(mons || mond));
                                }

                                // Merge showgroup
                                if (hasStateOr(ecrs, ecrd, "showgroup")) {
                                    String sgs = DomTools.getAttributeString(ecrs, "showgroup", "false", false);
                                    String sgd = DomTools.getAttributeString(ecrd, "showgroup", "false", false);
                                    if (sgs.equals("false") && sgd.equals("false"))
                                        ecrd.setAttribute("showgroup", "false");
                                    else
                                        ecrd.setAttribute("showgroup", "true");
                                }
                            }

                            // Element does not exist, clone it
                            else
                                ecsd.addContent((Element) ecrs.clone());
                        }
                    }
                }
            }
            ep.addContent(ecsd);

            // Check if any of the channels has a monitor
            iter = ecsd.getChildren("ChannelRef").iterator();
            while (iter.hasNext()) {
                Element ecr = (Element) iter.next();
                int cid = DomTools.getAttributeInt(ecr, "id", 0, true);
                Element ec = DomTools.getElementFromSection(doc, "ChannelDefs", "id", String.valueOf(cid));
                if (ec != null) {
                    // Check both Channel in ChannelDefs and ChannelRef in ChannelSetup for the monitor attribute
                    if (DomTools.getPrioritizedAttribute("monitor", "false", ecr, ec).equals("true")) {
                        // Find the groupid depending on priority
                        int gid = DomTools.getPrioritizedAttribute("groupid", groupId, ecr, ec);
                        // Store monitoring terminal id
                        monitoringTerminalId = terminalId;
                        // Calculate channel id for the monitor
                        monitorChannelId = (gid << ID_BITSHIFT) | cid;
                        if (log.isDebugEnabled())
                            log.debug("Monitor - Setting monitoring terminal " + terminalId + " (channel " + cid
                                    + ", group " + gid + ")");
                        monitorStart();
                        break;
                    }
                }
            }

            // Create roles
            Element ersd = new Element("RoleSetup");
            for (int i = 0; i < playerElementArray.length; i++) {
                if (playerElementArray[i] != null) {
                    Element erss = playerElementArray[i].getChild("RoleSetup");
                    if (erss != null) {
                        Iterator iters = erss.getChildren("RoleRef").iterator();
                        while (iters.hasNext()) {
                            Element errs = (Element) iters.next();

                            int ids = DomTools.getAttributeInt(errs, "id", 0, false);

                            Element errd = null;
                            if (ids > 0) {
                                Iterator iterd = ersd.getChildren().iterator();
                                while (iterd.hasNext()) {
                                    Element e = (Element) iterd.next();
                                    int idd = DomTools.getAttributeInt(e, "id", 0, false);
                                    if (ids == idd) {
                                        errd = e;
                                        break;
                                    }
                                }
                            }

                            // Element does not exist, clone it
                            if (errd == null) {
                                // Allow rolerefs with id 0 (empty slots) if only one
                                // player is linked. Merged players will result in
                                // undefined slot posistions
                                if (ids > 0 || playerElementArray.length == 1) {
                                    errd = (Element) errs.clone();
                                    ersd.addContent(errd);
                                }
                            }

                            // Here we have errd (RoleRef), calculate uid (unique id)
                            if (ids > 0 && errd != null) {
                                Element elemLow = DomTools.getElementFromSection(doc, "RoleDefs", "id",
                                        String.valueOf(ids));
                                int gid = DomTools.getPrioritizedAttribute("groupid", groupId, errd, elemLow);
                                errd.setAttribute("uid", String.valueOf((gid << ID_BITSHIFT) | ids));
                            }
                        }
                    }
                }
            }

            // Role already played by another player?
            iter = ersd.getChildren().iterator();
            while (iter.hasNext()) {
                Element err = (Element) iter.next();
                int rid = DomTools.getAttributeInt(err, "id", 0, false);
                if (rid > 0 && roles.contains(rid))
                    return RESULT_LINK_ERROR_ROLE_LINKED | rid;
            }

            // If not, add RoleSetup node
            ep.addContent(ersd);

            // Add playerId's
            Element epid = new Element("PlayerIds");
            for (int i = 0; i < playerIdArray.length; i++) {
                Element epr = new Element("PlayerRef");
                epr.setAttribute("id", String.valueOf(playerIdArray[i]));
                epid.addContent(epr);
            }
            ep.addContent(epid);

            // Add Player to PlayerSetup
            doc.getRootElement().getChild("PlayerSetup").addContent(ep);

            // Debug output
            if (Config.SERVER_WRITE_PLAYERSETUP) {
                String fname = "data/development/server_playersetup.xml";
                XMLOutputter xmlo = new XMLOutputter(Format.getPrettyFormat());
                try {
                    Writer wr = new FileWriter(fname);
                    xmlo.output(doc.getRootElement().getChild("PlayerSetup"), wr);
                    wr.close();
                } catch (IOException ex) {
                    log.error("IOException when trying to write model to file " + fname + "!", ex);
                }
                log.info("PlayerSetup successfully written to file " + fname);
            }
        }

        return RESULT_LINK_OK;
    }

    private boolean unlinkTerminal(int terminalId) {

        synchronized (doc) {
            // Remove all
            if (terminalId == ALL) {
                List content = doc.getRootElement().getChild("PlayerSetup").removeContent();
                doc.getRootElement().getChild("RecycleBin").addContent(content);
            }

            // Remove one
            else {
                Element ep = DomTools.getElementFromSection(doc, "PlayerSetup", "terminalid",
                        String.valueOf(terminalId));
                if (ep != null) {
                    doc.getRootElement().getChild("PlayerSetup").removeContent(ep);
                    doc.getRootElement().getChild("RecycleBin").addContent(ep);
                } else
                    return false;
            }

            // Stop monitor
            if (terminalId == ALL || terminalId == monitoringTerminalId) {
                log.debug("Monitor - Removing monitoring terminal " + monitoringTerminalId);
                monitorStop();
                monitoringTerminalId = 0;
            }
        }
        return true;
    }

    private void unlinkTerminal(Element playerElement) {
        if (playerElement != null) {
            synchronized (doc) {
                if (doc.getRootElement().getChild("PlayerSetup").removeContent(playerElement)) {

                    // Stop monitor
                    int terminalId = DomTools.getAttributeInt(playerElement, "terminalid", 0, true);
                    if (terminalId > 0 && terminalId == monitoringTerminalId) {
                        log.debug("Monitor - Removing monitoring terminal " + monitoringTerminalId);
                        monitorStop();
                        monitoringTerminalId = 0;
                    }

                    doc.getRootElement().getChild("RecycleBin").addContent(playerElement);
                }
            }
        }
    }

    private boolean swapTerminals(int terminalId1, int terminalId2) {

        // Dont do anything if same terminal is selected
        if (terminalId1 == terminalId2)
            return false;

        synchronized (doc) {

            Element ep1 = DomTools.getElementFromSection(doc, "PlayerSetup", "terminalid",
                    String.valueOf(terminalId1));
            Element ep2 = DomTools.getElementFromSection(doc, "PlayerSetup", "terminalid",
                    String.valueOf(terminalId2));

            // Dont do anything if neither of the selected terminals has players
            if (ep1 == null && ep2 == null)
                return false;

            // Swap the two players
            if (ep1 != null && ep2 != null) {

                // Lock all other functions while swapping
                isSwapping = true;

                // Clone both players
                swapPlayer1 = (Element) ep1.clone();
                swapPlayer2 = (Element) ep2.clone();

                // Swap the terminal ids
                swapPlayer1.setAttribute("terminalid", String.valueOf(terminalId2));
                swapPlayer2.setAttribute("terminalid", String.valueOf(terminalId1));

                // Remove relocated flag
                swapPlayer1.setAttribute("relocated", "false");
                swapPlayer2.setAttribute("relocated", "false");

                // Store monitoring terminal
                int newMonitoringTerminal = 0;
                if (terminalId1 == monitoringTerminalId)
                    newMonitoringTerminal = terminalId2;
                else if (terminalId2 == monitoringTerminalId)
                    newMonitoringTerminal = terminalId1;

                // Throw both players on the recycle bin
                unlinkTerminal(terminalId1);
                unlinkTerminal(terminalId2);

                // Update monitor
                if (newMonitoringTerminal > 0) {
                    log.debug("Monitor - Setting monitoring terminal " + newMonitoringTerminal);
                    monitoringTerminalId = newMonitoringTerminal;

                    // Do not allow monitoring self
                    if (monitoringTerminalId == monitoredTerminalId) {
                        log.warn("Unable to keep monitored terminal, trying to monitor self!");
                        setTerminalFlag(monitoredTerminalId, "monitored", false);
                        monitoredTerminalId = 0;
                    } else {
                        monitorStart();
                    }
                }

                // Create a re-linker thread
                Thread t = new Thread(new Runnable() {
                    public void run() {
                        try {
                            Thread.sleep(DELAY_SWAP);
                            synchronized (doc) {

                                Element eps = doc.getRootElement().getChild("PlayerSetup");
                                Element erb = doc.getRootElement().getChild("RecycleBin");

                                eps.addContent(swapPlayer1);
                                eps.addContent(swapPlayer2);

                                // Remove old from recycle bin
                                Element ep;
                                ep = DomTools.getElementFromSection(doc, "RecycleBin", "terminalid",
                                        DomTools.getAttributeString(swapPlayer1, "terminalid", "0", true));
                                if (ep != null)
                                    erb.removeContent(ep);
                                ep = DomTools.getElementFromSection(doc, "RecycleBin", "terminalid",
                                        DomTools.getAttributeString(swapPlayer2, "terminalid", "0", true));
                                if (ep != null)
                                    erb.removeContent(ep);
                            }

                            // Update terminal states
                            updateTerminals(ALL, ALL);

                            // Wait until finished before updating view
                            Thread.sleep(DELAY_START + DELAY_START_VARIANCE);
                            updateView();

                        } catch (Exception ex) {
                            log.error("Unable to swap, re-linker thread failed", ex);
                        }

                        isSwapping = false;
                        swapPlayer1 = null;
                        swapPlayer2 = null;
                    }
                }, "Tlink");

                t.start();
            }

            // Move player 1->2
            else {

                // Swap if direction is 2->1
                if (ep1 == null) {
                    Element e = ep1;
                    ep1 = ep2;
                    ep2 = e;
                    int t = terminalId1;
                    terminalId1 = terminalId2;
                    terminalId2 = t;
                }

                // Must make a copy of the source tree to put in the recycle bin
                Element epcopy = (Element) ep1.clone();
                doc.getRootElement().getChild("RecycleBin").addContent(epcopy);

                // The original tree is used with the destination terminal
                ep1.setAttribute("terminalid", String.valueOf(terminalId2));

                // Remove relocated flag
                ep1.setAttribute("relocated", "false");

                // Update monitor
                if (terminalId1 == monitoringTerminalId) {
                    if (log.isDebugEnabled())
                        log.debug("Monitor - Moving monitoring terminal from " + monitoringTerminalId + " to "
                                + terminalId2);
                    monitorStop();
                    monitoringTerminalId = terminalId2;

                    // Do not allow monitoring self
                    if (monitoringTerminalId == monitoredTerminalId) {
                        log.warn("Unable to keep monitored terminal, trying to monitor self!");
                        setTerminalFlag(monitoredTerminalId, "monitored", false);
                        monitoredTerminalId = 0;
                    } else {
                        monitorStart();
                    }
                }
            }
        }

        return true;
    }

    private void sendRoleActivitiyPacket(ServerTerminal destTerminal, ServerTerminal sourceTerminal) {

        if (destTerminal == null || sourceTerminal == null)
            return;

        Packet p = networkManager.prepareInfoPacket(destTerminal, sourceTerminal);
        if (p == null)
            return;

        if (log.isDebugEnabled())
            log.debug(
                    "Sending role activitiy packet from " + sourceTerminal.getId() + " to " + destTerminal.getId());

        synchronized (doc) {
            String id_s = String.valueOf(sourceTerminal.getId());

            // Try to get the element from playersetup
            Element ep = DomTools.getElementFromSection(doc, "PlayerSetup", "terminalid", id_s);

            // Look in the recycle bin if not found in PlayerSetup
            if (ep == null)
                ep = DomTools.getElementFromSection(doc, "RecycleBin", "terminalid", id_s);

            if (ep != null) {
                boolean available = sourceTerminal.isStarted();
                boolean incall = sourceTerminal.isInCall();
                int n = 0, mask = 0;

                // Build bitmask
                if (available)
                    mask |= Packet.ATTR_ROLE_ACTIVITY_FLAG_AVAILABLE;
                if (incall)
                    mask |= Packet.ATTR_ROLE_ACTIVITY_FLAG_INCALL;

                // Build array with roles from source terminal, there might be empty_slot's
                List lrr = ep.getChild("RoleSetup").getChildren();
                int[] uidarray = new int[lrr.size()];

                Iterator iter = lrr.iterator();
                while (iter.hasNext()) {
                    Element err = (Element) iter.next();
                    int uid = DomTools.getAttributeInt(err, "uid", 0, false);
                    if (uid > 0) {
                        if (log.isDebugEnabled())
                            log.debug("  - uid: " + uid + " online: " + available + ", incall: " + incall);
                        uidarray[n++] = mask | uid;
                    }
                }
                p.addAttributeList(Packet.ATTR_ROLE_ACTIVITY, uidarray, n);

                networkManager.sendInfoPacket(p);
            } else {
                log.error("Unable to send role activitiy packet! Player node not found!");
            }
        }
    }

    private void updatePhoneLineStatus(ServerTerminal sourceTerminal, ServerTerminal destTerminal) {
        int sourceTerminalId = (sourceTerminal != null) ? sourceTerminal.getId() : 0;
        int destTerminalId = (destTerminal != null) ? destTerminal.getId() : 0;

        Collection<ServerTerminal> terminalCollection = bundle.getTerminalCollection();
        Iterator<ServerTerminal> iter = terminalCollection.iterator();
        while (iter.hasNext()) {
            ServerTerminal terminal = iter.next();
            int terminalId = terminal.getId();
            if ((terminalId != sourceTerminalId) && (terminalId != destTerminalId) && terminal.isStarted()
                    && terminal.isConnected()) {
                sendRoleActivitiyPacket(terminal, sourceTerminal);
                sendRoleActivitiyPacket(terminal, destTerminal);
            }
        }
    }

    private void menuChoiceLoadConfiguration() {
        if (isSwapping)
            return;
        log.info("Menu: Load configuration");
        JFileChooser fc = new JFileChooser("data/configurations");
        fc.setDialogTitle("Load configuration...");
        fc.setMultiSelectionEnabled(false);
        fc.setFileFilter(new FileFilter() {
            public boolean accept(File f) {
                return !f.isHidden() && (f.isDirectory() || f.getName().endsWith(".xml"));
            }

            public String getDescription() {
                return "Configuration (*.xml)";
            }
        });

        int returnVal = JFileChooser.APPROVE_OPTION;
        if (Config.SERVER_AUTOLOAD_CONFIGURATION == null)
            returnVal = fc.showOpenDialog(frame);

        if (returnVal == JFileChooser.APPROVE_OPTION) {
            File file;
            if (Config.SERVER_AUTOLOAD_CONFIGURATION == null)
                file = fc.getSelectedFile();
            else
                file = new File(Config.SERVER_AUTOLOAD_CONFIGURATION);
            log.info("Loading configuration " + file);
            if (file.exists()) {
                if (buildConfigurationDocument(file)) {
                    isConfigLoaded = true;
                    updateView();
                } else
                    JOptionPane.showMessageDialog(frame,
                            "Invalid configuration file! Make sure that all required tags are defined!", "Error!",
                            JOptionPane.ERROR_MESSAGE);
            } else
                JOptionPane.showMessageDialog(frame, "Unable to load configuration! File not found!", "Error!",
                        JOptionPane.ERROR_MESSAGE);
        }
    }

    public void menuChoiceLoadExercise() {
        if (isSwapping)
            return;
        log.info("Menu: Load exercise");
        JFileChooser fc = new JFileChooser("data/exercises");
        fc.setDialogTitle("Load exercise...");
        fc.setMultiSelectionEnabled(false);
        fc.setFileFilter(new FileFilter() {
            public boolean accept(File f) {
                return !f.isHidden() && (f.isDirectory() || f.getName().endsWith(".xml"));
            }

            public String getDescription() {
                return "Exercise (*.xml)";
            }
        });

        int returnVal = JFileChooser.APPROVE_OPTION;
        if (Config.SERVER_AUTOLOAD_EXERCISE == null)
            returnVal = fc.showOpenDialog(frame);

        if (returnVal == JFileChooser.APPROVE_OPTION) {
            //File file;
            if (Config.SERVER_AUTOLOAD_EXERCISE == null)
                exerciseFile = fc.getSelectedFile();
            else
                exerciseFile = new File(Config.SERVER_AUTOLOAD_EXERCISE);
            log.info("Loading exercise " + exerciseFile);
            if (exerciseFile.exists()) {
                loadExercise(exerciseFile);
            } else
                JOptionPane.showMessageDialog(frame, "Unable to load exercise! File not found!", "Error!",
                        JOptionPane.ERROR_MESSAGE);
        }
    }

    private void loadExercise(File file) {
        try {
            SAXBuilder saxb = new SAXBuilder();
            Document linkdoc = saxb.build(file);

            Element eroot = linkdoc.getRootElement();
            if (eroot.getName().equals("Exercise")) {

                // Ok, read document
                Iterator iter = linkdoc.getRootElement().getChildren("TerminalLink").iterator();

                int selectedGroup = -1;
                StringBuffer errMsg = new StringBuffer();
                while (iter.hasNext()) {
                    Element epl = (Element) iter.next();

                    // Get the terminal id (param id or terminalid),
                    // Allow zero meaning always relocate
                    int tid = DomTools.getAttributeInt(epl, "id", 0, false);
                    if (tid == 0)
                        tid = DomTools.getAttributeInt(epl, "terminalid", 0, false);

                    // Get the group id (param groupid)
                    // Allow zero meaning select group in a dialog input box
                    int gid = DomTools.getAttributeInt(epl, "groupid", 0, false);
                    if (gid == 0) {
                        // Show group dialog only once
                        if (selectedGroup == -1) {
                            GroupSelectDialog dlg = new GroupSelectDialog(frame, doc, "Select group...");
                            selectedGroup = dlg.showDialog();
                        }

                        // All following players without given group id joins this selected group
                        // If pressed cancel in the dialog box, no terminals without given group
                        // id will be linked
                        gid = selectedGroup;
                    }

                    if (tid >= 0 && gid > 0) {
                        List list = epl.getChildren("PlayerRef");
                        int[] pids = new int[list.size()];
                        Iterator iter2 = list.iterator();
                        int i = 0;
                        while (iter2.hasNext()) {
                            Element epr = (Element) iter2.next();
                            pids[i++] = DomTools.getAttributeInt(epr, "id", 0, true);
                        }

                        int res = linkTerminal(tid, gid, pids, true);

                        if (res > 0) {
                            int id = res & ID_MASK_PLAYER;
                            synchronized (doc) {
                                if ((res & RESULT_LINK_ERROR_PLAYER_LINKED) != 0) {
                                    String pname = DomTools.getChildText(DomTools.getElementFromSection(doc,
                                            "PlayerDefs", "id", String.valueOf(id)), "Name", "P/" + id, false);
                                    errMsg.append("Unable to link: Player ");
                                    errMsg.append(pname);
                                    errMsg.append(" (id=");
                                    errMsg.append(id);
                                    errMsg.append(") is already linked to another terminal!\n");
                                } else {
                                    String rname = DomTools.getChildText(DomTools.getElementFromSection(doc,
                                            "RoleDefs", "id", String.valueOf(id)), "Name", "R/" + id, false);
                                    errMsg.append("Unable to link: Role ");
                                    errMsg.append(rname);
                                    errMsg.append(" (id=");
                                    errMsg.append(id);
                                    errMsg.append(") is already linked to another terminal!\n");
                                }
                            }
                        } else if (res == RESULT_LINK_ERROR_TERMINAL_LINKED) {
                            String tname = DomTools.getChildText(
                                    DomTools.getElementFromSection(doc, "TerminalDefs", "id", String.valueOf(tid)),
                                    "Name", "T/" + tid, false);
                            errMsg.append("Unable to link: Terminal ");
                            errMsg.append(tname);
                            errMsg.append(" (id=");
                            errMsg.append(tid);
                            errMsg.append(") is in use by another player!\n");
                        } else if (res == RESULT_LINK_ERROR_NO_PLAYERS) {
                            errMsg.append(
                                    "Unable to link: Player(s) missing in configuration or not defined in exercise!\n");
                        } else if (res == RESULT_LINK_ERROR_NOT_ENOUGH_TERMINALS) {
                            for (int pid : pids) {
                                String pname = DomTools.getChildText(DomTools.getElementFromSection(doc,
                                        "PlayerDefs", "id", String.valueOf(pid)), "Name", "P/" + pid, false);
                                errMsg.append("Unable to link: There is no available terminal for player ");
                                errMsg.append(pname);
                                errMsg.append(" (id=");
                                errMsg.append(pid);
                                errMsg.append(")!\n");
                            }
                        }
                    }
                }
                updateTerminals(ALL, ALL);
                updateView();
                if (errMsg.length() > 0) {
                    String e = errMsg.toString().trim();
                    JOptionPane.showMessageDialog(frame, e, "Error!", JOptionPane.ERROR_MESSAGE);
                    log.warn("Warnings when loading exercise file:\n" + e);
                }
            } else {
                JOptionPane.showMessageDialog(frame, "Unable to load exercise! Invalid root element!", "Error!",
                        JOptionPane.ERROR_MESSAGE);
                log.error("Unable to load exercise! Invalid root element!");
            }

        } catch (Exception ex) {
            JOptionPane.showMessageDialog(frame, "Unable to load exercise! Syntax error!", "Error!",
                    JOptionPane.ERROR_MESSAGE);
            log.error("Unable to load exercise!", ex);
        }
    }

    private void menuChoiceServerStart() {
        if (isSwapping)
            return;
        log.info("Menu: Starting server");
        try {
            // Create a HTTP server for information exchange
            httpServer = new Server();

            // Setup velocity
            Velocity.init();

            // Read the property files
            Properties serverProperties = new Properties();
            serverProperties.loadFromXML(new FileInputStream("data/properties/serverproperties.xml"));
            logEvents = serverProperties.getProperty("LogEvents", "false").equalsIgnoreCase("true");
            if (logEvents)
                log.info("Enabling event logging");

            // Setup HTTP server
            int httpPort = Integer.parseInt(serverProperties.getProperty("HttpPort", "36600"));
            httpConnector = new SelectChannelConnector();
            httpConnector.setPort(httpPort);
            httpServer.setConnectors(new Connector[] { httpConnector });

            // Dynamic context handler for the xml data provider
            ContextHandler xmlContextHandler = new ContextHandler();
            xmlContextHandler.setContextPath("/xml");
            Handler xmlHandler = new InfoRequestHandler(this, log);
            xmlContextHandler.setHandler(xmlHandler);
            httpServer.setHandlers(new Handler[] { xmlContextHandler });

            // Create a bundle
            bundle = new ServerBundle(doc);

            // Create server logger map containing all available loggers
            loggerMap = Collections.synchronizedMap(new TreeMap<Integer, ServerLogger>());

            // Start the UDP server
            int port = Integer.parseInt(serverProperties.getProperty("UdpPort", "36604"));
            networkManager = new ServerNetworkManager(port, bundle, this);

            networkManager.setNetworkHandler(this);

            isaTracePainter = serverProperties.getProperty("ISATracePainter", "line");
            panel.resetIsaChart();

        } catch (Exception ex) {
            log.error("Unable to start server! ", ex);
            JOptionPane.showMessageDialog(frame,
                    "Unable to start server! Check the validity of the configuration file, the serverproperties.xml and\nthe networkproperties.ini files!  Also make sure that no other server is already running!",
                    "Error!", JOptionPane.ERROR_MESSAGE);
            return;
        }

        try {
            serverStartedDate = null;

            networkManager.start();
            serverStartedDate = new Date();

            // Start the HTTP server
            httpServer.start();

            // Tell it to the view to show an icon
            panel.setServerStartedDate(serverStartedDate);

        } catch (Exception ex) {
            log.error("Unable to start server!", ex);
            JOptionPane.showMessageDialog(frame,
                    "Unable to start server! Check the validity of the configuration file, the serverproperties.xml and\nthe networkproperties.ini files!  Also make sure that no other server is already running!",
                    "Error!", JOptionPane.ERROR_MESSAGE);
            // Network manager has been started, stop it again!
            if (serverStartedDate != null) {
                networkManager.stop();
                serverStartedDate = null;
            }
        }
    }

    private void menuChoiceServerStop(boolean exiting) {
        if (isSwapping)
            return;
        log.info("Menu: Stop server");
        int r = JOptionPane.YES_OPTION;
        String s = "Are you sure you want to stop the server? All terminals will be unlinked!";
        if (!exiting)
            r = JOptionPane.showConfirmDialog(frame, s, "Stop?", JOptionPane.YES_NO_OPTION,
                    JOptionPane.WARNING_MESSAGE);

        if (r == JOptionPane.YES_OPTION) {

            log.info("Stopping server");

            isaStop(0);

            // Unlink all terminals
            unlinkTerminal(ALL);

            // Stop all groups
            synchronized (doc) {
                Iterator iter = doc.getRootElement().getChild("GroupDefs").getChildren().iterator();
                while (iter.hasNext()) {
                    Element eg = (Element) iter.next();
                    eg.setAttribute("state", "stopped");
                }
            }

            // Set all terminals to idle state
            updateTerminals(ALL, ALL);
            updateView();

            // Wait before closing terminals
            try {
                Thread.sleep(250);
            } catch (InterruptedException ex) {
            }

            // Close all terminals
            synchronized (doc) {
                Iterator iter = doc.getRootElement().getChild("TerminalDefs").getChildren().iterator();
                while (iter.hasNext()) {
                    Element et = (Element) iter.next();
                    if (DomTools.getAttributeBoolean(et, "online", false, false)) {
                        int tid = DomTools.getAttributeInt(et, "id", 0, true);
                        et.setAttribute("online", "false");
                        log.info("Sending close to terminal " + tid);
                        networkManager.sendSessionRequestClose(tid);
                    }
                }
            }
            updateView();

            // Close server
            try {
                Thread.sleep(250);
                networkManager.stop();
                Thread.sleep(250);
                httpServer.stop();
                httpConnector.close();
                Thread.sleep(250);
            } catch (Exception ex) {
                log.warn("Stop server failed!", ex);
            }

            panel.setServerStartedDate(null);
            serverStartedDate = null;
        }
    }

    /**
     * Method used for a quick restart of a running server.
     * The server is stopped, started, the last used exercise file is loaded
     * and the groups that where running when this method was called are started again.
     */
    private void serverRestart() {
        log.info("Restart server");
        List<Integer> activeGroups = new ArrayList<Integer>();
        synchronized (doc) {
            Element egd = doc.getRootElement().getChild("GroupDefs");
            Iterator iter = egd.getChildren().iterator();
            Element eg;
            Integer gid;
            while (iter.hasNext()) {
                eg = (Element) iter.next();
                gid = new Integer(eg.getAttributeValue("id"));
                if (gid != null && eg.getAttribute("state") != null
                        && eg.getAttributeValue("state").equalsIgnoreCase("started")) {
                    activeGroups.add(gid);
                }
            }
        }

        log.info("Stopping server");
        menuChoiceServerStop(true);

        log.info("Start server");
        menuChoiceServerStart();

        if (exerciseFile != null) {
            log.info("Load exercise file " + exerciseFile.getName());
            loadExercise(exerciseFile);
        }

        for (int i = 0; i < activeGroups.size(); i++) {
            log.info("Start group " + activeGroups.get(i));
            menuChoiceGroupStart(activeGroups.get(i).intValue(), false, null);
        }
        log.info("Server restart done");
    }

    private void menuChoiceNetworkMonitor() {
        if (networkStatusTimer != null) {
            networkStatusTimer.cancel();
            networkStatusTimer = null;
            log.debug("Stopped network connection monitoring");
        } else {
            networkStatusTimer = new Timer();
            networkStatusTimer.schedule(new TimerTask() {
                public void run() {
                    if (serverStartedDate != null) {
                        log.debug("Check network connection status");
                    }
                    if (serverStartedDate != null && networkManager.connectionProblem()) {
                        log.error("Possible network connection problem detected, restart server!");
                        try {
                            serverRestart();
                        } catch (Exception e) {
                            e.printStackTrace();
                        }
                    }
                }
            }, 30000, 5000);
            log.debug("Started network connection monitoring");
        }
        itemServerMonitor.setSelected(networkStatusTimer != null);
    }

    public boolean menuChoiceTerminalLink(int terminalId) {
        if (isSwapping)
            return false;
        log.info("Menu: Link terminal");
        TerminalLinkDialog dlg = new TerminalLinkDialog(frame, doc, terminalId);
        if (dlg.showDialog()) {
            int tid = dlg.getSelectedTerminalId();
            int gid = dlg.getSelectedGroupId();
            int res = linkTerminal(tid, gid, dlg.getSelectedPlayerIdArray(), true);
            if (res > 0) {
                synchronized (doc) {
                    int id = res & ID_MASK_PLAYER;
                    if ((res & RESULT_LINK_ERROR_PLAYER_LINKED) != 0) {
                        String pname = DomTools.getChildText(
                                DomTools.getElementFromSection(doc, "PlayerDefs", "id", String.valueOf(id)), "Name",
                                "P/" + id, false);
                        JOptionPane.showMessageDialog(frame,
                                "Unable to link: Player " + pname + " (id=" + id
                                        + ") is already linked to another terminal!",
                                "Error!", JOptionPane.ERROR_MESSAGE);
                    } else {
                        String rname = DomTools.getChildText(
                                DomTools.getElementFromSection(doc, "RoleDefs", "id", String.valueOf(id)), "Name",
                                "R/" + id, false);
                        JOptionPane.showMessageDialog(frame,
                                "Unable to link: Role " + rname + " (id=" + id
                                        + ") is already linked to another terminal!",
                                "Error!", JOptionPane.ERROR_MESSAGE);
                    }
                }
            } else if (res == RESULT_LINK_ERROR_TERMINAL_LINKED) {
                String tname = DomTools.getChildText(
                        DomTools.getElementFromSection(doc, "TerminalDefs", "id", String.valueOf(tid)), "Name",
                        "T/" + tid, false);
                JOptionPane.showMessageDialog(frame,
                        "Unable to link: Terminal " + tname + " (id=" + tid + ") is in use by another player!",
                        "Error!", JOptionPane.ERROR_MESSAGE);
            } else if (res == RESULT_LINK_ERROR_NO_PLAYERS) {
                JOptionPane.showMessageDialog(frame, "Unable to link: No player(s) selected!", "Error!",
                        JOptionPane.ERROR_MESSAGE);
            } else if (res == RESULT_LINK_ERROR_NOT_ENOUGH_TERMINALS) {
                int[] pids = dlg.getSelectedPlayerIdArray();
                String errMsg = "";
                for (int pid : pids) {
                    String pname = DomTools.getChildText(
                            DomTools.getElementFromSection(doc, "PlayerDefs", "id", String.valueOf(pid)), "Name",
                            "P/" + pid, false);
                    errMsg += "Unable to link: There is no available terminal for player " + pname + " (id=" + pid
                            + ")!\n";
                }
                JOptionPane.showMessageDialog(frame, errMsg, "Error!", JOptionPane.ERROR_MESSAGE);
            } else {
                updateTerminals(tid, gid);
                updateView();
            }
            return (res == RESULT_LINK_OK);
        }
        return false;
    }

    public boolean menuChoiceTerminalUnlink(int terminalId) {
        if (isSwapping)
            return false;
        log.info("Menu: Unlink terminal");
        if (terminalId == 0) {
            TerminalUnlinkDialog dlg = new TerminalUnlinkDialog(frame, doc);
            terminalId = dlg.showDialog();
        }
        if (terminalId > 0) {
            if (unlinkTerminal(terminalId)) {
                updateTerminals(terminalId, ALL);
                updateView();
                return true;
            } else
                JOptionPane.showMessageDialog(frame, "The selected terminal is not linked to any player(s)!",
                        "Info!", JOptionPane.INFORMATION_MESSAGE);
        }

        return false;
    }

    public boolean menuChoiceTerminalUnlink(List<Integer> terminalIds) {
        if (isSwapping)
            return false;
        log.info("Menu: Unlink terminals");

        int nbrTerminals = terminalIds.size();
        if (nbrTerminals > 1) {
            int r = JOptionPane.showConfirmDialog(frame,
                    "Are you sure you want to unlink " + nbrTerminals + " terminals?", "Unlink?",
                    JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE);
            if (r != JOptionPane.YES_OPTION)
                return false;
        }

        boolean update = false;
        Iterator<Integer> iteri = terminalIds.iterator();
        while (iteri.hasNext()) {
            boolean b = unlinkTerminal(iteri.next().intValue());
            update = update || b;
        }

        if (update) {
            updateTerminals(ALL, ALL);
            updateView();
        }

        return update;
    }

    private void menuChoiceTerminalUnlinkAll() {
        if (isSwapping)
            return;
        log.info("Menu: Unlink all terminals");
        int r = JOptionPane.showConfirmDialog(frame, "Are you sure you want to unlink all terminals?",
                "Unlink all?", JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE);
        if (r == JOptionPane.YES_OPTION) {
            unlinkTerminal(ALL);
            updateTerminals(ALL, ALL);
            updateView();
        }
    }

    public boolean menuChoiceTerminalUnlinkGroup(int groupId) {
        if (isSwapping)
            return false;
        log.info("Menu: Unlink terminals in group");

        String name = "G/" + groupId;
        synchronized (doc) {
            Element eg = DomTools.getElementFromSection(doc, "GroupDefs", "id", String.valueOf(groupId));
            name = DomTools.getChildText(eg, "Name", "G/" + groupId, false);
        }

        int r = JOptionPane.showConfirmDialog(frame,
                "Are you sure you want to unlink all terminals in group " + name + "?", "Unlink group?",
                JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE);

        if (r == JOptionPane.YES_OPTION) {

            boolean update = false;
            synchronized (doc) {
                while (true) {
                    Element ep = DomTools.getElementFromSection(doc, "PlayerSetup", "groupid",
                            String.valueOf(groupId));
                    if (ep == null)
                        break;
                    unlinkTerminal(ep);
                    update = true;
                }
            }
            if (update) {
                updateTerminals(ALL, ALL);
                updateView();
                return true;
            }
        }

        return false;
    }

    public void menuChoiceTerminalSwap(int terminalId1, int terminalId2) {
        if (isSwapping)
            return;
        log.info("Menu: Swap terminals");
        if (terminalId1 == 0 && terminalId2 == 0) {
            TerminalSwapDialog dlg = new TerminalSwapDialog(frame, doc);
            if (dlg.showDialog()) {
                int[] tids = dlg.getSelectedTerminals();
                terminalId1 = tids[0];
                terminalId2 = tids[1];
            }
        }

        if (swapTerminals(terminalId1, terminalId2)) {
            updateTerminals(ALL, ALL);
            updateView();
        }
    }

    public void menuChoiceTerminalMonitor(int terminalId) {

        // Do not allow monitoring self
        if (terminalId == monitoringTerminalId) {
            log.warn("Unable to set monitored terminal, trying to monitor self!");
            return;
        }

        // Stop currently monitored terminal
        if (monitoredTerminalId != 0) {
            setTerminalFlag(monitoredTerminalId, "monitored", false);
            monitorStop();
        }

        // Start only if selected is not the same as the currently monitored
        if (terminalId != monitoredTerminalId) {
            monitoredTerminalId = terminalId;
            setTerminalFlag(monitoredTerminalId, "monitored", true);
            monitorStart();
        } else {
            monitoredTerminalId = 0;
        }

        updateView();
    }

    public boolean menuChoiceGroupStart(int groupId) {
        return menuChoiceGroupStart(groupId, true, null);
    }

    public boolean menuChoiceGroupStart(int groupId, boolean confirm, String timeStr) {
        if (isSwapping)
            return false;
        log.info("logPath =" + logPath);
        log.info("Menu: Start group time=" + timeStr);

        // Debug autostart group
        if (Config.SERVER_AUTOSTART_GROUP > 0)
            groupId = Config.SERVER_AUTOSTART_GROUP;

        boolean showConfirmDialog = (groupId != 0) && confirm;

        // Select dialog if no group id specified
        if (groupId == 0) {
            GroupSelectDialog dlg = new GroupSelectDialog(frame, doc, "Start group...");
            groupId = dlg.showDialog();
        }

        if (groupId > 0) {
            Element eg = null;
            if (showConfirmDialog && (Config.SERVER_AUTOSTART_GROUP == 0)) {
                String name = "G/" + groupId;
                synchronized (doc) {
                    eg = DomTools.getElementFromSection(doc, "GroupDefs", "id", String.valueOf(groupId));
                    name = DomTools.getChildText(eg, "Name", "G/" + groupId, false);
                }
                int r = JOptionPane.showConfirmDialog(frame, "Are you sure you want to start group " + name + "?",
                        "Start group?", JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE);
                if (r != JOptionPane.YES_OPTION)
                    return false;
            }

            synchronized (doc) {
                if (eg == null)
                    eg = DomTools.getElementFromSection(doc, "GroupDefs", "id", String.valueOf(groupId));
                String state = DomTools.getAttributeString(eg, "state", "stopped", false);

                if (state.equals("stopped")) {
                    if (eg != null) {
                        eg.setAttribute("state", "started");
                        updateTerminals(ALL, groupId);
                        updateView();
                        if (logEvents) {

                            ServerLogger logger = new ServerLogger(groupId, logPath);
                            try {
                                logger.resume(Integer.parseInt(timeStr.substring(0, 2)),
                                        Integer.parseInt(timeStr.substring(3, 5)),
                                        Integer.parseInt(timeStr.substring(6, 8)));
                            } catch (Exception e) {
                                logger.resume();
                            }

                            loggerMap.put(groupId, logger);
                            logger.print("GROUP START id[" + groupId + "]");
                        }
                        return true;
                    }
                } else if (state.equals("paused")) {
                    eg.setAttribute("state", "started");
                    updateView();
                    ServerLogger logger = loggerMap.get(groupId);
                    if (logger != null) {
                        try {
                            logger.resume(Integer.parseInt(timeStr.substring(0, 2)),
                                    Integer.parseInt(timeStr.substring(3, 5)),
                                    Integer.parseInt(timeStr.substring(6, 8)));
                        } catch (Exception e) {
                            logger.resume();
                        }
                        logger.print("GROUP CONTINUE id[" + groupId + "]");
                    }
                    return true;
                } else {
                    if (confirm) {
                        JOptionPane.showMessageDialog(frame, "Group is already started!", "Info!",
                                JOptionPane.INFORMATION_MESSAGE);
                    }
                    ServerLogger logger = loggerMap.get(groupId);
                    if (logger != null) {
                        try {
                            logger.resume(Integer.parseInt(timeStr.substring(0, 2)),
                                    Integer.parseInt(timeStr.substring(3, 5)),
                                    Integer.parseInt(timeStr.substring(6, 8)));
                        } catch (Exception e) {
                            logger.resume();
                        }
                    }
                }
            }
        }

        return false;
    }

    private void menuChoiceGroupPause() {
        menuChoiceGroupPause(-1);
    }

    private void menuChoiceGroupPause(int gid) {
        if (isSwapping)
            return;
        boolean confirm = (gid == -1);
        if (gid == -1) {
            log.info("Menu: Pause group");
            GroupSelectDialog dlg = new GroupSelectDialog(frame, doc, "Pause group...");
            gid = dlg.showDialog();
        }
        if (gid > 0) {
            synchronized (doc) {
                Element eg = DomTools.getElementFromSection(doc, "GroupDefs", "id", String.valueOf(gid));
                String state = DomTools.getAttributeString(eg, "state", "stopped", false);

                if (state.equals("started")) {
                    isaStop(gid);
                    eg.setAttribute("state", "paused");
                    updateView();
                    ServerLogger logger = loggerMap.get(gid);
                    if (logger != null) {
                        logger.print("GROUP PAUSE id[" + gid + "]");
                        logger.suspend();
                    }
                } else if (state.equals("paused")) {
                    eg.setAttribute("state", "started");
                    updateView();
                    ServerLogger logger = loggerMap.get(gid);
                    if (logger != null) {
                        logger.resume();
                        logger.print("GROUP CONTINUE id[" + gid + "]");
                    }
                } else {
                    if (confirm) {
                        JOptionPane.showMessageDialog(frame, "Group is not started!", "Info!",
                                JOptionPane.INFORMATION_MESSAGE);
                    }
                }
            }
        }
    }

    public boolean menuChoiceGroupStop(int groupId) {
        return menuChoiceGroupStop(groupId, true);
    }

    public boolean menuChoiceGroupStop(int groupId, boolean confirm) {
        if (isSwapping)
            return false;
        log.info("Menu: Stop group");

        boolean showConfirmDialog = (groupId != 0) && confirm;

        // Select dialog if no group id specified
        if (groupId == 0) {
            GroupSelectDialog dlg = new GroupSelectDialog(frame, doc, "Stop group...");
            groupId = dlg.showDialog();
        }

        if (groupId > 0) {
            Element eg = null;
            if (showConfirmDialog) {
                String name = "G/" + groupId;
                synchronized (doc) {
                    eg = DomTools.getElementFromSection(doc, "GroupDefs", "id", String.valueOf(groupId));
                    name = DomTools.getChildText(eg, "Name", "G/" + groupId, false);
                }
                int r = JOptionPane.showConfirmDialog(frame, "Are you sure you want to stop group " + name + "?",
                        "Stop group?", JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE);
                if (r != JOptionPane.YES_OPTION)
                    return false;
            }
            isaStop(groupId);
            synchronized (doc) {
                if (eg == null)
                    eg = DomTools.getElementFromSection(doc, "GroupDefs", "id", String.valueOf(groupId));
                String state = DomTools.getAttributeString(eg, "state", "stopped", false);

                if (state.equals("started") || state.equals("paused")) {
                    eg.setAttribute("state", "stopped");
                    updateTerminals(ALL, groupId);
                    updateView();
                    ServerLogger logger = loggerMap.get(groupId);
                    if (logger != null) {
                        logger.print("GROUP STOP id[" + groupId + "]");
                        loggerMap.remove(groupId);
                        logger.close();
                    }
                    return true;
                } else {
                    if (confirm) {
                        JOptionPane.showMessageDialog(frame, "Group is already stopped!", "Info!",
                                JOptionPane.INFORMATION_MESSAGE);
                    }
                }
            }
        }

        return false;
    }

    public void actionPerformed(ActionEvent e) {
        JMenuItem item = (JMenuItem) e.getSource();

        // File menu
        if (item == itemLoadConfig) {
            if (serverStartedDate == null)
                menuChoiceLoadConfiguration();
            else
                JOptionPane.showMessageDialog(frame,
                        "The server must be stopped before loading a new configuration!", "Info!",
                        JOptionPane.INFORMATION_MESSAGE);
        } else if (item == itemLoadExercise) {
            if (isConfigLoaded && serverStartedDate != null) {
                menuChoiceLoadExercise();
                panel.deselectAll();
            } else
                JOptionPane.showMessageDialog(frame,
                        "Unable to load exercise! Configuration is not loaded or server is not started!", "Error!",
                        JOptionPane.ERROR_MESSAGE);
        } else if (item == itemExit) {
            if (Config.SERVER_EXIT_DIALOG) {
                if ((JOptionPane.showConfirmDialog(frame, "Are you sure you want to exit the server control panel?",
                        "Exit?", JOptionPane.YES_NO_OPTION,
                        JOptionPane.WARNING_MESSAGE) == JOptionPane.YES_OPTION)) {
                    if (serverStartedDate != null) {
                        menuChoiceServerStop(true);
                    }
                    log.info("Bye!");
                    System.exit(0);
                }
            } else {
                if (serverStartedDate != null) {
                    menuChoiceServerStop(true);
                }
                log.info("Bye!");
                System.exit(0);
            }
        }

        // Server menu
        else if (item == itemServerStart) {
            if (isConfigLoaded) {
                if (serverStartedDate == null) {
                    menuChoiceServerStart();
                    itemServerRestart.setEnabled(true);
                } else
                    JOptionPane.showMessageDialog(frame, "The server is already started!", "Info!",
                            JOptionPane.INFORMATION_MESSAGE);
            } else
                JOptionPane.showMessageDialog(frame,
                        "Unable to start server! Configuration file has not been loaded!", "Error!",
                        JOptionPane.ERROR_MESSAGE);
        } else if (item == itemServerStop) {
            if (isConfigLoaded) {
                if (serverStartedDate != null) {
                    menuChoiceServerStop(false);
                    itemServerRestart.setEnabled(false);
                } else
                    JOptionPane.showMessageDialog(frame, "The server is already stopped!", "Info!",
                            JOptionPane.INFORMATION_MESSAGE);
            } else
                JOptionPane.showMessageDialog(frame,
                        "Unable to stop server! Configuration file has not been loaded!", "Error!",
                        JOptionPane.ERROR_MESSAGE);
        } else if (item == itemServerRestart) {
            if (isConfigLoaded) {
                if (serverStartedDate != null)
                    serverRestart();
                else
                    JOptionPane.showMessageDialog(frame, "The server is not running!", "Info!",
                            JOptionPane.INFORMATION_MESSAGE);
            } else
                JOptionPane.showMessageDialog(frame,
                        "Unable to restart server! Configuration file has not been loaded!", "Error!",
                        JOptionPane.ERROR_MESSAGE);
        } else if (item == itemServerMonitor) {
            menuChoiceNetworkMonitor();
        } else if (item == itemTerminalLink) {
            if (isConfigLoaded && serverStartedDate != null) {
                if (menuChoiceTerminalLink(0))
                    panel.deselectAll();
            } else
                JOptionPane.showMessageDialog(frame,
                        "Unable to link terminal! Configuration is not loaded or server is not started!", "Error!",
                        JOptionPane.ERROR_MESSAGE);
        } else if (item == itemTerminalUnlink) {
            if (isConfigLoaded && serverStartedDate != null) {
                if (menuChoiceTerminalUnlink(0))
                    panel.deselectAll();
            } else
                JOptionPane.showMessageDialog(frame,
                        "Unable to unlink terminal! Configuration is not loaded or server is not started!",
                        "Error!", JOptionPane.ERROR_MESSAGE);
        } else if (item == itemTerminalUnlinkAll) {
            if (isConfigLoaded && serverStartedDate != null) {
                menuChoiceTerminalUnlinkAll();
                panel.deselectAll();
            } else
                JOptionPane.showMessageDialog(frame,
                        "Unable to unlink terminals! Configuration is not loaded or server is not started!",
                        "Error!", JOptionPane.ERROR_MESSAGE);
        } else if (item == itemTerminalSwap) {
            if (isConfigLoaded && serverStartedDate != null) {
                menuChoiceTerminalSwap(0, 0);
                panel.deselectAll();
            } else
                JOptionPane.showMessageDialog(frame,
                        "Unable to move terminal! Configuration is not loaded or server is not started!", "Error!",
                        JOptionPane.ERROR_MESSAGE);
        } else if (item == itemGroupStart) {
            if (isConfigLoaded && serverStartedDate != null) {
                if (menuChoiceGroupStart(0))
                    panel.deselectAll();
            } else
                JOptionPane.showMessageDialog(frame,
                        "Unable to start group! Configuration is not loaded or server is not started!", "Error!",
                        JOptionPane.ERROR_MESSAGE);
        } else if (item == itemGroupPause) {
            if (isConfigLoaded && serverStartedDate != null) {
                menuChoiceGroupPause();
                panel.deselectAll();
            } else
                JOptionPane.showMessageDialog(frame,
                        "Unable to pause group! Configuration is not loaded or server is not started!", "Error!",
                        JOptionPane.ERROR_MESSAGE);
        } else if (item == itemGroupStop) {
            if (isConfigLoaded && serverStartedDate != null) {
                if (menuChoiceGroupStop(0))
                    panel.deselectAll();
            } else
                JOptionPane.showMessageDialog(frame,
                        "Unable to stop group! Configuration is not loaded or server is not started!", "Error!",
                        JOptionPane.ERROR_MESSAGE);
        }
    }

    public int translateRoleIdToTerminalId(int roleId) {
        int terminalId = 0;
        synchronized (doc) {
            Iterator iter = doc.getRootElement().getChild("PlayerSetup").getChildren().iterator();
            while (iter.hasNext()) {
                Element ep = (Element) iter.next();
                Iterator iter2 = ep.getChild("RoleSetup").getChildren().iterator();
                while (iter2.hasNext()) {
                    Element err = (Element) iter2.next();
                    if (roleId == DomTools.getAttributeInt(err, "uid", 0, false)) {
                        if (terminalId != 0) {
                            int t = DomTools.getAttributeInt(ep, "terminalid", 0, true);
                            log.warn("Role " + (roleId & ID_MASK_ROLE) + " in group "
                                    + ((roleId & ID_MASK_GROUP) >> ID_BITSHIFT) + ") is being played on "
                                    + "more than one terminal (Terminal " + terminalId + " and " + t + ")!");
                        } else
                            terminalId = DomTools.getAttributeInt(ep, "terminalid", 0, true);
                    }
                }
            }
        }
        return terminalId;
    }

    public void terminalOnline(ServerTerminal terminal) {
        synchronized (doc) {
            int tid = terminal.getId();
            Element et = DomTools.getElementFromSection(doc, "TerminalDefs", "id", String.valueOf(tid));
            if (et != null) {
                // Update model
                et.setAttribute("online", "true");

                if ((tid == monitoredTerminalId) || (tid == monitoringTerminalId))
                    monitorStart();

                updateTerminals(tid, ALL);
                updateView();
            } else {
                log.warn("Unknown terminal has gone online (" + tid + "), ignoring!");
            }
        }
    }

    public void terminalOffline(ServerTerminal terminal) {
        synchronized (doc) {
            Element et = DomTools.getElementFromSection(doc, "TerminalDefs", "id",
                    String.valueOf(terminal.getId()));
            if (et != null) {
                // Update model
                et.setAttribute("online", "false");
                updateView();
            } else {
                log.warn("Unknown terminal has gone offline (" + terminal.getId() + "), ignoring!");
            }
        }
        isaClients.remove(terminal.getId());
    }

    public void terminalStarted(ServerTerminal terminal) {
        Collection<ServerTerminal> terminalCollection = bundle.getTerminalCollection();
        Iterator<ServerTerminal> iter = terminalCollection.iterator();
        while (iter.hasNext()) {
            ServerTerminal t = iter.next();
            if (terminal != t && t.isStarted() && t.isConnected()) {
                sendRoleActivitiyPacket(t, terminal);
                sendRoleActivitiyPacket(terminal, t);
            }
        }
    }

    public void terminalStopped(ServerTerminal terminal) {
        Collection<ServerTerminal> terminalCollection = bundle.getTerminalCollection();
        Iterator<ServerTerminal> iter = terminalCollection.iterator();
        while (iter.hasNext()) {
            ServerTerminal t = iter.next();
            if (terminal != t && t.isStarted() && t.isConnected()) {
                // The terminal who stops should not get an update, timeout will occur!
                sendRoleActivitiyPacket(t, terminal);
            }
        }
    }

    public boolean isaStartStop(final int terminalId) {
        ServerTerminal t = bundle.getTerminal(terminalId);
        // Get linked player
        Element eps = DomTools.getElementFromSection(doc, "PlayerSetup", "terminalid", String.valueOf(terminalId));
        // Get group id
        final int groupId = DomTools.getAttributeInt(eps, "groupid", 0, true);
        if (t.isConnected() && t.isStarted()) {
            Element eg = DomTools.getElementFromSection(doc, "GroupDefs", "id", String.valueOf(groupId));
            String state = DomTools.getAttributeString(eg, "state", "stopped", false);
            if (state.equals("started")) {
                log.debug("Send ISA request to terminal " + terminalId);
                networkManager.sendIsaStartRequest(terminalId, isaPeriod, isaNumChoices, isaExtendedMode,
                        isakeytext);
                if (isaClients.contains(terminalId)) {
                    isaClients.remove(terminalId);
                } else {
                    isaClients.add(terminalId);
                    return true;
                }
            }
        } else {
            log.debug("Client " + terminalId + " not connected");
        }
        return false;
    }

    private void isaStop(final int groupId) {
        int terminalId;
        int terminalGid;
        Iterator<Integer> iter = isaClients.iterator();
        Vector<Integer> removeClients = new Vector<Integer>();
        while (iter.hasNext()) {
            terminalId = iter.next();
            if (groupId > 0) {
                ServerTerminal t = bundle.getTerminal(terminalId);
                // Get linked player
                Element eps = DomTools.getElementFromSection(doc, "PlayerSetup", "terminalid",
                        String.valueOf(terminalId));
                // Get group id
                terminalGid = DomTools.getAttributeInt(eps, "groupid", 0, true);
                if (terminalGid == groupId) {
                    networkManager.sendIsaStartRequest(terminalId, isaPeriod, isaNumChoices, isaExtendedMode,
                            isakeytext);
                    removeClients.add(terminalId);
                }
            } else {
                networkManager.sendIsaStartRequest(terminalId, isaPeriod, isaNumChoices, isaExtendedMode,
                        isakeytext);
                removeClients.add(terminalId);
            }
        }
        for (int i = 0; i < removeClients.size(); i++) {
            isaClients.remove(removeClients.get(i));
        }
    }

    public boolean isaClient(int terminalId) {
        return isaClients.contains(terminalId);
    }

    public String getIsaTracePainter() {
        return isaTracePainter;
    }

    public boolean getIsaExtendedMode() {
        return isaExtendedMode;
    }

    private void radioPrintLog(ServerTerminal terminal, ServerChannel channel, boolean acquired, boolean success) {
        if (!logEvents)
            return;

        Element ep = DomTools.getElementFromSection(doc, "PlayerSetup", "terminalid",
                String.valueOf(terminal.getId()));
        if (ep != null) {
            String text;
            ServerLogger logger;
            int channelId = channel.getId() & ID_MASK_CHANNEL;
            Element ec = DomTools.getElementFromSection(doc, "ChannelDefs", "id", String.valueOf(channelId));
            String channelName = DomTools.getChildText(ec, "Name", "C/" + channelId, true);
            String playerName = DomTools.getChildText(ep, "Name", "P/?", true);
            int groupIdA = DomTools.getAttributeInt(ep, "groupid", 0, true);
            int groupIdB = (channel.getId() & ID_MASK_GROUP) >> ID_BITSHIFT;
            if (acquired) {
                if (success)
                    text = "RADIO ACQUIRESUCCESS ch[" + channelName + "] pl[" + playerName + "]";
                else
                    text = "RADIO ACQUIREFAIL ch[" + channelName + "] pl[" + playerName + "]";
            } else
                text = "RADIO RELEASE ch[" + channelName + "] pl[" + playerName + "]";
            logger = loggerMap.get(groupIdA);
            if (logger != null) {
                logger.print(text);
            }
            if (groupIdA != groupIdB) {
                logger = loggerMap.get(groupIdB);
                if (logger != null) {
                    logger.print(text);
                }
            }
        }
    }

    public void radioAcquired(ServerTerminal terminal, ServerChannel channel, boolean success) {
        radioPrintLog(terminal, channel, true, success);
    }

    public void radioReleased(ServerTerminal terminal, ServerChannel channel) {
        radioPrintLog(terminal, channel, false, true);
    }

    private void phonePrintLog(ServerTerminal sourceTerminal, ServerTerminal destTerminal, int sourceRoleId,
            int destRoleId, int type, boolean success) {
        if (!logEvents)
            return;
        if ((sourceRoleId == 0) || (destRoleId == 0))
            return;

        String text;
        ServerLogger logger;

        Element ers = DomTools.getElementFromSection(doc, "RoleDefs", "id",
                String.valueOf(sourceRoleId & ID_MASK_ROLE));
        Element erd = DomTools.getElementFromSection(doc, "RoleDefs", "id",
                String.valueOf(destRoleId & ID_MASK_ROLE));

        String sourceRoleName = DomTools.getChildText(ers, "Name", "R/" + (sourceRoleId & ID_MASK_ROLE), false);
        String destRoleName = DomTools.getChildText(erd, "Name", "R/" + (destRoleId & ID_MASK_ROLE), false);

        int groupIds = (sourceRoleId & ID_MASK_GROUP) >> ID_BITSHIFT;
        int groupIdd = (destRoleId & ID_MASK_GROUP) >> ID_BITSHIFT;

        Element egs = DomTools.getElementFromSection(doc, "GroupDefs", "id", String.valueOf(groupIds));
        Element egd = DomTools.getElementFromSection(doc, "GroupDefs", "id", String.valueOf(groupIdd));

        sourceRoleName = DomTools.getChildText(egs, "Name", "G/" + (groupIds), false) + "/" + sourceRoleName;
        destRoleName = DomTools.getChildText(egd, "Name", "G/" + (groupIdd), false) + "/" + destRoleName;

        if (type == 1) { // Ring
            if (success)
                text = "PHONE CALLSUCCESS rs[" + sourceRoleName + "] rd[" + destRoleName + "]";
            else
                text = "PHONE CALLFAIL rs[" + sourceRoleName + "] rd[" + destRoleName + "]";
        } else if (type == 2) { // Answer
            if (success)
                text = "PHONE ANSWERSUCCESS rs[" + sourceRoleName + "] rd[" + destRoleName + "]";
            else
                text = "PHONE ANSWERFAIL rs[" + sourceRoleName + "] rd[" + destRoleName + "]";
        } else // Hang up
            text = "PHONE HANGUP rs[" + sourceRoleName + "] rd[" + destRoleName + "]";

        logger = loggerMap.get(groupIds);
        if (logger != null) {
            logger.print(text);
        }
        if (groupIds != groupIdd) {
            logger = loggerMap.get(groupIdd);
            if (logger != null) {
                logger.print(text);
            }
        }
    }

    public void phoneRing(ServerTerminal sourceTerminal, ServerTerminal destTerminal, int sourceRoleId,
            int destRoleId, boolean success) {
        if (success)
            updatePhoneLineStatus(sourceTerminal, destTerminal);
        phonePrintLog(sourceTerminal, destTerminal, sourceRoleId, destRoleId, 1, success);
    }

    public void phoneAnswer(ServerTerminal sourceTerminal, ServerTerminal destTerminal, int sourceRoleId,
            int destRoleId, boolean success) {
        phonePrintLog(sourceTerminal, destTerminal, sourceRoleId, destRoleId, 2, success);
    }

    public void phoneHangup(ServerTerminal sourceTerminal, ServerTerminal destTerminal, int sourceRoleId,
            int destRoleId) {
        updatePhoneLineStatus(sourceTerminal, destTerminal);
        phonePrintLog(sourceTerminal, destTerminal, sourceRoleId, destRoleId, 3, true);
    }

    public void setIsaStartTime(long isaStartTime) {
        this.isaStartTime = isaStartTime;
    }

    public synchronized void addIsaValue(int terminalId, int value, int responseTime) {
        long t = System.currentTimeMillis();
        //log.info(t + " <- Add ISA value, ID: " + requestId + ", map size: " + isaLogMap.size());
        Element ep = DomTools.getElementFromSection(doc, "PlayerSetup", "terminalid", String.valueOf(terminalId));
        if (ep != null) {
            ServerLogger logger;
            String playerName = DomTools.getChildText(ep, "Name", "P/?", true);
            int groupId = DomTools.getAttributeInt(ep, "groupid", 0, true);
            Element eg = DomTools.getElementFromSection(doc, "GroupDefs", "id", String.valueOf(groupId));
            String state = DomTools.getAttributeString(eg, "state", "stopped", false);
            if (state.equals("started")) {
                logger = loggerMap.get(groupId);
                if (logger != null) {
                    logger.print("ISA RESPONSE val[" + value + "] rt[" + responseTime + "] pl[" + playerName + "]");
                    // Add entry to ISA panel
                    server.panel.addIsaValue(logger.getSimTime(), terminalId, value);
                } else { // Logging not active, use ISA start time in ISA panel
                         // Add entry to ISA panel
                    server.panel.addIsaValue((t - isaStartTime), terminalId, value);
                }
            }
        }
    }

    private String s2(int value) {
        if (value < 10)
            return "0" + value;
        else
            return String.valueOf(value);
    }

    public static LanziusServer server;

    public static void start() {

        server = new LanziusServer();

        // Make sure we have nice window decorations.
        // Only if workspace is not fullscreen
        if (!Config.SERVER_SIZE_FULLSCREEN)
            JFrame.setDefaultLookAndFeelDecorated(true);
        JDialog.setDefaultLookAndFeelDecorated(true);

        //Schedule a job for the event-dispatching thread:
        //creating and showing this application's GUI.
        javax.swing.SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                server.init();
            }
        });
    }

    public static void start(String configFilename) {
        start();

        File configFile = new File(configFilename);

        if (configFile.exists()) {
            if (server.buildConfigurationDocument(configFile)) {
                server.isConfigLoaded = true;
                // updateView() probably not needed as it is just started.
                server.menuChoiceServerStart();
                server.itemServerRestart.setEnabled(true);
            } else {
                System.out.println("Error in configuration file:" + configFile);
                System.exit(1);
            }
        } else {
            System.out.println("Unable to load configuration! File not found:" + configFile);
            System.exit(1);
        }
    }

    public static void start(String configFilename, String exerciseFilename) {
        start(configFilename);

        File exerciseFile = new File(exerciseFilename);
        if (exerciseFile.exists()) {
            server.loadExercise(exerciseFile);
        } else {
            System.out.println("Unable to load exercise! File not found:" + exerciseFile);
            System.exit(1);
        }
    }
}