// Datei: TelefonbuchServer.java
// Autor: Werner Brecht
// Datum: 25.07.2018
// Thema: Ein Telefonbuchserver, der einen Web-Browser als
//        Benutzerschnittstelle verwendet und per RMI zwei
//        Telefonbücher in den beiden Abteilungen Einkauf und
//        Vertrieb nebenläufig abfragt.
//
//        In den beiden Abteilungen wird
//          (1) das Telefonbuch aus der ersten Übungsaufgabe
//              übernommen und die Name-Nummer-Liste an die
//              jeweilige Abteilung angepasst.
//          (2) ein Abteilungsserver implementiert, der die RMI-
//              Aufrufe bearbeitet und auf das jeweilige
//              Telefonbuch zugreift.
//
//        Der Telefonbuchserver aus der zweiten Aufgabe wird um
//        die Methode verteileAbfrage() erweitert, die neben-
//        läufig (mit Threads) die RMI-Aufrufe startet.
//
//        Eine Beschreibung der Arbeitsweise des Programms ist
//        beim Grobentwurf des Lösungsbeispiels zur dritten
//        Aufgabe zu finden.
//
//        Beim Programmstart sind die Rechnernamen der beiden
//        Abteilungsserver als Params (e: Einkauf, v: Vertrieb)
//        anzugeben:
//          java TelefonbuchServer e=... v=...
//        Ihre Reihenfolge spielt keine Rolle.
// =============================================================
import java.util.*;
import java.io.*;
import java.util.regex.*;
import java.net.*;
import java.rmi.*;
class TelefonbuchServer {
  // Globale Variablen: Rechnernamen der Abteilungsserver
  //                    Registry-Ports der Abteilungsserver
  //                    Listen für die Ergebnisse der Threads,
  //                    die auf die Abteilungen zugreifen
  // -----------------------------------------------------------
  static String abtHostE = null;           // Abteilung Einkauf
  static int    regPortE = 0;
  static String abtHostV = null;           // Abteilung Vertrieb
  static int    regPortV = 0;
  static ArrayList<String> thrE = new ArrayList<String>();
  static ArrayList<String> thrV = new ArrayList<String>();
  public static void main(String[] args) throws Exception {
    // Parameterauswertung
    // ---------------------------------------------------------
    if(args.length != 2) {
      System.out.println();
      System.out.println("==================================");
      System.out.println("Aufruf mit 2 Params: e=... v=...");
      System.out.println("  Abteilungsserver angeben!");
      System.out.println("==================================");
      System.out.println();
      System.exit(0);
    }
    if(!
       (
        (
         (args[0].startsWith("e") || args[0].startsWith("E"))
         &
         (args[1].startsWith("v") || args[1].startsWith("V"))
        )
        ||
        (
         (args[0].startsWith("v") || args[0].startsWith("V"))
         &
         (args[1].startsWith("e") || args[1].startsWith("E"))
        )
       )
      ) {
        System.out.println();
        System.out.println("=================================");
        System.out.println("Parameter sind falsch aufgebaut:");
        System.out.println("  e=HostnameEink v=HostnameVertr");
        System.out.println("=================================");
        System.out.println();
        System.exit(0);
    }
    if(args[0].startsWith("e") || args[0].startsWith("E")) {
      abtHostE = args[0];
      abtHostV = args[1];
    }
    else {
      abtHostE = args[1];
      abtHostV = args[0];
    }
    if(abtHostE.startsWith("e"))
      abtHostE = abtHostE.replace("e=", "");
    else
      abtHostE = abtHostE.replace("E=", "");
    if(abtHostV.startsWith("v"))
      abtHostV = abtHostV.replace("v=", "");
    else
      abtHostV = abtHostV.replace("V=", "");
    // Registry-Ports festlegen:
    //   Einkauf  -> E -> 69 -> 60069
    //   Vertrieb -> V -> 86 -> 60086
    // ---------------------------------------------------------
    regPortE = 60069;
    regPortV = 60086;
   
    // Laufen die Abteilungsserver?
    //   Nein -> Abbruch
    //   Ja   -> Oben eintragen
    // ---------------------------------------------------------
    System.out.println();
    System.out.println("Zentraler Telefonbuchserver ist aktiv");
    System.out.println("-------------------------------------");
    System.out.println();
    System.out.println
                     ("ACHTUNG: Der Server kann nur arbeiten,");
    System.out.println
               ("         wenn beide Abteilungsserver laufen!");
    System.out.println();
    System.out.println("         Ist dies der Fall?");
    System.out.print("         Eingabe: (j/n)->");
    BufferedReader br = new BufferedReader(
                          new InputStreamReader(System.in));
    String ein = br.readLine();
    if(!(ein.equals("")||ein.equals("j")||ein.equals("J"))) {
      System.out.println();
      System.out.println("==================================");
      System.out.println("Bitte die Abteilungsserver starten");
      System.out.println("und Programm erneut aufrufen!");
      System.out.println("=================================");
      System.out.println();
      System.exit(0);
    }
    // Ergebnisliste bereitstellen und ServerSocket am Port 9876
    // erzeugen
    // ---------------------------------------------------------
    ArrayList<String> erg = new ArrayList<String>();
    erg.add("Anfang");
    String qs = "Anfang";
    int serverport = 9876;
    ServerSocket ss = new ServerSocket(serverport);
    Socket cs = null;
    // Start des zentralen Telefonbuchservers
    // ---------------------------------------------------------
    String host = InetAddress.getLocalHost().getHostName();
    System.out.println();
    System.out.println("Abteilungen: Einkauf, Vertrieb");
    System.out.println("Host       : " + host);
    System.out.println("Port       : " + serverport);
    System.out.println();
    // GET-Requests des Browsers entgegennehmen und analysieren.
    // Ggf. Query String erzeugen und Suchvorgänge starten.
    // Ergebnis ausgeben.
    // ---------------------------------------------------------
    while(true) {
      if(erg.get(0).equals("Beendet")) break;
      if(! qs.equals("Favicon")) 
        System.out.println("Warte auf Browser-Requests");
      cs = ss.accept();           // Auf Browser Requests warten
      qs = vomBrowser(cs);
      if(qs.equals("Favicon")) { cs.close(); continue; }
      System.out.println("Query String vom Browser -> " + qs);
      erg = analyse(qs);
      zumBrowser(cs, erg);
      cs.close();
    }
    ss.close();
  } // main()
// =============================================================
  static String vomBrowser(Socket cs) throws Exception {
    // Aus dem Socket cs lesen, Query String ausblenden
    // ---------------------------------------------------------
    BufferedReader br = new BufferedReader(
                    new InputStreamReader(cs.getInputStream()));
    String qs = br.readLine();
    // Favicon-Requests ignorieren (kein Query String)
    // ---------------------------------------------------------
    if(qs.startsWith("GET /favicon.ico")) return "Favicon";
    // Leitseite wird angefordert: qs=Anfang
    // ---------------------------------------------------------
    if(qs.startsWith("GET / H")) return "Anfang";
    // Benutzer hat Beenden gewählt
    // ---------------------------------------------------------
    if(qs.contains("&ende=Beenden")) return "Ende";
    // Query String ausblenden
    // ---------------------------------------------------------
    qs = qs.replace("GET ", "");
    qs = qs.replace(" HTTP/1.1", "");
    qs = qs.replace("/?", "");
    return qs;
  } // vomBrowser()
// =============================================================
  static ArrayList<String> analyse(String qs) throws Exception {
    // Query String qs kann sein (* = mögliche Benutzereingabe):
    //    Anfang
    //    Ende
    //    name=*&nummer=* (ev. nur 1 *)
    //
    // Ergebnisliste und Hilfsvariablen anlegen
    // ---------------------------------------------------------
    ArrayList<String> ksuErg = new ArrayList<String>();
    // Query Strings, die das Telefonbuch nicht ansprechen
    // (1) Dialogbeginn
    // ---------------------------------------------------------
    if(qs.equals("Anfang")) {
      ksuErg.add("Anfang");
      return ksuErg;
    }
    // (2) Dialogende
    // ---------------------------------------------------------
    if(qs.equals("Ende")) {
      ksuErg = verteileAbfrage(qs);
      return ksuErg;
    }
    // (3) Leere Eingaben
    // ---------------------------------------------------------
    if(qs.equals("name=&nummer=")) {
      ksuErg.add("Leere Eingabe");
      return ksuErg;
    }
    // Die URL-Kodierung rückgängig machen.
    // Das Programm ist unter Windows-7 entwickelt worden.
    // ---------------------------------------------------------
    qs = URLDecoder.decode(qs, "ISO-8859-1");
    // (4) Syntaktisch falsche Eingaben
    //     Fehler mit & bzw. mit =
    // ---------------------------------------------------------
    String regex = "^[^&]*&[^&]*$";
    if(! qs.matches(regex)) {
      ksuErg.add("Syntaxfehler");
      return ksuErg;
    }
    regex = "^[^=]+=[^=]+=[^=]*$";
    if(! qs.matches(regex)) {
      ksuErg.add("Syntaxfehler");
      return ksuErg;
    }
    // Der Query String ist korrekt aufgebaut und nicht leer,
    // aber noch nicht in der Form für das Telefonbuch:
    //   name=*&nummer=* (ev. nur 1 *)
    // Er wird umgewandelt und per RMI an die Abteilungsserver
    // übergeben.
    // ---------------------------------------------------------
    String[] token = null;
    String name = null;                     // zu suchender Name
    String nummer = null;                  // zu suchende Nummer
    String kqs = null;       // Korrekt aufgebauter Query String
    token = qs.split("&");
    name = token[0].replace("name=", "");
    nummer = token[1].replace("nummer=", "");
    kqs = name + "&" + nummer;
    // Kumilierte Suchergebnislisten
    // ---------------------------------------------------------
    ksuErg = verteileAbfrage(kqs);
    return ksuErg;
  } // analyse()
// =============================================================
  static ArrayList<String> verteileAbfrage(String kqs)
                                              throws Exception {
    class VerteilerThread extends Thread {
      String abteilung = null;
      String kqs = null;
      VerteilerThread(String abt, String kqs) {
        this.abteilung = abt;
        this.kqs = kqs;
      }
      public void run() {
        String kennung = "rmi://";
        RmiDekl dekl = null;
        try {
          if(abteilung.equals("Einkauf")) {
            kennung = kennung + TelefonbuchServer.abtHostE + ":"
                              + TelefonbuchServer.regPortE
                              + "/RmiAbfrage";
            dekl = (RmiDekl)Naming.lookup(kennung);
            if(kqs.equals("Ende"))
              dekl.beende();
            else
              TelefonbuchServer.thrE = dekl.frageTbuch(kqs);
          }
          else {
            kennung = kennung + TelefonbuchServer.abtHostV + ":"
                              + TelefonbuchServer.regPortV
                              + "/RmiAbfrage";
            dekl = (RmiDekl)Naming.lookup(kennung);
            if(kqs.equals("Ende"))
              dekl.beende();
            else
              TelefonbuchServer.thrV = dekl.frageTbuch(kqs);
          }
        } catch(Exception e) {}
      } // run()
    } // Lokale Klasse
    ArrayList<String> ksuErg = new ArrayList<String>();
    VerteilerThread vtE = new VerteilerThread("Einkauf", kqs);
    VerteilerThread vtV = new VerteilerThread("Vertrieb", kqs);
    vtE.start();
    vtV.start();
    vtE.join();
    vtV.join();
    if(kqs.equals("Ende"))
      ksuErg.add("Beendet");
    else {
      for(String s : thrE) ksuErg.add(s+"&Einkauf");
      for(String s : thrV) ksuErg.add(s+"&Vertrieb");
      ksuErg.add("Suche");
    }
    return ksuErg;
  } // verteileAbfrage()
// =============================================================
  static void zumBrowser(Socket cs, ArrayList<String> erg)
                                              throws Exception {
    int len = erg.size();
    if(len == 1) {
      String art = erg.get(0);
      // Fälle, bei denen keine Suche gestartet wurde
      // ---------------------------------------------------------
      if(art.equals("Anfang")) {
        schreibeKopf(cs);
        schreibeForm(cs);
        return;
      }
      if(art.equals("Beendet")) {
        schreibeKopf(cs);
        schreibeEnde(cs);
        return;
      }
      if(art.equals("Leere Eingabe")) {
        schreibeKopf(cs);
        schreibeLeer(cs);
        schreibeForm(cs);
        return;
      }
      if(art.startsWith("Syntaxfehler")) {
        schreibeKopf(cs);
        schreibeSyntax(cs);
        schreibeForm(cs);
        return;
      }
    }
    // Suche wurde gestartet: Finde (ev. leer bis auf Suche)
    //    Meier&4711&Einkauf
    //    ...
    //    Suche
    // ---------------------------------------------------------
    schreibeKopf(cs);
    if(len == 1) schreibeOhne(cs);   
    else         schreibeErg(cs, erg, len);
    schreibeForm(cs);
  } // zumBrowser()
// =============================================================
  // HTTP-Kopf und Anfang der HTML-Seite in den Socket schreiben
  // -----------------------------------------------------------
  static void schreibeKopf(Socket cs) throws Exception {
    PrintWriter pw = new PrintWriter(cs.getOutputStream());
    pw.println("HTTP/1.1 200 OK");
    pw.println("Content-Type: text/html");
    pw.println();
    pw.println("<html>");
    pw.println("<body>");
    pw.flush();
  } // schreibeKopf()
// =============================================================
  // HTML-Form und Seitenende in den Socket schreiben
  // -----------------------------------------------------------
  static void schreibeForm(Socket cs) throws Exception {
    PrintWriter pw = new PrintWriter(cs.getOutputStream());
    pw.println("<h2 align=center>Telefonbuchabfrage</h2>");
    pw.println("<h3>Sie können nach einem Namen oder nach "
              +"einer Nummer<br>oder (nebenläufig) nach "
              +"beidem suchen.<br>In Ihren Eingaben darf kein &"
              +" und kein = vorkommen!</h3>");
    String host = InetAddress.getLocalHost().getHostName();
    int port = cs.getLocalPort();
    String url = "http://"+host+":"+port;
    pw.println("<form method=get action=\"" + url + "\">");
    pw.println("<table>");
    pw.println("<tr>");
    pw.println("<td>Name:</td>");
    pw.println("<td><input name=name></td>");
    pw.println("<td></td>");
    pw.println("</tr>");
    pw.println("<tr>");
    pw.println("<td>Nummer:</td>");
    pw.println("<td><input name=nummer></td>");
    pw.println("<td></td>");
    pw.println("</tr>");
    pw.println("<tr>");
    pw.println("<td><input type=submit value=Abschicken></td>");
    pw.println("<td><input type=reset></td>");
    pw.println("<td><input type=submit name=ende value=Beenden></td>");
    pw.println("</tr>");
    pw.println("</table>");
    pw.println("</form>");
    pw.println("</body>");
    pw.println("</html>");
    pw.flush();
  } // schreibeForm()
// =============================================================
  // HTML-Endeseite in den Socket schreiben
  // -----------------------------------------------------------
  static void schreibeEnde(Socket cs) throws Exception {
    PrintWriter pw = new PrintWriter(cs.getOutputStream());
    pw.println("<h2>Ende der Telefonbuchabfrage</h2>");
    pw.println("<h2>Auf Wiedersehen!</h2>");
    pw.println("</body>");
    pw.println("</html>");
    pw.flush();
  } // schreibeEnde()
// =============================================================
  // Schreiben bei leeren Eingaben
  // -----------------------------------------------------------
  static void schreibeLeer(Socket cs) throws Exception {
    PrintWriter pw = new PrintWriter(cs.getOutputStream());
    pw.println("<h2>Ihre Eingaben waren leer!</h2>");
    pw.println("<h2>   Bitte wiederholen!</h2>");
    pw.println("<h2>====================================</h2>");
    pw.println("<br>");
    pw.flush();
  } // schreibeLeer()
// =============================================================
  // Schreiben bei Syntaxfehler
  // -----------------------------------------------------------
  static void schreibeSyntax(Socket cs) throws Exception {
    PrintWriter pw = new PrintWriter(cs.getOutputStream());
    pw.println("<h2>Ihre Eingaben waren fehlerhaft!</h2>");
    pw.println("<h2>Bitte kein & und kein = eingeben!</h2>");
    pw.println("<h2>====================================</h2>");
    pw.println("<br>");
    pw.flush();
  } // schreibeSyntax()
// =============================================================
  // Schreiben bei leerem Ergebnis
  // -----------------------------------------------------------
  static void schreibeOhne(Socket cs) throws Exception {
    PrintWriter pw = new PrintWriter(cs.getOutputStream());
    pw.println("<h2>Ihre Suche hatte leider keine Treffer!</h2>");
    pw.println("<h2></h2>");
    pw.println("<h2>====================================</h2>");
    pw.println("<br>");
    pw.flush();
  } // schreibeOhne()
// =============================================================
  // Schreiben bei nicht leerem Ergebnis
  //   Meier&4711&Einkauf
  // -----------------------------------------------------------
  static void schreibeErg(Socket cs, ArrayList<String> erg,
                                     int len) throws Exception {
    String[] token = null;
    PrintWriter pw = new PrintWriter(cs.getOutputStream());
    pw.println("<h2>Ihre Suche hat folgendes Ergebnis:</h2>");
    pw.println("<h2></h2>");
    len--;
    pw.println("<table>");
    pw.println("<tr><th>Name</th><th>Nummer</th>" +
                                     "<th>Abteilung</th></tr>");
    for(int i=0; i<len; i++) {
      token = erg.get(i).split("&");
      pw.println("<tr><td>" + token[0] + "</td>");
      pw.println("    <td>" + token[1] + "</td>");
      pw.println("    <td>" + token[2] + "</td></tr>");
    }
    pw.println("</table>");
    pw.println("<h2>====================================</h2>");
    pw.flush();
  } // schreibeErg()
} // class