Java tutorial
/*************************************************************** * This file is part of the [fleXive](R) framework. * * Copyright (c) 1999-2014 * UCS - unique computing solutions gmbh (http://www.ucs.at) * All rights reserved * * The [fleXive](R) project is free software; you can redistribute * it and/or modify it under the terms of the GNU Lesser General Public * License version 2.1 or higher as published by the Free Software Foundation. * * The GNU Lesser General Public License can be found at * http://www.gnu.org/licenses/lgpl.html. * A copy is found in the textfile LGPL.txt and important notices to the * license from the author are found in LICENSE.txt distributed with * these libraries. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * For further information about UCS - unique computing solutions gmbh, * please see the company website: http://www.ucs.at * * For further information about [fleXive](R), please see the * project website: http://www.flexive.org * * * This copyright notice MUST APPEAR in all copies of the file! ***************************************************************/ package com.flexive.war.servlet; import com.flexive.shared.CacheAdmin; import com.flexive.shared.EJBLookup; import com.flexive.shared.FxSharedUtils; import com.flexive.shared.XPathElement; import com.flexive.shared.content.FxContent; import com.flexive.shared.content.FxPK; import com.flexive.shared.exceptions.FxApplicationException; import com.flexive.shared.structure.FxType; import com.flexive.shared.tree.FxTreeMode; import com.flexive.shared.tree.FxTreeNode; import com.flexive.shared.value.BinaryDescriptor; import com.flexive.shared.value.FxBinary; import com.flexive.shared.value.FxValue; import org.apache.commons.lang.StringUtils; import javax.servlet.*; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.io.UnsupportedEncodingException; import java.net.URLDecoder; import java.net.URLEncoder; /** * <p>Provides streaming downloads for all binary objects ({@link com.flexive.shared.value.FxBinary FxBinary}). * The requested value is identified by its XPath or by a unique tree path.</p> * <h4>Link format:</h4> * <pre>/download/pk{n.m}/xpath/filename.ext</pre> * <pre>/download/tree/[edit,live]/fqn-path</pre> * <p> * When no XPath is provided, the first mandatory binary property of the instance's type is chosen. * </p> * <h4>URL Parameters:</h4> * Optional parameters can be appended to the requested path (e.g. {@code ?param=value¶m2}): * <ul> * <li><strong>inline=true</strong> to download the content "inline" (i.e. skip the attachment response header) * <li><strong>hintBinaryId=[id]</strong> hint at the expected binary ID. Used from UI content editors that may change * the order of binaries (the binary will only be returned if it's actually present in the content, to preserve security)</li> * </ul> * * * @author Daniel Lichtenberger (daniel.lichtenberger@flexive.com), UCS - unique computing solutions gmbh (http://www.ucs.at) * @version $Rev$ */ public class DownloadServlet implements Servlet { public final static String BASEURL = "/download/"; private ServletConfig servletConfig; @Override public void init(ServletConfig servletConfig) throws ServletException { this.servletConfig = servletConfig; } @Override public ServletConfig getServletConfig() { return servletConfig; } @Override public String getServletInfo() { return this.getClass().getName(); } @Override public void destroy() { // nothing to do } @Override public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException { HttpServletRequest request = (HttpServletRequest) servletRequest; HttpServletResponse response = (HttpServletResponse) servletResponse; String uri = FxServletUtils.stripSessionId(URLDecoder.decode( request.getRequestURI().substring(request.getContextPath().length() + BASEURL.length()), "UTF-8")); final FxPK pk; String xpath = null; if (uri.startsWith("tree")) { // get PK via tree path final String[] parts = StringUtils.split(uri, "/", 3); if (parts.length != 3 || !"tree".equals(parts[0])) { FxServletUtils.sendErrorMessage(response, "Invalid download request: " + uri); return; } // get tree node by FQN path final FxTreeMode treeMode = "edit".equals(parts[1]) ? FxTreeMode.Edit : FxTreeMode.Live; final long nodeId; try { nodeId = EJBLookup.getTreeEngine().getIdByFQNPath(treeMode, FxTreeNode.ROOT_NODE, "/" + parts[2]); } catch (FxApplicationException e) { FxServletUtils.sendErrorMessage(response, "Failed to resolve file path: " + e.getMessage()); return; } if (nodeId == -1) { response.setStatus(HttpServletResponse.SC_NOT_FOUND); return; } // use content associated with tree node try { pk = EJBLookup.getTreeEngine().getNode(treeMode, nodeId).getReference(); } catch (FxApplicationException e) { FxServletUtils.sendErrorMessage(response, "Failed to load tree node " + nodeId + ": " + e.getMessage()); return; } } else { // get PK if (!uri.startsWith("pk")) { FxServletUtils.sendErrorMessage(response, "Invalid download request: " + uri); return; } try { pk = FxPK.fromString(uri.substring(2, uri.indexOf('/'))); } catch (IllegalArgumentException e) { FxServletUtils.sendErrorMessage(response, "Invalid primary key in download request: " + uri); return; } // extract xpath try { xpath = FxSharedUtils.decodeXPath(uri.substring(uri.indexOf('/') + 1, uri.lastIndexOf('/'))); } catch (IndexOutOfBoundsException e) { // no XPath provided, use default binary XPath } } // load content final FxContent content; try { content = EJBLookup.getContentEngine().load(pk); } catch (FxApplicationException e) { FxServletUtils.sendErrorMessage(response, "Failed to load content: " + e.getMessage()); return; } if (xpath == null) { // get default binary XPath from type final FxType type = CacheAdmin.getEnvironment().getType(content.getTypeId()); if (type.getMainBinaryAssignment() != null) { xpath = type.getMainBinaryAssignment().getXPath(); } else { FxServletUtils.sendErrorMessage(response, "Invalid xpath/filename in download request: " + uri); return; } } final String binaryIdParam = request.getParameter("hintBinaryId"); final long hintBinaryId = StringUtils.isNumeric(binaryIdParam) ? Long.parseLong(binaryIdParam) : -1; // get binary descriptor final BinaryDescriptor descriptor; if (hintBinaryId != -1) { final BinaryDescriptor foundDescriptor = findBinaryDescriptor(content, hintBinaryId); if (foundDescriptor == null) { FxServletUtils.sendErrorMessage(response, "Expected binary ID not present in content" + pk + ": " + hintBinaryId); return; } descriptor = foundDescriptor; } else { try { descriptor = (BinaryDescriptor) content.getValue(xpath).getBestTranslation(); } catch (Exception e) { FxServletUtils.sendErrorMessage(response, "Failed to load binary value: " + e.getMessage()); return; } } // stream content try { response.setContentType(descriptor.getMimeType()); response.setContentLength((int) descriptor.getSize()); if (request.getParameter("inline") == null || "false".equals(request.getParameter("inline"))) { response.setHeader("Content-Disposition", "attachment; filename=\"" + descriptor.getName() + "\";"); } descriptor.download(response.getOutputStream()); } catch (Exception e) { FxServletUtils.sendErrorMessage(response, "Download failed: " + e.getMessage()); //noinspection UnnecessaryReturnStatement return; } finally { response.getOutputStream().close(); } } /** * Returns a link (absolute to the server context) to download the binary stored under * the given XPath for the given object. <code>fileName</code> is the filename visible to * the browser, the actual name of the downloaded file is determined by the stored filename. * * @param pk the object pk * @param xpath the XPath of the binary property to be downloaded * @param fileName the filename visible to the browser * @return a link (absolute to the server context) to download the given binary */ public static String getLink(FxPK pk, String xpath, String fileName) { return getLink(null, pk, xpath, fileName); } /** * Returns a link (absolute to the server context) to download the binary stored under * the given XPath for the given object. <code>fileName</code> is the filename visible to * the browser, the actual name of the downloaded file is determined by the stored filename. * * @param downloadServletPath a custom download servlet path * @param pk the object pk * @param xpath the XPath of the binary property to be downloaded * @param fileName the filename visible to the browser * @return a link (absolute to the server context) to download the given binary */ public static String getLink(String downloadServletPath, FxPK pk, String xpath, String fileName) { try { if (StringUtils.isEmpty(downloadServletPath)) { downloadServletPath = BASEURL; } return downloadServletPath + "pk" + pk + "/" + URLEncoder.encode(FxSharedUtils.escapeXPath(xpath), "UTF-8") + "/" + fileName; } catch (UnsupportedEncodingException e) { // shouldn't happen with UTF-8 throw new IllegalArgumentException(e); } } /** * Returns a link (absolute to the server context) to download the binary stored under * the given XPath. <code>fileName</code> is the filename visible to * the browser, the actual name of the downloaded file is determined by the stored filename. * Note: XPath must contain pk. * * @param downloadServletPath a custom download servlet path. If not set the default * {@link com.flexive.war.servlet.DownloadServlet#BASEURL} will be used. * @param fullXPath a full XPath (i.e. including the pk of a stored content instance) * @param filename the filename visible to the browser * @return a link (absolute to the server context) to download the binary stored under * the given XPath. */ public static String getLink(String downloadServletPath, String fullXPath, String filename) { return getLink(downloadServletPath, XPathElement.getPK(fullXPath), XPathElement.stripType(fullXPath), filename); } static BinaryDescriptor findBinaryDescriptor(FxContent content, long hintBinaryId) { for (String xp : content.getAllPropertyXPaths()) { final FxValue value = content.getValue(xp); if (value instanceof FxBinary) { final BinaryDescriptor val = (BinaryDescriptor) value.getBestTranslation(); if (val.getId() == hintBinaryId) { return val; } } } return null; } }