Download | Plain Text | Line Numbers


/*
 * Copyright (c) 2010, Manuel Mausz. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 *   - Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 *
 *   - Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in the
 *     documentation and/or other materials provided with the distribution.
 *
 *   - The names of the authors may not be used to endorse or promote products
 *     derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
 * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
 
import static java.lang.System.err;
import static java.lang.System.out;
 
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
 
import java.rmi.registry.*;
import java.rmi.server.*;
import java.rmi.*;
 
import java.text.SimpleDateFormat;
import java.lang.StringBuffer;
import java.util.Collections;
import java.util.Comparator;
import java.util.Properties;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Arrays;
import java.util.Date;
import java.util.Set;
import java.util.Map;
 
import java.io.*;
 
/*
 * Server implementation for Lab#2 of DSLab WS10
 * See angabe.pdf for details
 *
 * This code is not documented at all. This is volitional
 *
 * @author Manuel Mausz (0728348)
 */
public class Server
  implements Serializable
{
  public class ServerRecords
    extends HashMap<String, S2SInterface>
  {}
 
  /*==========================================================================*/
 
  public class UserRecord
      implements Serializable
  {
    public final String name;
    public final String server;
    public String pass = null;
    public boolean committed = false;
    public S2CInterface s2ciface = null;
 
    UserRecord(String name, String server)
    {
      this.name   = name;
      this.server = server;
    }
 
    /*------------------------------------------------------------------------*/
 
    public synchronized boolean isCommitted()
    {
      return committed;
    }
 
    /*------------------------------------------------------------------------*/
 
    public synchronized void commit()
    {
      committed = true;
    }
  }
 
  /*==========================================================================*/
 
  public class UserRecords
    extends HashMap<String, UserRecord>
  {
    public synchronized boolean register(String user, String pass)
    {
      if (!registerPrepare(user, bindingName))
        return false;
 
      synchronized(servers)
      {
        ArrayList<String> rollbacklist = new ArrayList<String>();
        boolean ret = true;
 
        for(String server : servers.keySet())
        {
          try
          {
            ret = servers.get(server).registerPrepare(user, bindingName);
          }
          catch(RemoteException e)
          {
            ret = false;
          }
 
          if (!ret)
            break;
          rollbacklist.add(server);
        }
 
        if (!ret)
        {
          for(String server : rollbacklist)
          {
            try
            {
              servers.get(server).registerRollback(user);
            }
            catch(RemoteException e)
            {}
          }
          registerRollback(user);
          return false;
        }
 
        for(String server : servers.keySet())
        {
          try
          {
            servers.get(server).registerCommit(user);
          }
          catch(RemoteException e)
          {}
        }
 
        registerCommit(user);
        get(user).pass = pass;
      }
 
      return true;
    }
 
    /*------------------------------------------------------------------------*/
 
    public synchronized boolean registerPrepare(String user, String server)
    {
      if (containsKey(user))
        return false;
      put(user, new UserRecord(user, server));
      return true;
    }
 
    /*------------------------------------------------------------------------*/
 
    public synchronized boolean registerCommit(String user)
    {
      if (!containsKey(user))
        return false;
      get(user).commit();
      return true;
    }
 
    /*------------------------------------------------------------------------*/
 
    public synchronized boolean registerRollback(String user)
    {
      out.println("rollback");
      if (!containsKey(user))
        return true;
      if (get(user).isCommitted())
        return false;
      remove(user);
      return true;
    }
 
    /*------------------------------------------------------------------------*/
 
    public synchronized UserRecord login(String user, String pass, S2CInterface s2ciface)
    {
      UserRecord record = get(user);
      if (record == null)
        return null;
 
      synchronized(record)
      {
        if (record.pass == null || !record.pass.equals(pass) || !record.isCommitted())
          return null;
 
        try
        {
          if (record.s2ciface != null)
          {
            record.s2ciface.notify("Another user logged in using your credentials. You will get logged out now.");
            record.s2ciface.logout();
          }
        }
        catch(RemoteException e)
        {}
 
        record.s2ciface = s2ciface;
      }
      return record;
    }
 
    /*------------------------------------------------------------------------*/
 
    public synchronized void logout(UserRecord user)
    {
      if (user != null)
      {
        synchronized(user)
        {
          user.s2ciface = null;
        }
      }
    }
 
    /*------------------------------------------------------------------------*/
 
    public synchronized void notify(String user, String msg)
      throws RemoteException
    {
      UserRecord record = get(user);
      if (record == null)
        throw new RemoteException("User doesn't exist.");
      synchronized(record)
      {
        if (!record.isCommitted())
          throw new RemoteException("User doesn't exist.");
 
        if (!record.server.equals(bindingName))
        {
          S2SInterface tmp = servers.get(record.server);
          if (tmp != null)
          {
            try
            {
              tmp.notifyUser(user, msg);
            }
            catch(RemoteException e)
            {}
          }
          return;
        }
 
        if (record.s2ciface != null)
          record.s2ciface.notify(msg);
      }
    }
  }
 
  /*==========================================================================*/
 
  public class EventRecord
      implements Serializable
  {
    public final String name;
    public String location;
    public int duration;
    public String author = null;
    public final String server;
    public boolean committed = false;
    public HashMap<Date, Integer> dates;
    public Date date = null;
    public HashSet<String> invitees;
    public HashSet<String> voted;
 
    EventRecord(String name, String server)
    {
      this.name   = name;
      this.server = server;
      dates    = new HashMap<Date, Integer>();
      invitees = new HashSet<String>();
      voted    = new HashSet<String>();
    }
 
    /*------------------------------------------------------------------------*/
 
    public synchronized boolean isCommitted()
    {
      return committed;
    }
 
    /*------------------------------------------------------------------------*/
 
    public synchronized void commit()
    {
      committed = true;
    }
  }
 
  /*==========================================================================*/
 
  public class EventRecords
    extends HashMap<String, EventRecord>
  {
    private SimpleDateFormat datefmt = new SimpleDateFormat("dd.MM.yyyy/HH:mm");
 
    public synchronized boolean create(UserRecord user, String event,
        String location, int duration)
    {
      if (!createPrepare(event, bindingName))
        return false;
 
      synchronized(servers)
      {
        ArrayList<String> rollbacklist = new ArrayList<String>();
        boolean ret = true;
 
        for(String server : servers.keySet())
        {
          try
          {
            ret = servers.get(server).createPrepare(event, bindingName);
          }
          catch(RemoteException e)
          {
            ret = false;
          }
 
          if (!ret)
            break;
          rollbacklist.add(server);
        }
 
        if (!ret)
        {
          for(String server : rollbacklist)
          {
            try
            {
              servers.get(server).createRollback(event);
            }
            catch(RemoteException e)
            {}
          }
          createRollback(event);
          return false;
        }
 
        for(String server : servers.keySet())
        {
          try
          {
            servers.get(server).createCommit(event);
          }
          catch(RemoteException e)
          {}
        }
 
        createCommit(event);
        EventRecord record = get(event);
        synchronized(record)
        {
          record.location = location;
          record.duration = duration;
          synchronized(user)
          {
            record.author = user.name;
          }
        }
      }
 
      return true;
    }
 
    /*------------------------------------------------------------------------*/
 
    public synchronized boolean createPrepare(String event, String server)
    {
      if (containsKey(event))
        return false;
      put(event, new EventRecord(event, server));
      return true;
    }
 
    /*------------------------------------------------------------------------*/
 
    public synchronized boolean createCommit(String event)
    {
      if (!containsKey(event))
        return false;
      get(event).commit();
      return true;
    }
 
    /*------------------------------------------------------------------------*/
 
    public synchronized boolean createRollback(String event)
    {
      if (!containsKey(event))
        return true;
      if (get(event).isCommitted())
        return false;
      remove(event);
      return true;
    }
 
    /*------------------------------------------------------------------------*/
 
    public synchronized void addDate(UserRecord user, String event, Date date)
      throws RemoteException
    {
      EventRecord record = get(event);
      if (record == null)
        throw new RemoteException("Event doesn't exist.");
      synchronized(record)
      {
        if (!record.isCommitted())
          throw new RemoteException("Event doesn't exist.");
        synchronized(user)
        {
          if (record.author == null || !record.author.equals(user.name))
            throw new RemoteException("You're not the author of that event.");
        }
        if (record.date != null)
          throw new RemoteException("Event already have been finalized.");
        if (record.dates.containsKey(date))
          throw new RemoteException("Date already exists.");
        record.dates.put(date, 0);
      }
    }
 
    /*------------------------------------------------------------------------*/
 
    public synchronized void invite(UserRecord user, String event, String invitee)
      throws RemoteException
    {
      EventRecord record = get(event);
      if (record == null)
        throw new RemoteException("Event doesn't exist.");
      synchronized(record)
      {
        if (!record.isCommitted())
          throw new RemoteException("Event doesn't exist.");
        synchronized(user)
        {
          if (record.author == null || !record.author.equals(user.name))
            throw new RemoteException("You're not the author of that event.");
        }
        if (record.author.equals(invitee))
          throw new RemoteException("You can't invite yourself.");
        if (record.date != null)
          throw new RemoteException("Event already have been finalized.");
        if (record.invitees.contains(invitee))
          throw new RemoteException("User already invited.");
 
        users.notify(invitee, "You have been invited to event \"" + event + "\"");
        record.invitees.add(invitee);
      }
    }
 
    /*------------------------------------------------------------------------*/
 
    public synchronized String toString(String event)
      throws RemoteException
    {
      EventRecord record = get(event);
      if (record == null)
        throw new RemoteException("Event doesn't exist.");
 
      synchronized(record)
      {
        if (!record.isCommitted())
          throw new RemoteException("Event doesn't exist.");
        if (!record.server.equals(bindingName))
        {
          S2SInterface tmp = servers.get(record.server);
          if (tmp == null)
            throw new RemoteException("Internal Error: Remote server doesn't exist anymore.");
          return tmp.eventToString(event);
        }
 
        StringBuffer sb = new StringBuffer(100);
        sb.append("Event: ").append(record.name).append("\n");
        sb.append("Location: ").append(record.location).append("\n");
        sb.append("Duration: ").append(record.duration).append(" min.\n");
        sb.append("Author: ").append(record.author).append("\n");
 
        if (record.date != null)
          sb.append("Date: ").append(datefmt.format(record.date)).append("\n");
        else
        {
          if (record.dates.size() == 0)
            sb.append("Options: None set\n");
          else
          {
            sb.append("Options:\n");
            for(Date date : record.dates.keySet())
              sb.append("  * ").append(datefmt.format(date)).append(" ... ")
                .append(record.dates.get(date)).append(" votes\n");
          }
        }
 
        if (record.invitees.size() == 0)
          sb.append("Invitees: None set\n");
        else
          sb.append("Invitees: ").append(Utils.join(record.invitees, ", ")).append("\n");
 
        return sb.toString();
      }
    }
 
    /*------------------------------------------------------------------------*/
 
    public synchronized void vote(String event, String user, Date[] dates)
      throws RemoteException
    {
      EventRecord record = get(event);
      if (record == null)
        throw new RemoteException("Event doesn't exist.");
 
      synchronized(record)
      {
        if (!record.isCommitted())
          throw new RemoteException("Event doesn't exist.");
        if (!record.server.equals(bindingName))
        {
          S2SInterface tmp = servers.get(record.server);
          if (tmp == null)
            throw new RemoteException("Internal Error: Remote server doesn't exist anymore.");
          try
          {
            tmp.vote(event, user, dates);
          }
          catch(RemoteException e)
          {
            throw new RemoteException(e.getCause().getMessage());
          }
          return;
        }
 
        if (record.date != null)
          throw new RemoteException("Event already have been finalized.");
        if (record.voted.contains(user))
          throw new RemoteException("You have already voted.");
        if ((record.author == null || !record.author.equals(user))
            && !record.invitees.contains(user))
          throw new RemoteException("You're not allowed to vote for that event.");
 
        Set recorddates = record.dates.keySet();
        for(Date date : dates)
        {
          if (!recorddates.contains(date))
            throw new RemoteException("Date \"" + datefmt.format(date) + "\" is not a valid option.");
        }
 
        for(Date date : dates)
          record.dates.put(date, record.dates.get(date) + 1);
        record.voted.add(user);
      }
    }
 
    /*------------------------------------------------------------------------*/
 
    public synchronized void finalize(UserRecord user, String event)
      throws RemoteException
    {
      EventRecord record = get(event);
      if (record == null)
        throw new RemoteException("Event doesn't exist.");
      synchronized(record)
      {
        synchronized(user)
        {
          if (!record.isCommitted())
            throw new RemoteException("Event doesn't exist.");
          if (record.author == null || !record.author.equals(user.name))
            throw new RemoteException("You're not the author of that event.");
          if (record.date != null)
            throw new RemoteException("Event has already been finalized.");
 
          record.date = Collections.max(record.dates.entrySet(),
              new Comparator<Map.Entry<Date, Integer>>() {
            public int compare(Map.Entry<Date, Integer> o1, Map.Entry<Date, Integer> o2)
            {
              return o1.getValue().compareTo(o2.getValue());
            }
          }).getKey();
 
          String text = "Event \"" + event + "\" has been scheduled on "
            + datefmt.format(record.date) + ".";
          users.notify(user.name, text);
          for(String invitee : record.invitees)
            users.notify(invitee, text);
        }
      }
    }
  }
 
  /*==========================================================================*/
 
  public class WaitForServers
      implements Runnable
  {
    private ArrayList<String> serverlist;
    WaitForServers()
    {
      serverlist = new ArrayList<String>();
      serverlist.addAll(Arrays.asList(serverNames));
      serverlist.add(bindingName);
      Collections.sort(serverlist);
    }
 
    /*------------------------------------------------------------------------*/
 
    public void shutdown()
    {
      scheduler.shutdown();
      synchronized(mainLock)
      {
        mainLock.notify();
      }
    }
 
    /*------------------------------------------------------------------------*/
 
    public void run()
    {
      synchronized(registry)
      {
        try
        {
          String[] tmp = registry.list();
          Collections.sort(Arrays.asList(tmp));
          if (serverlist.equals(Arrays.asList(tmp)))
          {
            synchronized(servers)
            {
              for(String server : tmp)
              {
                if (server.equals(bindingName))
                  continue;
                servers.put(server, ((RemoteInterface)registry.lookup(server)).s2sInterfaceFactory());
              }
            }
            shutdown();
          }
        }
        catch(NotBoundException e)
        {
          err.println("Error: " + e.getMessage()); /* this shouldn't happen */
          shutdown();
        }
        catch(RemoteException e)
        {
          err.println("Unable to lookup servers: " + e.getMessage());
          shutdown();
        }
      }
    }
  }
 
  /*==========================================================================*/
 
  public class Interactive
    extends CommandInteractive
    implements Runnable
  {
    private final InputStream sin;
    private final Object mainLock;
 
    Interactive(InputStream sin, Object mainLock)
      throws NoSuchMethodException, IOException
    {
      this.sin      = sin;
      this.mainLock = mainLock;
 
      setIgnoreEmptyMode(false);
      cmdHandler.register("unknown", this, "cmdUnknown");
    }
 
    /*------------------------------------------------------------------------*/
 
    public void cmdUnknown(String cmd, String[] args)
      throws IOException
    {
      stop();
    }
 
    /*------------------------------------------------------------------------*/
 
    public void shutdown()
    {}
 
    /*------------------------------------------------------------------------*/
 
    public void run()
    {
      try
      {
        run(sin);
      }
      catch(CommandHandler.Exception e)
      {
        err.println("Internal Error: " + e.getMessage());
      }
      catch(IOException e)
      {
        /* ignore that exception
         * thread will shutdown and unlock the main thread
         * which will shutdown the application
         */
      }
 
      shutdown();
      synchronized(mainLock)
      {
        mainLock.notify();
      }
    }
  }
 
  /*==========================================================================*/
 
  private static String bindingName;
  private static boolean initRegistry;
  private static String[] serverNames;
  private static InputStream stdin = null;
  private static Thread tInteractive = null;
  private static ScheduledExecutorService scheduler = null;
  private static Registry registry = null;
  private static RemoteInterface riface = null;
  private static ServerRecords servers;
  public UserRecords users;
  public EventRecords events;
  public volatile boolean ready = false;
  private static final Object mainLock = new Object();
 
  /*--------------------------------------------------------------------------*/
 
  Server()
  {
    servers = new ServerRecords();
    users   = new UserRecords();
    events  = new EventRecords();
  }
 
  /*--------------------------------------------------------------------------*/
 
  public static void usage()
    throws Utils.Shutdown
  {
    out.println("Usage: Server bindingName initRegistry serverNames\n");
    out.println("bindingName\t...the name this server shall use to bind its remote");
    out.println("\t\t   reference in the RMI registry");
    out.println("initRegistry\t...a boolean value, i.e. either true or false,");
    out.println("\t\t   indicating whether this server is responsible for");
    out.println("\t\t   creating the RMI registry or not");
    out.println("serverNames\t...a list of names, separated by space characters,");
    out.println("\t\t   indicating the name of the other servers' remote references");
 
    // Java is some piece of crap which doesn't allow me to set exitcode w/o
    // using System.exit. Maybe someday Java will be a fully functional
    // programming language, but I wouldn't bet my money
    //System.exit(1);
    throw new Utils.Shutdown("FUCK YOU JAVA");
  }
 
  /*--------------------------------------------------------------------------*/
 
  public void bailout(String error)
    throws Utils.Shutdown
  {
    if (error != null)
      err.println("Error: " + error);
    shutdown();
 
    // Java is some piece of crap which doesn't allow me to set exitcode w/o
    // using System.exit. Maybe someday Java will be a fully functional
    // programming language, but I wouldn't bet my money
    //System.exit(2);
    throw new Utils.Shutdown("FUCK YOU JAVA");
  }
 
  /*--------------------------------------------------------------------------*/
 
  public void parseArgs(String[] args)
  {
    if (args.length != 3)
      usage();
 
    bindingName = args[0];
    if (bindingName.length() == 0)
      bailout("bindingName is empty");
 
    initRegistry = false;
    if (args[1].equals("true"))
      initRegistry = true;
    else if (args[1].equals("false"))
      initRegistry = false;
    else
      bailout("initRegistry must be either \"true\" or \"false\"");
 
    serverNames = args[2].split(" ");
    if (Arrays.asList(serverNames).contains(bindingName))
      bailout("bindingName found in serverNames. You shouldn't do that!");
  }
 
  /*--------------------------------------------------------------------------*/
 
  public void createInterface()
  {
    String regHost = null;
    int regPort = 0;
 
    try
    {
      InputStream in = ClassLoader.getSystemResourceAsStream("registry.properties");
      if (in == null)
        bailout("Properties file doesn't exist or isn't readable");
      Properties props = new Properties();
      props.load(in);
      for(String prop : props.stringPropertyNames())
      {
        if (prop.equals("registry.host"))
          regHost = props.getProperty(prop);
        else if (prop.equals("registry.port"))
        {
          try
          {
            regPort = Integer.parseInt(props.getProperty(prop));
            if (regPort <= 0 || regPort > 65536)
            {
              err.println("Property " + prop + " must be a valid port number (1 - 65535). Skipping...");
              regPort = 0;
            }
          }
          catch(NumberFormatException e)
          {
            err.println("Property " + prop + " must be numeric. Skipping...");
          }
        }
        else
          err.println("Property " + prop + " is unknown. Skipping...");
      }
      in.close();
    }
    catch(IOException e)
    {
      bailout("Unable to read from properties file: " + e.getMessage());
    }
 
    if (!initRegistry && (regHost == null || regHost.length() <= 0))
      bailout("Registry host is not set");
    if (regPort == 0)
      bailout("Registry port is not set");
 
    try
    {
      if (initRegistry)
        registry = LocateRegistry.createRegistry(regPort);
      else
        registry = LocateRegistry.getRegistry(regHost, regPort);
 
      riface = new RemoteInterfaceImpl(this);
      //DEBUG RemoteServer.setLog(System.out);
      registry.bind(bindingName, riface);
    }
    catch(RemoteException e)
    {
      bailout("Unable to get/create registry: " + e.getMessage());
    }
    catch(AlreadyBoundException e)
    {
      registry = null;
      bailout("Unable to bind remote interface. Already bound: " + e.getMessage());
    }
 
    out.println("Remote interface successfully exported!");
  }
 
  /*--------------------------------------------------------------------------*/
 
  public void writeData(String file)
  {
    if (file == null)
      return;
 
    try
    {
      ObjectOutputStream sout = new ObjectOutputStream(new FileOutputStream(file));
      sout.writeObject(users);
      sout.writeObject(events);
      sout.close();
    }
    catch(FileNotFoundException e)
    {}
    catch(IOException e)
    {}
  }
 
  /*--------------------------------------------------------------------------*/
 
  public void readData(String file)
  {
    if (file == null)
      return;
 
    try
    {
      ObjectInputStream sin = new ObjectInputStream(new FileInputStream(file));
      users = (UserRecords)sin.readObject();
      events = (EventRecords)sin.readObject();
      sin.close();
    }
    catch(ClassNotFoundException e)
    {}
    catch(FileNotFoundException e)
    {}
    catch(IOException e)
    {}
  }
 
  /*--------------------------------------------------------------------------*/
 
  public void shutdown()
  {
    for(String user : users.keySet())
    {
      UserRecord record = users.get(user);
      synchronized(record)
      {
        record.s2ciface = null;
      }
    }
 
    try
    {
      if (scheduler != null)
      {
        scheduler.shutdownNow();
        scheduler.awaitTermination(Long.MAX_VALUE, TimeUnit.NANOSECONDS);
      }
    }
    catch(InterruptedException e)
    {}
 
    try
    {
      if (riface != null)
        riface.unexportAll();
    }
    catch(RemoteException e)
    {}
 
    try
    {
      if (registry != null)
        registry.unbind(bindingName);
    }
    catch(NotBoundException e)
    {}
    catch(RemoteException e)
    {}
 
    try
    {
      if (tInteractive != null)
      {
        tInteractive.interrupt();
        tInteractive.join();
      }
    }
    catch(InterruptedException e)
    {}
 
    try
    {
      if (stdin != null)
        stdin.close();
    }
    catch(IOException e)
    {}
 
    //PERSISTENT writeData(bindingName + ".data");
  }
 
  /*--------------------------------------------------------------------------*/
 
  public void run(String[] args)
  {
    boolean shutdown = false;
 
    parseArgs(args);
    //PERSISTENT readData(bindingName + ".data");
 
    synchronized(mainLock)
    {
      createInterface();
 
      try
      {
        InputStream stdin = java.nio.channels.Channels.newInputStream(
            new FileInputStream(FileDescriptor.in).getChannel());
        tInteractive = new Thread(new Interactive(stdin, mainLock));
        tInteractive.start();
      }
      catch(NoSuchMethodException e)
      {
        bailout("Unable to setup interactive command handler");
      }
      catch(IOException e)
      {
        bailout("Unable to create object output stream: " + e.getMessage());
      }
 
      try
      {
        out.println("Waiting for the other servers to come online...");
        scheduler = Executors.newScheduledThreadPool(1);
        ScheduledFuture<?> waitServerTimer = scheduler.scheduleAtFixedRate(
            new WaitForServers(), 0, 500, TimeUnit.MILLISECONDS);
        mainLock.wait();
        synchronized(servers)
        {
          if (!scheduler.isShutdown() || servers.size() != serverNames.length)
            shutdown = true;
        }
      }
      catch(InterruptedException e)
      {
        /* if we get interrupted -> ignore */
      }
 
      if (!shutdown)
      {
        ready = true;
        out.println("Server startup successful!");
        try
        {
          mainLock.wait();
        }
        catch(InterruptedException e)
        {
          /* if we get interrupted -> ignore */
        }
      }
    }
 
    out.println("Shutting down...");
    shutdown();
  }
 
  /*--------------------------------------------------------------------------*/
 
  public static void main(String[] args)
  {
    try
    {
      Server srv = new Server();
      srv.run(args);
    }
    catch(Utils.Shutdown e)
    {}
  }
}