com.flexive.war.servlet.ThumbnailServlet.java Source code

Java tutorial

Introduction

Here is the source code for com.flexive.war.servlet.ThumbnailServlet.java

Source

/***************************************************************
 *  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.*;
import com.flexive.shared.configuration.SystemParameters;
import com.flexive.shared.content.FxContent;
import com.flexive.shared.content.FxPK;
import com.flexive.shared.exceptions.FxApplicationException;
import com.flexive.shared.exceptions.FxNoAccessException;
import com.flexive.shared.exceptions.FxStreamException;
import com.flexive.shared.media.FxMediaSelector;
import com.flexive.shared.stream.BinaryDownloadCallback;
import com.flexive.shared.stream.FxStreamUtils;
import com.flexive.shared.structure.FxAssignment;
import com.flexive.shared.structure.FxProperty;
import com.flexive.shared.structure.FxPropertyAssignment;
import com.flexive.shared.value.BinaryDescriptor;
import com.flexive.shared.value.FxBinary;
import com.flexive.shared.value.FxValue;
import com.flexive.war.FxThumbnailURIConfigurator;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

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;

/**
 * Thumbnail servlet.
 * Usage: <code>/thumbnail/[options][/filename.extension]</code>
 * <br/>
 * Options are optional but must at least contain a way to identify the thumbnail to display.
 * There can be multiple options seperated by a "/".
 * Options may not contain spaces and values have to be properly URL encoded!
 * <p/>
 * Valid options are:
 * <ul>
 * <li><code>pk{n.m|TYPE}</code>  - id and version of the content, if no version is given the live version is used, for
 * TYPE the default binary of the xpath's assignment is served if present</li>
 * <li><code>xp{path}</code>  - URL encoded XPath of the property containing the image (optional, else default will be used)</li>
 * <li><code>lang{lang}</code> - 2-digit ISO language code
 * <li><code>lfb{0,1}</code> - language fallback: 0=generate error if language not found, 1=fall back to default language
 * <li><code>e{3 digit error number}u{error url}</code> - URL encoded error url to redirect for http errors specified in {error number}, if no number is given then the url is a fallback for unclassified errors
 * <li><code>s{0,1,2,3}</code>  - use a predefined image/thumbnail size</li>
 * <li><code>w{n}</code> - scale to width</li>
 * <li><code>h{n}</code> - scale to height</li>
 * <li><code>rot{90,180,270}</code> - rotate 90, 180 or 270 degrees (rotate is always executed before flip operations)</li>
 * <li><code>flip{h,v}</code> - flip horizontal or vertical (rotate is always executed before flip operations)</li>
 * <li><code>cropx{x}y{y}w{w}h{h}</code> - crop a box from image defined by x,y,w(idth),h(eight), scaling applies to cropped image!</li>
 * </ul>
 * <p/>
 * Optional query parameters:
 * <ul>
 * <li><code>hintBinaryId={id}</code> - explicitly select the binary ID from the content (same as the {@link DownloadServlet} parameter)</li>
 * </ul>
 * <p/>
 *
 * Sizes for the <code>s</code> parameters:
 * 0 ... original image
 * 1 ... image scaled to fit a 42x42 box
 * 2 ... image scaled to fit a 85x85 box
 * 3 ... image scaled to fit a 232x232 box
 * Selecting a predefined image or thumbnail disables all image manipultion parameters: the image will be served exactly how it
 * exists in the database/storage.
 * <p/>
 * <br/>
 * Examples:
 * <code>/thumbnail/pk27.1/s2/test.jpg</code>
 * <code>/thumbnail/s0/w100/h300/pk4711.MAX/vermax/rot90/test.jpg</code>
 * <p/>
 *
 * @author Daniel Lichtenberger (daniel.lichtenberger@flexive.com), UCS - unique computing solutions gmbh (http://www.ucs.at)
 * @author Markus Plesser (markus.plesser@flexive.com), UCS - unique computing solutions gmbh (http://www.ucs.at)
 * @version $Rev$
 */
public class ThumbnailServlet implements Servlet {

    private static final Log LOG = LogFactory.getLog(ThumbnailServlet.class);
    /** Key for caching the thumbnail servlet mapping within the request */
    private static final String REQ_MAPPING = "_ThumbnailServlet_mapping";

    private ServletConfig servletConfig = null;

    /**
     * Callback to set mimetype and size
     */
    private static class ThumbnailBinaryCallback implements BinaryDownloadCallback {

        /**
         * current response
         */
        private HttpServletResponse response;

