Как обеспечить загрузку файла из поддержки JSF bean?

Есть ли способ предоставить загрузку файла из метода поддержки bean JSF-поддержки? Я много пробовал. Основная проблема заключается в том, что я не могу понять, как получить OutputStream ответа, чтобы записать содержимое файла. Я знаю, как это сделать с помощью Servlet, но это невозможно вызвать из формы JSF и требует нового запроса.

Как я могу получить OutputStream ответа от текущего FacesContext?

Ответ 1

Введение

Вы можете получить все через ExternalContext. В JSF 1.x вы можете получить исходный HttpServletResponse объект ExternalContext#getResponse(). В JSF 2.x вы можете использовать кучу новых методов делегатов, таких как ExternalContext#getResponseOutputStream() без необходимости захватывать HttpServletResponse из-под JSF вытяжки.

В ответ вам следует установить заголовок Content-Type, чтобы клиент знал, какое приложение будет ассоциироваться с предоставленным файлом. И вы должны установить заголовок Content-Length, чтобы клиент мог вычислить ход загрузки, иначе он будет неизвестен. И вы должны установить заголовок Content-Disposition на attachment, если вы хотите открыть диалоговое окно "Сохранить как", иначе клиент попытается отобразить его в строке. Наконец, просто напишите содержимое файла в выходной поток ответа.

Наиболее важной частью является вызов FacesContext#responseComplete(), чтобы сообщить JSF, что он не должен выполнять навигацию и рендеринг после того, как вы написали файл ответ, иначе конец ответа будет загрязнен содержимым HTML на странице или более старыми версиями JSF, вы получите IllegalStateException с сообщением типа getoutputstream() has already been called for this response, когда реализация JSF вызывает getWriter() для визуализации HTML.

Общий пример JSF 2.x

public void download() throws IOException {
    FacesContext fc = FacesContext.getCurrentInstance();
    ExternalContext ec = fc.getExternalContext();

    ec.responseReset(); // Some JSF component library or some Filter might have set some headers in the buffer beforehand. We want to get rid of them, else it may collide.
    ec.setResponseContentType(contentType); // Check http://www.iana.org/assignments/media-types for all types. Use if necessary ExternalContext#getMimeType() for auto-detection based on filename.
    ec.setResponseContentLength(contentLength); // Set it with the file size. This header is optional. It will work if it omitted, but the download progress will be unknown.
    ec.setResponseHeader("Content-Disposition", "attachment; filename=\"" + fileName + "\""); // The Save As popup magic is done here. You can give it any file name you want, this only won't work in MSIE, it will use current request URL as file name instead.

    OutputStream output = ec.getResponseOutputStream();
    // Now you can write the InputStream of the file to the above OutputStream the usual way.
    // ...

    fc.responseComplete(); // Important! Otherwise JSF will attempt to render the response which obviously will fail since it already written with a file and closed.
}

Общий пример JSF 1.x

public void download() throws IOException {
    FacesContext fc = FacesContext.getCurrentInstance();
    HttpServletResponse response = (HttpServletResponse) fc.getExternalContext().getResponse();

    response.reset(); // Some JSF component library or some Filter might have set some headers in the buffer beforehand. We want to get rid of them, else it may collide.
    response.setContentType(contentType); // Check http://www.iana.org/assignments/media-types for all types. Use if necessary ServletContext#getMimeType() for auto-detection based on filename.
    response.setContentLength(contentLength); // Set it with the file size. This header is optional. It will work if it omitted, but the download progress will be unknown.
    response.setHeader("Content-Disposition", "attachment; filename=\"" + fileName + "\""); // The Save As popup magic is done here. You can give it any file name you want, this only won't work in MSIE, it will use current request URL as file name instead.

    OutputStream output = response.getOutputStream();
    // Now you can write the InputStream of the file to the above OutputStream the usual way.
    // ...

    fc.responseComplete(); // Important! Otherwise JSF will attempt to render the response which obviously will fail since it already written with a file and closed.
}

Пример обычного статического файла

Если вам необходимо передать статический файл из локальной файловой системы на диске, замените код следующим образом:

File file = new File("/path/to/file.ext");
String fileName = file.getName();
String contentType = ec.getMimeType(fileName); // JSF 1.x: ((ServletContext) ec.getContext()).getMimeType(fileName);
int contentLength = (int) file.length();

// ...

Files.copy(file.toPath(), output);

Пример общего динамического файла

Если вам нужно передать динамически сгенерированный файл, например PDF или XLS, просто укажите output там, где используемый API ожидает OutputStream.

например. iText PDF:

String fileName = "dynamic.pdf";
String contentType = "application/pdf";

// ...

Document document = new Document();
PdfWriter writer = PdfWriter.getInstance(document, output);
document.open();
// Build PDF content here.
document.close();

например. Apache POI HSSF:

String fileName = "dynamic.xls";
String contentType = "application/vnd.ms-excel";

