# Wert aus einem Future raus bekommen



## DaBe1812 (6. Sep 2022)

Hi,
ich bastle gerade mit Futures rum und habe ein kleines Problem. Ich habe eine Page um eine Massenoperation durch zu führen, die gut und gerne mal Minuten lang läuft. Jetzt will ich den User nicht hängen lassen, also erstmal Sanduhr eingeblendet, bis die Operation durch ist. jetzt möchte ich dem User aber auch sagen, bei wie viel Prozent der Prozess gerade ist, und da kommt der Fehler.

Also der Handler sieht so aus:

```
@Named("massenabbauhandler")
@ViewScoped
public class MassenabbauHandler implements Serializable {

    private static final long serialVersionUID = 1L;

    private Integer inProgress = 0;
    private Massenabbau massenabbau = new Massenabbau();
    private List<String> abbauObjects = new ArrayList<>();
    private String abbauStep = "Bitte laden Sie eine Datei hoch";
    private boolean startBlocked = true;
    private List<String> posCols = new ArrayList<>();
    private boolean error = false;

    @Inject private MassenabbauWorker worker;

    public void startAbbau() throws InterruptedException, ExecutionException {
        setInProgress(1);

        worker.setMassenabbau(massenabbau);
        
        Future<String> workerResult = worker.work();
        
        while(!workerResult.isDone()) {
            Thread.sleep(1000);
            setInProgress(worker.getStatus());
        }

        setInProgress(100);

        abbauStep = workerResult.get();

        this.massenabbau = null;
        
        FacesMessage.Severity severity = FacesMessage.SEVERITY_INFO;
        String summary = "Abbau erfolgreich";
        String detail = "Die Systeme wurden erfolgreich abgebaut";
        
        if(abbauStep.contains("!!ERROR!!")) {
            severity = FacesMessage.SEVERITY_ERROR;
            summary = "Fehler beim Abbau";
            detail = abbauStep.replace("!!ERROR!!", "");
            abbauStep = "FEHLER " + detail;
            error = true;
        }

        FacesContext.getCurrentInstance().addMessage(null, new FacesMessage(severity , summary, detail));
        PrimeFaces.current().ajax().update("abbauForm:dataPanel");
    }
}
```

Der MassenabbauWorker dann so:

```
@Stateless
public class MassenabbauWorker {

    private static final Logger LOG = LogManager.getLogger(MassenabbauWorker.class);
    
    private String object;
    private List<Map<String,String>> hostList;
    private String spalte;
    private LocalDate abbauDatum;
    private String ticketNo;

    private double numHosts = 0d;
    private double actHost = 0d;

    @Inject private MassenabbauDbHandler dbHandler;
    
    @Resource private ManagedExecutorService mes;

    public void setMassenabbau(Massenabbau massenabbau) {
        object = massenabbau.getAbbauObject();
        hostList = massenabbau.getSheet().getTableSheetContent();
        spalte = massenabbau.getHostCol();
        abbauDatum = massenabbau.getAbbaudatum();
        ticketNo = massenabbau.getTicket();
        numHosts = hostList.size();
    }

    public Future<String> work() {
        return mes.submit(() -> { return doAbbau();});
    }

    public String doAbbau() {
        LocalDateTime start = LocalDateTime.now();
        String ret = "";
    try {
      tu Zeug und zähl Zeilen
      return "Abbau von " + hostList.size() + " Systemen abgeschlossen in " + durString;
    } catch (Exception e) {
      LOG.error("Fehler beim Abbau: ", e);
      return "!!ERROR!!" + e.getMessage();
    }       
     }
 
    public Integer getStatus() {
        Double prozent = (actHost / numHosts) * 100;
        Integer rounded = Math.round(prozent.floatValue());
        System.out.println("Status: " + actHost + "/" + numHosts + " sind Prozente: " + rounded);
        return (rounded < 1 ? 1 : rounded);
    }
}
```

Es knallt natürlich in der Schleife, wo ich wissen will, wie viel Prozent durch sind, weil ich auf den worker direkt zugreife und nicht auf das Future Objekt, aber das Future lässt mich ja nicht auf die Funktion zugreifen, oder?
Kann ich irgendwie schauen, wie weit das Future ist?


----------



## LimDul (6. Sep 2022)

