# Hilfe zu Design mit EJB



## beta20 (3. Nov 2019)

Hallo zusammen,

ich möchte gerne folgendes realisieren:
- Verschiedene Emailadressen per idle() abrufen um zu prüfen, ob neue Email ankommen.

Hierzu habe ich folgenden Code gefunden, der auch genau das macht:
https://gist.github.com/DeBukkIt/19f194935a2007c8ac0a64e798cd7fb8

Nun ist mein Problem aber, dass ich ebenfalls EJB verwende.
Das heißt:
Ich möchte auch innerhalb der oben genannten Klasse (der Link) auf eines meiner anderen EJB Klassen machen, was aber so dann nicht geht....

Wie kann man das genau machen?
Wenn ich das richtig sehe habe ich:

MyEmailIdleService
a) Ruft alle Emailadresse ab, bei denen eine Prüfung erfolgen soll
b) Macht dann einen Thread per Emailadresse auf und überwacht diesen

Funktionen wären dann:
void start();
void stop(String emailAdresse);

Kann hier jemand bitte helfen wie das genau aussehen kann?


----------



## mihe7 (3. Nov 2019)

Was ist falsch an https://javaee.github.io/javamail/docs/api/com/sun/mail/imap/IdleManager.html ?


----------



## beta20 (3. Nov 2019)

Habe ich noch nicht gesehen - benötige ich dann die oben geposteten Code von diesem Link überhaupt noch?


----------



## mihe7 (3. Nov 2019)

beta20 hat gesagt.:


> Habe ich noch nicht gesehen - benötige ich dann die oben geposteten Code von diesem Link überhaupt noch?


Denke mal nicht - ich hatte die Anforderung bisher nicht, in einer Java EE-Anwendung Postfächer überwachen zu müssen. Da JavaMail sowieso zum Einsatz kommt, frage ich mich halt, warum man nicht verwendet, was die Lib bietet. Sieht auf den ersten Blick jedenfalls recht einfach aus.


----------



## beta20 (3. Nov 2019)

ok...
Muss ich dann für jedes meiner Emailadressen, die ich überwachen möchte ein Objekt von "IdleManager" erstellen?


----------



## mihe7 (3. Nov 2019)

beta20 hat gesagt.:


> ok...
> Muss ich dann für jedes meiner Emailadressen, die ich überwachen möchte ein Objekt von "IdleManager" erstellen?


In der Doku steht: "The IdleManager is created with a Session, which it uses only to control debug output. A single IdleManager instance can watch multiple Folders from multiple Stores and multiple Sessions."


----------



## beta20 (3. Nov 2019)

das habe ich auch gesehen...

Aber ich möchte die Möglichkeit haben einzelne Emailadresse auch nicht mehr zu überwachen...
Das brauche ich doch pro Emailadresse einen IdleManager?
Damit ich die stop() - Methode anwenden kann?

Wie kann ich das machen?
Ich könnte ja zB alle IdleManager Objekte in einer Liste speichern. Leider hat IdleManager keine ID, sonst könnte ich sowas machen wie

public void stop(String id){

// Suche ID aus der Liste heraus und wenn auf dieses Objekt dann stop() an...

}


----------



## mihe7 (3. Nov 2019)

beta20 hat gesagt.:


> Das brauche ich doch pro Emailadresse einen IdleManager?
> Damit ich die stop() - Methode anwenden kann?


Du brauchst die stop()-Methode dafür nicht. Die Überwachung durch den IdleManager funktioniert andersrum. Auch das steht in der Doku (den wichtigen Part habe ich mal hervorgehoben): "Note that, after processing new messages in your listener, *or doing any other operations on the folder* in any other thread, you need to tell the IdleManager to watch for more new messages. Unless, of course, you close the folder."

Heißt: sobald Du irgendwas mit einem überwachten Folder machst, musst Du dem IdleManager erneut sagen, dass er den Folder überwachen soll.


----------



## beta20 (3. Nov 2019)

hm... 
Das heißt ich bräuchte eine while Schleife?

Im ersten Link wurde das ja auch so implementiert:
while(!stopped) {
....
}

Ich würde gerne auch loggen was das Programm so macht, aber wenn ich die Klasse aus der API nehme, kann ich das nicht machen?


----------



## mihe7 (3. Nov 2019)

beta20 hat gesagt.:


> Das heißt ich bräuchte eine while Schleife?


Nein, auch das steht in der Doku (im Code-Beispiel): Der Listener startet die Überwachung einfach neu.


----------



## beta20 (3. Nov 2019)

ja, aber wenn ich dann von "außen" eingreifen möchte und die Überwachung eines einzelnen Postfachs stoppen möchte, dann brauche ich doch eine Methode wie stop() ?


----------



## mihe7 (3. Nov 2019)

Du kannst Dir natürlich eine Methode stop() schreiben, die muss nichts anderes tun, als irgendeine Operation auf dem Folder auszuführen.


----------



## beta20 (3. Nov 2019)

Also so ganz komme ich damit leider noch nicht klar.... Ich habe jetzt doch mal den Beispielcode aus meinem ersten Post genommen, da das Grundprinzip hier schon passt - und auch Dinge wie alle 9 Minuten der Server connected wird um einen Timeout vermeiden etc.

