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)
{}
}
}