Mails über JavaMail abrufen und im GWT Client anzeigen

Kumaro

Mitglied
Hi Leute,
ich bin gerade dabei mich mit GWT zu beschäftigen und habe daher angefangen eine Webmail Application zu schreiben.

Was ich bisher mache:

Ich gebe über eine Loginseite meinen USER und PSW ein und sende diese über einen RPC Call an den MailService auf dem WebServer. Dieser verbindet sich dann via javaMail API mit dem Mailserver und erzeugt alle notwendigen Objekte Session, Store, Folder.
Anschließend werden die Nachrichten in einem Message Array abgelegt.
Das ganze sieht dann wie folgt aus:


Java:
.....
try{
                //System Properties holen
                Properties properties = System.getProperties();
                //Mail-Server properties: Session verlangt die Informationen über Host, User, Passwd etc.
                properties.put("mail.imap.host", host);
                properties.put("mail.imap.auth", "true");
        
                //Initialisierung der Auth-Klasse zur Mail-Account-Authentisierung; in Session benutzt
                Authenticator auth = new MailAuthenticator();
        
                //Verbindung zum MailServer
                Session mailSession = Session.getDefaultInstance(properties, auth);
                //Gibt in der Console eine Debug-Meldung zum Verlauf aus
                mailSession.setDebug(true);
        
                Folder mailFolder = openIMAPInboxReadOnly(mailSession);
        
//HIER LIEGT NUN ALLES DRIN          
Message messages[] = mailFolder.getMessages();
                                  
                closeInbox(mailFolder);
          
           }catch(Exception err){
        
              System.out.println("Die Anmeldung ist fehlgeschlagen!");
              System.out.println(err);
           }
    
        }
    /**
    * Erzeugt das Authenticator-Objekt zur Anmeldung am MailAccount
    * @author Kumaro
    *
    */
    private class MailAuthenticator extends javax.mail.Authenticator {
    
        public PasswordAuthentication getPasswordAuthentication(){
                //System.out.println("User: "+ user + "/ Pasw: " + password);
            return new PasswordAuthentication(user, password);
        
        }
    }



    /**
    * Erzeugt das Store-Objekt über IMAP und legt die EMails dann im MailFolder ab und gibt dieses Objekt zurück
    * @param mailSession
    * @return mailFolder
    * @throws MessagingException
    */
    private Folder openIMAPInboxReadOnly(Session mailSession) throws MessagingException{
        //Store dient zum Ablegen der Nachrichten
        Store mailStore = mailSession.getStore("imap");
        mailStore.connect();
        //Folder ist ein Ordner-Objekt für Mails
        Folder mailFolder = mailStore.getFolder("INBOX");
        mailFolder.open(Folder.READ_ONLY);
        return mailFolder;
    }



    /**
    * Beendet die Verbindung zum InboxOrdner
    * @param mailFolder
    * @throws MessagingException
    */
    private void closeInbox(Folder mailFolder) throws MessagingException{
        mailFolder.close(false);
        mailFolder.getStore().close();
    }



Mein Problem ist nun:

Wie bekomm ich das Message[] Objekt nun zurück an den (GWT) Client damit dieser die Mails im Browser auflisten kann? Ich kann leider nicht das Message[]-Objekt über den callback zurückgeben da Message ein Element aus der javaMail API ist die es in GWT nicht gibt.

Also nochmal kurz:

Wie kann ich die Mails vom Server auf den Client bekommen so dass ich sie dort anzeigen kann??



Ich hoffe ihr könnt mir weiter helfen :)

Viele Grüße.
 
Zuletzt bearbeitet:

Kumaro

Mitglied
Keiner eine Idee? :( Es muss doch eine Möglichkeit geben das Message-Objekt so umzuwandeln dass man es in GWT auch nutzen kann...
 

Improof

Erfahrenes Mitglied
Hi Kumaro! :)

