2010-06-23 18 views
1

Je crée un composant personnalisé qui est un visualiseur d'image pour un numéro de produit donné. J'accéder à ces fichiers en utilisant une version modifiée de ImageServlet de BalusC:JSF 2 cc: attribut pass dans le bean backing

@WebServlet(name="ImageLoader", urlPatterns={"/ImageLoader"}) 
public class ImageLoader extends HttpServlet { 

    private static final int DEFAULT_BUFFER_SIZE = 10240; // 10KB. 

    private static String imagePath = "\\\\xxx.xxx.x.x\\root\\path\\to\\images\\"; 

    /** 
     * This code is a modified version of the ImageServlet found at balusc.blogspot.com. 
    * It expects the parameters id and n. 
    * <ul> 
    * <li><b>id:</b> the product number</li> 
    * <li><b>n:</b> the image number to load.</li> 
    */ 
    public void goGet(HttpServletRequest request, HttpServletResponse response) 
    throws ServletException, IOException { 

     String productNumber = URLDecoder.decode(request.getParameter("id"),"utf-8"); 
     String img = URLDecoder.decode(request.getParameter("n"),"utf-8"); 

     if (productNumber == null || img == null) { 
      response.sendError(HttpServletResponse.SC_NOT_FOUND); // 404. 
      return; 
     } 

     String path = generatePath(productNumber); 

     File image = new File(generatePath(productNumber), generateImageName(img)); 

     // Check if file actually exists in filesystem. 
     if (!image.exists()) { 
      response.sendError(HttpServletResponse.SC_NOT_FOUND); // 404. 
      return; 
     } 

     String contentType = getServletContext().getMimeType(image.getName()); 

     if (contentType == null || !contentType.startsWith("image")) { 
      response.sendError(HttpServletResponse.SC_NOT_FOUND); // 404. 
      return; 
     } 

     // Init servlet response. 
     response.reset(); 
     response.setBufferSize(DEFAULT_BUFFER_SIZE); 
     response.setContentType(contentType); 
     response.setHeader("Content-Length", String.valueOf(image.length())); 
     response.setHeader("Content-Disposition", "inline; filename=\"" + image.getName() + "\""); 

     // Prepare streams. 
     BufferedInputStream input = null; 
     BufferedOutputStream output = null; 

     try { 
      // Open streams. 
      input = new BufferedInputStream(new FileInputStream(image), DEFAULT_BUFFER_SIZE); 
      output = new BufferedOutputStream(response.getOutputStream(), DEFAULT_BUFFER_SIZE); 

      // Write file contents to response. 
      byte[] buffer = new byte[DEFAULT_BUFFER_SIZE]; 
      int length; 
      while ((length = input.read(buffer)) > 0) { 
       output.write(buffer, 0, length); 
      } 
     } finally { 
      close(output); 
      close(input); 
     } 
    } 

    private String generateImageName(String n) { 
     int imageNum = Integer.parseInt(n); 

     StringBuilder ret = new StringBuilder("img-"); 
     if (imageNum < 10) { 
      ret.append("00"); 
     } 
     else if(imageNum < 100) { 
      ret.append("0"); 
     } 
     ret.append(n); 
     ret.append(".jpg"); 
     return ret.toString(); 
    } 


    public static String generatePath(String productNumber) { 
     Long productNumberLng = Long.parseLong(productNumber); 

     StringBuilder ret = new StringBuilder(imagePath); 

     Long thousandPath = productNumberLng - (productNumberLng % 1000); 
     ret.append(thousandPath); 
     ret.append("s\\"); 
     ret.append(productNumber); 
     ret.append("\\"); 
     ret.append(productNumber); 
     ret.append("\\"); 

     return ret.toString(); 
    } 

    private static void close(Closeable resource) { 
     if (resource != null) { 
      try { 
       resource.close(); 
      } catch (IOException e) { 
       e.printStackTrace(); 
      } 
     } 
    } 
} 

Ensuite, je créé une pièce composite:

<?xml version='1.0' encoding='UTF-8' ?> 
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> 
<html xmlns="http://www.w3.org/1999/xhtml" 
     xmlns:ui="http://java.sun.com/jsf/facelets" 
     xmlns:cc="http://java.sun.com/jsf/composite" 
     xmlns:h="http://java.sun.com/jsf/html" 
     xmlns:f="http://java.sun.com/jsf/core"> 

    <!-- INTERFACE --> 
    <cc:interface> 
     <cc:attribute name="productNumber" shortDescription="The product number whose images should be displayed." 

        type="java.lang.Long" /> 
     <cc:attribute name="listID" shortDescription="This ID is the html ID of the &lt;ul&gt; element." /> 
    </cc:interface> 

    <!-- IMPLEMENTATION --> 
    <cc:implementation> 
     <div id="#{cc.clientId}"> 
      <ul id="#{cc.attrs.listID}"> 
       <ui:repeat value="#{imageLoaderUtilBean.images}" var="image"> 
        <li> 

         <h:graphicImage value="#{image.url}" alt="#{image.name}" /> 
        </li> 
       </ui:repeat> 
      </ul> 
     </div> 
    </cc:implementation> 
</html> 

Comme vous pouvez le voir, je suis juste saisir la liste des images à partir d'une gestion haricot. La seule raison pour laquelle cela est vraiment nécessaire est parce que j'ai besoin de savoir combien d'images il y a pour un produit donné. Cela peut varier considérablement (de 8 à 100). Voici ce code:

@ManagedBean 
@RequestScoped 
public class ImageLoaderUtilBean { 