Inwiefern knallt es? Ich hätte jetzt naiv gedacht, dass das so funktionieren sollte.


----------



## DaBe1812 (6. Sep 2022)

Mist, gerade runter gefahren. Also an der Stelle (Zeile 26 im Beispiel) sagt er mir, dass es keinen Open RequestScope gibt und er schmiert ab. Wenn du glaubst, dass es genau so gehen müsste, dann guck ich morgen nochmal nach der genauen Meldung und pack sie hier drunter.


----------



## LimDul (6. Sep 2022)

Das sieht dann aber nach ein Problem mit den CDI-Scopes, dass irgendwo er eine Request-Scoped Bean braucht, die nicht da ist. Kann natürlich mit dem Future Zeig zu tun haben, aber würde ich eher nicht glauben. 

Evtl. in der setInProgressMethode? (Wobei er dann schon vorher in Zeile 18 hätte knallen müssen). Seltsam


----------



## DaBe1812 (7. Sep 2022)

So, wahrscheinlich kommt der Fehler daher, dass die Worker-Klasse schon abgeschmiert ist, bevor ich das erste mal an einen Zwischenstand kommen möchte. Scheinbar ist der MassenabbauDbHandler das Problem. Sobald der das erste Mal verwendet wird, kommt folgende Exception:

```
org.jboss.weld.contexts.ContextNotActiveException: WELD-001303: No active contexts for scope type javax.enterprise.context.RequestScoped
```
Die Klasse war @Model und ich habe mal auf @RequestScope geändert. So sieht sie jetzt etwa aus:

```
@RequestScoped
public class MassenabbauDbHandler {

    private static final DateTimeFormatter dtf = DateTimeFormatter.ofPattern("dd.MM.yyyy");
    
    @PersistenceContext(unitName = "proipsolddb")
    private EntityManager em;

    @SuppressWarnings("unchecked")
    public List<String> getGuids(String hostname, String feldindex) {
        String sql = OldSQLMassenabbau.GETGUID;
        sql = sql.replace(":feldindex", feldindex);
        sql = sql.replace(":hostname", hostname);
        
        Query query = em.createNativeQuery(sql);
        List<String> resultList = query.getResultList();

        return resultList;
    }

    @SuppressWarnings("unchecked")
    public Integer getMaxVersion(String guid) {
        String sql = OldSQLMassenabbau.GETMAXVERSION;
        sql = sql.replace(":guid", guid);

        Query query = em.createNativeQuery(sql);
        List<Object> resultList = query.getResultList();

        Object o = resultList.get(0);
        
        if(o instanceof Number) {
            return ((Number) o).intValue();
        }
        return null;
    }

    @Transactional
    public void setEndedatum(String guid, int version, LocalDate abbauDatum) {
  //Code
    }

    @Transactional
    public void insertNewKatalog(String guid, Integer version, LocalDate abbauDatum) {
  //Code
    }
 
  //... usw.
}
```
Ich denke da liegt schon das Hauptproblem. Bevor ich auf das Future gewechselt habe, lief das System problemlos.


----------



## LimDul (7. Sep 2022)

Ich glaube, ich sehe das Problem.

Über den Future startest du einen eigenen Thread. In diesem Thread gibt es keinen Request-Scope, das ist ein Server Thread, der eben nicht einen Request abarbeitet. Das heißt beim Zugriff MassenabbauDbHandler (der als Proxy injected ist) wird versucht den Request zu ermitteln und diesen Request gibt es im Kontext des Threads nicht.

Warum muss die Bean MassenabbauDbHandler Request-Scoped sein? Eigentlich könnte die auch ApplicationScoped sein, oder?


----------



## DaBe1812 (7. Sep 2022)

Da bin ich mir immer nicht sicher, wie man das am besten verteilt. Ich hatte letztlich wieder einen Fehler, weil ich Request und View nicht richtig gesetzt hatte.
Also technisch gesehen wird die Bean immer durch den Server verarbeitet. Es gibt nichts in der Bean, was sessionrelevant ist, also wenn 5 sessions auf diese Bean zugreifen macht das keinen Unterschied. Dann würde ich sie ApplicationScoped machen.
Muss ich dann in den anderen Klassen auch noch etwas ändern?


----------



## LimDul (7. Sep 2022)

In den anderen Klassen musst nichts ändern. 

