Hier sind einige Informationen über JSF 2 gespeichert. !!!Inhalt [{TableOfContents title='Page contents' numbered='true'}] !!!Composite Tags * Auch bezeichnet als On-The-Fly-Tags, Facelet-2.0-Tags * Include xmlns:x="http://java.sun.com/jsf/composite/path" heißt dass alle Tags unter /resources/path/... geladen werden. * Tags fügen immer eine Komponente im Komponentenbaum ein. * Tags sind UI-Naming-Container, daher entstehen keine Konflikte beim mehrfachen Einfügen der 2.0-Tags. Allerdings ist das auch beim Zugriffspfad zu beachten. * Definition des Interface durch die View * Erweiterung des Interface durch ** __ActionSource__ Es können endlich Actions über EL-Expressions gebunden werden. Dies war bisher das größte Problem bei der Herstellung vollwertiger Komponenten durch Facelets. ** __ValueHolder__ ** __EditableValueHolder__ ** __RenderFacet__ 2.0-Tags können vollwertige Facets enthalten genau wie echte Komponenten. Dadurch ist die Verwendung hässlicher UI-Inserts nicht mehr nötig. ** __InsertFacet__ Facets können auch in Subkomponenten applied werden. * Zugriff aus die Komponente durch EL-Expressions durch #{cc} oder ** Die Attribute werden nicht mehr unkontrolliert nach innen propagiert, was früher teils zu üblen Fehlern geführt hat. Der Zugriff erfolgt nun über #{cc.attrs} also zum Beispiel #{cc.attrs.value} für den Parameter ** Zugriff auf die id über #{cc.id} und sogar auf die Client-Id #{cc.clientId} !!Client-Behavior Composite-Tags können auch Client-Behaviors unterstützen. Leider ist der Tag anscheinend in einigen Versionen nicht im Content-Assist verfügbar: {{{ <cc:interface> <cc:attribute name="value"/> <cc:clientBehavior event="action" name="action" default="true" targets="button"/> </cc:interface> <cc:implementation> <h:commandButton id="button" value="#{cc.attrs.value}"> <cc:insertChildren/> </h:commandButton> </cc:implementation> }}} Mit dieser Konstruktion unterstützt der Composite-Tag das Client-Behavior "action". Soll umgekeht ein Composite-Tag gebaut werden, der selbst ein Client-Behavior zur Verfügung stellt, stellt [JSF Ext] weitere Möglichkeiten zur Verfügung. !!Actions Eine Composite Component kann auch konfiguriert werden, so dass Action-Listener wie z.B. <f:setPropertyActionListener>, <e:load> etc. verwendet werden können: {{{ <?xml version="1.0" encoding="UTF-8"?> <html xmlns="http://www.w3.org/1999/xhtml" xmlns:ui="http://java.sun.com/jsf/facelets" xmlns:h="http://java.sun.com/jsf/html" xmlns:f="http://java.sun.com/jsf/core" xmlns:e="http://java.sun.com/jsf/ext" xmlns:c="http://java.sun.com/jsp/jstl/core" xmlns:cc="http://java.sun.com/jsf/composite" > <cc:interface> <cc:attribute name="label"/> <cc:attribute name="action" targets="link" method-signature="void action()"/> <cc:clientBehavior event="action" name="action" targets="link" default="true"/> <cc:actionSource name="action" targets="link" default="true"/> </cc:interface> <cc:implementation> <h:commandLink id="link"> <h:outputText value="#{cc.attrs.label}"/> </h:commandLink> </cc:implementation> </html> }}} __Erklärung:__ <cc:attribute name="action"> ermöglicht das optionale Verwenden einer MethodExpression als Attribut action. <cc:clientBehavior event="action"> ermöglicht das Verwenden von ClientBehavior, wie z.B. <f:ajax> auf dem Event action. <cc:actionSource name="action"> ermöglicht das Verwenden von ActionListener Components, wie z.B. <f:setPropertyActionListener>. !!Custom UIComponent Eine Composite Component wird normaler Weise durch eine Bean vom Typ UINamingContainer repräsentiert. Diese kann im Bedarfsfall abgeleitet werden. Dadurch kann die Einfachheit des Renderings der Composite Component mit der Mächtigkeit von Java-Code kombiniert werden: {{{ @FacesComponent("intersult.Test") public class TabPanel extends UINamingContainer { public String getTest() { return "Hello World!"; } } }}} und im XHTML instantiiert: {{{ <?xml version="1.0" encoding="UTF-8"?> <html xmlns="http://www.w3.org/1999/xhtml" xmlns:ui="http://java.sun.com/jsf/facelets" xmlns:h="http://java.sun.com/jsf/html" xmlns:f="http://java.sun.com/jsf/core" xmlns:c="http://java.sun.com/jsp/jstl/core" xmlns:cc="http://java.sun.com/jsf/composite" xmlns:e="http://java.sun.com/jsf/ext" xmlns:ext="http://java.sun.com/jsf/composite/ext" > <cc:interface componentType="intersult.Test"> <h:outputText value="#{cc.test}"/> ... </cc:interface> <cc:implementation> ... </cc:implementation> </html> }}} __Erklärung:__ Die Custom Composite Component wird über die Variable cc ebenso wie die gewöhnliche UINamingContainer referenziert. Dazu kommen Methoden und Properties, die in der abgeleiteten Klasse implementiert werden. !!Backing Beans Manche Komponenten brauchen den Zugriff auf Variablen, die über Requests hinweg erhalten bleiben. Die mit <cc:attribute> definierten Attribute werden im View-Scope gespeichert und können daher dafür verwendet werden. Gleichzeitig können diese beim Verwenden der Komponente vom Benutzer durch eine EL-Expression an einen beliebigen Scope gebunden werden: {{{ <?xml version="1.0" encoding="UTF-8"?> <html xmlns="http://www.w3.org/1999/xhtml" xmlns:ui="http://java.sun.com/jsf/facelets" xmlns:h="http://java.sun.com/jsf/html" xmlns:f="http://java.sun.com/jsf/core" xmlns:c="http://java.sun.com/jsp/jstl/core" xmlns:cc="http://java.sun.com/jsf/composite" xmlns:e="http://java.sun.com/jsf/ext" xmlns:ext="http://java.sun.com/jsf/composite/ext" > <cc:interface> <cc:attribute name="value" default="0"/> </cc:interface> <cc:implementation> <div id="#{cc.clientId}"> <h:form id="header-form"> <table id="#{cc.clientId}:header" style="width: 100%;" cellpadding="0" cellspacing="0"> <tr> <c:forEach id="repeat" items="#{cc.children}" var="tab" varStatus="tabStatus"> <e:div element="td" styleClass="#{cc.attrs.value == tabStatus.index ? 'tab-active' : 'tab-inactive'}" style="padding-left: 5px;"> <e:ajax event="click" rendered="#{cc.attrs.value != tabStatus.index}" render=":#{cc.clientId}"> <e:set value="#{tabStatus.index}" target="#{cc.attrs.value}"/> </e:ajax> <h:outputText value="#{tab.attributes.header}"/> </e:div> </c:forEach> <td style="padding-right: 5px; width: 100%;"/> </tr> </table> </h:form> <e:div id="content"> <e:insert component="#{cc.children[cc.attrs.value]}"/> </e:div> </div> </cc:implementation> </html> }}} __Erklärung:__ Der Set-Tag weißt den aktuellen Index (tabStatus.index) dem Attribut "value" zu (cc.attrs.value). Dieses Attribut wird verwendet, um den entsprechenden Tab-Content zu laden. Die Tabs selbst sind wiederum einfache Composite Tags mit einem Attribut "header". !!!AJAX !!AJAX Status Die Component zeit den AJAX-Status an. {{{ <?xml version="1.0" encoding="UTF-8"?> <html xmlns="http://www.w3.org/1999/xhtml" xmlns:ui="http://java.sun.com/jsf/facelets" xmlns:h="http://java.sun.com/jsf/html" xmlns:f="http://java.sun.com/jsf/core" xmlns:c="http://java.sun.com/jsp/jstl/core" xmlns:cc="http://java.sun.com/jsf/composite" > <cc:interface> <cc:attribute name="status" default="begin"/> </cc:interface> <cc:implementation> <script type="text/javascript"> jsf.ajax.addOnEvent(function(data) { document.getElementById('#{cc.clientId}:status').style.visibility = data.status == '#{cc.attrs.status}' ? 'visible' : 'hidden'; }); </script> <div id="#{cc.clientId}:status" style="visibility: hidden;"> <cc:insertChildren/> </div> </cc:implementation> </html> }}} Verwendung zum Beispiel wie folgt: {{{ <test:ajaxStatus> <h:graphicImage value="/images/wait30trans.gif"/> </test:ajaxStatus> }}} !!AJAX-Push Momentan relativ einfach zu konfigurieren ist der Primefaces-Push, der seit der Version 3.4 auf Atmosphere-Framework basiert. Atmosphere-Framework ist momentan das führende Framework für alle möglichen Push-Varianten, zwischen Server und Browser, falls man sich nicht vollzeit mit diesem Thema beschäftigen möchte. pom.xml: {{{ <dependency> <groupId>org.primefaces</groupId> <artifactId>primefaces</artifactId> <version>3.4-SNAPSHOT</version> </dependency> ... <repositories> <repository> <id>prime-repo</id> <name>Prime Technology Maven Repository</name> <url>http://repository.primefaces.org</url> <layout>default</layout> </repository> </repositories> }}} web.xml, es ist eine Web-App 3.0 erforderlich: {{{ <?xml version="1.0" encoding="ISO-8859-1"?> <web-app xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" version="3.0"> ... <servlet> <servlet-name>Push Servlet</servlet-name> <servlet-class>org.primefaces.push.PushServlet</servlet-class> <async-supported>true</async-supported> </servlet> <servlet-mapping> <servlet-name>Push Servlet</servlet-name> <url-pattern>/primepush/*</url-pattern> </servlet-mapping> ... </web-app> }}} Java: {{{ public void someMethod() { PushContextFactory.getDefault().getPushContext().push("/test", null); } }}} Der Eintrag "/test" ist der sogenannte Channel über dem gepusht wird. Im XHTML wird entweder ein AJAX-Tag verwendet: {{{ <p:socket channel="/test" autoConnect="true"> <p:ajax event="message" update=":push-form:chat"/> </p:socket> }}} Oder wenn der Update manuell gemacht werden soll, kann auch Javascript verwendet werden: {{{ <p:socket channel="/test" autoConnect="true" onMessage="someMethod"/> }}} Die Methode wird dann mit einem Parameter "data" aufgerufen, welches dem JSON-Serialisiertem Objekt aus dem Server-Push-Aufruf context.push("/test", data); entspricht. !!Probleme mit AJAX-Submits Formulare können nicht mehr abgeschickt werden, wenn diese über ein Parent-Element mit AJAX neu gerenderet wurden. Abhilfe bringt die explizite Angabe der Formular-Id beim Rerender. Alternativ kann [JSF Ext] mit ins Projekt eingebunden werden, die Komponenten-Bibliothek enthält Javascript-Code, der das Problem transparent gelöst. Eine weitere Ursache sind sich veränderte Client-Ids zwischen Rendering und Form Submit. Eine Lösung ist, den Naming-Containern und den Submit-Elementen Ids zu geben. Dies erleichtert generell das Debugging von Formularen, da die Ids oft in Fehlermeldungen angezeigt werden, die betreffenden Elemente werden so leicht gefunden. !!!HTML Element Tag Der Tag rendert ein HTML-Element mit ClientId und übergibt alle Attribute und Childrens. {{{ <?xml version="1.0" encoding="UTF-8"?> <html xmlns="http://www.w3.org/1999/xhtml" xmlns:f="http://java.sun.com/jsf/core" xmlns:h="http://java.sun.com/jsf/html" xmlns:c="http://java.sun.com/jsp/jstl/core" xmlns:ui="http://java.sun.com/jsf/facelets" xmlns:cc="http://java.sun.com/jsf/composite" > <cc:interface name="test"> <cc:attribute name="element" required="true"/> <cc:attribute name="rendered"/> </cc:interface> <cc:implementation> <c:if test="#{!rendered or rendered}"> <h:outputText value="<#{cc.attrs.element} id="#{cc.clientId}"" escape="false"/> <c:forEach items="#{cc.attributes}" var="attribute"> <h:outputText value=" #{attribute.key}="#{attribute.value}"" escape="false" rendered="#{!attribute.key.startsWith('javax.faces') and !attribute.key.startsWith('com.sun') and attribute.key != 'element' and attribute.key != 'rendered'}"/> </c:forEach> <h:outputText value=">" escape="false"/> <cc:insertChildren/> <h:outputText value="</#{cc.attrs.element}>" escape="false"/> </c:if> </cc:implementation> </html> }}} !!!Validierung prüfen {{{ FacesContext.getCurrentInstance().isValidationFailed(); #{facesContext.validationFailed} }}} !!!Setup web.xml {{{ <context-param> <param-name>javax.faces.STATE_SAVING_METHOD</param-name> <param-value>server</param-value> </context-param> <context-param> <param-name>javax.faces.PROJECT_STAGE</param-name> <param-value>Development</param-value> </context-param> <context-param> <param-name>javax.faces.FACELETS_LIBRARIES</param-name> <param-value>/WEB-INF/test.taglib.xml</param-value> </context-param> <context-param> <param-name>javax.faces.FACELETS_SKIP_COMMENTS</param-name> <param-value>true</param-value> </context-param> <servlet> <servlet-name>Faces Servlet</servlet-name> <servlet-class>javax.faces.webapp.FacesServlet</servlet-class> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>Faces Servlet</servlet-name> <url-pattern>/faces/*</url-pattern> </servlet-mapping> }}} Der Eintrag javax.faces.FACELETS_SKIP_COMMENTS sorgt dafür, dass in XML-Kommentaren "<!-- Comment -->" keine EL-Expressions ausgewertet werden. Wird dieses Konfiguration nicht angegeben, kann es zu schwer auffindbaren Fehlern kommen. !!!EL-Expressions 2.2 Die neuen EL-Expressions erlauben das Binding an Action-Methods, die Parameter besitzen: {{{ <h:commandButton value="Test" action="#{test.action('param')}"/> }}} In der pom.xml die Libs referenzieren: {{{ <dependency> <groupId>javax.el</groupId> <artifactId>el-api</artifactId> <version>2.2</version> </dependency> <dependency> <groupId>org.glassfish.web</groupId> <artifactId>el-impl</artifactId> <version>2.2</version> </dependency> <repository> <id>maven.java.net</id> <name>Java.net Maven2 Repository</name> <url>http://download.java.net/maven/2</url> </repository> }}} Und in der web.xml die Factory festlegen: {{{ <context-param> <param-name>com.sun.faces.expressionFactory</param-name> <param-value>com.sun.el.ExpressionFactoryImpl</param-value> </context-param> }}} In [Google Application Engine] klappt das nicht, dort kann nur JBoss EL verwendet werden. !!!Debugging Zunächst eine Übersicht, was momentan möglich ist: * Importieren eines Maven-WAR-Projekts in Eclipse * Importieren von Abhänigigen JSF-JARs in Eclipse * Nutzen von Content-Assist für Code-Completion von Tag-Librarys in XHTML-Dateien * Ändern von XHTML- und anderen Dateien ohne Server-Neustart, sowohl im WAR- als auch in abhängigen JAR-Projekten * Direktes Referenzieren abhängiger Maven-Artifakte, die sich im Workspace befinden !!Debug Page Die Debug-Page von Facelets öffnet sich bei einer Exception. Sie kann aber auch manuell geöffnet werden, dies ist nützlich um die Bean-Scopes zu untersuchen oder den Component-Tree. Dazu einfach das Tag einfügen: {{{ <ui:debug hotkey="q"/> }}} Das Hotkey arbeitet in diesem Fall mit CTRL+SHIFT+Q. Falls das Hotkey nicht funktioniert, ist es wahrscheinlich durch etwas anderes abgefangen und man ersetzt es durch ein anderes. !!Gelbe Messages Werden gelbe Warnings wie z.B. "The form component needs to have a UIForm in its ancestry. Suggestion: enclose the necessary components within <h:form>" beim Rendern einer Seite unten angezeigt, können diese gefunden werden, indem ein Breakpoint bei MessageUtils.getExceptionMessage(String messageId, Object... params) gesetzt wird. !!Rote Messages Wenn Messages der Art "Warning: This page calls for XML namespace declared with prefix div but no taglibrary exists for that namespace." auftauchen, liegt es an fehlenden Namespace-Definitionen der entsprechenden Seite. In großen Projekten mit verschachtelten Includes können diese schwer zu finden sein. Die Fehlermeldung selbst wird in der Class com.sun.faces.facelets.tag.CompositeTagLibrary erzeugt, hier kann ein Breakpoint gesetzt werden. !!Logging JSF verwendet eine Logging-Konfiguration, die über die JVM-Logging-Properties eingestellt wird. Diese befinden sich in %JAVA_HOME%/jre/lib/logging.properties konfiguriert wird. Dort wird zunächst das Logging des java.util.logging.ConsoleHandler auf ALL gestellt, damit Logging-Events höher als INFO auch ausgegeben werden. Dann können Einträge hinzugefügt werden, wie zum Beispiel für javax.enterprise.resource.webcontainer.jsf.context, um Exceptions bei EL-Expressions auszugeben: {{{ java.util.logging.ConsoleHandler.level = ALL javax.enterprise.resource.webcontainer.jsf.context.level=FINE }}} Eine Übersicht der möglichen Logging-Events befindet sich in der Enum com.sun.faces.util.FacesLogger !!Project Stage Der Project-Stage kann über den FacesContext abgefragt werden: {{{ FacesContext.currentInstance().isProjectState(ProjectStage.Development) }}} Oder im Frontend: {{{ #{facesContext.application.projectState == 'Development'} }}} !!!Configuration Bei [JSF2] ist nur noch sehr wenig Konfiguration notwendig. Einige Dinge sind dennoch nützlich zu wissen, vor allem wenn etwas nicht so klappt wie es soll. Dies tritt vor allem in der Entwicklungsumgebung und beim Zugriff auf spezielle Features vor. !!Hintergrund der Initialisierung Leider ist die Initialisierung von [JSF2] ein komplexer Vorgang mit mehreren Einsprüngen auf dem Servlet Container. Zunächst ist es hilfreich diese zu kennen, um mit der Analyse des Source-Codes beginnen zu können: * com.sun.faces.config.ConfigureListener.contextInitialized * com.sun.faces.config.ConfigManager.initialize * com.sun.faces.config.processor.FactoryConfigProcessor.process !!Annotation Processing In den meisten Servlet-Containern werden die JSF-Annotations automatisch verarbeitet, wie @ManagedBean, @SessionScoped und so weiter. Allerdings klappt das nicht immer, wie Beispielsweise bei embedded Tomcat. Dann kann mit folgendem Servlet-Context-Parameter abgeholfen werden: {{{ <context-param> <param-name>com.sun.faces.injectionProvider</param-name> <param-value>com.sun.faces.vendor.WebContainerInjectionProvider</param-value> </context-param> }}} !!!Tag-Handler In JSF 2 werden Tag-Handler bentutzt, um die Tags zu erzeugen. Diese werden von javax.faces.view.facelets.TagHandler abgeleitet. Die beiden wichtigsten Fälle sind: * __Component einfügen:__ Leitet man einen Handler von javax.faces.view.facelets.ComponentHandler ab, instantiiert dieser selbst die in der .taglib.xml festgelegte UIComponent und ruft dann die Methode applyNextHandler auf. Durch das Weiterleiten des Aufrufs an super.applyNextHandler instantiiert man die inneren Komponenten. * __Keine Component:__ Wenn man den Parent-Tag verändern möchte, wie zum Beispiel ein Behavior hinzufügen, braucht man keinen ComponentHandler. Ableiten von javax.faces.view.facelets.TagHandler ist ausreichend. Es gibt dort nur eine Methode apply, die man auch über nextHandler.apply delegieren kann, für den seltenen Fall dass ein Tag der keine Component einfügt Children hat. !!Component Tree Wenn eine Komponente nicht im Component-Tree erscheint, kann mit folgendem Konstrukt im Handler abgeholfen werden: {{{ @Override public void onComponentCreated(FaceletContext context, UIComponent component, UIComponent parent) { parent.getChildren().add(component); } }}} Ein [interessanter Artikel|http://weblogs.java.net/blog/edburns/archive/2009/10/15/jsf2-facelet-tag-handlers] dazu. !!!FacesContext manuell erzeugen Innerhalb eines Servlet-Auftrufs kann der Kontext einfach erzeugt werden: {{{ FacesContextFactory factory = (FacesContextFactory)FactoryFinder.getFactory(FactoryFinder.FACES_CONTEXT_FACTORY); LifecycleFactory lifecycleFactory = (LifecycleFactory) FactoryFinder.getFactory(FactoryFinder.LIFECYCLE_FACTORY); Lifecycle lifecycle = lifecycleFactory.getLifecycle(LifecycleFactory.DEFAULT_LIFECYCLE); factory.getFacesContext(servletContext, request, response, lifecycle); }}} Außerhalb von Servlets braucht man den ServletContext, HttpServletRequest und HttpServletResponse, der nicht ganz einfach zu implementieren ist. !!!Render Kits Die Seiten können auf unterschiedliche Arten gerendered werden, da für die Standard-Komponenten eigenständige Renderer vorhanden sind. Diese Renderer werden zu Render Kits zusammengefasst, die durch eine Render Kit Id identifiziert werden. Das standard Render Kit hat die Id "HTML_BASIC", diese Konstante ist unter RenderKitFactory.HTML_BASIC_RENDER_KIT abrufbar. Das aktuelle Render Kit kann durch den Request-Parameter "javax.faces.RenderKitId" angesprochen werden, dieser befindet sich als Konstante unter ResponseStateManager.RENDER_KIT_ID_PARAM. Also zum Beispiel die URL: {{{ http://localhost/test/faces/index.xhtml?javax.faces.RenderKitId=test.RenderKit }}} Dieser Parameter kann auch durch ein Hidden-Field übergeben werden, sodass zum Beispiel Tabellen-, PDF- und andere Downloads generisch für Web-Seiten entwickelt werden können. !!!Doctype !!Doctype in JSF 2.2 JSF 2.2 unterstützt HTML5 und meint daher, einen eventuell in der Page-Deklaration vorhandenen Doctype vereinfachen zu müssen auf <!DOCTYPE html>. Üblich sind <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> oder <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> Zu lösen ist das durch einen Eintrag in der faces-config.xml: {{{ <?xml version="1.0" encoding="UTF-8"?> <faces-config version="2.2" xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-facesconfig_2_2.xsd" > ... <faces-config-extension> <facelets-processing> <file-extension>.xhtml</file-extension> <process-as>xhtml</process-as> </facelets-processing> </faces-config-extension> ... </faces-config> }}} !!!Download Bei JSF ist im kein Download-Servlet erforderlich, es kann einfach über eine Action-Methode abgewickelt werden: {{{ <h:commandLink value="#{fileUpload.filename}" action="#{fileUpload.download}" rendered="#{fileUpload.file != null}"/> }}} Die zugehörige Action-Methode: {{{ public void download() throws IOException { ExternalContext externalContext = FacesContext.getCurrentInstance().getExternalContext(); externalContext.setResponseContentType(mimeType); externalContext.addResponseHeader("Content-Disposition", "attachment; filename=" + filename); externalContext.setResponseContentLength(file.length); OutputStream outputStream = externalContext.getResponseOutputStream(); outputStream.write(file); FacesContext.getCurrentInstance().responseComplete(); } }}} __Erklärung:__ Der Download wird unmittelbar durch Anklicken des Links gestartet. Es wird kein kurzzeitiger Browser-Tab oder -Fenster geöffnet, die sich ohne sinnvollen Inhalt gleich wieder schließen, wie man das oft bei Web-Seiten sieht. Als Variante kann der Browser bestimmte Mime-Types direkt anzeigen, wie z.B. GIF-, PNG-, JPG-Bilder, PDF-Dateien, je nach installierter Software noch mehr. Dies erreicht man durch hinzufügen von target="_blank" beim Link und setzen der Content-Disposition auf "inline": {{{ <h:commandLink value="#{fileUpload.filename}" target="_blank" action="#{fileUpload.download}" rendered="#{fileUpload.file != null}"/> }}} {{{ public void download() throws IOException { ExternalContext externalContext = FacesContext.getCurrentInstance().getExternalContext(); externalContext.setResponseContentType(mimeType); externalContext.setResponseContentLength(file.length); OutputStream outputStream = externalContext.getResponseOutputStream(); outputStream.write(file); FacesContext.getCurrentInstance().responseComplete(); } }}}