Как загрузить несколько файлов с помощью JSF 2.2

Я пытаюсь добавить несколько файлов с помощью h:inputFile. Я быстро просмотрел исходный код и, похоже, у него нет возможности рендерить multiple="multiple". Есть ли способ обойти это без написания пользовательского компонента? Если нет, существует ли доступный пользовательский компонент JSF2.2, который может обрабатывать несколько загрузок файлов Ajax?

Обновление: Я прошел multiple="multiple" с помощью тега passthrough, но когда я отлаживал FileRenderer, соответствующий фрагмент кода перезаписывает первый файл со вторым:

for (Part cur : parts) {
  if (clientId.equals(cur.getName())) {
    component.setTransient(true);
    setSubmittedValue(component, cur);
  }
}

Как вы можете видеть, поскольку существует два Part с тем же clientId, он всегда использует последний вместо передачи списка.

Пожалуйста, порекомендуйте альтернативу, если она есть.

Ответ 1

Как загрузить несколько файлов с помощью JSF 2.2

Вы действительно можете добиться этого с помощью другой функции JSF 2.2: атрибуты passsthrough. Установите атрибут multiple в качестве атрибута прокси (поддержка браузера в настоящее время довольно широкий).

<html ... xmlns:a="http://xmlns.jcp.org/jsf/passthrough">
...
<h:inputFile ... a:multiple="true" />

Однако сам компонент <h:inputFile> не поддерживает захват нескольких Part из запроса и установку его как свойства массива или Collection bean. Он только установил бы последнюю часть, соответствующую имени поля ввода. В принципе, для поддержки нескольких частей необходимо создать настраиваемый рендерер (и вы должны немедленно воспользоваться возможностью просто поддержать атрибут multiple сразу, не прибегая к атрибутам пересылки).

Для того, чтобы создать "обходной путь" без создания всего средства визуализации, вы могли бы вручную захватить все части с помощью HttpServletRequest с помощью небольшого метода полезности:

public static Collection<Part> getAllParts(Part part) throws ServletException, IOException {
    HttpServletRequest request = (HttpServletRequest) FacesContext.getCurrentInstance().getExternalContext().getRequest();
    return request.getParts().stream().filter(p -> part.getName().equals(p.getName())).collect(Collectors.toList());
}

Итак, приведенная ниже конструкция должна работать с вышеуказанным методом утилиты:

<h:inputFile value="#{bean.part}" a:multiple="true" />
<h:commandButton ... action="#{bean.submit}" />

private Part file;

public void submit() throws ServletException, IOException {
    for (Part part : getAllParts(file)) {
        String fileName = part.getSubmittedFileName();
        InputStream fileContent = part.getInputStream();
        // ... 
        // Do your thing with it.
        // E.g. https://stackoverflow.com/q/14211843/157882
    }
}

public Part getFile() {
    return null; // Important!
}

public void setFile(Part file) {
    this.file = file;
}

Заметьте, что геттер может для безопасности и ясности лучше всегда возвращать null. Фактически, весь метод геттера должен был быть ненужным, но это то, чем оно является.

В более современных браузерах вы можете даже выбрать целые папки. Для этого требуется только еще новый атрибут directory. Это поддерживается, поскольку Firefox 46 (уже с 42, но должен быть явно включен примерно в: config). Webkit-браузеры (Chrome 11+, Safari 4+ и Edge) поддерживают это через собственный атрибут webkitdirectory. Поэтому, если вы укажете оба атрибута, вы в целом безопасны.

<h:inputFile ... a:multiple="true" a:directory="true" a:webkitdirectory="true" />

Заметьте, что это не отправляет физические папки, а только файлы, содержащиеся в этих папках.


Обновить: если вы используете служебную библиотеку JSF OmniFaces, начиная с версии 2.5 <o:inputFile>, который должен сделать несколько и выбор каталога менее утомительным.

<o:inputFile value="#{bean.files}" multiple="true" />

<o:inputFile value="#{bean.files}" directory="true" />

Значение может быть привязано к a List<Part>.

private List<Part> files; // +getter+setter

Ответ 2

Я думаю, что можно использовать множественную загрузку файлов с использованием стандартного JSF 2.2 с помощью тега passthrough.

Шаг 1:

<html xmlns="http://www.w3.org/1999/xhtml"
  xmlns:h="http://xmlns.jcp.org/jsf/html"
  xmlns:pt="http://xmlns.jcp.org/jsf/passthrough">
...

<h:form id="form" enctype="multipart/form-data">
    <h:inputFile id="file" value="#{fileUploadBean.uploadedFile}" pt:multiple="multiple" />
    ...

Шаг 2:

Класс рендеринга JSF FileRenderer для типа javax.faces.File семейства компонентов javax.faces.Input не корректно обрабатывает этот случай.

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

Я думаю, что лучшей стратегией всегда является значение компонента List<Part> вместо Part, как предложено здесь и реализовано здесь.

Шаг 3:

Последнее, что нужно сделать, это сконфигурировать такой модифицированный несколько класс рендеринга файлов в faces-config.xml, добавив в корневой элемент <faces-config> следующее:

<render-kit>
    <renderer>
        <description>Multiple File Renderer</description>
        <component-family>javax.faces.Input</component-family>
        <renderer-type>javax.faces.File</renderer-type>
        <renderer-class>com.example.MultipleFileRenderer</renderer-class>
    </renderer>
</render-kit>

Ответ 3

Даже это довольно давно: учитывая ваш собственный комментарий, я бы порекомендовал компонент, например PrimeFaces fileUploadMultiple, отметив, что не нужно забывать о необходимых изменениях в web.xml и все необходимые библиотеки для загрузки. Смотрите это как обходное решение или полное решение, основанное на ваших потребностях. PrimeFaces - довольно приятный компонент-lib