Prinzipiell solltest du dir für solche Angelegenheiten ein einheitliches System auch für deine eigenen Klassen verwenden, denn - wie du schon gesagt hast - ist es ja nicht möglich, die Klassen einer Library an den Client zu senden.
Ich arbeite zur Zeit ausschließlich mit GWT und in diesem Zusammenhang auch mit SmartGWT. Hab mir dafür auch schon selbst einige Libs geschrieben, genau für solche, aber auch andere Anwendungsfälle. Für dein Problem würde ich Json und GWT Overlay Types empfehlen (siehe hier: http://www.gwtproject.org/doc/latest/DevGuideCodingBasicsOverlay.html).


Am besten machst du es so:

Schreibe dir zunächst eine eigene Bean-Klasse auf dem Server. Die nennst du z.B. "MessageResponseObject".

Java:
public class MessageResponseObject
{
    private String content;

    public String getContent() {
        return content;
    }

    public void setContent(String content) {
        this.content = content;
    }
}

Natürlich sollten da auch noch Eigenschaften, wie z.B. Sender und Empfänger rein.
Als nächstes brauchen wir das Gegenstück für den Client. Ich habe mir dafür erstmal eine Vaterklasse für alle Responses angelegt, von denen ich die jeweiligen Overlay Types erben lasse. Diese "Grundklasse" sieht so aus:

Java:
public abstract class JsonBaseResponse extends JavaScriptObject
{
    protected JsonBaseResponse() { }
   
    public static final native <T extends JsonBaseResponse> T getResponse(String json) /*-{
        return eval('(' + json + ')');
    }-*/;
}

Als Overlay Type bezeichnet man eine Klasse, die von JavaScriptObject erbt. Damit der GWT Compiler ohne Fehler durchläuft müssen außerdem alle Methoden final sein und es muss ein protected Konstruktor ohne Parameter vorhanden sein.

Mit dieser Vaterklasse als Grundlage können wir nun die eigentlich Response schreiben:

Java:
public class MessageResponse extends JsonBaseResponse
{
    protected MessageResponse() { }
   
    public final native String getContent() /*-{
        return this.content;
    }-*/;
}

Um aus deinem Server-seitigen ResponseObject einen Json String zu machen, würde ich Gson empfehlen. Das ist eine Library von Google, mit der das Umwandeln von Json -> Java und Java -> Json um ein vielfaches erleichtert wird. Schau einfach mal hier: https://code.google.com/p/google-gson/ (ist übrigens kostenlos).

Dein Server Code sieht dann in etwa wie folgt aus:

Java:
public String serverMethod()
    {
        // ... anderer Code; Es wird ein MessageResponseObject 'respObj' erstellt.
       
        Gson gson = new Gson();
        return gson.toJson(respObj, MessageResponseObject.class);
    }

Du kannst den Json String natürlich auch ohne Gson generieren lassen. Weils damit aber einfacher geht, hab ich das allerdings noch nie ohne Gson gemacht ;)
Den Json String kannst du dir mal auf dem Client ausgeben lassen. Für dieses Beispielobjekt würde der in etwa so aussehen:

Javascript:
{
    content : "eigentlicher Inhalt"
}

Wie du im Overlay Type sehen kannst, sprechen wir mit "this.content" genau die Variable aus dem JavaScript-Json an und können sie zurückgeben.

Dein Client Code des Callbacks sieht dann so aus:

Java:
@Override
    public void execute(String json) {
        MessageResponse resp = MessageResponse.getResponse(json);
        String content = resp.getContent();
       
        // ... weitere Verarbeitung
    }

Wie du siehst kannst du durch die "getResponse()" Methode der Base Response ganz einfach den Overlay Type erstellen und damit arbeiten.

Ich würde übrigens einheitlich alle Rückgaben (sofern es denn nicht nur ein Wert z.B. integer ist, sondern ein Objekt) über Json und die Overlay Types implementieren.

Und noch ein kleiner Hinweis am Rande: Du solltest über die "getResponse()"-Methode nur Json-String evaluieren lassen, denen du selbst vertraust! Also nur rein Programmtechnisch genierte und keine direkten Usereingaben oder sonst was, da hier sonst eine Sicherheitslücke entstehen kann! Aber wenn du - wie gesagt - nur so damit arbeitest wie in diesem Beispiel, kann dir da nichts passieren :)


Wenn du nochmal Hilfe brauchst, dann kannst mich auch gerne direkt per PN anschreiben, wenn es nur um ein spezielles Problem geht. Bin ja wie gesagt ziemlich fit in der ganzen GWT-Thematik, auch was bestimmte Sonderfälle und -Funktionen betrifft. Ansonsten natürlich wie immer ins Forum, damit jeder was davon hat ;)

