# JSF session bean mit worker thread updaten



## bananenkasper (27. Mai 2010)

Hallo zusammen,

ich baue gerade an einer Webseite (JSF - EJB - JPA).
Da die Suchen in der Datenbank teilweise recht lange dauern, möchte ich sie gerne asynchron absetzen.
D.h. durch einen anderen Thread ausführen lassen und den "JSF life cycle thread" direkt zurückkehren lassen.

Ananlog zu Swing: ich möchte das meine GUI nicht "einfriert", sondern das Ergebnis der Suche angezeigt wird, sobald es eben verfügbar ist.
In Swing würd ich es so machen:

GUI -> Anfrage -> EventDispachingThread -> WorkerThread -> rechnen -> SwingUtilitis.invokeLater() -> GUI

Vermutlich stehe ich grad böse auf dem Schlauch, aber wie würde ich das in einer JSF-Umgebung realisieren?
Das refreshen der HTML-seite müsste man evtl. über einen AJAX-gesteuerten, periodischen refresh lösen, aber das ist noch eine andere Geschichte.

Nochmal in kurz:

Wie kann ich mit einem extra-Thread auf den session-context zugreifen, und (threadsicher) ein update der Daten durchführen?

Schonmal Danke für jede Antwort!


----------



## MrWhite (27. Mai 2010)

Hallo,

ich wuerde die Suchen ueber einen Service an einen Threadpool uebergeben und dann diesen Service entweder per ajax pollen (richfaces hat z.b. a4joll oder a4jush fuer einen fake push) oder eine ajax push api wie comet oder so nutzen.

Wo genau ist das Problem?


----------



## bananenkasper (27. Mai 2010)

Ich habe mal ein sehr einfaches Beispiel gebastelt, dass das Problem verdeutlichen sollte:

JSF:

```
<%@ page language="java" contentType="text/html; charset=UTF-8"
	pageEncoding="UTF-8"%>
<%@ taglib prefix="f" uri="http://java.sun.com/jsf/core"%>
<%@ taglib prefix="h" uri="http://java.sun.com/jsf/html"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
<f:view>
<h:outputText value="#{sessionBean(2).info}"></h:outputText>
<h:form>
<h:inputText value="#{sessionBean(2).query}"></h:inputText>
<h:commandButton value="Search" action="#{sessionBean(2).search}"></h:commandButton>
</h:form>
</f:view>
</body>
</html>
```


```
package de.test;
public class SessionBean {
	private static class Worker implements Runnable{
		private final SessionBean ref;
		Worker(SessionBean ref){
			this.ref = ref;
		}
		public void run() {
			ref.setInfo(ref.getQuery());	
			System.out.println("Success!");
		}
	}
	private volatile String info = "replace me...";
	private volatile String query = "enter query";
	public String getInfo() {
		return info;
	}
	public void setInfo(String info) {
		this.info = info;
	}
	public String getQuery() {
		return query;
	}
	public void setQuery(String query) {
		this.query = query;
	}
	public String search(){
		new Thread(new Worker(this)).start();
		return null;
	}
}
```

Klappt manchmal, manchmal nicht. Ist auch ganz klar gegen die JSF Konventionen.


```
package de.test;

import javax.faces.application.Application;
import javax.faces.context.FacesContext;

public class SessionBean2 {
	public static <V> V getManagedObject(String elString, Class<V> c, FacesContext context) {
		Application a = context.getApplication();
		V v = c.cast(a.evaluateExpressionGet(context, elString, c));
		return v;
	}
	private static class Worker implements Runnable{
		private final FacesContext context;
		Worker(FacesContext context){
			this.context = context;
		}
		public void run() {
			final SessionBean2 bean = getManagedObject("#{sessionBean2}", SessionBean2.class, context);
			if(bean == null)
				System.err.println("We do not have any FacesContext!");
			bean.setInfo(bean.getQuery());	
			System.out.println("Success!");
		}
	}
	private String info = "replace me...";
	private String query = "enter query";
	public String getInfo() {
		return info;
	}
	public void setInfo(String info) {
		this.info = info;
	}
	public String getQuery() {
		return query;
	}
	public void setQuery(String query) {
		this.query = query;
	}
	public String search(){
		new Thread(new Worker(FacesContext.getCurrentInstance())).start();
		return null;
	}
}
```
Scheitert an einer NullPointerException, da es für diesen neuen Thread kein FacesContext gibt.

Ich hoffe es ist einigermaßen klar geworden was das Problem ist.


----------



## MrWhite (27. Mai 2010)

Ich verstehe dein Problem immer noch nicht (ohne den Code im Detail gelesen zu haben). Deine Threads müssen doch nur die Ergebnisse in irgendeine Liste reinschreiben. Irgendein anderer Service (z.B. deine Bean) kann doch dann immer prüfen, ob das Ergebnis der Anfrage schon vorhanden ist, sei es periodisch oder durch Interaktionen des Nutzers.

Wo ist das Problem?

1.) JSF setzt Abfrage ab
2.) Bean startet asynchrone Abfrage
3.) Abfrage schreibt Ergebnis irgendeine Datenstruktur (Liste/Hashtable oder sowas), bei Fertigstellung
4.) Bean prüft z.B. periodisch durch JSF abgefragt die Datenstruktur, ob das Ergebnis schon vorhanden ist

Das ist doch konzeptionell recht simpel, oder? Das ist auch schnell programmiert.


----------



## bananenkasper (27. Mai 2010)

Das Problem ist, dass ich dabei den Session-Kontext verliere.

Klar, ich kann raus aus dem life cycle thread, und irgendwo was speichern.
Das kann ich dann periodisch abfragen.
Aber ich komme sozusagen "nicht wieder zurück in meinen FacesContext".

Mehrere Benutzer würden dann alle auf das selbe Objekt zugreifen...


----------



## MrWhite (28. Mai 2010)

bananenkasper hat gesagt.:


> Mehrere Benutzer würden dann alle auf das selbe Objekt zugreifen...



Ja und? Jede Abfrage hat doch eine ID.

Ausserdem könntest du z.B. mit SEAM einfach eine Komponente bauen, die nur im Session oder Conversation Scope des Users ist.


----------



## bananenkasper (28. Mai 2010)

ja, und die ID mappe ich dann zu jedem einzelnen Ergebnis?
und wenn eine Session ausläuft?

Wie halte ich die Map sauber?

Das ist doch nicht das ware.

"vanilla" JSF kann auch Objekte im Session-scope ablegen, bringt aber nix, da der neue Thread (wie bereits mehrfach erläutert) auf diesen Session-scope keinen zugriff hat.


----------



## MrWhite (28. Mai 2010)

Mein Gott, leg dein Thread-Objekt halt dann innerhalb deiner Session-Bean in einer Liste ab und frage dann über die Bean immer wieder ab, ob der Thread schon fertig ist.

Wenn er fertig ist, dann hole dein Suchergebnis vom Threadobjekt ab und präsentiere es dem Nutzer. Das sollte doch funktionieren.


----------

