私の主な質問は、JSF 2とCDIを使用し、ブックマーク可能なURLを使用してバイナリファイル(PDF、ドキュメントなど)を提供するための「グッドプラクティス」はありますか?
JSF 2仕様(JSR 314)を読みましたが、「リソース処理」の段落が存在することがわかりました。ただし、warファイルまたはjarファイルに配置された静的ファイルを提供するためにのみ使用されているようです。特定のResourceHandlerを登録することによってここで対話する方法が存在するかどうかは本当にわかりませんでした...
実際、私はSeamの2つの方法に慣れていました。それは、メソッドを使用してAbstractResource
クラスを拡張し、 URLプレフィックスの後に提供するパスを宣言し、ファイルでを宣言することです。getResource(HttpServletRequest, HttpServletResponse)
getResourcePath()
<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;
}
}
ここでは、そのEL式を正しく解釈するために、Tomcat 7(6を使用していました)に切り替える必要がありました。動作し@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が得られたとしても、実際には「JFS2」ではありません...
アドバイスはありますか?