El <p:graphicImage>
nécessite une méthode getter spéciale. Elle sera notamment invoquée deux fois par image générée, chacune dans une requête HTTP complètement différente.
La première requête HTTP, qui a demandé le résultat HTML d'une page JSF, invoquera le getter pour la première fois afin de générer le HTML. <img>
avec la bonne URL unique et générée automatiquement dans l'élément src
qui contient des informations sur le bean et le getter qui doivent être invoqués chaque fois que le navigateur web est sur le point de demander l'image. Notez que le getter fait à ce moment no doivent retourner le contenu de l'image. Il ne serait pas utilisé de quelque manière que ce soit, car ce n'est pas ainsi que fonctionne le HTML (les images ne sont pas "intégrées" dans la sortie HTML, mais elles sont au contraire demandées séparément).
Une fois que le navigateur Web a récupéré le résultat HTML en tant que réponse HTTP, il analyse la source HTML afin de présenter le résultat visuellement à l'utilisateur final. Lorsque le navigateur web rencontre un <img>
lors de l'analyse de la source HTML, il enverra une toute nouvelle requête HTTP sur l'URL spécifiée dans l'élément src
afin de télécharger le contenu de cette image et de l'intégrer dans la présentation visuelle. La méthode getter sera invoquée pour la deuxième fois et renverra l'attribut réel contenu de l'image.
En votre cas particulier Apparemment, PrimeFaces était soit incapable d'identifier et d'invoquer le getter afin de récupérer le contenu réel de l'image, soit le getter n'a pas renvoyé le contenu de l'image attendu. L'utilisation de #{item}
Le nom de la variable et le nombre d'appels dans le journal suggèrent que vous l'utilisiez dans un <ui:repeat>
ou un <h:dataTable>
. Il est fort probable que le bean de sauvegarde soit "request scoped" et que le modèle de données ne soit pas correctement préservé lors de la requête de l'image et que JSF ne soit pas en mesure d'invoquer le getter lors du bon tour d'itération. Un bean à portée de vue ne fonctionnerait pas non plus car l'état de la vue JSF n'est disponible nulle part lorsque le navigateur demande l'image.
Pour résoudre ce problème, le mieux est de réécrire la méthode getter de manière à ce qu'elle puisse être invoquée pour chaque demande, en passant l'identifiant unique de l'image en tant qu'objet <f:param>
au lieu de s'appuyer sur les propriétés de certains beans de soutien qui peuvent être "désynchronisées" lors des requêtes HTTP suivantes. Il serait tout à fait logique d'utiliser pour cela un bean géré séparé, à portée applicative, qui n'a pas d'état. De plus, un InputStream
ne peut être lu qu'une seule fois, pas plusieurs fois.
En d'autres termes : ne jamais déclarer StreamedContent
ni aucun InputStream
ou même UploadedFile
en tant que propriété d'un bean ; ne la créez que dans le getter d'une propriété apatride. @ApplicationScoped
lorsque le navigateur demande le contenu de l'image. .
Par exemple
<p:dataTable value="#{bean.students}" var="student">
<p:column>
<p:graphicImage value="#{studentImages.image}">
<f:param name="studentId" value="#{student.id}" />
</p:graphicImage>
</p:column>
</p:dataTable>
Où le StudentImages
Le haricot arrière peut ressembler à ça :
@Named // Or @ManagedBean
@ApplicationScoped
public class StudentImages {
@EJB
private StudentService service;
public StreamedContent getImage() throws IOException {
FacesContext context = FacesContext.getCurrentInstance();
if (context.getCurrentPhaseId() == PhaseId.RENDER_RESPONSE) {
// So, we're rendering the HTML. Return a stub StreamedContent so that it will generate right URL.
return new DefaultStreamedContent();
}
else {
// So, browser is requesting the image. Return a real StreamedContent with the image bytes.
String studentId = context.getExternalContext().getRequestParameterMap().get("studentId");
Student student = studentService.find(Long.valueOf(studentId));
return new DefaultStreamedContent(new ByteArrayInputStream(student.getImage()));
}
}
}
Veuillez noter qu'il s'agit d'un cas très particulier dans lequel l'exécution d'une logique d'entreprise dans une méthode getter est tout à fait légitime, compte tenu de la façon dont la méthode getter est utilisée. <p:graphicImage>
travaille sous les couvertures. L'invocation de la logique métier dans les getters est généralement désapprouvée. Pourquoi JSF appelle les getters plusieurs fois . N'utilisez pas ce cas spécial comme excuse pour d'autres cas standard (non spéciaux). Veuillez également noter que vous ne pouvez pas utiliser la fonctionnalité EL 2.2 permettant de passer des arguments de méthode comme suit #{studentImages.image(student.id)}
car cet argument n'apparaîtra pas dans l'URL de l'image. Ainsi, vous devez vraiment les passer en tant que <f:param>
.
Si vous utilisez OmniFaces 2.0 ou plus récent alors envisagez d'utiliser son <o:graphicImage>
qui peut être utilisé de manière plus intuitive, avec une méthode getter à l'échelle de l'application déléguant directement à la méthode du service et supportant les arguments de méthode EL 2.2.
Ainsi donc :
<p:dataTable value="#{bean.students}" var="student">
<p:column>
<o:graphicImage value="#{studentImages.getImage(student.id)}" />
</p:column>
</p:dataTable>
Avec
@Named // Or @ManagedBean
@ApplicationScoped
public class StudentImages {
@EJB
private StudentService service;
public byte[] getImage(Long studentId) {
return studentService.find(studentId).getImage();
}
}
Voir aussi le blog sur le sujet.