Das mit den Kontexten in CDI ist immer schwarze Magie


----------



## DaBe1812 (7. Sep 2022)

Okay, dann sage ich mal danke dir und der schwarzen CDI-Magie, der Test läuft gerade (ich Depp hab mal 700 Sätze zum Testen genommen _facepalm_) und bisher keine Ausfälle.
Jetzt werde ich mal in mich gehen und sämtliche anderen dbHandler anschauen, ob die nicht evtl. auch ApplicationScoped sein sollten.


----------



## DaBe1812 (7. Sep 2022)

Was mir dabei aber auffällt, ist, dass es jetzt langsamer ist, als früher. Könnte das daran liegen, dass ich alle Transaktionen in eine extra Funktion gepackt habe? Das sieht im Worker etwa so aus:

```
List<String> guids = dbHandler.getGuids(hostname, "2020");

if(guids.isEmpty()) continue;
for(String guid : guids) {
    Integer version = dbHandler.getMaxVersion(guid);
    if(version != null) {
        dbHandler.setEndedatum(guid,version,abbauDatum);
        dbHandler.insertNewKatalog(guid,version,abbauDatum);
        dbHandler.insertNewDaten(guid,version);
        dbHandler.insertNewVerweis(guid,version);
        dbHandler.updateLogsysDaten(guid, version, abbauDatum, hostname);
        dbHandler.updateVerweise(guid, version);
        dbHandler.setTicketNo(guid, version, ticketNo, "2018");
    }
}
```
und im dbHandler dahinter dann so:

```
@Transactional
public void setEndedatum(String guid, int version, LocalDate abbauDatum) {
    String sql = OldSQLMassenabbau.SETENDEDATUMKATALOG;
    sql = sql.replace(":endedatum", abbauDatum.format(dtf));
    sql = sql.replace(":guid", guid);
    sql = sql.replace(":version", Integer.toString(version));
    Query query = em.createNativeQuery(sql);
    query.executeUpdate();

    sql = OldSQLMassenabbau.SETENDEDATUMOBJEKTDATEN;
    sql = sql.replace(":endedatum", abbauDatum.format(dtf));
    sql = sql.replace(":guid", guid);
    sql = sql.replace(":version", Integer.toString(version));
    query = em.createNativeQuery(sql);
    query.executeUpdate();
}

@Transactional
public void insertNewKatalog(String guid, Integer version, LocalDate abbauDatum) {
    String sql = OldSQLMassenabbau.INSERTNEWKATALOG;
    sql = sql.replace(":startdatum", abbauDatum.format(dtf));
    sql = sql.replace(":guid", guid);
    sql = sql.replace(":version", Integer.toString(version));
    Query query = em.createNativeQuery(sql);
    query.executeUpdate();
}

@Transactional
public void insertNewDaten(String guid, Integer version) {
    String sql = OldSQLMassenabbau.INSERTNEWOBJEKTDATEN;
    sql = sql.replace(":guid", guid);
    sql = sql.replace(":version", Integer.toString(version));
    Query query = em.createNativeQuery(sql);
    query.executeUpdate();
}

@Transactional
public void insertNewVerweis(String guid, Integer version) {
    String sql = OldSQLMassenabbau.INSERTNEWVERWEISE;
    sql = sql.replace(":guid", guid);
    sql = sql.replace(":version", Integer.toString(version));
    Query query = em.createNativeQuery(sql);
    query.executeUpdate();
}
//Auszugsweise
```
Ich muss hier aktuell auch noch bei nativeQueries bleiben, weil die Tabellen in der aktuellen Version noch nicht wirklich OOP-tauglich sind


----------



## LimDul (7. Sep 2022)

Kann gut sein, sind jetzt halt 7 eigene Transaktionen mit Commit pro Zeile. Die eine Transaktion pro Zeile zu verlagern würde schon helfen.


----------



## DaBe1812 (7. Sep 2022)

Hm, also hab jetzt mal testweise alles in eine Funktion gepackt, spart 2 Minuten (von 12 auf 10) ist nicht der Geschwindigkeitsvorteil, den ich mir erhofft hatte. Oder lassen sich die Transaktionen batchen? Das hatte ich vorher, da hatte ich die DB-Verbindung aber noch nicht servermanaged, sondern hab mit einer normalen Connection und Prepared Statements gearbeitet.


----------

