/* * 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 + " "); 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 + " "); 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 + " "); 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 + " "); 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 + " "); 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 + " "); 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 + " ..."); return; } int i = 1; HashSet dates = new HashSet(); 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 + " "); 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 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) {} } }