Das Überwachen der Email funktioniert, aber mein Problem ist:
a) Aufruf einer anderen EJB dann

Vielleicht mal vom Grundprinzip, was ich machen will:
Ich will mehrere Emailadressen überwachen, ob neue Email vorhanden sind.
Wenn ja (und hier kommt dann der Aufruf einer anderen EJB zum Einsatz), möchte ich in meinem Backend prüfen, ob es zu dem Betreff und dem Sender in meiner Datenbank bereits einen Eintrag gibt.
Wenn ja, dann werden weitere Dinge geschehen (Speicherung in DB etc. - soll jetzt aber nicht das Thema sein)...

Im Moment sieht es nun so aus:
1) TicketEmailWatcherService...
-> Ich denke, diese muss ich später noch mit @Startup deklarieren, dass diese beim Starten der Applikation gestartet wird und die startListener() - Methode mit @PostConstruct ?

Hier hole ich mir die Emailadressen aus der Datenbank, die überprüft werden sollen.
Ebenfalls erstelle ich das Store - Objekt, um mich später zu connecten....

Grundproblem:
Am liebsten wäre mir in der "TicketEmailWatcherService", dass ich dann eine Liste der Emails zurückbekomme, die neu sind.
Und dann kann ich mit den "Message" - Objekten machen, was ich will. Nachricht in der DB speichern usw.
Aber wie kann ich das machen??


```
public class TicketEmailWatcherService {

    private final Logger LOGGER = LoggerFactory.getLogger(TicketEmailWatcherService.class);

    @EJB
    private TicketService ticketService;

    @EJB
    private EmployeeService employeeService;

    @EJB
    private EmailSettingPasswordService emailSettingPasswordService;

    @EJB
    private TicketEmailSettingService ticketEmailSettingService;

    public void startListener()
            throws InvalidKeyException, NoSuchAlgorithmException, NoSuchPaddingException, IllegalBlockSizeException,
            BadPaddingException, EmailSettingNotFoundException, IOException, MessagingException {

        LOGGER.info("START startListener");

        List<TicketEmailSetting> list = ticketEmailSettingService.findAllTicketEmailSettingListByStatusActive();

        if (list == null || list.isEmpty())
            return;

        for (TicketEmailSetting ticketEmailSetting : list) {

            Store store = connect(ticketEmailSetting.getEmailSetting());
            MailPushEventHandler mailEventHandler = new MailPushEventHandler(store);

        }

        LOGGER.info("END startListener");
    }
```