    @ManagedProperty(value = "#{param.id}") 
    private Long productNumber; 

    private List<EvfImage> images; 

    public List<EvfImage> getImages() { 

     if (images == null) { 
      setImages(findImages()); 
     } 
     return images; 
    } 

    public void setImages(List<EvfImage> images) { 
     this.images = images; 
    }  

    public Long getProductNumber() { 
     return productNumber; 
    } 

    public void setProductNumber(Long productNumber) { 
     this.productNumber = productNumber; 
    } 

    private List<EvfImage> findImages() { 


     FilenameFilter jpegFilter = new FilenameFilter() { 

      @Override 
      public boolean accept(File directory, String filename) { 
       return filename.toLowerCase().endsWith(".jpg"); 
      } 
     }; 

     File directory = new File(ImageLoader.generatePath(productNumber.toString())); 
     if (!directory.exists()) { 
      return new ArrayList<EvfImage>(); 
     } 
     File[] files = directory.listFiles(jpegFilter); 

     List<EvfImage> ret = new ArrayList<EvfImage>(); 

     for (int i = 1; i <= files.length; i++) { 
      EvfImage img = new EvfImage(); 
      img.setName("file.getName()"); 
      img.setUrl("/ImageLoader?id=" + productNumber + "&amp;n=" + i); 
      ret.add(img); 
     } 

     return ret; 
    } 

} 

Il y a un objet simple pour maintenir les données que j'itérer sur:

public class EvfImage { 

    private String url; 
    private String name; 

    public String getName() { 
     return name; 
    } 

    public void setName(String name) { 
     this.name = name; 
    } 

    public String getUrl() { 
     return url; 
    } 

    public void setUrl(String url) { 
     this.url = url; 
    } 

} 

Enfin, je teste ce composant composite en utilisant une URL de http://localhost:8080/project-name/testImages.xhtml?id=213123. Voici le code pour testImages.xhtml:

<?xml version='1.0' encoding='UTF-8' ?> 
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> 
<html xmlns="http://www.w3.org/1999/xhtml" 
     xmlns:h="http://java.sun.com/jsf/html" 
     xmlns:sdCom="http://java.sun.com/jsf/composite/components/sd"> 
    <h:head> 
     <title>Facelet Title</title> 
    </h:head> 
    <h:body> 
     <sdCom:imageViewer listID="test" /> 
    </h:body> 
</html> 

Voici le problème: le seul point d'interaction entre l'application et le composant composite doit être la balise <sdCom:imageViewer listID="test" />. Cependant, ceci est une abstraction qui fuit. Le bean géré reçoit le numéro de produit en fonction du paramètre id de la demande. C'est très indésirable. Il crée un couplage beaucoup plus serré entre le composant et l'application qui l'utilise. Idéalement, je devrais utiliser l'étiquette comme suit: <sdCom:imageViewer listID="test" productNumber="213123"/>. Cependant, je n'arrive pas à trouver un moyen de le faire et je sais encore combien d'images j'ai besoin de créer.

Merci à l'avance, Zack

Edit: Il serait parfaitement acceptable d'appeler un servlet qui prend le numéro de produit et retourne le nombre d'images qui ce produit a. Cependant, je dois encore trouver un moyen d'exécuter une boucle n fois (pour la boucle) par opposition à l'exécuter une fois pour chaque objet dans une collection (boucle foreach). Je suis assez content de toute solution qui implique de supprimer ce @ManagedProperty("#{param.id}") du backing bean.

+1

Ne serait-il pas préférable d'ajouter un autre attribut à votre composant composite qui peut être passé arbitrairement 'EvfImage' tableaux? De cette façon, votre composant devient indépendant du bean géré. – Behrang

+0

Bytecode Ninja: Merci pour la réponse. Cela ressemble à un compromis décent.Dans mes circonstances particulières, il n'est pas aussi désirable que de passer juste un numéro de produit (dans ce cas), mais cela ne contourne pas non plus l'interface du composant. Je vais utiliser votre suggestion à moins que quelqu'un ne propose quelque chose de mieux. Merci! –

+0

Bytecode Ninja: Comment est-ce que je limiterais la liste qui est passée dedans pour s'assurer qu'elle autorise seulement des listes d'EvfImages? –

Répondre

2

Un remplacement de @ManagedProperty(value="#{param.id}") dans ImageLoaderUtilBean serait

<sdCom:imageViewer listID="test" productNumber="#{param.id}" /> 

en combinaison avec ce qui suit dans cc:implementationavantui:repeat:

<c:set target="#{imageLoaderUtilBean}" property="productNumber" value="#{cc.attrs.productNumber}" /> 

Lorsque le c: est le (en fait découragé) Facelets' builtin Bibliothèque JSTL qui doit être déclarée comme suit:

xmlns:c="http://java.sun.com/jsp/jstl/core" 

Facelets n'a pas de remplacement pour c:set (encore?).

+0

qui exigerait encore que je casse l'interface du composant composite. Cela supposerait que chaque application qui utilise ce composant a un paramètre de requête de 'id'. Par conséquent, une seule étiquette dicterait la structure de l'application qui l'utilise. En outre, cela entraînerait des problèmes d'incorporation de cette balise dans un panneau à onglets chargé paresseux, etc. –

+0

Je vois ce que vous voulez dire. J'ai fait une erreur, j'ai mis à jour la réponse. Je ne suis cependant pas sûr que cela fonctionnera correctement puisque JSTL exécute "out sync" de JSF. C'est toute la théorie :) – BalusC

Questions connexes