使用可收藏的URL为JSF 2 / CDI提供文件

时间:2011-06-08 17:58:26

标签: java binary jsf-2 cdi serving

我的主要问题是:使用带有CDI的JSF 2并使用可收藏的URL,是否有“良好实践”来提供二进制文件(PDF,文档等)?

我已阅读JSF 2 spec (JSR 314),我发现它存在“资源处理”段落。但它似乎只用于提供放在war或jar文件中的静态文件。通过注册一些特定的ResourceHandler,我真的不知道它是否存在一种交互方式...

实际上,我已经习惯了Seam的两种方式:使用getResource(HttpServletRequest, HttpServletResponse)方法和getResourcePath()扩展AbstractResource类,以声明<webapp>/seam/resource/网址后要投放的路径前缀并在SeamResourceServlet文件中声明web.xml

这就是我所做的。

我第一次看到How to download a file stored in a database with 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;
    }
}

在这里,我必须switch to Tomcat 7(我使用6)才能正确解释EL表达式。 @SessionScoped使用@RequestScoped<h:commandLink value="Download" action="#{containerAction.download(file.key)}" />没有(当我点击按钮时,没有任何事情发生)。

但后来我想使用链接而不是按钮。

我尝试了h:link,但它会生成一些丑陋的javascript链接(不可收藏)。

阅读JSF 2规范,似乎有一个“Bookmarkability”功能,但它并不清楚如何使用它。

实际上,它似乎只适用于视图,所以我尝试创建一个空视图并创建了一个<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

但是,我有一个h:outputLink

视图启动时的逻辑,它使用getWritter来初始化响应。

所以我创建了一个Servlet来完成工作并创建了以下<h:outputLink value="#{facesContext.externalContext.request.contextPath}/download/"> <h:outputText value="Download"/> <f:param name="key" value="#{file.key}"/> </h:outputLink>

{{1}}

但即使最后一种技术为我的文件提供了一个可收藏的URL,它也不是真正的“JFS 2”......

你有什么建议吗?

3 个答案:

答案 0 :(得分:2)

我同意BalusC。通常,应用程序不仅仅是JSF应用程序,而是Java EE应用程序。

在Java EE中处理http请求时,除了JSF视图之外还存在其他事情。在Java EE 6中,您的命名CDI bean也可以使用JAX-RS直接映射到路径。这是使用Servlet的替代方法。在这种情况下,您将使用@Produces和@Path(参见例如Input and Output binary streams using JERSEY?)。

另一方面,JSF中<f:viewParam>的一个优点是您可以轻松地将验证器附加到它。 Servlet和JAX-RS资源目前都不支持。

使用

<h:link>比使用<h:outputLink value="#{facesContext.externalContext.request.contextPath}/...">始终更舒服。但是,可以通过将此部件包装在Facelets标签或复合组件中来减轻这种情况。

(如果规范的未来版本在JSF中提供链接标记以直接链接到JAX-RS资源(使用可选的容器启动验证以确保链接合法),那将是很棒的。)< / em>的

答案 1 :(得分:1)

JSF从一开始就设计为MVC框架,而不是一些REST文件服务。

servlet非常适合这项工作。用@WebServlet注释它以获得更好的Java EE 6感觉。

答案 2 :(得分:0)

实际上使用PrettyFaces URLRewriteFilter - &gt;直接解决了这个问题。 http://ocpsoft.org/prettyfaces/serving-dynamic-file-content-with-prettyfaces/

此博客解释了如何完成您想要做的事情,而无需使用全新的MVC框架。