```
import java.io.IOException;

import javax.mail.Message;
import javax.mail.MessagingException;
import javax.mail.Store;

public class MailPushEventHandler extends MailPushEmpfaenger {

    public MailPushEventHandler(Store store) {
        super(store);
    }

    @Override
    public void onMailReceived(Object[] messages) {

        if (messages != null && messages.length > 0) {

            for (Object msg : messages) {

                try {
                    String textFromMessage = getTextFromMessage((Message) msg);

                } catch (MessagingException e) {
                    e.printStackTrace();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }


[CODE=java]public class MailPushEmpfaenger {

    private final Logger LOGGER = LoggerFactory.getLogger(MailPushEmpfaenger.class);

    private String randomInstanceID;
    private boolean stopped;

    private IMAPFolder inbox;

    private Thread listeningThread;
    private Thread reconnectionThread;

    /**
     * Constructor
     *
     * @param host
     * @param username
     * @param password
     */
    public MailPushEmpfaenger(Store store) {

        randomInstanceID = String.valueOf(new Random().nextInt(Integer.MAX_VALUE));

        listeningThread = new Thread(new Runnable() {

            @Override
            public void run() {

                while (!stopped) {

                    try {

                        LOGGER.info("[MailRX " + randomInstanceID + "] Connecting and logging in...");

//                        // Connect with IMAP Server
//                        Properties props = new Properties();
//                        props.setProperty("mail.store.protocol", "imaps");
//                        Session session = Session.getDefaultInstance(props, null);
//                        Store store = session.getStore("imaps");
//                        store.connect(host, username, password);

                        LOGGER.info("[MailRX " + randomInstanceID + "] Connected.");

                        // Open inbox
                        inbox = (IMAPFolder) store.getFolder("INBOX");
                        inbox.open(IMAPFolder.READ_WRITE);

                        // initial Mail Check
                        checkNewMessages();

                        while (!stopped) {
                            LOGGER.info("[MailRX " + randomInstanceID + "] IDLE-ing");

                            inbox.idle(true);
                            checkNewMessages();
                        }

                    } catch (MessagingException e) {
                        e.printStackTrace();
                    }
                }
            }
        });

        reconnectionThread = new Thread(new Runnable() {
            @Override
            public void run() {

                while (!stopped) {

                    try {
                        // Sleep 9 minutes
                        Thread.sleep(9 * 60 * 1000);

                        LOGGER.info("[MailRX " + randomInstanceID + "] NOOP");

                        inbox.doCommand(new IMAPFolder.ProtocolCommand() {
                            @Override
                            public Object doCommand(IMAPProtocol protocol) throws ProtocolException {
                                protocol.simpleCommand("NOOP", null);
                                return null;
                            }
                        });

                    } catch (Exception e) {
                        e.printStackTrace();
                    }

                }
            }
        });

        listeningThread.start();
        reconnectionThread.start();
    }

    /**
     * Check for new Emails
     *
     * @throws MessagingException
     */
    private Object[] checkNewMessages() throws MessagingException {

        LOGGER.info("[MailRX " + randomInstanceID + "] Checking for new messages...");

        if (!inbox.isOpen()) {
            inbox.open(IMAPFolder.READ_WRITE);
        }

        if (inbox.getMessageCount() > 0) {

            ArrayList<Message> msgs = new ArrayList<Message>();

            Message[] tempMsgs = inbox.getMessages(inbox.getMessageCount() - 10, inbox.getMessageCount());
            for (Message tempMsg : tempMsgs) {
                if (!tempMsg.isSet(Flags.Flag.SEEN)) {
                    msgs.add(tempMsg);
                    tempMsg.setFlag(Flags.Flag.SEEN, true);
                }
            }

            onMailReceived(msgs.toArray());
            LOGGER.info("[MailRX " + randomInstanceID + "] Forwarded " + msgs.size() + " E-Mails.");

            return msgs.toArray();
        }
        return null;
    }

    public void onMailReceived(Object[] messages) {
        // Overwrite me
    }

    /**
     * Return the primary text content of the message.</br>
     * Source: https://javaee.github.io/javamail/FAQ#mainbody
     *
     * @param p The message
     * @return The message's main body text content
     * @throws MessagingException If something went wrong
     * @throws IOException        If something else went wrong
     */
    public String getTextFromMessage(Part p) throws MessagingException, IOException {
        if (p.isMimeType("text/*")) {
            String s = (String) p.getContent();
            return s;
        }

        if (p.isMimeType("multipart/alternative")) {
            Multipart mp = (Multipart) p.getContent();
            String text = null;
            for (int i = 0; i < mp.getCount(); i++) {
                Part bp = mp.getBodyPart(i);
                if (bp.isMimeType("text/plain")) {
                    if (text == null)
                        text = getTextFromMessage(bp);
                    continue;
                } else if (bp.isMimeType("text/html")) {
                    String s = getTextFromMessage(bp);
                    if (s != null)
                        return s;
                } else {
                    return getTextFromMessage(bp);
                }
            }
            return text;
        } else if (p.isMimeType("multipart/*")) {
            Multipart mp = (Multipart) p.getContent();
            for (int i = 0; i < mp.getCount(); i++) {
                String s = getTextFromMessage(mp.getBodyPart(i));
                if (s != null)
                    return s;
            }
        }

        return null;
    }

    public void stop() {
        this.stopped = true;
    }

}
```
}[/CODE]


----------



## mihe7 (3. Nov 2019)

beta20 hat gesagt.:


> Das Überwachen der Email funktioniert, aber mein Problem ist:
> a) Aufruf einer anderen EJB dann


Die EJB Spec "verbietet", dass Du selbst Threads erstellst. Natürlich kann Dich die Spec nicht daran hindern, aber Du solltest keinesfalls aus einem solchen Thread Dinge verwenden, die unter der Fuchtel des Application Servers stehen. Außerdem ist es keine gute Idee, für jede Verbindung einen eigenen Thread zu verbraten.

Wenn Du trotzdem daran festhalten willst, würde mir spontan einfallen, eine LinkedBlockingQueue aus diesen Threads heraus zu füllen und diese Queue aus einem managed Thread (ManagedExecutorService) heraus zu lesen. Die EJB und Deine Threads teilen sich also eine Queue.


----------



## beta20 (3. Nov 2019)

hm, so wirklich ganz verstehe ich nicht was du meinst...
Hast du ein Beispiel und was muss ich dann umbauen von dem Code oben?


----------



## mihe7 (3. Nov 2019)

Schemenhaft: in Deiner EJB injectest Du einen ManagedExecutorService und erstellst die Queue.

```
@Resource
private ManagedExecutorService executor;
private Queue<Object> messages;
private volatile boolean running;

@PostConstruct 
protected void init() {
    messages = new LinkedBlockingQueue<>();
    running = true;
    executor.submit(() -> {
        while (running) {
            Object obj = messages.take();
            doSomethingWith(obj);
        }
    });
}
```

Bei der Erzeugung der MailPushHandler gibst Du die Queue messages mit, in onMailReceived schiebst Du die Objekte in die Queue (queue.offer()).


----------



## mrBrown (3. Nov 2019)

Nur als kleine Info am Rande: achte doch bitte darauf, deine Beiträge in einem halbwegs passenden Unterforum zu erstellen, dies hier ist ebenso wie deine anderen Beiträge alles andere als ein Anfängerthema


----------



## beta20 (4. Nov 2019)

Ok, danke...

die Fragen, die sich mir dann allerdings ergeben:

a) Was benötige ich dann noch von dem obigen Code? Muss ich das mit den Threads komplett löschen?
b) Was macht: Object obj = messages.take();
c) Wie kann ich einzelne Postfächer stoppen? (Watch stoppen) ?
d) Was ist mit: queue.offer()  gemeint?