        /**
         * Constructor
         *
         * @param response current response
         */
        public ThumbnailBinaryCallback(HttpServletResponse response) {
            this.response = response;
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public void setMimeType(String mimeType) {
            response.setContentType(mimeType);
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public void setBinarySize(int size) {
            response.setContentLength(size);
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void init(ServletConfig servletConfig) throws ServletException {
        this.servletConfig = servletConfig;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public ServletConfig getServletConfig() {
        return servletConfig;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void service(ServletRequest servletRequest, ServletResponse servletResponse)
            throws ServletException, IOException {
        final HttpServletRequest request = (HttpServletRequest) servletRequest;
        final HttpServletResponse response = (HttpServletResponse) servletResponse;
        final String uri = request.getRequestURI();
        FxThumbnailURIConfigurator conf = new FxThumbnailURIConfigurator(URLDecoder.decode(uri, "UTF-8"));
        if (conf.getPK() == null && !conf.isUseType()) {
            if (LOG.isDebugEnabled()) {
                LOG.debug("Empty request for thumbnail servlet: " + uri);
            }
            response.sendError(HttpServletResponse.SC_NOT_FOUND);
            return;
        }
        long binaryId;
        try {
            if (conf.isUseType()) {
                if (conf.getXPath().indexOf('/') == -1) {
                    //probably a property
                    FxProperty prop = CacheAdmin.getFilteredEnvironment().getProperty(conf.getXPath());
                    binaryId = getStructureBinaryId(conf, prop.getDefaultValue());
                } else {
                    FxAssignment as = CacheAdmin.getFilteredEnvironment().getAssignment(conf.getXPath());
                    if (as instanceof FxPropertyAssignment) {
                        FxPropertyAssignment pa = (FxPropertyAssignment) as;
                        binaryId = getStructureBinaryId(conf, pa.getDefaultValue());
                    } else
                        binaryId = BinaryDescriptor.SYS_UNKNOWN;
                }
            } else {
                final String hintBinaryParam = request.getParameter("hintBinaryId");
                final long hintBinaryId = StringUtils.isNumeric(hintBinaryParam) ? Long.parseLong(hintBinaryParam)
                        : -1;
                if (hintBinaryId != -1) {
                    // load the content to check if the binary belongs to that content (version)
                    final FxContent content = EJBLookup.getContentEngine().load(conf.getPK());
                    final BinaryDescriptor binaryDescriptor = DownloadServlet.findBinaryDescriptor(content,
                            hintBinaryId);
                    if (binaryDescriptor == null) {
                        LOG.warn("Invalid hintBinaryId for thumbnail of " + conf.getPK() + ": " + hintBinaryId);
                        response.sendError(HttpServletResponse.SC_NOT_FOUND);
                    }

                    binaryId = hintBinaryId;
                } else {
                    //authorization check and binary lookup
                    binaryId = EJBLookup.getContentEngine().getBinaryId(conf.getPK(),
                            XPathElement.stripType(conf.getXPath()), conf.getLanguage(), conf.useLangFallback());
                }
            }
        } catch (FxNoAccessException na) {
            binaryId = BinaryDescriptor.SYS_NOACCESS;
        } catch (FxApplicationException e) {
            binaryId = BinaryDescriptor.SYS_UNKNOWN;
        }
        try {
            response.setDateHeader("Expires", System.currentTimeMillis() + 24L * 3600 * 1000);
            response.setDateHeader("Last-Modified", System.currentTimeMillis());
            response.setHeader("Cache-Control", "private");
            response.setHeader("Pragma", "cache");
            FxStreamUtils.downloadBinary(new ThumbnailBinaryCallback(response), CacheAdmin.getStreamServers(),
                    response.getOutputStream(), binaryId, conf);
        } catch (FxStreamException e) {
            LOG.error(e);
            throw new ServletException(e);
        }
    }

    /**
     * Get the binary id of a structure element (property or property assignment)
     *
     * @param conf FxThumbnailURIConfigurator
     * @param val  FxValue containing the value
     * @return binary id
     * @throws FxApplicationException on errors
     */
    private long getStructureBinaryId(FxThumbnailURIConfigurator conf, FxValue val) throws FxApplicationException {
        long binaryId;
        if (val instanceof FxBinary) {
            if (conf.getLanguage() != null)
                binaryId = ((FxBinary) val).getTranslation(conf.getLanguage().getId()).getId();
            else
                binaryId = ((FxBinary) val).getBestTranslation().getId();
        } else
            binaryId = BinaryDescriptor.SYS_UNKNOWN;
        return binaryId;
    }

    /**
     * Return a thumbnail link for the given object.
     *
     * @param pk   the object id
     * @param size the preview size (if null, the default size is used by the servlet)
     * @return a thumbnail link for the given object.
     */
    public static String getLink(FxPK pk, BinaryDescriptor.PreviewSizes size) {
        return getLink(pk, size, null);
    }

    /**
     * Return a thumbnail link for the given object.
     *
     * @param pk    the object id
     * @param size  the preview size (if null, the default size is used by the servlet)
     * @param xpath the binary xpath (if null, the default preview for the object will be used)
     * @return a thumbnail link for the given object.
     */
    public static String getLink(FxPK pk, BinaryDescriptor.PreviewSizes size, String xpath) {
        return getLink(pk, size, xpath, 0);
    }

    /**
     * Return a thumbnail link for the given object.
     *
     * @param pk        the object id
     * @param size      the preview size (if null, the default size is used by the servlet)
     * @param xpath     the binary xpath (if null, the default preview for the object will be used)
     * @param timestamp the binary timestamp, to be added to the URL for fine-grained cache control
     * @return a thumbnail link for the given object.
     */
    public static String getLink(FxPK pk, BinaryDescriptor.PreviewSizes size, String xpath, long timestamp) {
        return getLink(pk, size, xpath, timestamp, null);
    }

    /**
     * Return a thumbnail link for the given object.
     *
     * @param pk        the object id or <code>null</code> if a type's assignment default binary is requested
     * @param size      the preview size (if null, the default size is used by the servlet)
     * @param xpath     the binary xpath (if null, the default preview for the object will be used)
     * @param timestamp the binary timestamp, to be added to the URL for fine-grained cache control
     * @param language  the language (for multi-lingual objects, otherwise the default language will be used)
     * @return a thumbnail link for the given object.
     */
    public static String getLink(FxPK pk, BinaryDescriptor.PreviewSizes size, String xpath, long timestamp,
            FxLanguage language) {
        try {
            return getMappingURI() + "pk" + (pk == null ? "TYPE" : pk)
                    + (size != null ? "/s" + size.getBlobIndex() : "")
                    + (StringUtils.isNotBlank(xpath)
                            ? "/xp" + URLEncoder.encode(FxSharedUtils.escapeXPath(xpath), "UTF-8")
                            : "")
                    + "/ts" + timestamp + (language != null ? "/lang" + language.getIso2digit() : "");
        } catch (UnsupportedEncodingException e) {
            // shouldn't happen with UTF-8
            throw new IllegalArgumentException(e);
        }
    }

    /**
     * Request a thumbnail link for the FxMediaSelector's settings
     *
     * @param selector FxMediaSelector containing requested settings
     * @return a thumbnail link for the given selector
     */
    public static String getLink(FxMediaSelector selector) {
        try {
            StringBuilder sb = new StringBuilder(100);
            sb.append(getMappingURI()).append("pk").append(selector.getPK()).append("/s")
                    .append(selector.getSize().getBlobIndex());
            if (StringUtils.isNotBlank(selector.getXPath()))
                sb.append("/xp").append(URLEncoder.encode(FxSharedUtils.escapeXPath(selector.getXPath()), "UTF-8"));
            if (selector.getLanguageIso() != null)
                sb.append("/lang").append(selector.getLanguageIso());
            if (selector.isScaleHeight())
                sb.append("/h").append(selector.getScaleHeight());
            if (selector.isScaleWidth())
                sb.append("/w").append(selector.getScaleWidth());
            if (selector.hasTimestamp())
                sb.append("/ts").append(selector.getTimestamp());
            if (selector.hasFilename())
                sb.append("/").append(selector.getFilename());
            return sb.toString();
        } catch (UnsupportedEncodingException e) {
            // shouldn't happen with UTF-8
            throw new IllegalArgumentException(e);
        }
    }

    private static String getMappingURI() {
        final FxContext ctx = FxContext.get();
        if (ctx.getAttribute(REQ_MAPPING) == null) {
            try {
                ctx.setAttribute(REQ_MAPPING,
                        EJBLookup.getConfigurationEngine().get(SystemParameters.THUMBNAIL_MAPPING));
            } catch (FxApplicationException e) {
                throw e.asRuntimeException();
            }
        }
        return (String) ctx.getAttribute(REQ_MAPPING);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public String getServletInfo() {
        return getClass().getName();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void destroy() {

    }
}