Forums de discussion

Problem generating a large ZIP file download on the fly

thumbnail
Koen Olaerts, modifié il y a 7 années.

Problem generating a large ZIP file download on the fly

New Member Publications: 19 Date d'inscription: 21/12/10 Publications récentes
Hello,

I am trying to provide download functionality in my portlet where the user selects multiple documents to download as one zip file. Because this zip file can become rather large, I wanted to use a ZipOutputStream so the download can already start while the zip file is being populated with all the selected documents.

I have added a Primefaces fileDownload component to my view.
<p:commandbutton ajax="false" value="#{i18n['download']}" styleclass="action">
	<p:filedownload value="#{documentListBean.downloadSelectedDocuments()}" />
</p:commandbutton>


In the backing bean method my files are being collected and added to the responseOutputStream.
try {
	FacesContext fc = FacesContext.getCurrentInstance();
	ExternalContext ec = fc.getExternalContext();
	ec.setResponseContentType("application/zip");
	ec.setResponseHeader("Content-disposition","attachment; filename=download.zip");
	OutputStream responseOutputStream = ec.getResponseOutputStream();

	try (ZipOutputStream zipOutputStream = new ZipOutputStream(responseOutputStream)) {
		for (Document document : selectedDocuments) {
			ZipEntry zipEntry = new ZipEntry(document.getName()+"."+document.getExtension());
			zipOutputStream.putNextEntry(zipEntry);

			FileEntry fileEntry = DLAppLocalServiceUtil.getFileEntry(document.getId());

			ByteStreams.copy(fileEntry.getContentStream(), zipOutputStream);

			zipOutputStream.closeEntry();
		}
	} catch (IOException e) {
		LOGGER.error("Exception occured!");
	}

	responseOutputStream.flush();
	responseOutputStream.close();
} catch (IOException e) {
	e.printStackTrace();
}


Everything seems to work neatly, but at the end an exception is being logged by the faces bridge (not by the catch in my code).
java.lang.IllegalStateException: Cannot obtain Writer because OutputStream is already in use
        at com.liferay.portlet.MimeResponseImpl.getWriter(MimeResponseImpl.java:92)
        at javax.portlet.filter.ResourceResponseWrapper.getWriter(ResourceResponseWrapper.java:117)
        at com.liferay.faces.bridge.context.internal.BridgeContextImpl.getResponseOutputWriter(BridgeContextImpl.java:1274)
        at com.liferay.faces.bridge.context.internal.ExternalContextCompat_2_0_Impl.getResponseOutputWriter(ExternalContextCompat_2_0_Impl.java:556)
        at com.sun.faces.application.view.FaceletViewHandlingStrategy.createResponseWriter(FaceletViewHandlingStrategy.java:1113)
        at com.sun.faces.application.view.FaceletViewHandlingStrategy.renderView(FaceletViewHandlingStrategy.java:399)
        at com.sun.faces.application.view.MultiViewHandler.renderView(MultiViewHandler.java:125)
        at javax.faces.application.ViewHandlerWrapper.renderView(ViewHandlerWrapper.java:286)
        at com.liferay.faces.bridge.application.internal.ViewHandlerCompatImpl.renderView(ViewHandlerCompatImpl.java:51)
        at com.sun.faces.lifecycle.RenderResponsePhase.execute(RenderResponsePhase.java:120)
        at com.sun.faces.lifecycle.Phase.doPhase(Phase.java:101)
        at com.sun.faces.lifecycle.LifecycleImpl.render(LifecycleImpl.java:139)
        at com.liferay.faces.bridge.internal.BridgePhaseResourceImpl.execute(BridgePhaseResourceImpl.java:275)
        at com.liferay.faces.bridge.internal.BridgeImpl.doFacesRequest(BridgeImpl.java:157)
        at javax.portlet.faces.GenericFacesPortlet.serveResource(GenericFacesPortlet.java:178)
        at com.liferay.portlet.FilterChainImpl.doFilter(FilterChainImpl.java:122)
        at com.liferay.portlet.ScriptDataPortletFilter.doFilter(ScriptDataPortletFilter.java:82)
        at com.liferay.portlet.FilterChainImpl.doFilter(FilterChainImpl.java:116)
        at com.liferay.portal.kernel.portlet.PortletFilterUtil.doFilter(PortletFilterUtil.java:71)
        at com.liferay.portal.kernel.servlet.PortletServlet.service(PortletServlet.java:112)
        at javax.servlet.http.HttpServlet.service(HttpServlet.java:728)
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:305)
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:210)
        at com.liferay.portal.kernel.servlet.filters.invoker.InvokerFilterChain.doFilter(InvokerFilterChain.java:116)
        at com.liferay.portal.kernel.servlet.filters.invoker.InvokerFilter.doFilter(InvokerFilter.java:119)
        ...
        at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:408)
        at org.apache.coyote.http11.AbstractHttp11Processor.process(AbstractHttp11Processor.java:1023)
        at org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:589)
        at org.apache.tomcat.util.net.JIoEndpoint$SocketProcessor.run(JIoEndpoint.java:312)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
        at java.lang.Thread.run(Thread.java:745)


Even when I try to download multiple times it continues to work correctly. Also the other functionality of the portlet just keeps on working, it's just the exception that gets printed every time the download is done.

After looking around I always see that I'm supposed to be using the ResourceHandler functionality that JSF provides. The problem I have with that approach is that a Resource only has a getInputStream() method and doesn't allow me to write directly to an OutputStream. I can see no good way, with storing the whole zip file either in a ByteBuffer or as a temporary file on disk, to convert my ZipOutputStream to an InputStream. Am I missing something here? I tried to use a CircularByteBuffer, but while this works, it was significantly slower (only a couple of Kb's per second) than the code above that seems to work (the full 22 Mb's in only a couple of seconds) but generates an exception in the logs.
thumbnail
Neil Griffin, modifié il y a 7 années.

RE: Problem generating a large ZIP file download on the fly

Liferay Legend Publications: 2655 Date d'inscription: 27/07/05 Publications récentes
Hi Koen,

What version of Liferay Portal and what versions of the Liferay Faces dependencies are you using in your PrimeFaces portlet?

Thanks,

Neil
thumbnail
Koen Olaerts, modifié il y a 7 années.

RE: Problem generating a large ZIP file download on the fly

New Member Publications: 19 Date d'inscription: 21/12/10 Publications récentes
Hello Neil,

I'm using Liferay 6.2 EE SP9 with Liferay Faces 3.2.5-ga6 and Primefaces 5.1.
thumbnail
Neil Griffin, modifié il y a 7 années.

RE: Problem generating a large ZIP file download on the fly

Liferay Legend Publications: 2655 Date d'inscription: 27/07/05 Publications récentes
Hi Koen,

In order for us to better support you, could you upgrade your Liferay Faces dependencies according to our new version scheme? You can determine the dependencies from the drop down list at the www.liferayfaces.org home page.

Thank you,

Neil
thumbnail
Koen Olaerts, modifié il y a 7 années.

RE: Problem generating a large ZIP file download on the fly

New Member Publications: 19 Date d'inscription: 21/12/10 Publications récentes
Hello Neil,

Thank you for the input. I will give this a try and come back to you with the results.