----------



## mihe7 (4. Nov 2019)

a) alles - es ging ja nur darum, eine Verbindung zwischen den von Dir und den vom Application Server verwalteten Threads herzustellen, um auf die Ressourcen (wie EJBs) zugreifen zu können. Diese Verbindung erfolgt über eine Queue. Daher:


mihe7 hat gesagt.:


> Bei der Erzeugung der MailPushHandler gibst Du die Queue messages mit, in onMailReceived schiebst Du die Objekte in die Queue (queue.offer()).


b) https://docs.oracle.com/javase/7/docs/api/java/util/concurrent/LinkedBlockingQueue.html#take()
c) wie bisher - Du rufst die stop-Methode deines MailPushHandlers auf.
d) https://docs.oracle.com/javase/7/docs/api/java/util/concurrent/LinkedBlockingQueue.html#offer(E)

Das Prinzip ist ganz einfach: Du hast einen Postfach-Thread. Der erhält irgendwann neue Nachrichten und schiebt diese in einen FIFO-Puffer (Queue). Auf der anderen Seite hast Du einen vom Application Server verwalteten Worker-Thread, der einfach eine Nachricht nach der anderen aus der Queue liest und damit irgendwas macht.


----------



## beta20 (4. Nov 2019)

OK - also habe ich immernoch Threads, die dann aber vom Application Server bearbeitet werden...
Und wie sieht der Aufbau dann der Klassen aus? Welche Modifikation brauche ich in den oberen Klassen?

a) Wie starte ich denn den Listener, der prüft, ob neue Email da sind oder nicht?

b) Im Idealfall rufe ich dann in der public void onMailReceived(Object[] messages) eine Methode via EJB auf, die dann weiteres macht (Ticket erstellen aus den Infos der Message)
Demnach bräuchte ich noch eine Instanzierung in* public class MailPushEventHandler extends MailPushEmpfaenger* ?

c) Wie muss  doSomethingWith(obj); abgeändert werden?


----------



## mihe7 (4. Nov 2019)

a) Das passiert bei der Erzeugung Deines Handlers doch automatisch
b) Nein, im Idealfall rufst Du in onMailReceived die offer-Methode der Queue auf.
c) doSomethingWith(obj) ist lediglich ein Platzhalter, den Du halt mit den für Dich passenden Anweisungen ersetzen musst. 



beta20 hat gesagt.:


> Welche Modifikation brauche ich in den oberen Klassen?


Das habe ich in #16 bereits beschrieben. Der Code aus #16 muss in Deine EJB (TicketEmailWatcherService). Bei der Erzeugung des MailPushEventHandlers musst Du die messages-Queue mitgeben (-> Anpassung des Konstruktors von MailPushEventHandler). Und in MailPushEventHandler#onMessageReceived legst Du das zu verarbeitende Objekte in die Queue mit der offer-Methode.


----------



## beta20 (4. Nov 2019)

welche Klassen stecken denn hinter: private Queue<Object> messages; ?
Bekomme in Eclipse einen Fehler, dass er "take" nicht findet...
"The method take() is undefined for the type Queue<Object>"


----------



## mrBrown (4. Nov 2019)

Mach aus Queue eine BlockingQueue.

(Bei solchen Problemen hilft ein kleiner Blick ins Javadoc, im Link von @mihe7 steht drin, woher die Methode kommt  )


----------



## beta20 (4. Nov 2019)

OK, danke - klappt....
Fangen wir Schritt für Schritt an:

Passt dieser Code nun im "TicketEmailWatcherService" (EJB Klasse) ?


```
@Resource
    private ManagedExecutorService executor;
    private BlockingQueue<Object> messages;
    private volatile boolean running;

    /**
     * Init watching Emailaddresses
     */
    @PostConstruct
    protected void initStartTicketListener() {

        LOGGER.info("START initStartTicketListener");

        messages = new LinkedBlockingQueue<>();
        running = true;
        executor.submit(() -> {
            while (running) {

                try {
                    Object obj = messages.take();

                    List<TicketEmailSetting> list = ticketEmailSettingService
                            .findAllTicketEmailSettingListByStatusActive();
                    if (list == null || list.isEmpty())
                        return;

                    for (TicketEmailSetting ticketEmailSetting : list) {
                        Store store = connect(ticketEmailSetting.getEmailSetting());
                        MailPushEventHandler mailEventHandler = new MailPushEventHandler(store, messages);
                    }
                } catch (Exception e) {

                }
            }
        });

        LOGGER.info("END initStartTicketListener");
    }
```


----------



## mihe7 (4. Nov 2019)

beta20 hat gesagt.:


> Passt dieser Code nun im "TicketEmailWatcherService" (EJB Klasse) ?


Nein. Der Teil

```
List<TicketEmailSetting> list = ticketEmailSettingService
                            .findAllTicketEmailSettingListByStatusActive();
                    if (list == null || list.isEmpty())
                        return;

                    for (TicketEmailSetting ticketEmailSetting : list) {
                        Store store = connect(ticketEmailSetting.getEmailSetting());
                        MailPushEventHandler mailEventHandler = new MailPushEventHandler(store, messages);
                    }
```
gehört nicht in den Thread. Der Code dient ja dem Start der Überwachung.