Gruß
Daniel
 

Improof

Erfahrenes Mitglied
Hi Kumaro,

ich hab gerade nochmal deine Frage genauer gelesen und jetzt erst gemerkt, dass du ja eigentlich ein Array zurückgeben möchtest.
Da musst du dann natürlich ein paar kleine Änderungen an dem vorher von mir geposteten Code vornehmen:

  1. Der Rückgabetyp der Service-Methode sollte String[] oder List<String> sein
  2. Anstatt "return gson.toJson(...)" erstellst du ein Array oder eine ArrayList und packst da die von Gson generierten Json-Strings rein. Dieses Array / diese Liste gibst du dann an den Client zurück (siehe 1.)
  3. Auf dem Client hast du dann natürlich auch einen Callback vom Typ String[] bzw. List<String>
  4. In der "onSuccess()"-Methode baust du eine for-Schreife ein, in der du jeden Json-String aus der Liste holst.
  5. Mit der "getResponse()"-Methode evaluierst du für jeden Json-String deine MessageResponse

Sieht dann in etwa so aus:

Java:
@Override
public void onSuccess(List<String> result) {
    for (String json : result) {
        MessageResponse resp = MessageResponse.getResponse(json);
        String content = resp.getContent();
      
        // ... weitere Verarbeitung
    }
}

Gruß
Daniel
 

Kumaro

Mitglied
Hi Daniel, vielen Dank für deine Antwort :).
Ich werde mir das mal genauer anschauen und ausprobieren.
SmartGWT macht auch einen interessanten Eindruck aber irgendwie findet man da sehr wenig zu.

Wie hast du denn mit GWT angefangen? Hast du eventuell nützliche Links :)?

Viele Grüße
 
Zuletzt bearbeitet:

Kumaro

Mitglied
Nur mal so am Rande, vielleicht ist das eine blöde Frage aber ich versteh um ehrlich zu sein noch nicht so recht wieso ich die BeanKlasse und das "JsonBaseResponse" und "MessageResponse" brauche.

Bei der BeanKlasse auf dem Server, was genau soll die bewirken? Die stellt lediglich das MessageObjekt da in das ich meine Nachrichten packe oder seh ich das falsch? Und was meinst du mit
Natürlich sollten da auch noch Eigenschaften, wie z.B. Sender und Empfänger rein.
?

Könntest du eventuell noch einmal auf "JsonBaseResponse" und "MessageResponse" eingehen? Irgendwie steh ich da gerade auf dem Schlauch wozu das nötig ist.

Vielen dank :)

Gruß
 

Improof

Erfahrenes Mitglied
Hi Kumaro :)

Erstmal zu deinem ersten Post:

SmartGWT macht auch einen interessanten Eindruck aber irgendwie findet man da sehr wenig zu.
Also wenn du meine Meinung dazu hören willst: SmartGWT ist klasse! Es erleichtert die Entwicklung ungemein und sieht dabei auch noch super aus. Allerdings, wie du schon gemerkt hast, findet man nicht sehr viel dazu und die Doku lässt auch zu wünschen übrig... Hier muss man viel experimentieren! Aber wenn man's mal verstanden hat, will man gar nicht mehr ohne ;)

Wie hast du denn mit GWT angefangen? Hast du eventuell nützliche Links :)?
Also angefangen hab ich vor ca. 1 1/2 Jahren. Wir machen eine Neuentwicklung einer mittlerweile stark veralteten Software im Unternehmen und da hat man sich entschieden, das ganze als Webapplikation aufzubauen. So bin ich da rein gekommen (ich war damals eigentlich sogar noch in der Ausbildung).
Links puuuh....naja so genau eigentlich nicht. Ich hab jetzt nicht die eine Seite gehabt, auf der alles steht. Aber zu reinem GWT findest du viel auf stackoverflow, wenn du richtig danach suchst ;)


So und jetzt erklär ich das nochmal genau mit den Responses:

Du könntest es - theoretisch - auch ganz ohne all diese Klassen machen. Ich hab's früher so gemacht (wie gesagt, hab in der Ausbildung damit angefangen), allerdings jetzt bemerkt, dass es anders viel schöner zu programmieren und vom Code her viel verständlicher ist und musste dann vieles wieder umbauen. Aber aus meinem Fehler kannst du ja lernen ;)

Also wie gesagt prinzipiell könntest du, wenn du nur den Inhalt der versendeten Mail an den Client senden willst, auch einfach nur diesen String schicken. Aber da du ja das ganze Message-Objekt versenden willst, brauchst du wohl noch mehr Informationen (deswegen hab ich auch beispielhaft Empfänger und Sender genannt). Jetzt könntest du ein sogenanntes Shared-Object verwenden: Dieses liegt weder im *.client, noch im *.server Package deiner GWT-Applikation,normalerweise erstellt man dafür dann ein *.shared Package. Dieses muss zusätzlich zu deinem Client-Package auch in der Module-XML eingetragen werden, damit der JavaScript Code vom GWT-Compiler generiert wird.

Problem: Man sieht nicht, ob das Objekt nun server- oder clientseitig verwendet wird. Da können manchmal ganz üble Fehler entstehen. Und bei dieser Art Fehler gibt GWT nur "JavaScriptException" aus...ohne Hinweis, wo in deinem Java Code der Fehler ist. Ich sag nur: Viel Spaß bei der Suche...hat mich Stunden, sogar Tage an Programmierzeit gekostet.

Darum lieber ein Objekt für den Server und eines für den Client. Das Server-Objekt könntest du weglassen und den Json-Code selbst zusammenbauen und an den Client schicken...aber warum das machen, wenn man ein Objekt auch einfach parsen lassen kann (Gson sei hier nochmals erwähnt)?

Und die Response auf dem Client - der Overlay Type - könntest du auch weg lassen. Aber dann müsstest du mit den GWT Klassen JSONObject, JSONArray, JSONValue usw. usw. arbeiten - wie gesagt ein Graus. Der Overlay Type funktioniert da anders, da der Json-String einfach in ein JavaScriptObject geparsed wird und zwar mit höchster Geschwindigkeit, da die native Methode 'eval' des Browsers zum Einsatz kommt. Deshalb auch die JsonBaseResponse: Das ist einfach eine abstrakte Vaterklasse für all deine Responses, denn du wirst sicher noch irgendwann ein weiteres mal eine andere Response bei einer ganz anderen Funktion brauchen. Die statische "getResponse"-Methode gibt durch den Generic Parameter immer das entsprechende Response-Object zurück, von dem aus es aufgerufen wurde.
Also:
  • MessageResponse.getResponse(hier-json-string-einfügen) -> gibt eine MessageResponse zurück.
  • TestResponse.getResponse(hier-json-string-einfügen) -> gibt eine TestResponse zurück
  • BlablaResonse.getResponse(...) -> ...
  • ...

Du musst auf der Client-Seite immer in JavaScript denken. Und ein Json-String ist nichts anderes als ein JavaScript-Objekt.


Fazit:
Es ist rein deine Entscheidung, ob du meinen Vorschlag verwendest oder nicht. Klar müssen dafür dann für jede größere Server Rückgabe gleich zwei neue Beans angelegt werden. Aber allein der auf dem Client einfache Zugriff auf die Daten macht die Sache erheblich leichter zu programmieren und lesbarer.

Java:
@Override
public void onSuccess(List<String> result) {
    for (String json : result) {
        MessageResponse resp = MessageResponse.getResponse(json);
        String content = resp.getContent();
    
        // ... weitere Verarbeitung
    }
}


Ich hoffe das hats jetzt nochmal genau auf den Punkt gebracht :)

Gruß
Daniel
 

Kumaro

Mitglied
Hi Daniel!

Die Erklärung war super! Vielen Dank!

Ich werde mich morgen mal mit der Umsetzung versuchen und bei evtl. Problemen nochmal nachfragen wenn ich darf :)?

Vielen Dank nochmal!

Gruß
 

Improof

Erfahrenes Mitglied
Hi Kumaro! :)

