Мой главный вопрос: есть ли "хорошая практика" для обслуживания бинарных файлов (PDF, документы и т. Д.) С использованием JSF 2 с CDI и с использованием закладок URL?
Я прочитал JSF 2 spec (JSR 314) и я вижу, что существует абзац "Обработка ресурсов".Но, похоже, он используется только для обслуживания статических файлов, помещенных в файлы war или jar.Я действительно не понял, существует ли способ взаимодействия здесь путем регистрации какого-то определенного ResourceHandler ...
На самом деле, я привык к способу 2 Seam сделать это: расширение AbstractResource
класс с getResource(HttpServletRequest, HttpServletResponse)
методом и getResourcePath()
для объявления, какой путь будет использоваться после <webapp>/seam/resource/
префикса URL и объявления 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
(когда я нажимал кнопку, ничего не происходило).
Но тогда я хотел использовать ссылку вместо кнопки.
Iпробовал <h:commandLink value="Download" action="#{containerAction.download(file.key)}" />
, но генерирует некрасивую ссылку на javascript (не для закладки).
Читая спецификацию JSF 2, кажется, что есть функция "Bookmarkability", но не совсем понятно, как ее использовать.
На самом деле, похоже, что он работает только с представлениями, поэтому я попытался создать пустое представление и создал 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" ...
У вас есть какой-нибудь совет?