----------



## beta20 (4. Nov 2019)

Also so???


```
@PostConstruct
    protected void initStartTicketListener() {

        LOGGER.info("START initStartTicketListener");

        try {

            List<TicketEmailSetting> list = ticketEmailSettingService.findAllTicketEmailSettingListByStatusActive();
            if (list == null || list.isEmpty())
                return;

            for (TicketEmailSetting ticketEmailSetting : list) {
                Store store = connect(ticketEmailSetting.getEmailSetting());
                MailPushEventHandler mailEventHandler = new MailPushEventHandler(store, messages);
            }
        } catch (Exception e) {

        }

        messages = new LinkedBlockingQueue<>();
        running = true;
        executor.submit(() -> {
            while (running) {

                try {
                    Object obj = messages.take();
                } catch (InterruptedException e) {

                }
            }
        });

        LOGGER.info("END initStartTicketListener");
    }
```

dann verstehe ich aber nicht, was hier passieren soll:

```
executor.submit(() -> {
            while (running) {
                try {
                    Object obj = messages.take();
                } catch (InterruptedException e) {
                }
            }
```


----------



## mihe7 (4. Nov 2019)

beta20 hat gesagt.:


> Also so???


Fast. Nimm den try-catch-Block (EDIT: der auf Ebene von initStartTicketListener) nach unten, dann ist messages auch schon initialisiert, wenn Du den Konstruktor von MailPushEventHandler aufrufst 



beta20 hat gesagt.:


> dann verstehe ich aber nicht, was hier passieren soll:


Aktuell entnimmst Du nur das Objekt aus der Queue, machst aber damit noch nichts. Du könntest an der Stelle die Nachricht in der DB speichern, oder was auch immer Du damit anfangen willst.


----------



## beta20 (4. Nov 2019)

Ok, try / catch is nach unten:

```
/**
     * Init watching Emailaddresses
     */
    @PostConstruct
    protected void initStartTicketListener() {

        LOGGER.info("START initStartTicketListener");

        messages = new LinkedBlockingQueue<>();
        running = true;

        try {

            List<TicketEmailSetting> list = ticketEmailSettingService.findAllTicketEmailSettingListByStatusActive();
            if (list == null || list.isEmpty())
                return;

            for (TicketEmailSetting ticketEmailSetting : list) {
                Store store = connect(ticketEmailSetting.getEmailSetting());
                MailPushEventHandler mailEventHandler = new MailPushEventHandler(store, messages);
            }
        } catch (Exception e) {

        }

        executor.submit(() -> {
            while (running) {

                try {
                    Object obj = messages.take();
                } catch (InterruptedException e) {

                }
            }
        });

        LOGGER.info("END initStartTicketListener");
    }
```

Die Klasse habe ich so angepasst:

```
public class MailPushEventHandler extends MailPushEmpfaenger {

    public MailPushEventHandler(Store store, Queue<Object> messagesQueue) {
        super(store);
    }

    @Override
    public void onMailReceived(Object[] messages) {

        if (messages != null && messages.length > 0) {

            for (Object msg : messages) {

                try {
                    String textFromMessage = getTextFromMessage((Message) msg);

                } catch (MessagingException e) {
                    e.printStackTrace();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

}
```


Im Grunde genommen bräuchte doch den Code von onMailReceived in meiner TicketEmailWatcherService - Klasse in der while - Schleife?

Mit all den "neuen" Email, die in Object[] messages sind, soll dann eben was passieren (Ticket in Datenbank anlegen).
Also eigentlich sowas wie:


```
executor.submit(() -> {
            while (running) {

                try {

                    Object obj = messages.take();
                   MailPushEventHandler m = (MailPushEventHandler) obj;
                   Object[] messages = m.getMessages();

 if (messages != null && messages.length > 0) {
            for (Object msg : messages) {
                try {
                    // Rufe andere EJB Klasse auf und speichere in DB
                } catch (MessagingException e) {
                    e.printStackTrace();
                } catch (IOException e) {

                    e.printStackTrace();
                }
            }
                } catch (InterruptedException e) {
                }
            }
        });
    }
```


----------



## mihe7 (4. Nov 2019)

beta20 hat gesagt.:


> Die Klasse habe ich so angepasst:


Du musst die übergebene Queue natürlich noch in einer Instanzvariablen speichern.


```
public class MailPushEventHandler extends MailPushEmpfaenger {
    Queue<Object> messagesQueue;

    public MailPushEventHandler(Store store, Queue<Object> messagesQueue) {
        super(store);
        this.messageQueue = messagesQueue;
    }

    @Override
    public void onMailReceived(Object[] messages) {

        if (messages != null && messages.length > 0) {

            for (Object msg : messages) {
                messagesQueue.offer(msg);
            }
        }
    }
}
```



beta20 hat gesagt.:


> Mit all den "neuen" Email, die in Object[] messages sind, soll dann eben was passieren (Ticket in Datenbank anlegen).
> Also eigentlich sowas wie:


Nein, das obj aus der Queue ist ja bereits eine neue Message (oder was auch immer Du in die Queue schiebst):


