Мой главный вопрос: существует ли «хорошая практика» для обслуживания двоичных файлов (PDF, документов и т. д.) с использованием JSF 2 с CDI и использованием URL-адресов с закладками?
Я прочитал спецификацию JSF 2 (JSR 314 ), и я вижу, что существует параграф «Обработка ресурсов». Но, похоже, он используется только для обслуживания статических файлов, помещенных в файлы war или jar. Я действительно не понял, существует ли способ взаимодействовать здесь, зарегистрировав какой-то конкретный ResourceHandler...
На самом деле, я привык к 2 способу Seam сделать это: расширить AbstractResource
с методом getResource(HttpServletRequest, HttpServletResponse)
и getResourcePath()
для объявления пути для обслуживания после префикса URL-адреса <webapp>/seam/resource/
и объявления SeamResourceServlet
в файле web.xml
.
Вот что я сделал.
Я впервые увидел Как загрузить файл, хранящийся в базе данных с помощью JSF 2.0, и попытаться реализовать его.
<f:view ...
<f:metadata>
<f:viewParam name="key" value="#{containerAction.key}"/>
<f:event listener="#{containerAction.preRenderView}" type="preRenderComponent" />
</f:metadata>
...
<rich:dataGrid columns="1" value="#{containerAction.container.files}" var="file">
<rich:panel>
<h:panelGrid columns="2">
<h:outputText value="File Name:" />
<h:outputText value="#{file.name}" />
</h:panelGrid>
<h:form>
<h:commandButton value="Download" action="#{containerAction.download(file.key)}" />
</h:form>
</rich:panel>
</rich:dataGrid>
А вот и фасоль:
@Named
@SessionScoped
public class ContainerAction {
private Container container;
/// Injections
@Inject @DefaultServiceInstance
private Instance<ContainerService> containerService;
/// Control methods
public void preRenderView(final ComponentSystemEvent event) {
container = containerService.get().loadFromKey(key);
}
/// Action methods
public void download(final String key) throws IOException {
final FacesContext facesContext = FacesContext.getCurrentInstance();
HttpServletResponse response = (HttpServletResponse) facesContext.getExternalContext().getResponse();
final ContainerFile containerFile = containerService.get().loadFromKey(key);
final InputStream containerFileStream = containerService.get().read(containerFile);
response.setHeader("Content-Disposition", "attachment;filename=\""+containerFile.getName()+"\"");
response.setContentType(containerFile.getContentType());
response.setContentLength((int) containerFile.getSize());
IOUtils.copy(containerFileStream, response.getOutputStream());
response.flushBuffer();
facesContext.responseComplete();
}
/// Getters / setters
public Container getContainer() {
return container;
}
}
Здесь мне пришлось переключиться на Tomcat 7 (я использовал 6), чтобы правильно интерпретировать это выражение EL . С @SessionScoped
работало, а с @RequestScoped
нет (при нажатии на кнопку ничего не происходило).
Но потом я захотел использовать ссылку вместо кнопки.
Я попробовал <h:commandLink value="Download" action="#{containerAction.download(file.key)}" />
, но он генерирует какую-то уродливую ссылку на javascript (не для закладок).
Читая спецификацию JSF 2, кажется, что есть функция «Закладка», но не совсем понятно, как ее использовать.
На самом деле это работает только с представлениями, поэтому я попытался создать пустое представление и создал h:link
:
<h:link outcome="download.xhtml" value="Download">
<f:param name="key" value="#{file.key}"/>
</h:link>
<f:view ...>
<f:metadata>
<f:viewParam name="key" value="#{containerFileDownloadAction.key}"/>
<f:event listener="#{containerFileDownloadAction.download}" type="preRenderComponent" />
</f:metadata>
</f:view>
@Named
@RequestScoped
public class ContainerFileDownloadAction {
private String key;
@Inject @DefaultServiceInstance
private Instance<ContainerService> containerService;
public void download() throws IOException {
final FacesContext facesContext = FacesContext.getCurrentInstance();
// same code as previously
...
facesContext.responseComplete();
}
/// getter / setter for key
...
}
Но тогда у меня был java.lang.IllegalStateException: "getWriter()" has already been called for this response
.
Логика, когда представление инициируется, использует getWritter для инициализации ответа.
Итак, я создал сервлет, который выполняет свою работу, и создал следующее h:outputLink
:
<h:outputLink value="#{facesContext.externalContext.request.contextPath}/download/">
<h:outputText value="Download"/>
<f:param name="key" value="#{file.key}"/>
</h:outputLink>
Но даже если этот последний метод дает мне URL-адрес для закладки для моего файла, на самом деле это не «JFS 2»…
У вас есть совет?