diff --git a/src/main/java/dslab/Email.java b/src/main/java/dslab/Email.java index a255687..7f0ca96 100644 --- a/src/main/java/dslab/Email.java +++ b/src/main/java/dslab/Email.java @@ -2,6 +2,8 @@ package dslab; import dslab.exception.MalformedInputException; +import java.util.Objects; + public class Email { private String username; private String domain; @@ -43,4 +45,18 @@ public class Email { public String toString() { return username + '@' + domain; } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Email email = (Email) o; + return Objects.equals(getUsername(), email.getUsername()) && + Objects.equals(getDomain(), email.getDomain()); + } + + @Override + public int hashCode() { + return Objects.hash(getUsername(), getDomain()); + } } diff --git a/src/main/java/dslab/mailbox/DMAPConnection.java b/src/main/java/dslab/mailbox/DMAPConnection.java new file mode 100644 index 0000000..936ad3e --- /dev/null +++ b/src/main/java/dslab/mailbox/DMAPConnection.java @@ -0,0 +1,4 @@ +package dslab.mailbox; + +public class DMAPConnection { +} diff --git a/src/main/java/dslab/mailbox/DMAPListener.java b/src/main/java/dslab/mailbox/DMAPListener.java new file mode 100644 index 0000000..47cb298 --- /dev/null +++ b/src/main/java/dslab/mailbox/DMAPListener.java @@ -0,0 +1,12 @@ +package dslab.mailbox; + +import dslab.Message; +import dslab.transfer.ClientConnection; + +import java.io.BufferedReader; +import java.io.PrintWriter; +import java.net.Socket; +import java.util.logging.Logger; + +public class DMAPListener { +} diff --git a/src/main/java/dslab/mailbox/DMTPConnection.java b/src/main/java/dslab/mailbox/DMTPConnection.java new file mode 100644 index 0000000..bbb19f2 --- /dev/null +++ b/src/main/java/dslab/mailbox/DMTPConnection.java @@ -0,0 +1,142 @@ +package dslab.mailbox; + +import dslab.Email; +import dslab.Message; +import dslab.exception.MalformedInputException; +import dslab.exception.UnknownRecipientException; + +import java.io.*; +import java.net.Socket; +import java.net.SocketException; +import java.util.ArrayList; +import java.util.LinkedList; +import java.util.concurrent.ConcurrentHashMap; +import java.util.logging.Logger; + +public class DMTPConnection implements Runnable { + Logger logger = Logger.getLogger(DMTPConnection.class.getName()); + private final Socket socket; + private PrintWriter out; + private BufferedReader in; + + private Message msg = new Message(); + + private final ConcurrentHashMap> messageStorage; + + public DMTPConnection(Socket connection, ConcurrentHashMap> messageStorage) { + this.socket = connection; + this.messageStorage = messageStorage; + } + + @Override + public void run() { + logger.finer("Preparing for DMTP communication in " + this.toString()); + try { + this.out = new PrintWriter(socket.getOutputStream(), true); + this.in = new BufferedReader(new InputStreamReader(socket.getInputStream())); + out.println("ok DMTP"); + + String userInput; + + try { + if (!("begin".equals(in.readLine()))) { + out.println("error protocol error"); + shutdown(); + } + } catch (SocketException e) { + // Thrown if socket has already been closed by shutdown() method + logger.finer("Received interrupt. Exiting " + this.toString()); + shutdown(); + } + out.println("ok"); + + while (!Thread.currentThread().isInterrupted() && (userInput = in.readLine()) != null) { + if ("quit".equals(userInput)) { + out.println("ok bye"); + shutdown(); + } else if ("send".equals(userInput)) { + try { + storeMessage(); + out.println("ok"); + } catch (UnknownRecipientException e) { + out.println(e.getMessage()); + shutdown(); + } + } else if ("to".equals(userInput.split("\\s+")[0])) { + msg.setTo(new ArrayList<>()); + String[] emailAddresses = userInput.split("\\s+")[1].split(","); + int count = 0; + try { + for (String emailAddress : emailAddresses) { + msg.addTo(new Email(emailAddress)); + count++; + } + out.println("ok " + count); + } catch (MalformedInputException mie) { + out.println(mie.getMessage()); + } + } else if ("from".equals(userInput.split("\\s+")[0])) { + try { + Email from = new Email(userInput.split("\\s+")[1]); + this.msg.setFrom(from); + out.println("ok"); + } catch (MalformedInputException mie) { + out.println(mie.getMessage()); + } + } else if ("subject".equals(userInput.split("\\s+")[0])) { + String subject = ""; + if (userInput.split("\\s+").length > 1) + subject = userInput.split("\\s+", 2)[1]; + logger.info("Setting subject to: " + subject); + msg.setSubject(subject); + out.println("ok"); + } else if ("data".equals(userInput.split("\\s+")[0])) { + String data = userInput.split("\\s+", 2)[1]; + logger.info("Setting data to: " + data); + msg.setData(data); + out.println("ok"); + } else { + out.println("error protocol error"); + shutdown(); + } + } + } catch (InterruptedIOException ioe) { + logger.info("Received interrupt from parent. Shutting down..."); + shutdown(); + } catch (IOException e) { + logger.severe("Failed to get IO-Stream"); + e.printStackTrace(); + shutdown(); + } + } + + private synchronized void storeMessage() throws UnknownRecipientException { + String errorUnknownRecipient = ""; + for (Email recipient : this.msg.getTo()) { + if (this.messageStorage.containsKey(recipient)) { + this.messageStorage.get(recipient).add(this.msg); + } else { + if (errorUnknownRecipient.isBlank()) + errorUnknownRecipient = "error unknown recipient " + recipient.getUsername(); + } + } + + if (!errorUnknownRecipient.isBlank()) + throw new UnknownRecipientException(errorUnknownRecipient); + this.msg = new Message(); + } + + public void shutdown() { + logger.info("Shutting down client connection " + this.toString()); + try { + if (socket != null) + socket.close(); + in.close(); + out.close(); + } catch (IOException e) { + logger.severe("Error closing socket and/or IO-streams"); + e.printStackTrace(); + } + Thread.currentThread().interrupt(); + } +} diff --git a/src/main/java/dslab/mailbox/DMTPListener.java b/src/main/java/dslab/mailbox/DMTPListener.java new file mode 100644 index 0000000..c791547 --- /dev/null +++ b/src/main/java/dslab/mailbox/DMTPListener.java @@ -0,0 +1,71 @@ +package dslab.mailbox; + +import dslab.Email; +import dslab.Message; + +import java.io.IOException; +import java.io.InterruptedIOException; +import java.net.ServerSocket; +import java.net.Socket; +import java.net.SocketException; +import java.util.ArrayList; +import java.util.LinkedList; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; +import java.util.logging.Logger; + +public class DMTPListener extends Thread { + private final ServerSocket serverSocket; + private final Logger logger = Logger.getLogger(DMTPListener.class.getName()); + private final ArrayList clients = new ArrayList<>(); + private final ExecutorService executorService = Executors.newCachedThreadPool(); + private final ConcurrentHashMap> storage; + + public DMTPListener(ServerSocket serverSocket, ConcurrentHashMap> storage) { + this.serverSocket = serverSocket; + this.storage = storage; + } + + @Override + public void run() { + while (!Thread.currentThread().isInterrupted()) { + logger.finer("Waiting for request on serverSocket " + serverSocket.toString()); + try { + Socket s = serverSocket.accept(); + logger.fine("Processing incoming socket " + s.toString()); + DMTPConnection dmtpConnection = new DMTPConnection(s, storage); + clients.add(dmtpConnection); + executorService.submit(dmtpConnection); + } catch (InterruptedIOException | SocketException e) { + logger.finer("Received interrupt. Exiting " + this.toString()); + this.shutdown(); + } catch (IOException e) { + logger.severe("Error starting serverSocket " + serverSocket.toString()); + e.printStackTrace(); + this.shutdown(); + } + } + } + + public void shutdown() { + logger.finer("Shutting down DMTPHandler " + this.toString()); + for (DMTPConnection client : clients) { + if (client != null) + client.shutdown(); + } + executorService.shutdown(); + try { + if (!executorService.awaitTermination(60, TimeUnit.SECONDS)) { + executorService.shutdownNow(); + if (!executorService.awaitTermination(60, TimeUnit.SECONDS)) + System.err.println("Pool did not terminate"); + } + } catch (InterruptedException ie) { + executorService.shutdownNow(); + Thread.currentThread().interrupt(); + } + this.interrupt(); + } +} diff --git a/src/main/java/dslab/mailbox/MailboxServer.java b/src/main/java/dslab/mailbox/MailboxServer.java index 154403c..c393ade 100644 --- a/src/main/java/dslab/mailbox/MailboxServer.java +++ b/src/main/java/dslab/mailbox/MailboxServer.java @@ -4,21 +4,28 @@ import java.io.IOException; import java.io.InputStream; import java.io.PrintStream; import java.net.ServerSocket; +import java.util.LinkedList; +import java.util.concurrent.ConcurrentHashMap; import java.util.logging.Logger; import at.ac.tuwien.dsg.orvell.Shell; import at.ac.tuwien.dsg.orvell.StopShellException; import at.ac.tuwien.dsg.orvell.annotation.Command; import dslab.ComponentFactory; +import dslab.Email; +import dslab.Message; import dslab.util.Config; public class MailboxServer implements IMailboxServer, Runnable { private static final Logger logger = Logger.getLogger(MailboxServer.class.getName()); + private final String domain; private ServerSocket dmtpServerSocket; private ServerSocket dmapServerSocket; private final Shell shell; private final Integer dmtpServerPort; private final Integer dmapServerPort; + private final ConcurrentHashMap> messageStorage = new ConcurrentHashMap<>(); + private final ConcurrentHashMap userStorage = new ConcurrentHashMap<>(); /** * Creates a new server instance. @@ -29,7 +36,19 @@ public class MailboxServer implements IMailboxServer, Runnable { * @param out the output stream to write console output to */ public MailboxServer(String componentId, Config config, InputStream in, PrintStream out) { - // TODO initialize email and user storage (concurrent hashmap?) + this.domain = config.getString("domain"); + Config userConfig = new Config(config.getString("users.config")); + + // Load users from config into userStorage for authentication + for (String key : userConfig.listKeys()) { + userStorage.put(key, userConfig.getString(key)); + } + // Load Email Addresses into messageStorage + for (String key : userStorage.keySet()) { + Email current = new Email(key, domain); + messageStorage.put(current, new LinkedList<>()); + } + this.shell = new Shell(in, out); this.shell.register(this); this.shell.setPrompt("Mailboxserver> "); @@ -39,7 +58,7 @@ public class MailboxServer implements IMailboxServer, Runnable { @Override public void run() { - logger.info("Creating DMTP serverSocket for TransferServer + " + this.toString()); + logger.info("Creating DMTP and DMAP serverSockets for MailboxServer + " + this.toString()); try { this.dmtpServerSocket = new ServerSocket(dmtpServerPort); this.dmapServerSocket = new ServerSocket(dmapServerPort); @@ -48,14 +67,16 @@ public class MailboxServer implements IMailboxServer, Runnable { e.printStackTrace(); shutdown(); } - // TODO spawn listener for transfer servers - // TODO spawn listener for user clients + // TODO spawn listener for transfer servers (DMTPListener) + // TODO spawn listener for user clients (DMAPListener) this.shell.run(); } @Command @Override public void shutdown() { + // TODO shutdown DMTPListener + // TODO shutdown DMAPListener try { if (dmtpServerSocket != null) dmtpServerSocket.close();