```
executor.submit(() -> {
            while (running) {
                try {
                    Object obj = messages.take();
                    // Rufe andere EJB Klasse auf und speichere obj in DB
                } catch (MessagingException e) {
                    e.printStackTrace();
                } catch (IOException e) {

                    e.printStackTrace();
                }
            }
                } catch (InterruptedException e) {
                }
            }
        });
    }
```


----------



## beta20 (4. Nov 2019)

Ah ok, danke dir.

Dann bleibt jetzt aber noch die Frage wie ich die Überwachung von einzelnen Postfächer stoppe?
In der TicketEmailWatcherService schwebt mir sowas vor wie:

public void stop(String emailAddress){
???
}

Was ich brauche ist eine eindeutige ID / Emailadresse, in der ich das "watching" dann eines MailPushEventHandler stoppe?


----------



## mihe7 (4. Nov 2019)

beta20 hat gesagt.:


> Was ich brauche ist eine eindeutige ID / Emailadresse, in der ich das "watching" dann eines MailPushEventHandler stoppe?


Du erzeugst ja für jede Adresse einen MailPushEventHandler, d. h. Du musst lediglich die Adresse über eine Map auf den Handler abbilden.

Also etwas wie:


```
private Map<String, MailPushEventHandler> handlers;

    @PostConstruct
    protected void initStartTicketListener() {

        LOGGER.info("START initStartTicketListener");

        handlers = new HashMap<>();
        messages = new LinkedBlockingQueue<>();
        running = true;

        try {

            List<TicketEmailSetting> list = ticketEmailSettingService.findAllTicketEmailSettingListByStatusActive();
            if (list == null || list.isEmpty())
                return;

            for (TicketEmailSetting ticketEmailSetting : list) {
                Store store = connect(ticketEmailSetting.getEmailSetting());
                handlers.put(ticketEmailSetting.getEmail(), new MailPushEventHandler(store, messages));
            }
        } catch (Exception e) {

        }
...
```



```
public void stop(String emailAddress){
    MailPushEventHandler handler = handlers.get(emailAddress);
    if (handler != null) {
        handler.stop();
        handlers.remove(emailAddress, handler);
    }
}
```


----------



## beta20 (5. Nov 2019)

Danke, werde es mir später im Detail anschauen


----------



## beta20 (5. Nov 2019)

Läuft nun super. Vielen vielen Dank.
Muss ich den Handler noch initialisieren? Wenn ja, wie? 
*Map<String, MailPushEventHandler> handlers *


Ich habe noch ein Problem bei folgendem Code, welcher allerdings nur ein paar Mal auftritt.
Ich schätze, wenn ich die Email schon gelesen habe oder ähnliches.


```
if (inbox.getMessageCount() > 0) {

            ArrayList<Message> msgs = new ArrayList<Message>();

            Message[] tempMsgs = inbox.getMessages(inbox.getMessageCount() - 10, inbox.getMessageCount());
            for (Message tempMsg : tempMsgs) {
                if (!tempMsg.isSet(Flags.Flag.SEEN)) {
                    msgs.add(tempMsg);
                    tempMsg.setFlag(Flags.Flag.SEEN, true);
                }
            }
```

