2010-06-23 3 views
1

Я создаю пользовательский компонент, который является средством просмотра изображений для заданного номера продукта. Я доступ к этим файлам, используя модифицированную версию ImageServlet BalusC в:JSF 2 cc: передать атрибут в бэк-файл

@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(); 
      } 
     } 
    } 
} 

Далее я создал составной компонент:

<?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> 

Как вы можете видеть, я просто захватывая список изображений с управляемым боб. Единственная причина, по которой это действительно необходимо, - это то, что мне нужно знать, сколько изображений есть для данного продукта. Это может сильно варьироваться (от 8 до 100). Вот этот код:

@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; 
    } 

} 

Существует простой объект для проведения данных I итерации по:

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; 
    } 

} 

Наконец, я проверить это составной компонент, используя URL-адрес в http://localhost:8080/project-name/testImages.xhtml?id=213123. Вот код для 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> 

Здесь проблема: единственная точка взаимодействия между приложением и составным компонентом должна быть тегом <sdCom:imageViewer listID="test" />. Однако это непрозрачная абстракция. Управляемому компоненту присваивается номер продукта на основе параметра запроса id. Это очень нежелательно. Он создает гораздо более плотную связь между компонентом и приложением, которое его использует. В идеале я должен использовать тег следующим образом: <sdCom:imageViewer listID="test" productNumber="213123"/>. Однако я не могу найти способ сделать это и все еще знать, сколько изображений мне нужно создать.

Спасибо заранее, Zack

Edit: Было бы вполне приемлемо для вызова сервлета, который принимает в номере продукта и возвращает количество изображений, что продукт имеет. Тем не менее, мне еще предстоит найти способ запустить цикл n раз (для цикла), а не запускать его один раз для каждого объекта в коллекции (цикл foreach). Я очень доволен любым решением, которое включает удаление этого @ManagedProperty("#{param.id}") из бэк-бэка.

+1

Не было бы лучше добавить другой атрибут к составному компоненту, который может быть передан произвольным массивам 'EvfImage'? Таким образом, ваш компонент становится независимым от управляемого компонента. – Behrang

+0

Bytecode Ninja: Спасибо за ответ. Это звучит как достойный компромисс.В моих конкретных обстоятельствах это не так желательно, как передача только номера продукта (в этом случае), но он также не обходит интерфейс компонента. Я буду использовать ваше предложение, если кто-то не придумает что-то лучше. Благодаря! –

+0

Bytecode Ninja: Как бы я ограничил List, который был передан, чтобы убедиться, что он разрешает только списки EvfImages? –

ответ

2

замена @ManagedProperty(value="#{param.id}") в ImageLoaderUtilBean будет

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

в сочетании со следующим в cc:implementationперед темui:repeat:

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

Если c: является (на самом деле не рекомендуется) Facelets' встроенный JSTL, которая должна быть объявлена ​​следующим образом:

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

Лицевые нити не заменяют c:set (еще?).

+0

, который все равно потребует от меня сломать интерфейс составного компонента. Это предполагает, что каждое приложение, использующее этот компонент, имеет параметр запроса 'id'. Поэтому один тег будет диктовать структуру приложения, которое его использует. Кроме того, это приведет к возникновению проблем, связанных с этим тегом в панели с ленивым загруженным вкладкой и т. Д. –

+0

Я понимаю, что вы имеете в виду. Я сделал ошибку, я обновил ответ. Однако я не уверен, что это будет работать правильно, поскольку JSTL запускает «синхронизацию» JSF. Это все теория :) – BalusC