Freut mich, dass ich helfen konnte. Natürlich kannst du dich bei Problemen nochmal an mich wenden.
Am Besten postest du es gleich hier oder in einem neuen Thread, wenns ein ganz neues Problem ist, dann haben auch alle was von der Antwort (bin eigentlich jeden Tag mal online und schau was es neues gibt ;))

Gruß
Daniel
 

Kumaro

Mitglied
Hi Daniel,

So, ich hab das ganze jetzt wie folgt umgesetzt:

Server-Seite:
Java:
public MailServiceImpl() {}
   
   public ArrayList<String> getMails(String user, String pass){
....

           Message messages[] = mailFolder.getMessages();               
            responseJsonList = new ArrayList<String>();
               
                for(int i=0; i < messages.length; i++ ){
                    MessageResponseObject messageResp = new MessageResponseObject();
                    messageResp.setDate(messages[i].getSentDate().toString());
                    messageResp.setFrom(messages[i].getFrom().toString());
                    messageResp.setSubject(messages[i].getSubject());
                    messageResp.setContent(messages[i].getContent().toString());
                    String jsonString = createJsonString(messageResp);
                                       
                    responseJsonList.add(jsonString);
                    System.out.println("Anzahl an NAchrichten im Array: " + responseJsonList.size());
                }
                closeInbox(mailFolder);
               
                          
           }catch(Exception err){
               
              System.out.println("Die Anmeldung ist fehlgeschlagen!");
              System.out.println(err);
           }
                   
            return responseJsonList;
        }



    /**
    * Create a JSON String for the network transport
    * @param messageResp
    * @return
    */
    private String createJsonString(MessageResponseObject messageResp){
       
        Gson gson = new Gson();
        return gson.toJson(messageResp, MessageResponseObject.class);
    }

Java:
public class MessageResponseObject {

    private String from;
    private String to;
    private String subject;
    private String date;
    private String content;
   
   
    public String getFrom() {
        return from;
    }
   
    public void setFrom(String from) {
        this.from = from;
    }
   
    public String getTo() {
        return to;
    }
   
    public void setTo(String to) {
        this.to = to;
    }
   
    public String getSubject() {
        return subject;
    }
   
    public void setSubject(String subject) {
        this.subject = subject;
    }
   
    public String getDate() {
        return date;
    }
   
    public void setDate(String date) {
        this.date = date;
    }
   
    public String getContent() {
        return content;
    }
   
    public void setContent(String content) {
        this.content = content;
    }

}





Client-Seite

Java:
//erzeugt callback vom Typ Message[]
            AsyncCallback<ArrayList<String>> callback = new AsyncCallback<ArrayList<String>>() {
               
                @Override
                public void onFailure(Throwable caught) {
                    System.out.println("Fehler");
                 }

                @Override
                public void onSuccess(ArrayList<String> messageJsonResults) {
                   
                    System.out.println(messageJsonResults.get(0));
                   
                }
               
            };
           
            //RPC Call zum MailService auf dem Server
            mailService.getMails(user, password, callback);
           
        }

Java:
package webmail.client;

public class MessageResponse extends JsonBaseResponse{

    protected MessageResponse(){}
   
    public final native String getContent()
        /*{ return this.content; } */;
   
}



public class JsonBaseResponse extends JavaScriptObject {
   
    protected JsonBaseResponse(){}
   
    public static final native <T extends JsonBaseResponse> T getResponse(String json) 
        /*-{ return eval('(' + json + ')'); } */;

}


Leider habe ich jetzt das Problem das ich folgenden Fehler bekomme:

Nov 16, 2014 5:59:04 PM com.google.appengine.tools.development.ApiProxyLocalImpl log
SCHWERWIEGEND: javax.servlet.ServletContext log: Exception while dispatching incoming RPC call
com.google.gwt.user.server.rpc.UnexpectedException: Service method 'public abstract java.util.ArrayList webmail.client.MailService.getMails(java.lang.String,java.lang.String)' threw an unexpected exception: java.lang.NoClassDefFoundError: com/google/gson/Gson

Scheinbar hat er Probleme die Gson Klasse zu finden oder wie soll man das verstehen?
Ich hab sie aber ganz normal in eclipse eingebunden und eclipse selber meckert auch nicht.

Hast du vielleicht eine Idee was ich übersehe?
 

Neue Beiträge