Exception kommt hier: for (Message tempMsg : tempMsgs) {
Folgende Exception wird geworfen: javax.mail.MessageRemovedException

Wie könnte ich das noch beheben?


----------



## mihe7 (5. Nov 2019)

Die Exception dürfte eine Zeile tiefer kommen. Du könntest vorher noch tempMsg.isExpunged() prüfen und eine ggf. doch auftretende Exception abfangen.


----------



## beta20 (5. Nov 2019)

also du meinst:
*if*(tempMsg.isExpunged()) {
*continue*;
                }

BTW:
Sehe gerade, dass hier dieser Code verwendet wird:

```
Message[] tempMsgs = inbox.getMessages(inbox.getMessageCount() - 10, inbox.getMessageCount());
```

Was macht das mit -10 ?
Das wurde doch nur aus Testzwecken so verwendet, dass eben nur die letzten 10 Email genommen werden?


----------



## mihe7 (5. Nov 2019)

beta20 hat gesagt.:


> also du meinst:


Zum Beispiel oder 

```
if (!tempMsg.isExpunged() && !tempMsg.isSet(Flags.Flag.SEEN)) {
```



beta20 hat gesagt.:


> Was macht das mit -10 ?
> Das wurde doch nur aus Testzwecken so verwendet, dass eben nur die letzten 10 Email genommen werden?


Ja, die letzten 10 Emails. Warum: keine Ahnung, das ist nicht von mir


----------



## beta20 (6. Nov 2019)

Hierzu eine generelle Frage:
- Wenn ich zB. 1000 Emailadressen gleichzeitig überwache, laufe ich hier gewiss in Performance - Probleme?


----------



## mihe7 (6. Nov 2019)

beta20 hat gesagt.:


> Wenn ich zB. 1000 Emailadressen gleichzeitig überwache, laufe ich hier gewiss in Performance - Probleme?


Natürlich:


mihe7 hat gesagt.:


> Außerdem ist es keine gute Idee, für jede Verbindung einen eigenen Thread zu verbraten.


----------



## beta20 (11. Nov 2019)

Ok, danke....
D.h. man bräuchte genügend Ressourcen (Server) ?


----------



## mihe7 (11. Nov 2019)

Nein, man braucht non-blocking I/O.


----------



## beta20 (11. Nov 2019)

hm, das heißt?


----------



## mihe7 (11. Nov 2019)

D. h. dass Du nicht je Verbindung einen Thread hast, sondern ein Thread für zig Verbindungen gleichzeitig zuständig ist. Der IdleManager aus #2 arbeitet mit non-blocking I/O.


----------



## beta20 (11. Nov 2019)

hm, und wie macht man sowas?


----------



## mihe7 (11. Nov 2019)

Siehe Kommentare bis #12.


----------



## beta20 (11. Nov 2019)

Verstehe nicht worauf du hinaus willst..


----------



## mihe7 (11. Nov 2019)

Wenn Du den IdleManager (s. #2) verwendest und keine Threads, bekommst Du non-blocking I/O gratis. Vielleicht habe ich Dich auch missverstanden und Du willst wissen, wie man selbst mit non-blocking I/O verwendet. Unter http://tutorials.jenkov.com/java-nio/index.html scheint es ein Tutorial dazu zu geben.


----------



## beta20 (12. Nov 2019)

ja, ich verwende den IdleManager.
Dann passt es doch schon bereits? Oder verstehe ich dich jetzt falsch?

Die Frage, die ich mir eben stelle ist, wenn ich bspw. 1000 Emailadressen überwache, ob ich Probleme bekomme im Server.
Dass es dann vllt zu einer Verzögerung bei der Überwachung kommt, ist dann verständlich.
Die Frage ist dann eben, ob mehr Server (mehr Kapazität, mehr Threads) dies dann ausgleichen?


----------



## mihe7 (12. Nov 2019)

beta20 hat gesagt.:


> Die Frage, die ich mir eben stelle ist, wenn ich bspw. 1000 Emailadressen überwache, ob ich Probleme bekomme im Server.
> Dass es dann vllt zu einer Verzögerung bei der Überwachung kommt, ist dann verständlich.
> Die Frage ist dann eben, ob mehr Server (mehr Kapazität, mehr Threads) dies dann ausgleichen?


Ach, so. Das kann man pauschal nicht beantworten, da es davon abhängig, wie der Server implementiert ist. Aber grds. ja.


----------



## beta20 (3. Dez 2019)

mihe7 hat gesagt.:


> Schemenhaft: in Deiner EJB injectest Du einen ManagedExecutorService und erstellst die Queue.
> 
> ```
> @Resource
> ...



Habe hierzu nochmal eine allgemeine Frage.
Ich würde gerne in meiner Applikation einen "Scheduler" haben. Also um konkret zu sein:
- Ich habe eine DB - Tabelle "MySchedules". Hier steht dann das "plannedExecutionDate".

Im Moment habe ich eine Methode mit @Schedule annotiert, die dann jede Minute läuft.
Hier wird geprüft, ob es in der DB Einträge in der "MySchedules" gibt, die noch nicht losgelaufen sind und das "plannedExecutionDate" kleiner als die jetztige Uhrzeit ist.
Wenn das zutrifft, dann wird das Objekt von "MySchedules" in eine Liste von MySchedules hinzugefügt und anschließend eine entsprechende Methode aufgerufen (Kunde erstellen whatever):


```
@Timeout
   @AccessTimeout(value = 20, unit = TimeUnit.MINUTES)
    @Schedule(minute = "*/1", hour = "*", persistent = false)
    public void execute() throws Exception {
        LOGGER.info("START SCHEDULER");
      
  try {
            if (currentlyRunning) {
                LOGGER.warn("Scheduler is currently running...");
                return;
            }

            if (schedulingService == null)
                schedulingService = new SchedulingService();

            currentlyRunning = true;

            /**
             * Alle noch nicht ausgeführten Schedules bekommen
             */
            List<ScheduleExecution> scheduleExecutionList = scheduleExecutionService
                    .findAllScheduleExecutionOutstanding();

            if (scheduleExecutionList == null || scheduleExecutionList.size() == 0) {
                LOGGER.info("NO PLANNED SCHEDULES FOUND");
                currentlyRunning = false;
                return;
            }

            LOGGER.info("FOUND SCHEDULES: " + scheduleExecutionList.size());

            for (ScheduleExecution scheduleExecution : scheduleExecutionList) {
                synchronized (this) {
                    schedulingService.addTaskToExecutor(taskId, new Runnable() {
                        @Override
                        public void run() {
                            try {
                               // executeAction(scheduleExecution);
                            } catch (Execption e) {
                                LOGGER.error(ExceptionUtils.getFullStackTrace(e));
                            }
                        }
                    }, 0);
                }
            }
            currentlyRunning = false;

        }

        catch (Exception e) {
            LOGGER.error(ExceptionUtils.getFullStackTrace(e));
            currentlyRunning = false;
        }

        LOGGER.info("END SCHEDULER");

    }
```

Meine Klasse "SchedulingService" sieht so aus:


```
public class SchedulingService {

    private static Map<String, ScheduledFuture> scheduleFutures = new HashMap<>();
    private ScheduledExecutorService scheduledExecutorService = null;
    private int executorPoolSize = 5;

    /**
     *
     * Will create ScheduledService Object
     *
     */
    public void createScheduledExecutor(int executorPoolSize) {
        scheduledExecutorService = Executors.newScheduledThreadPool(executorPoolSize);
        addRecurringTaskToExecutor("removeStopedScheduledFuturesFromMap", new Runnable() {
            @Override
            public void run() {
                List<String> tasksToBeRemoved = new ArrayList<>();
                synchronized (scheduleFutures) {
                    scheduleFutures.keySet().stream().filter(task -> {
                        return scheduleFutures.get(task).isDone();
                    }).forEachOrdered(task -> {
                        tasksToBeRemoved.add(task);
                    });

                    tasksToBeRemoved.stream().forEach(key -> {
                        scheduleFutures.remove(key);
                    });
                }

            }
        }, 0, 10);
    }

    /**
     *
     * Will Stop the ScheduledService
     *
     */
    public void stopScheduledExecutor() {
        scheduledExecutorService.shutdown();
    }

    /**
     *
     * return ScheduledService Object if exists otherwise first create the object
     * then return
     *
     */
    public ScheduledExecutorService getExecutorServiceObject() {
        if (scheduledExecutorService == null) {
            createScheduledExecutor(this.executorPoolSize);
        }
        return scheduledExecutorService;
    }

    /**
     *
     * Add new tasks to executors
     *
     */
    public void addRecurringTaskToExecutor(String taskID, Runnable event, int initialDelay, int periodicDelay) {
        synchronized (scheduleFutures) {
            System.out.println("Scheduling new task : " + taskID);
            scheduleFutures.put(taskID, getExecutorServiceObject().scheduleAtFixedRate(event, initialDelay,
                    periodicDelay, TimeUnit.SECONDS));
        }
    }

    public void addTaskToExecutor(String taskID, Runnable event, int initialDelay) {
        synchronized (scheduleFutures) {
            System.out.println("Scheduling new task : " + taskID);
            scheduleFutures.put(taskID, getExecutorServiceObject().schedule(event, initialDelay, TimeUnit.SECONDS));
        }
    }

    public void addTaskToExecutor(String taskID, Runnable event) {
        addTaskToExecutor(taskID, event, 0);
    }

    /**
     *
     * Will remove tasks from scheduler
     *
     */
    public void removeTaskFromExecutor(String taskID, boolean forceStop) {
        synchronized (scheduleFutures) {
            System.out.println("Stopping " + taskID);
            if (scheduleFutures.containsKey(taskID))
                scheduleFutures.get(taskID).cancel(forceStop);
            else
                System.out.println(taskID + " Already Stopped");

        }
    }
}
```

Nun meine Frage, macht das Sinn?
Weil du vorher was geschrieben hast, dass man nicht selbst Threads anlegen soll?

Ich freue mich über Feedback


----------



## mihe7 (3. Dez 2019)

beta20 hat gesagt.:


> Ich würde gerne in meiner Applikation einen "Scheduler" haben.


`@Resource ManagedScheduledExecutorService scheduler;` ?


beta20 hat gesagt.:


> Weil du vorher was geschrieben hast, dass man nicht selbst Threads anlegen soll?


Vorher ist gut... das war heute vor einem Monat  Und ja, das sagt die Spezifikation.


----------



## beta20 (3. Dez 2019)

ok, danke  - das heißt der obige Code sollte ich nicht anwenden??
In meiner Klasse verwende ich bereits: "ScheduledExecutorService".

- Prinzipiell muss natürlich auch gegeben sein, dass ich den Schedule:
a) Starten
b) Erneut starten
c) stoppen
-> D.h. dass ich auch einzelne Tasks abbrechen möchte, daher auch die ID eines Tasks.

Wenn ich nun aber "ManagedScheduledExecutorService scheduler; " verwenden soll, dann ist mir nicht klar wie ich meinen obigen Code damit ersetzen muss?
Ggf. ist die Bezeichnung auch verwirrend. Ich will nicht über EJB steuern, wann der Schedule laufen soll. Der Schedule soll *IMMER *aktiv sein. Ich habe in meinem Beispiel das nur so eingestellt, dass eben jede Minute geprüft wird, ob neue Tasks auszuführen sind.
Wann ein Task ausgeführt werden soll, steht dann in der Datenbank.

Um das mal zu verdeutlichen, was ich gerne machen möchte:

Ich kann Schedules einstellen. Diese Infos werden in meiner DB gespeichert (Tabelle: ScheduleExecution)



Hier sehe ich dann eine Übersicht der derzeit ausgeführten Tasks. Hier kann ich sie u.a. auch:
- Abbrechen
- oder erneut anstarten (wenn fehlgeschlagen)

- Ebenfalls sollte es möglich sein den kompletten Service auszustellen.
- Zudem sollte es möglich sein die Anzahl an gleichzeitig ausführbaren Tasks (pro Serverinstanz) einzustellen.


Hier auch noch ein Video:


----------

