Загрузка изображений с клиента на сервер с использованием пользовательского тега JSF

Я хотел бы загрузить изображения в папку внутри сервера.

Почему-то не могу. Я не понимаю, почему мой фильтр не срабатывает. И почему файл не загружается. Может ли кто-нибудь взглянуть на мой код и помочь мне найти причину, по которой файлы не загружаются?

Я вставлю все, что сделал до сих пор, чтобы вы могли помочь мне найти ошибку:

1. В папку lib добавлены файлы commons-fileupload-1.2.1.jar и commons-io-1.4.jar (автоматически добавляются в путь к классам)

введите здесь описание изображения

2.Создал xml, который сделает доступной библиотеку тегов (она находится в папке WEB-INF)

<?xml version="1.0" encoding="UTF-8"?>
<facelet-taglib version="2.0"
   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-facelettaglibrary
package com.corejsf;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import javax.el.ValueExpression;
import javax.faces.FacesException;
import javax.faces.component.EditableValueHolder;
import javax.faces.component.UIComponent;
import javax.faces.context.ExternalContext;
import javax.faces.context.FacesContext;
import javax.faces.context.ResponseWriter;
import javax.faces.render.FacesRenderer;
import javax.faces.render.Renderer;
import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest;
import org.apache.commons.fileupload.FileItem;

@FacesRenderer(componentFamily="javax.faces.Input",
   rendererType="com.corejsf.Upload")
public class UploadRenderer extends Renderer {
   public void encodeBegin(FacesContext context, UIComponent component)  
      throws IOException {
      if (!component.isRendered()) return;
      ResponseWriter writer = context.getResponseWriter();

      String clientId = component.getClientId(context);

      writer.startElement("input", component);
      writer.writeAttribute("type", "file", "type");
      writer.writeAttribute("name", clientId, "clientId");
      writer.endElement("input");
      writer.flush();
   }

   public void decode(FacesContext context, UIComponent component) {
      ExternalContext external = context.getExternalContext(); 
      HttpServletRequest request = (HttpServletRequest) external.getRequest();
      String clientId = component.getClientId(context);
      FileItem item = (FileItem) request.getAttribute(clientId);

      Object newValue;
      ValueExpression valueExpr = component.getValueExpression("value");
      if (valueExpr != null) {
         Class<?> valueType = valueExpr.getType(context.getELContext());
         if (valueType == byte[].class) {
            newValue = item.get();
         }
         else if (valueType == InputStream.class) {
            try {
               newValue = item.getInputStream();
            } catch (IOException ex) {
               throw new FacesException(ex);
            }
         }
         else {
            String encoding = request.getCharacterEncoding();
            if (encoding != null)
               try {
                  newValue = item.getString(encoding);
               } catch (UnsupportedEncodingException ex) {
                  newValue = item.getString(); 
               }
            else 
               newValue = item.getString(); 
         }
         ((EditableValueHolder) component).setSubmittedValue(newValue);  
         ((EditableValueHolder) component).setValid(true);  
      }

      Object target = component.getAttributes().get("target");

      if (target != null) {
         File file;
         if (target instanceof File)
            file = (File) target;
         else {
            ServletContext servletContext 
               = (ServletContext) external.getContext();
            String realPath = servletContext.getRealPath(target.toString());
            file = new File(realPath); 
         }

         try { // ugh--write is declared with "throws Exception"
            item.write(file);
         } catch (Exception ex) { 
            throw new FacesException(ex);
         }
      }
   }   
}
0.xsd"> <namespace>http://corejsf.com</namespace> <tag> <tag-name>upload</tag-name> <component> <component-type>javax.faces.Input</component-type> <renderer-type>com.corejsf.Upload</renderer-type> </component> </tag> </facelet-taglib>

3.Создайте пакет для реализации тега и поместите его в новый пакет с именем com.corejsf;

введите здесь описание изображения

Вот источник:

package com.corejsf;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import javax.el.ValueExpression;
import javax.faces.FacesException;
import javax.faces.component.EditableValueHolder;
import javax.faces.component.UIComponent;
import javax.faces.context.ExternalContext;
import javax.faces.context.FacesContext;
import javax.faces.context.ResponseWriter;
import javax.faces.render.FacesRenderer;
import javax.faces.render.Renderer;
import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest;
import org.apache.commons.fileupload.FileItem;

@FacesRenderer(componentFamily="javax.faces.Input",
   rendererType="com.corejsf.Upload")
public class UploadRenderer extends Renderer {
   public void encodeBegin(FacesContext context, UIComponent component)  
      throws IOException {
      if (!component.isRendered()) return;
      ResponseWriter writer = context.getResponseWriter();

      String clientId = component.getClientId(context);

      writer.startElement("input", component);
      writer.writeAttribute("type", "file", "type");
      writer.writeAttribute("name", clientId, "clientId");
      writer.endElement("input");
      writer.flush();
   }

   public void decode(FacesContext context, UIComponent component) {
      ExternalContext external = context.getExternalContext(); 
      HttpServletRequest request = (HttpServletRequest) external.getRequest();
      String clientId = component.getClientId(context);
      FileItem item = (FileItem) request.getAttribute(clientId);

      Object newValue;
      ValueExpression valueExpr = component.getValueExpression("value");
      if (valueExpr != null) {
         Class<?> valueType = valueExpr.getType(context.getELContext());
         if (valueType == byte[].class) {
            newValue = item.get();
         }
         else if (valueType == InputStream.class) {
            try {
               newValue = item.getInputStream();
            } catch (IOException ex) {
               throw new FacesException(ex);
            }
         }
         else {
            String encoding = request.getCharacterEncoding();
            if (encoding != null)
               try {
                  newValue = item.getString(encoding);
               } catch (UnsupportedEncodingException ex) {
                  newValue = item.getString(); 
               }
            else 
               newValue = item.getString(); 
         }
         ((EditableValueHolder) component).setSubmittedValue(newValue);  
         ((EditableValueHolder) component).setValid(true);  
      }

      Object target = component.getAttributes().get("target");

      if (target != null) {
         File file;
         if (target instanceof File)
            file = (File) target;
         else {
            ServletContext servletContext 
               = (ServletContext) external.getContext();
            String realPath = servletContext.getRealPath(target.toString());
            file = new File(realPath); 
         }

         try { // ugh--write is declared with "throws Exception"
            item.write(file);
         } catch (Exception ex) { 
            throw new FacesException(ex);
         }
      }
   }   
}

4.Затем я добавил фильтр сервлета, чтобы различать перехватываемые запросы, и поместил его в тот же пакет, что и реализация пользовательского тега

введите здесь описание изображения

Это его источник:

package com.corejsf;

import java.io.File;
import java.io.IOException;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.FileUploadException;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.ServletFileUpload;

public class UploadFilter implements Filter {
   private int sizeThreshold = -1;
   private String repositoryPath;

   public void init(FilterConfig config) throws ServletException {
      repositoryPath = config.getInitParameter(
         "com.corejsf.UploadFilter.repositoryPath");
      try {
         String paramValue = config.getInitParameter(
            "com.corejsf.UploadFilter.sizeThreshold");
         if (paramValue != null) 
            sizeThreshold = Integer.parseInt(paramValue);
      }
      catch (NumberFormatException ex) {
         ServletException servletEx = new ServletException();
         servletEx.initCause(ex);
         throw servletEx;
      }
   }

   public void destroy() {
   }

