Обслуживание файлов с помощью JSF 2 / CDI с использованием закладок URL - PullRequest
1 голос
/ 08 июня 2011

Мой главный вопрос: есть ли "хорошая практика" для обслуживания бинарных файлов (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" ...

У вас есть какой-нибудь совет?

Ответы [ 3 ]

2 голосов
/ 13 июня 2011

Я согласен с BalusC.Обычно приложение - это не просто приложение JSF, а приложение Java EE.

Недаром существуют другие вещи, кроме представлений JSF, для обработки запросов http в Java EE.В Java EE 6 ваш именованный компонент CDI может также напрямую отображаться на путь с использованием JAX-RS.Это альтернатива использованию сервлетов.В этом случае вы должны использовать @Produces и @Path (см., Например, Входные и выходные двоичные потоки с использованием JERSEY? ).

С другой стороны, одним из преимуществ <f:viewParam> в JSF является то, что вы можете легко подключать к нему валидаторы.На данный момент ни ресурсы сервлетов, ни ресурсы JAX-RS не поддерживают это.

<h:link> также удобнее использовать, чем писать <h:outputLink value="#{facesContext.externalContext.request.contextPath}/..."> все время.Это, однако, может быть смягчено путем помещения этой части в тег Facelets или составной компонент.

(Было бы здорово, если бы в будущей версии спецификации был указан тег ссылки в JSF для прямой ссылки на ресурсы JAX-RS (с дополнительной проверкой запуска контейнера, чтобы убедиться, что ссылка допустима)).

1 голос
/ 09 июня 2011

JSF изначально разрабатывался как среда MVC, а не как файловая служба REST.

Сервлет отлично подходит для этой работы.Поместите его с помощью @WebServlet, чтобы лучше почувствовать Java EE 6.

0 голосов
/ 31 марта 2012

На самом деле существует прямое решение этой проблемы с использованием PrettyFaces URLRewriteFilter -> http://ocpsoft.org/prettyfaces/serving-dynamic-file-content-with-prettyfaces/

В этом блоге объясняется, как делать именно то, что вы хотите, без необходимости использоватьсовершенно новый MVC-фреймворк.

...