// ...

HSSFWorkbook workbook = new HSSFWorkbook();
// Build XLS content here.
workbook.write(output);
workbook.close();

Обратите внимание, что вы не можете установить длину содержимого здесь. Поэтому вам нужно удалить строку, чтобы задать длину содержимого ответа. Это технически не проблема, единственным недостатком является то, что у конечного пользователя будет неизвестный процесс загрузки. В случае, если это важно, вам действительно нужно сначала записать в локальный (временный) файл, а затем предоставить его, как показано в предыдущей главе.

Отключите ajax!

Вам нужно только убедиться, что метод действия не вызывается с помощью запроса ajax, но он вызывается обычным запросом при срабатывании <h:commandLink> и <h:commandButton>. Запросы Ajax обрабатываются JavaScript, который, в свою очередь, по соображениям безопасности не имеет возможности принудительно активировать диалог "Сохранить как" с содержимым ответа ajax.

Если вы используете, например, PrimeFaces <p:commandXxx>, тогда вам нужно убедиться, что вы явно отключите ajax с помощью атрибута ajax="false". Если вы используете ICEfaces, вам нужно вставить в компонент команды <f:ajax disabled="true" />.

Полезный метод

Если вы используете служебную библиотеку JSF OmniFaces, вы можете использовать один из трех удобных Faces#sendFile(), используя либо File, либо InputStream, либо byte[], и указывая, должен ли файл загружаться как вложение (true) или inline (false).

public void download() throws IOException {
    Faces.sendFile(file, true);
}

Да, этот код завершен как есть. Вам не нужно вызывать responseComplete() и так далее. Этот метод также правильно обрабатывает заголовки IE и имена файлов UTF-8. Здесь вы можете найти исходный код.

Ответ 2

public void download() throws IOException
{

    File file = new File("file.txt");

    FacesContext facesContext = FacesContext.getCurrentInstance();

    HttpServletResponse response = 
            (HttpServletResponse) facesContext.getExternalContext().getResponse();

    response.reset();
    response.setHeader("Content-Type", "application/octet-stream");
    response.setHeader("Content-Disposition", "attachment;filename=file.txt");

    OutputStream responseOutputStream = response.getOutputStream();

    InputStream fileInputStream = new FileInputStream(file);

    byte[] bytesBuffer = new byte[2048];
    int bytesRead;
    while ((bytesRead = fileInputStream.read(bytesBuffer)) > 0) 
    {
        responseOutputStream.write(bytesBuffer, 0, bytesRead);
    }

    responseOutputStream.flush();

    fileInputStream.close();
    responseOutputStream.close();

    facesContext.responseComplete();

}

Ответ 3

Это то, что сработало для меня:

public void downloadFile(String filename) throws IOException {
    final FacesContext fc = FacesContext.getCurrentInstance();
    final ExternalContext externalContext = fc.getExternalContext();

    final File file = new File(filename);

    externalContext.responseReset();
    externalContext.setResponseContentType(ContentType.APPLICATION_OCTET_STREAM.getMimeType());
    externalContext.setResponseContentLength(Long.valueOf(file.lastModified()).intValue());
    externalContext.setResponseHeader("Content-Disposition", "attachment;filename=" + file.getName());

    final HttpServletResponse response = (HttpServletResponse) externalContext.getResponse();

    FileInputStream input = new FileInputStream(file);
    byte[] buffer = new byte[1024];
    final ServletOutputStream out = response.getOutputStream();

    while ((input.read(buffer)) != -1) {
        out.write(buffer);
    }

    out.flush();
    fc.responseComplete();
}

Ответ 4

вот полный фрагмент кода http://bharatonjava.wordpress.com/2013/02/01/downloading-file-in-jsf-2/

 @ManagedBean(name = "formBean")
 @SessionScoped
 public class FormBean implements Serializable
 {
   private static final long serialVersionUID = 1L;

   /**
    * Download file.
    */
   public void downloadFile() throws IOException
   {
      File file = new File("C:\\docs\\instructions.txt");
      InputStream fis = new FileInputStream(file);
      byte[] buf = new byte[1024];
      int offset = 0;
      int numRead = 0;
      while ((offset < buf.length) && ((numRead = fis.read(buf, offset, buf.length -offset)) >= 0)) 
      {
        offset += numRead;
      }
      fis.close();
      HttpServletResponse response =
         (HttpServletResponse) FacesContext.getCurrentInstance()
        .getExternalContext().getResponse();

     response.setContentType("application/octet-stream");
     response.setHeader("Content-Disposition", "attachment;filename=instructions.txt");
     response.getOutputStream().write(buf);
     response.getOutputStream().flush();
     response.getOutputStream().close();
     FacesContext.getCurrentInstance().responseComplete();
   }
 }

Вы можете изменить логику чтения файлов, если вы хотите, чтобы файл генерировался во время выполнения.