   public void doFilter(ServletRequest request, 
      ServletResponse response, FilterChain chain) 
      throws IOException, ServletException {

      if (!(request instanceof HttpServletRequest)) {
         chain.doFilter(request, response);
         return;
      }

      HttpServletRequest httpRequest = (HttpServletRequest) request;

      boolean isMultipartContent 
         = ServletFileUpload.isMultipartContent(httpRequest);
      if (!isMultipartContent) {
         chain.doFilter(request, response);
         return;
      }

      DiskFileItemFactory factory = new DiskFileItemFactory();
      if (sizeThreshold >= 0)
         factory.setSizeThreshold(sizeThreshold);
      if (repositoryPath != null) 
         factory.setRepository(new File(repositoryPath));
      ServletFileUpload upload = new ServletFileUpload(factory);

      try {
         @SuppressWarnings("unchecked") List<FileItem> items 
            = (List<FileItem>) upload.parseRequest(httpRequest);
         final Map<String, String[]> map = new HashMap<String, String[]>();
         for (FileItem item : items) {
            String str = item.getString();
            if (item.isFormField())
               map.put(item.getFieldName(), new String[] { str });
            else
               httpRequest.setAttribute(item.getFieldName(), item);
         }

         chain.doFilter(new 
            HttpServletRequestWrapper(httpRequest) {
               public Map<String, String[]> getParameterMap() {
                  return map;
               }                   
               // busywork follows ... should have been part of the wrapper
               public String[] getParameterValues(String name) {
                  Map<String, String[]> map = getParameterMap();
                  return (String[]) map.get(name);
               }
               public String getParameter(String name) {
                  String[] params = getParameterValues(name);
                  if (params == null) return null;
                  return params[0];
               }
               public Enumeration<String> getParameterNames() {
                  Map<String, String[]> map = getParameterMap();
                  return Collections.enumeration(map.keySet());
               }
            }, response);
      } catch (FileUploadException ex) {
         ServletException servletEx = new ServletException();
         servletEx.initCause(ex);
         throw servletEx;
      }      
   }   
}

5.Затем я зарегистрировал фильтр в файле web.xml. (Я хотел использовать аннотацию, но не знал, как это сделать. Кто-нибудь знает, как это сделать с помощью аннотации?) Также добавил corejsf.taglib.xml

<!-- NEEDED FOR FILE UPLOAD -->
<filter>
      <filter-name>Upload Filter</filter-name>
      <filter-class>com.corejsf.UploadFilter</filter-class>
      <init-param>
         <param-name>sizeThreshold</param-name>
         <param-value>1024</param-value>
      </init-param>
