Many people ask how to achieve file downloads from a portlet project.
I'm going to try putting this mystery to rest by first explaining the limitations and then showing some old and new ways of overcoming them in Liferay. To be clear on terminology we'll call anything, other than the usual presentation content, that we want to deliver to the user a resource. Examples of resources are: images, binary documents, css stylesheets, js files, and typically things that we want to dynamically generate access to users to download. They are also often generated on the fly, like a PDF report, or a captcha image.
To begin, the JSR-168/Portlet 1.0 spec did not have support for delivering anything other than a blob of text/html content. Nor did it support delivering such content outside of a response which included the portal's wrappings.
Here is a brief (and not all encompassing) recent history of resource downloads in Liferay.
Liferay <= 4.1.x: At this point the ONLY ways to provide download links were:
- provide a direct link to the resource
- delegate the functionality to a servlet
In Liferay's core this was very simple because we use Struts, so creating an action (and mapping) of type org.apache.struts.action.Action was relatively painless and we could quickly develop any download mechanism we needed. The only issue was that the URL could not be generated using the <portlet /> tag. A developer had to be aware of the path associated with the Action in order to access it.
For example, consider the following action-mapping from version 4.3.x's Document Library portlet:
<action path="/document_library/get_file" type="com.liferay.portlet.documentlibrary.action.GetFileAction" />
If you look at the code for com.liferay.portlet.documentlibrary.action.GetFileAction you will see that it implements a method:
public ActionForward strutsExecute( ActionMapping mapping, ActionForm form, HttpServletRequest req, HttpServletResponse res) throws Exception;
This method, though found in com.liferay.portal.struts.PortletAction, is simply a helper method which makes the PortletAction behave as a servlet by calling super.execute(mapping, form, req, res) directly.
In order to put this method to work we create an url as follows:
<a href="<%= themeDisplay.getPathMain() %>/document_library/get_file?folderId=<%= folderId %>&name=<%= HttpUtil.encodeURL(name) %>"> <%= fileEntry.getTitle() %> </a>
Notice that we haven't used any tag to generate the URL and we needed to include the portals context.
So, at this point if you were creating a portlet to be JSR-168 complient (outside of ext, or core), you could create an URL to a static resource like so (JSP example):
<a href="<%= renderRequest.getContextPath() %>/static_path/file_name.pdf"> file_name.pdf </a>
To create an URL to a dynamic resource, you would have to delegate to some servlet, like so:
<a href="<%= renderRequest.getContextPath() %>/servlet_path?p1=va&p2=v2"> <%= someLinkText %> </a>
Liferay <= 4.4.x and > 4.1.x: At this point we started to experiment with delivering portlet resources in a way similar to what was proposed in the upcoming JSR-286/Portlet 2.0 spec.
We had at our disposal a feature we had created which allowed us to deliver resources from portlets. It was used by specifying LiferayWindowState.EXCLUSIVE on a portlet URL. If using it from a actionURL it could deliver binary resources.
Example of returning a captcha image, from 4.3.x:
<portlet:actionURL windowState="<%= LiferayWindowState.EXCLUSIVE.toString() %>" var="captchaURL"> <portlet:param name="struts_action" value="/message_boards/captcha" /> </portlet:actionURL>
On a renderURL it could be used for handling things like Ajax requests.
<form action="<liferay-portlet:renderURL
windowState="<%= LiferayWindowState.EXCLUSIVE.toString() %>">
<portlet:param name="struts_action" value="/password_generator/view" />
</liferay-portlet:renderURL>"
method="post"
name="<portlet:namespace />fm"
onSubmit="AjaxUtil.submit(this, {update: this.parentNode}); return false;">
Unfortunately, this feature was only usable from within the EXT environment or from within the core for handling binary resources, because final handling of the response required accessing the original HttpServletResponse.
Here is the response handling side of the capcha example:
public void processAction(
ActionMapping mapping, ActionForm form, PortletConfig config,
ActionRequest req, ActionResponse res)
throws Exception {
try {
PortletSession ses = req.getPortletSession();
String captchaText = _producer.createText();
ses.setAttribute(WebKeys.CAPTCHA_TEXT, captchaText);
HttpServletResponse httpRes =
((ActionResponseImpl)res).getHttpServletResponse();
_producer.createImage(httpRes.getOutputStream(), captchaText);
setForward(req, ActionConstants.COMMON_NULL);
}
catch (Exception e) {
_log.error(e);
}
}
Solution for external portlets: In order to provide a feature which was usable from portlets outside of EXT & core, we created a interface available in portal-kernel.jar and made RenderResponseImpl implement this interface. The interface provide the following methods:
public interface LiferayRenderResponse extends RenderResponse {
public void addDateHeader(String name, long date);
public void addHeader(String name, String value);
public void addIntHeader(String name, int value);
public void setDateHeader(String name, long date);
public void setHeader(String name, String value);
public void setIntHeader(String name, int value);
public void setResourceName(String resourceName);
}
Additionally, we added support for changing the content type returned from RenderResponse, but ONLY if the WinowState was equal to LiferayWindowState.EXCLUISVE.
Here is an example which returns a png image:
public void doView(RenderRequest req, RenderResponse res)
throws IOException, PortletException {
boolean logo = ParamUtil.getBoolean(req, "logo");
if (logo && req.getWindowState().equals(LiferayWindowState.EXCLUSIVE)) {
LiferayRenderResponse liferayRes = (LiferayRenderResponse)res;
liferayRes.setContentType("image/png");
liferayRes.addHeader(
HttpHeaders.CACHE_CONTROL, "max-age=3600, must-revalidate");
OutputStream out = liferayRes.getPortletOutputStream();
InputStream in = // some InputStream
if (in == null) {
out.close();
}
else {
byte[] buffer = new byte[4096];
int len;
while ((len = in.read(buffer)) != -1) {
out.write(buffer, 0, len);
}
out.flush();
in.close();
out.close();
}
}
else {
include(viewJSP, req, res);
}
}
Notice that you can specify the contentType, set headers, and write binary/text data directly to the portlet's OutputStream. One note is that the content type MUST be set before attempting to call liferayRes.getPortletOutputStream(); otherwise an exception will be raised.
And the url looks like this:
<img src="<portlet:renderURL portletMode="view" windowState="<%= LiferayWindowState.EXCLUSIVE.toString() %>"> <portlet:param name="logo" value="true" /> </portlet:renderURL>" alt="Image returned by portlet." />
This was pretty handy for everything from XML and JSON AJAX data, to dynamically generated binary resources.
Liferay >= 5.0.0: In the new release of Liferay, while still supporting all of the methods described above, we also support the brand new (and as of yet still unofficial) JSR-286/Portlet 2.0 feature ResourceRequest which is specifically designed for handling any resource delivery purely as a function of a portal & portlet which are JSR-286 compliant. It can equally well handle everything from binary files to JSON data returned by an AJAX request.
An example usage looks like this:
public void serveResource(ResourceRequest req, ResourceResponse res)
throws PortletException, IOException {
boolean logo = ParamUtil.getBoolean(req, "logo");
if (logo) {
res.setContentType("image/png");
res.addProperty(
HttpHeaders.CACHE_CONTROL, "max-age=3600, must-revalidate");
OutputStream out = res.getPortletOutputStream();
InputStream in = // some InputStream
if (in == null) {
out.close();
}
else {
byte[] buffer = new byte[4096];
int len;
while ((len = in.read(buffer)) != -1) {
out.write(buffer, 0, len);
}
out.flush();
in.close();
out.close();
}
}
}
And the URL looks like this:
<img src="<portlet:resourceURL> <portlet:param name="logo" value="true" /> </portlet:resourceURL>" alt="Image returned by portlet." />
Well, there you have it.

