web-dev-qa-db-fra.com

Afficher une image dynamique de la base de données avec p: graphicImage et StreamedContent

J'essaie d'afficher les octets d'image qui sont enregistrés dans la base de données en tant que StreamedContent dans le <p:graphicImage> comme suit:

<p:graphicImage  value="#{item.imageF}" width="50"  id="grpImage" height="80"/>
private StreamedContent content; // getter and setter

public StreamedContent getImageF() {

    if (student.getImage() != null) {
        InputStream is = new ByteArrayInputStream(student.getImage());
        System.out.println("Byte :"+student.getImage());
        content = new DefaultStreamedContent(is, "", student.getStuID());
        System.out.println("ddd ------------------------------- " + content);
        return content;
    }

    return content;
}

Cela renvoie une image vierge. Comment cela est-il causé et comment puis-je le résoudre?

La sortie standard imprime les éléments suivants:

INFO: Byte :[B@a2fb48
INFO: ddd ------------------------------- org.primefaces.model.DefaultStreamedContent@b0887b
INFO: Byte :[B@a2fb48
INFO: ddd ------------------------------- org.primefaces.model.DefaultStreamedContent@1d06a92
INFO: Byte :[B@d52f0b
INFO: ddd ------------------------------- org.primefaces.model.DefaultStreamedContent@39a60
INFO: Byte :[B@d52f0b
INFO: ddd ------------------------------- org.primefaces.model.DefaultStreamedContent@8c3daa
INFO: Byte :[B@124728a
INFO: ddd ------------------------------- org.primefaces.model.DefaultStreamedContent@1dbe05b
INFO: Byte :[B@124728a
INFO: ddd ------------------------------- org.primefaces.model.DefaultStreamedContent@66a266
INFO: Byte :[B@a2fb48
INFO: ddd ------------------------------- org.primefaces.model.DefaultStreamedContent@1293976
INFO: Byte :[B@a2fb48
INFO: ddd ------------------------------- org.primefaces.model.DefaultStreamedContent@17b7399
INFO: Byte :[B@d52f0b
INFO: ddd ------------------------------- org.primefaces.model.DefaultStreamedContent@1e245a5
INFO: Byte :[B@d52f0b
INFO: ddd ------------------------------- org.primefaces.model.DefaultStreamedContent@4a7153
INFO: Byte :[B@124728a
INFO: ddd ------------------------------- org.primefaces.model.DefaultStreamedContent@1561bfd
INFO: Byte :[B@124728a
INFO: ddd ------------------------------- org.primefaces.model.DefaultStreamedContent@47a8c2
47
minhltnt

Le <p:graphicImage> Nécessite une méthode getter spéciale. Il sera notamment appelé deux fois par image générée, chacun 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 l'élément HTML <img> Avec la bonne URL unique et générée automatiquement dans le src attribut qui contient des informations sur quel bean et getter doivent être invoqués chaque fois que le navigateur Web est sur le point de demander l'image. Notez que le getter n'a à ce moment pas besoin de retourner le contenu de l'image. Il ne serait utilisé d'aucune façon car ce n'est pas ainsi que fonctionne HTML (les images ne sont pas "intégrées" dans la sortie HTML, mais elles sont plutôt demandées séparément).

Une fois que le navigateur Web récupère le résultat HTML en tant que réponse HTTP, il analysera la source HTML afin de présenter visuellement le résultat à l'utilisateur final. Une fois que le navigateur a rencontré un élément <img> Lors de l'analyse de la source HTML, il enverra une toute nouvelle requête HTTP sur l'URL comme spécifié dans son attribut src afin de télécharger le contenu de cette image et l'intégrer dans la présentation visuelle. Ceci invoquera la méthode getter pour la deuxième fois qui à son tour devrait retourner le contenu de l'image actual.

Dans votre cas particulier PrimeFaces n'était apparemment pas en mesure d'identifier et d'invoquer le getter afin de récupérer le contenu réel de l'image, ou le getter n'a pas renvoyé le contenu de l'image attendu. L'utilisation du nom de variable #{item} 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 ait la portée de la demande et que le modèle de données ne soit pas correctement conservé lors de la demande de l'image et JSF ne pourra pas invoquer le getter pendant le bon tour d'itération. Un bean de portée de vue ne fonctionnerait pas non plus car l'état de vue JSF n'est nulle part disponible lorsque le navigateur demande réellement l'image.


Pour résoudre ce problème, votre meilleur pari est de réécrire la méthode getter en tant que telle afin qu'elle puisse être invoquée à la demande dans laquelle vous passez l'identifiant d'image unique en tant que <f:param> Au lieu de compter sur un bean de sauvegarde des propriétés qui peuvent "être désynchronisées" lors des requêtes HTTP suivantes. Il serait tout à fait logique d'utiliser un bean géré à portée d'application distinct pour cela qui n'a aucun état. De plus, un InputStream ne peut être lu qu'une seule fois, pas plusieurs fois.

En d'autres termes: ne déclarez jamais StreamedContent ni InputStream ni même UploadedFile comme propriété de bean; créez-le uniquement dans le getter d'un bean @ApplicationScoped sans état lorsque le navigateur Web demande réellement 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 bean de sauvegarde StudentImages peut ressembler à ceci:

@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 spécial dans lequel l'exécution de la logique métier dans une méthode getter est tout à fait légitime, compte tenu du fonctionnement de <p:graphicImage> Sous les couvertures. Invoquer la logique métier dans les getters est généralement mal vu, voir aussi 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 pour passer des arguments de méthode comme so #{studentImages.image(student.id)} car cet argument ne se retrouvera pas dans l'URL de l'image. Vous devez donc vraiment les passer en tant que <f:param>.


S'il vous arrive d'utiliser OmniFaces 2.0 ou plus récent , alors envisagez d'utiliser son <o:graphicImage> à la place qui peut être utilisé de manière plus intuitive, avec une méthode getter de portée d'application déléguant directement à la méthode de service et à la prise en charge des arguments de la 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.

92
BalusC

Essayez d'inclure un type MIME. Dans votre exemple publié, vous l'avez en tant que "". L'image vide peut être parce qu'elle ne reconnaît pas le flux en tant que fichier image puisque vous avez fait de ce champ une chaîne vide. Ajoutez donc un type mime image/png ou image/jpg et voyez si cela fonctionne:

String mimeType = "image/jpg";
StreamedContent file = new DefaultStreamedContent(bytes, mimeType, filename);  
5
rcheuk

Il y a quelques possibilités ici (et veuillez poster toute la classe si ce n'est pas le cas).

1) Vous n'initialisez pas correctement l'image 2) Votre flux est vide, vous n'obtenez rien

Je suppose que student.getImage () a une signature d'octet [] alors assurez-vous d'abord que ces données sont réellement intactes et représentent une image. Deuxièmement - vous ne spécifiez pas un type de contenu qui devrait être "image/jpg" ou quoi que vous utilisiez.

Voici un code standard pour le vérifier, j'utilise Primefaces 2 pour cela.

/** 'test' package with 'test/test.png' on the path */
@RequestScoped
@ManagedBean(name="imageBean")
public class ImageBean
{
    private DefaultStreamedContent content;

    public StreamedContent getContent()
    {
        if(content == null)
        {
            /* use your database call here */
            BufferedInputStream in = new BufferedInputStream(ImageBean.class.getClassLoader().getResourceAsStream("test/test.png"));
            ByteArrayOutputStream out = new ByteArrayOutputStream();

            int val = -1;
            /* this is a simple test method to double check values from the stream */
            try
            {
                while((val = in.read()) != -1)
                    out.write(val);
            }
            catch(IOException e)
            {
                e.printStackTrace();
            }

            byte[] bytes = out.toByteArray();
            System.out.println("Bytes -> " + bytes.length);
            content = new DefaultStreamedContent(new ByteArrayInputStream(bytes), "image/png", "test.png");
        }

        return content;
    }
}

et du balisage ...

<html 
    xmlns="http://www.w3.org/1999/xhtml" 
    xmlns:h="http://Java.Sun.com/jsf/html"
    xmlns:p="http://primefaces.prime.com.tr/ui"
>

    <h:head>

    </h:head>

    <h:body>
        <p:graphicImage value="#{imageBean.content}" />
    </h:body>
</html>

Si ce code fonctionne, vous êtes correctement configuré. Malgré le fait qu'il s'agit d'un code poubelle pour les flux (ne l'utilisez pas en production) il devrait vous donner un point de dépannage. Je suppose que vous pourriez avoir quelque chose qui se passe dans votre JPA ou autre infrastructure de base de données où vous êtes octet [] est vide ou il est mal formaté. Alternativement, vous pouvez simplement avoir un problème de type de contenu.

Enfin, je clonerais les données du bean pour que student.getImage () ne soit copié que dans un nouveau tableau puis utilisé. De cette façon, si vous avez quelque chose d'inconnu (quelque chose d'autre qui déplace l'objet ou change l'octet [], vous ne dérangez pas vos flux.

Faites quelque chose comme:

byte[] data = new byte[student.getImage().length]
for(int i = 0; i < data.length; i++)
  data[i] = student.getImage()[i];

afin que votre bean ait une copie (ou Arrays.copy () - tout ce qui fait flotter votre bateau). Je ne saurais trop insister sur le fait que quelque chose de simple comme ce type de contenu est généralement ce qui ne va pas. Bonne chance.

4
Daniel B. Chapman

La réponse de BalusC est (comme d'habitude) la bonne.

Mais gardez à l'esprit une chose (comme il l'a déjà déclaré). La demande finale est effectuée à partir du navigateur pour obtenir l'URL à partir du <img> tag. Cela ne se fait pas dans un "contexte jsf".

Donc, si vous essayez par exemple accéder au phaseId (enregistrement ou autre raison)

context.getCurrentPhaseId().getName()

Cela entraînera un NullPointerException et le message d'erreur trompeur que vous obtiendrez sera:

org.primefaces.application.resource.StreamedContentHandler () - Error in streaming dynamic resource. Error reading 'image' on type a.b.SomeBean

Il m'a fallu un certain temps pour comprendre quel était le problème.

3
morecore