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.text.SimpleDateFormat;
import java.text.ParseException;
 
import java.rmi.registry.*;
import java.rmi.server.*;
import java.rmi.*;
 
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.Collections;
import java.util.Properties;
import java.util.HashSet;
import java.util.Arrays;
import java.util.Date;
import java.util.Map;
 
import java.io.*;
 
/*
 * Client 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 Client
{
  public class WaitForServer
      implements Runnable
  {
    public void shutdown()
    {
      scheduler.shutdown();
      synchronized(mainLock)
      {
        mainLock.notify();
      }
    }
 
    public void run()
    {
      synchronized(registry)
      {
        try
        {
          int i;
          String[] tmp = registry.list();
          if ((i = Arrays.asList(tmp).indexOf(serverName)) != -1)
          {
            c2siface = ((RemoteInterface)registry.lookup(Arrays.asList(tmp).get(i))).c2sInterfaceFactory();
            if (c2siface != null)
              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;
    private boolean loggedin = false;
 
    Interactive(InputStream sin, Object mainLock)
      throws NoSuchMethodException, IOException
    {
      this.sin      = sin;
      this.mainLock = mainLock;
 
      cmdHandler.register("!register", this, "cmdRegister");
      cmdHandler.register("!login",    this, "cmdLogin");
      cmdHandler.register("!create",   this, "cmdCreate");
      cmdHandler.register("!addDate",  this, "cmdAddDate");
      cmdHandler.register("!invite",   this, "cmdInvite");
      cmdHandler.register("!get",      this, "cmdGet");
      cmdHandler.register("!vote",     this, "cmdVote");
      cmdHandler.register("!finalize", this, "cmdFinalize");
      cmdHandler.register("!logout",   this, "cmdLogout");
      cmdHandler.register("!exit",     this, "cmdExit");
      cmdHandler.register("!help",     this, "cmdHelp");
      cmdHandler.register("unknown",   this, "cmdUnknown");
    }
 
    /*------------------------------------------------------------------------*/
 
    public void cmdRegister(String cmd, String[] args)
    {
      if (args.length != 2)
      {
        err.println("Invalid Syntax: " + cmd + " <username> <password>");
        return;
      }
 
      try
      {
        if (c2siface.register(args[0], args[1]))
          out.println("Successfully registered.");
        else
          err.println("Username already registered.");
      }
      catch(RemoteException e)
      {
        err.println("Error: " + e.getCause().getMessage());
      }
    }
 
    /*------------------------------------------------------------------------*/
 
    public void cmdLogin(String cmd, String[] args)
    {
      if (args.length != 2)
      {
        err.println("Invalid Syntax: " + cmd + " <username> <password>");
        return;
      }
 
      try
      {
        if (c2siface.login(args[0], args[1], s2ciface))
        {
          out.println("Successfully logged in.");
          loggedin = true;
        }
        else
          err.println("Wrong username or password.");
      }
      catch(RemoteException e)
      {
        err.println("Error: " + e.getCause().getMessage());
      }
    }
 
    /*------------------------------------------------------------------------*/
 
    public void cmdCreate(String cmd, String[] args)
    {
      if (args.length != 3)
      {
        err.println("Invalid Syntax: " + cmd + " <name> <location> <duration in minutes>");
        return;
      }
 
      int duration = 0;
      try
      {
        duration = Integer.parseInt(args[2]);
        if (duration <= 0)
        {
          err.println("Error: Invalid argument: Duration must be a positive number.");
          return;
        }
      }
      catch(NumberFormatException e)
      {
        err.println("Error: Invalid argument: Duration must be numeric.");
        return;
      }
 
      try
      {
        if (c2siface.create(args[0], args[1], duration))
          out.println("Event created successfully.");
        else
          err.println("Unable to create Event. Event already exists.");
      }
      catch(RemoteException e)
      {
        err.println("Error: " + e.getCause().getMessage());
      }
    }
 
    /*------------------------------------------------------------------------*/
 
    public void cmdAddDate(String cmd, String[] args)
    {
      SimpleDateFormat datefmt = new SimpleDateFormat("dd.MM.yyyy/HH:mm");
      if (args.length != 2)
      {
        err.println("Invalid Syntax: " + cmd + " <name of event> <date: " + datefmt.toPattern() + ">");
        return;
      }
 
      Date date = null;
      try
      {
        date = datefmt.parse(args[1]);
      }
      catch(ParseException e)
      {}
      if (date == null)
      {
        err.println("Error: Invalid date argument. Syntax: <" + datefmt.toPattern() + ">.");
        return;
      }
 
      try
      {
        c2siface.addDate(args[0], date);
        out.println("Date option added.");
      }
      catch(RemoteException e)
      {
        err.println("Error: Unable to add date: " + e.getCause().getMessage());
      }
    }
 
    /*------------------------------------------------------------------------*/
 
    public void cmdInvite(String cmd, String[] args)
    {
      if (args.length != 2)
      {
        err.println("Invalid Syntax: " + cmd + " <name of event> <username>");
        return;
      }
 
      try
      {
        c2siface.invite(args[0], args[1]);
        out.println("User invited.");
      }
      catch(RemoteException e)
      {
        err.println("Error: Unable to invite user: " + e.getCause().getMessage());
      }
    }
 
    /*------------------------------------------------------------------------*/
 
    public void cmdGet(String cmd, String[] args)
    {
      if (args.length != 1)
      {
        err.println("Invalid Syntax: " + cmd + " <name of event>");
        return;
      }
 
      try
      {
        out.print(c2siface.event2String(args[0]));
        out.flush();
      }
      catch(RemoteException e)
      {
        err.println("Error: Unable to fetch event: " + e.getCause().getMessage());
      }
    }
 
    /*------------------------------------------------------------------------*/
 
    public void cmdVote(String cmd, String[] args)
    {
      SimpleDateFormat datefmt = new SimpleDateFormat("dd.MM.yyyy/HH:mm");
      if (args.length < 2)
      {
        err.println("Invalid Syntax: " + cmd + " <name of event> <date: " + datefmt.toPattern() + "> ...");
        return;
      }
 
      int i = 1;
      HashSet<Date> dates = new HashSet<Date>();
 
      try
      {
        for(i = 1; i < args.length; ++i)
          dates.add(datefmt.parse(args[i]));
      }
      catch(ParseException e)
      {
        err.println("Error: Invalid date \"" + args[i] + "\". Syntax: <" + datefmt.toPattern() + ">.");
        return;
      }
 
      try
      {
        c2siface.vote(args[0], dates.toArray(new Date[dates.size()]));
        out.println("Vote registered.");
      }
      catch(RemoteException e)
      {
        err.println("Error: Unable to vote: " + e.getCause().getMessage());
      }
    }
 
    /*------------------------------------------------------------------------*/
 
    public void cmdFinalize(String cmd, String[] args)
    {
      if (args.length != 1)
      {
        err.println("Invalid Syntax: " + cmd + " <name of event>");
        return;
      }
 
      try
      {
        c2siface.finalize(args[0]);
      }
      catch(RemoteException e)
      {
        err.println("Error: Unable to finalize: " + e.getCause().getMessage());
      }
    }
 
    /*------------------------------------------------------------------------*/
 
    public void cmdLogout(String cmd, String[] args)
    {
      if (args.length != 0)
      {
        err.println("Invalid Syntax: " + cmd);
        return;
      }
 
      try
      {
        c2siface.logout();
        loggedin = false;
        out.println("You have been logged out.");
      }
      catch(RemoteException e)
      {
        err.println("Error: " + e.getCause().getMessage());
      }
    }
 
    /*------------------------------------------------------------------------*/
 
    public void cmdExit(String cmd, String[] args)
    {
      if (loggedin)
        cmdLogout("!logout", new String[0]);
      stop();
    }
 
    /*------------------------------------------------------------------------*/
 
    public void cmdHelp(String cmd, String[] args)
      throws IOException
    {
      for(Map.Entry<String, Object[]> entry : cmdHandler.entrySet())
      {
        if (entry.getKey().equals("unknown") || entry.getKey().equals("!help"))
          continue;
        out.println(entry.getKey());
      }
    }
 
    /*------------------------------------------------------------------------*/
 
    public void cmdUnknown(String cmd, String[] args)
      throws IOException
    {
      out.println("Error: Unknown command: " + cmd + " " + Utils.join(Arrays.asList(args), " "));
      out.println("Try !help for a list of commands");
    }
 
    /*------------------------------------------------------------------------*/
 
    public void printPrompt()
    {
      out.print(">: ");
      out.flush();
    }
 
    /*------------------------------------------------------------------------*/
 
    public void shutdown()
    {}
 
    /*------------------------------------------------------------------------*/
 
    public void run()
    {
      try
      {
        run(sin);
      }
      catch(RemoteException e)
      {
        err.println("Remote Error: " + e.getMessage());
      }
      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 serverName;
  private InputStream stdin = null;
  private Thread tInteractive = null;
  private ScheduledExecutorService scheduler = null;
  private Registry registry = null;
  private C2SInterface c2siface = null;
  private S2CInterfaceImpl s2ciface = null;
  private final Object mainLock = new Object();
 
  /*--------------------------------------------------------------------------*/
 
  public static void usage()
    throws Utils.Shutdown
  {
    out.println("Usage: Server serverName\n");
    out.println("serverName\t...the name of the remote reference in the RMI registry");
    out.println("\t\t   of the server that shall be responsible for this client.");
 
    // 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 != 1)
      usage();
 
    serverName = args[0];
    if (serverName.length() == 0)
      bailout("serverName is empty");
  }
 
  /*--------------------------------------------------------------------------*/
 
  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 (regHost == null || regHost.length() <= 0)
      bailout("Registry host is not set");
    if (regPort == 0)
      bailout("Registry port is not set");
 
    try
    {
      registry = LocateRegistry.getRegistry(regHost, regPort);
    }
    catch(RemoteException e)
    {
      bailout("Unable to get registry: " + e.getMessage());
    }
  }
 
  /*--------------------------------------------------------------------------*/
 
  public void remoteLogout()
  {
    if (tInteractive != null)
      tInteractive.interrupt();
  }
 
  /*--------------------------------------------------------------------------*/
 
  public void shutdown()
  {
    try
    {
      if (scheduler != null)
      {
        scheduler.shutdownNow();
        scheduler.awaitTermination(Long.MAX_VALUE, TimeUnit.NANOSECONDS);
      }
    }
    catch(InterruptedException e)
    {}
 
    try
    {
      if (s2ciface != null)
        s2ciface.unexport();
    }
    catch(RemoteException e)
    {}
 
    try
    {
      if (tInteractive != null)
      {
        tInteractive.interrupt();
        tInteractive.join();
      }
    }
    catch(InterruptedException e)
    {}
 
    try
    {
      if (stdin != null)
        stdin.close();
    }
    catch(IOException e)
    {}
  }
 
  /*--------------------------------------------------------------------------*/
 
  public void run(String[] args)
  {
    parseArgs(args);
 
    synchronized(mainLock)
    {
      createInterface();
 
      try
      {
        out.println("Waiting for the server to come online...");
        scheduler = Executors.newScheduledThreadPool(1);
        ScheduledFuture<?> waitServerTimer = scheduler.scheduleAtFixedRate(
            new WaitForServer(), 0, 500, TimeUnit.MILLISECONDS);
        mainLock.wait();
        if (!scheduler.isShutdown() || c2siface == null)
          bailout(null);
      }
      catch(InterruptedException e)
      {
        /* if we get interrupted -> ignore */
      }
 
      try
      {
        s2ciface = new S2CInterfaceImpl(this);
      }
      catch(RemoteException e)
      {
        bailout("Unable to export client callback");
      }
 
      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());
      }
 
      out.println("Client startup successful!");
      try
      {
        mainLock.wait();
      }
      catch(InterruptedException e)
      {
        /* if we get interrupted -> ignore */
      }
    }
 
    shutdown();
  }
 
  /*--------------------------------------------------------------------------*/
 
  public static void main(String[] args)
  {
    try
    {
      Client cli = new Client();
      cli.run(args);
    }
    catch(Utils.Shutdown e)
    {}
  }
}