</filter>

   <filter-mapping>
      <filter-name>Upload Filter</filter-name>
      <url-pattern>/faces/upload/*</url-pattern>
   </filter-mapping> 

    <context-param>
      <param-name>javax.faces.PROJECT_STAGE</param-name>
      <param-value>Development</param-value>
   </context-param>
   <context-param>
      <param-name>facelets.LIBRARIES</param-name>
      <param-value>/WEB-INF/corejsf.taglib.xml</param-value>
   </context-param>   

6. В моей папке WebContent я создал подпапку с именем upload (назначение загруженных файлов)

введите здесь описание изображения

7. Внутри страницы jsf я использую тег для загрузки и отправки, а также использую метод управляемого компонента для создания имен файлов:

<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:corejsf="http://corejsf.com">
            ....
    <h:form enctype="multipart/form-data">

     <corejsf:upload target="upload/#{placeAddController.prepareUniqueIdentifier}" />

         ....

        <h:commandButton value="Dalje" style=" font-weight: bold;  font-size:150%; action="/submittedImage" />  

    ...

    </h:form>

И управляемый компонент Java:

@ManagedBean
@RequestScoped
public class PlaceAddControler {
…
public String prepareUniqueIdentifier() {
        return UUID.randomUUID().toString()+"png";
    }   

-Все вроде нормально, но чего-то не хватает или не так. Как вы думаете, почему не загружается?


person javing    schedule 18.06.2011    source источник


Ответы (1)


Фильтр, по-видимому, не был вызван. Поместите точки останова отладки в метод doFilter() или добавьте операторы Logger или операторы System.out.println() для бедняков, чтобы узнать, какой код точно выполняется, а какой нет и какие именно переменные были установлены.

Фильтр будет вызываться только тогда, когда URL-адрес запроса соответствует <url-pattern> фильтра. Он должен соответствовать шаблону URL-адреса URL-адреса запроса, который вы видите в адресной строке браузера на странице JSF с формой загрузки. Поскольку вы настроили шаблон URL-адреса /faces/upload/*, он будет вызываться только тогда, когда URL-адрес запроса выглядит примерно так:

http://localhost:8080/contextname/faces/upload/form.xhtml

Что касается вопроса, как аннотировать фильтр, используйте @WebFilter .

@WebFilter(urlPatterns={"/faces/upload/*"})
public class UploadFilter implements Filter {
    // ...
}

Не связанные с проблемой, в коде есть недочеты (да, я знаю, большинство не ваше, просто хочу вас предупредить):

  1. Этот фильтр не поддерживает параметры запроса с несколькими значениями, такими как foo=val1&foo=val2&foo=val3, которые вы можете получить, когда в формах используется множественный выбор или множественный флажок. Таким образом, только последнее выбранное/отмеченное значение попадает в карту параметров. Я бы рекомендовал соответствующим образом исправить код фильтра.

  2. Хранение загруженных файлов в папке веб-контента бесполезно, если вам нужно постоянное хранилище. Всякий раз, когда вы повторно развертываете файл WAR/EAR веб-приложения, исходная развернутая папка веб-приложения будет полностью удалена, включая файлы, которые были добавлены во время выполнения веб-приложения. Веб-сервер не сохраняет изменения в только что расширенной папке веб-приложения. Если вам нужно более постоянное хранилище, вы должны хранить файлы вне папки веб-приложения, предпочтительно по абсолютному пути. Например. /var/webapp/upload.

Смотрите также:

person BalusC    schedule 18.06.2011
comment
Я пытаюсь это исправить. Я поместил вызовы println() в методы фильтра, но я не вижу сообщений, я думаю, что не попал в него. Как я могу проверить, правильно ли я нацеливаюсь на них (я не вижу изменений в URL-адресе). Я заметил, что, поскольку я использую составную форму, сообщения проверки не появляются. Может ли проверка как-то помешать загрузке файла? - person javing; 19.06.2011
comment
Это потому, что фильтр не вызывается. Фильтр отвечает за синтаксический анализ запроса multipart/form-data и возвращение параметров в карту параметров запроса, чтобы JSF мог получить к нему обычный доступ с помощью request.getParameter(name). Что касается отображения фильтра, попробуйте заменить <url-pattern>/faces/upload/*</url-pattern> на <servlet-name>Faces Servlet</servlet-name> (которое должно совпадать с именем настоящего сервлета FacesServlet в вашем web.xml). - person BalusC; 19.06.2011
comment
Я изменил на ‹servlet-name›, но теперь я получаю исключение File not found: java.io.FileNotFoundException: C:\jeeAplicationServer\glassfishv3\glassfish\domains\domain1\eclipseApps\scarecrow1\upload (Access is denied) at java.io.FileOutputStream.open(Native Method)... Моя папка для загрузки в настоящее время находится вне папки WebContent, она находится в корне проектов - person javing; 19.06.2011
comment
Исключение, я думаю, хорошая новость, я думаю, что теперь, если дошли до фильтра. Это последняя строка кода, которая создает проблемы, она говорит: item.write(file); Я думаю, что это связано с путем к папке загрузки (в корне проекта). Как я мог знать, что это правильный путь? - person javing; 19.06.2011
comment
То, что он достигает фильтра сейчас, является признаком того, что URL-адрес запроса определенно не соответствует шаблону URL-адреса в сопоставлении фильтра. Что касается исключения, вам также определенно не следует использовать корень проекта. Он должен быть полностью вне проекта и сервера. Используйте /var/webapp/uploads или что-то в этом роде (что в Windows заканчивается на C:\var\webapp\uploads). - person BalusC; 19.06.2011
comment
Я понимаю. Но у меня теперь есть сомнения. Если позже я развернусь на реальном сервере, то я буду хранить их в папке вне проекта где-то на хостинге, верно? (В случае, если я не хочу хранить изображения в базе данных) - person javing; 19.06.2011
comment
Также у меня было еще одно сомнение, почти забыл. Могу ли я каким-либо образом вызвать фильтр из метода в управляемом компоненте и передать ответ на запрос. Может быть, так мне проще, и мне не нужно беспокоиться о сопоставлении URL-адреса? - person javing; 19.06.2011
comment
Вы должны указать серверному администратору, что веб-приложению требуется доступ r/w к папке по пути x. Вы можете настроить путь x с помощью системного свойства, чтобы администратор сервера мог устанавливать путь x извне. Например. добавив аргумент виртуальной машины -Dupload.location=/var/webapp/upload, чтобы вы могли получить его с помощью System.getProperty("upload.location"). Что касается доступа к фильтру в bean-компоненте, это невозможно и также не сработает, в любом случае уже слишком поздно. Фильтр должен быть запущен до запуска JSF, иначе JSF вообще не сможет выполнить действие компонента. Вы хоть понимаете, что делает фильтр? - person BalusC; 19.06.2011
comment
Честно говоря, я частично понимаю фильтр. Что он делает, так это берет объекты, не закодированные в URL, и преобразовывает их в HTTP-запросы. Я следую этому руководству: http://www.d.umn.edu/~tcolburn/cs4531/assignments/team/2011/file_upload/file_upload.xhtml Я действительно не понимаю, почему я не могу сделать то же самое только с сервлетом. Я подумал о том, чтобы прочитать еще немного о сервлетах и ​​фильтрах, а затем вернуться к этой задаче. Мне нужно прочитать больше теории, прежде чем реализовать это самостоятельно. - person javing; 20.06.2011
comment
Стандартная кодировка формы — application/x-www-form-urlencoded. Фараметры формы отображаются URL-адресом, закодированным в теле запроса, например name1=value1&name2=value2&name3=value3. Однако этот формат не поддерживает двоичные данные, такие как загруженные файлы. Поэтому вместо этого вы должны использовать кодировку multipart/form-data. Таким образом, параметры очень по-разному кодируются в теле запроса. Пример можно найти здесь en.wikipedia.org/wiki/MIME#Multipart_messages. Однако стандартный API сервлетов не поддерживает этот формат. request.getParameter() вызовы ничего не возвращают. JSF полагается на параметры, которые... - person BalusC; 20.06.2011
comment
..доступен request.getParameter(). Таким образом, фильтр анализирует запрос multipart/form-data и помещает параметры обратно в пользовательский объект HttpServletRequest и передает его, чтобы JSF мог использовать request.getParameter() обычным способом. На самом деле вы также можете использовать вместо этого сервлет, но это невозможно в сочетании с JSF, поскольку FacesServlet уже выполняет задание по управлению запросом/ответом. Отсюда и фильтр. Вы также можете просто забыть JSF для этой конкретной формы/страницы, но это не имеет смысла. - person BalusC; 20.06.2011
comment
Большое спасибо за эти 2 комментария. Теперь я вижу вещи более ясно. Я прочитаю о фильтрах в руководстве по JEE 6 (глава 10) и создам свой собственный, чтобы попытаться поймать этот запрос, я преобразую его обратно в форму HttpRequest, затем извлеку изображения и сохраню их в файловая система. - person javing; 20.06.2011
comment
В чем проблема с текущим фильтром? (кроме того, он неправильно обрабатывает несколько значений параметров) Он работает, верно? Вы можете найти другой пример фильтра и компонента в этой статье: balusc.blogspot.com/2009/12/ Обратите внимание, что вы также можете использовать Tomahawk 2.0, чтобы в конечном итоге получить меньше пользовательского кода: stackoverflow.com/questions/5418292/jsf-2-0-file-upload - person BalusC; 20.06.2011
comment
Забыл принять этот ответ :) Я наконец сделал это. Наконец-то я понял, почему я использую фильтр для реализации собственной загрузки файлов. Я рад этому, но я заметил, что у меня нет некоторых преимуществ, которые у меня были с моим старым компонентом загрузки файлов Primefaces (множественный выбор файла, индикатор выполнения). Я думаю, что предпочитаю использовать простые лица, чем этот подход, но я рад, что понял, как это делается. - person javing; 30.06.2011