All Downloads are FREE. Search and download functionalities are using the official Maven repository.

net.anotheria.anosite.photoserver.presentation.delivery.DeliveryServlet Maven / Gradle / Ivy

package net.anotheria.anosite.photoserver.presentation.delivery;

import net.anotheria.anoplass.api.APIFinder;
import net.anotheria.anosite.photoserver.api.access.AccessAPI;
import net.anotheria.anosite.photoserver.api.access.AccessParameter;
import net.anotheria.anosite.photoserver.api.photo.PhotoAO;
import net.anotheria.anosite.photoserver.api.photo.PhotoAPI;
import net.anotheria.anosite.photoserver.api.photo.PhotoAPIConfig;
import net.anotheria.anosite.photoserver.api.photo.PhotoAPIException;
import net.anotheria.anosite.photoserver.api.photo.PhotoNotFoundPhotoAPIException;
import net.anotheria.anosite.photoserver.api.upload.PhotoUploadAPIConfig;
import net.anotheria.anosite.photoserver.presentation.shared.BaseServlet;
import net.anotheria.anosite.photoserver.presentation.shared.PhotoDimension;
import net.anotheria.anosite.photoserver.presentation.shared.PhotoUtil;
import net.anotheria.anosite.photoserver.shared.IdCrypter;
import net.anotheria.anosite.photoserver.shared.vo.PreviewSettingsVO;
import net.anotheria.util.StringUtils;
import net.anotheria.util.concurrency.IdBasedLock;
import net.anotheria.util.concurrency.IdBasedLockManager;
import net.anotheria.util.concurrency.SafeIdBasedLockManager;
import net.anotheria.webutils.util.CookieUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;

/**
 * Servlet for delivering photos by request.
 */
public class DeliveryServlet extends BaseServlet {

	/**
	 * Full access cookie name.
	 */
	public static final String FULL_ACCESS_COOKIE_NAME = "ps_fa";
	/**
	 * {@link Logger} instance.
	 */
	private static final Logger LOGGER = LoggerFactory.getLogger(DeliveryServlet.class);

	// Parameter names and other constants
	/**
	 * Basic serialVersionUID variable.
	 */
	private static final long serialVersionUID = -3250585990874211552L;
	/**
	 * Internal server error message.
	 */
	private static final String INTERNAL_SERVER_ERROR_MSG = "Internal server error";
	/**
	 * Request parameter name for preview flag.
	 */
	private static final String PARAM_PREVIEW = "preview";
	/**
	 * Request parameter name for blur flag.
	 */
	private static final String PARAM_BLUR = "blur";
	/**
	 * Parameter name for access token.
	 */
	private static final String PARAM_ACCESS_TOKEN = "at";
    /**
     * Parameter name for cropping type.
     */
    private static final String PARAM_CROPPING_TYPE = "ct";
    /**
     * Parameter name for source.
     */
    private static final String PARAM_SOURCE= "s";

	// APIs
	/**
	 * {@link AccessAPI} instance.
	 */
	private static final AccessAPI ACCESS_API = APIFinder.findAPI(AccessAPI.class);
	/**
	 * {@link PhotoAPI} instance.
	 */
	private static final PhotoAPI photoAPI = APIFinder.findAPI(PhotoAPI.class);
	/**
	 * {@link PhotoAPIConfig} instance.
	 */
	private static final PhotoAPIConfig photoAPIConfig = PhotoAPIConfig.getInstance();
	/**
	 * {@link PhotoUploadAPIConfig} instance.
	 */
	private static final PhotoUploadAPIConfig photoUploadAPIConfig = PhotoUploadAPIConfig.getInstance();
	/**
	 * {@link IdBasedLockManager} instance.
	 */
	private static final IdBasedLockManager LOCK_MANAGER = new SafeIdBasedLockManager();

	/**
	 * Sets appropriate HTTP status and other response info if photo can't be found.
	 *
	 * @param response
	 * 		{@link HttpServletResponse}
	 */
	private static void responseSetNotFound(final HttpServletResponse response) {
		response.setStatus(HttpServletResponse.SC_MOVED_TEMPORARILY);
		response.setHeader("Location", DeliveryConfig.getInstance().getPhotoNotFoundLink());
	}

	/**
	 * Writes debug messages.
	 *
	 * @param messages
	 * 		messages to be written to log
	 */
	private static void debug(String... messages) {
		if (messages == null || messages.length == 0)
			return;

		if (LOGGER.isDebugEnabled())
			for (String message : messages)
				LOGGER.debug(message);
	}

	@Override
	protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
		String[] params = parsePathParameters(req);
		debug("Params[" + Arrays.toString(params) + "].");

		// checking encoded photo id parameter
		if (params == null || params.length < 1 || StringUtils.isEmpty(params[0])) {
			String message = "Wrong parameters[" + Arrays.toString(params) + "].";
			LOGGER.info("doGet(req, resp) fail. " + message);
			responseSetNotFound(resp);
			return;
		}

		final String rawPhotoId = params[0];

		long photoId;
		try {
			photoId = IdCrypter.decodeToLong(rawPhotoId); // decode encoded photo id
		} catch (RuntimeException re) {
			String message = "Wrong photo id[" + rawPhotoId + "] parameter.";
			LOGGER.info("doGet(req, resp) fail. " + message);
			responseSetNotFound(resp);
			return;
		}

		int size = -1;
		// getting size for requested photo
		if (params.length > 1) {
			try {
				size = Integer.parseInt(params[1]);
			} catch (NumberFormatException nfe) {
				String message = "Wrong size[" + params[1] + "] parameter.";
				LOGGER.info("doGet(req, resp) fail. " + message);

				responseSetNotFound(resp);
				return;
			}
			// check is requested size is allowed
			if (!photoAPIConfig.isIgnoreAllowedSizes() && !photoAPIConfig.isAllowedSize(size)) {
				LOGGER.info("doGet(req, resp) fail. " + "Requested size[" + params[1] + "] not allowed.");
				responseSetNotFound(resp);
				return;
			}
		}


		PhotoAO photo = getPhoto(photoId);

		if (photo == null) {
			responseSetNotFound(resp);
			return;
		}

        int croppingType = DeliveryConfig.getInstance().getCroppingType().getValue();
        if (! StringUtils.isEmpty(req.getParameter(PARAM_CROPPING_TYPE))){
            try {
                croppingType = Integer.parseInt(req.getParameter(PARAM_CROPPING_TYPE));

                if(CroppingType.valueOf(croppingType)==null){
                    responseSetNotFound(resp);
                    return;
                }
            } catch (NumberFormatException e){
                responseSetNotFound(resp);
                return;
            }
        }

		boolean cropped = req.getParameter(PARAM_PREVIEW) != null;
		boolean resized = size != -1;
		boolean blurred = req.getParameter(PARAM_BLUR) != null || photo.isBlurred();

        Map optionalParameters = new HashMap();
        optionalParameters.put(AccessParameter.SOURCE, req.getParameter(PARAM_SOURCE));
        optionalParameters.put(AccessParameter.BLUR, req.getParameter(PARAM_BLUR));
        optionalParameters.put(AccessParameter.PREVIEW, req.getParameter(PARAM_PREVIEW));
        optionalParameters.put(AccessParameter.ACCESS_TOKEN, req.getParameter(PARAM_ACCESS_TOKEN));
        optionalParameters.put(AccessParameter.ACCESS_COOKIE_NAME, req.getParameter(FULL_ACCESS_COOKIE_NAME));

        // get full access cookie value
        String fullAccessCookieValue = DeliveryConfig.getInstance().getRestrictionBypassCookie();
        // check whether current user has full access granted by cookie
        if (fullAccessCookieValue == null || ! fullAccessCookieValue.equals(CookieUtil.getCookieValue(req, FULL_ACCESS_COOKIE_NAME)))
            switch (ACCESS_API.isViewAllowed(photo, optionalParameters)){
                case VIEW_ALLOWED:
                    break;
                case BLURRED_VIEW_ALLOWED:
                    blurred = true;
                    break;
                case VIEW_DENIED: // same as default
                default:
                    resp.setStatus(HttpServletResponse.SC_FORBIDDEN);
                    return;
            }

		try {
			// check is delivering for original photos enabled
			if (!cropped && !DeliveryConfig.getInstance().isOriginalPhotosAccessible()) {
				debug("Delivering of original photo is denied. Original photo id: " + photo.getId() + ", path: " + photo.getFilePath());
				responseSetNotFound(resp);
				return;
			}

			// delivering original photo
			if (!cropped && !resized && !blurred) {
				debug("Returning original photo: " + photo.getFilePath());

				File photoFile = new File(photo.getFilePath());
				writeJpegHeaders(resp);
				streamImageFile(resp, photoFile);
				return;
			}

			// preparing cached photo postfix
			String cachedFile = photo.getFilePath();
			cachedFile += cropped ? "_c_t" + croppingType : "";
			cachedFile += resized ? "_s" + size : "";
			cachedFile += blurred ? "_b" : "";

			// checking cached photo and steaming it if exist
			if (streamPhoto(cachedFile, resp))
				return;


			// locking all incoming photo modification requests for same picture
			IdBasedLock lock = LOCK_MANAGER.obtainLock(cachedFile);
			lock.lock();
			try {
				// checking again cached photo and steaming it if exist
				if (streamPhoto(cachedFile, resp))
					return;

				// modifying photo and storing to new photo file
				modifyPhoto(photo.getFilePath(), cachedFile, cropped, photo.getPreviewSettings(), blurred, resized, size, croppingType);
			} finally {
				lock.unlock();
			}

			// streaming cached file
			streamPhoto(cachedFile, resp);
		} catch (Exception e) {
			String message = "doGet(req, resp) fail. " + INTERNAL_SERVER_ERROR_MSG;
			LOGGER.warn(message, e);
			responseSetNotFound(resp);
		}
	}

	/**
	 * Stream photo file if file exist.
	 *
	 * @param photoPath
	 * 		- full photo file path
	 * @param resp
	 * 		- response
	 * @return true if photo steamed or false
	 * @throws java.io.IOException
	 */
	private boolean streamPhoto(final String photoPath, final HttpServletResponse resp) throws IOException {
		File cachedPhoto = new File(photoPath);
		if (cachedPhoto.exists() && cachedPhoto.length() > 0) {
			LOGGER.debug("Returning cached photo: " + photoPath);
			writeJpegHeaders(resp);
			streamImageFile(resp, cachedPhoto);
			return true;
		}

		return false;
	}

	/**
	 * Modify photo with some settings and store it to some file.
	 *
	 * @param photoPath
	 * 		- full photo file path
	 * @param resultPhotoPath
	 * 		- full result photo file path
	 * @param croped
	 * 		- is need crop
	 * @param pvSettings
	 * 		- crop settings
	 * @param blurred
	 * 		- is blur
	 * @param resized
	 * 		- is resize
	 * @param size
	 * 		- size
     * 	@param croppingType
     * 		- value of {@link CroppingType} enum
	 * @throws java.io.IOException
	 */
	private void modifyPhoto(final String photoPath, final String resultPhotoPath, final boolean croped, final PreviewSettingsVO pvSettings,
							 final boolean blurred, final boolean resized, final int size, final int croppingType) throws IOException {
		debug("Changing original photo: " + photoPath);

		// read photo file
		PhotoUtil putil = new PhotoUtil();
		putil.read(new File(photoPath));

		// if preview param is present we have to crop image first
		if (croped) {
			PhotoDimension move = new PhotoDimension(pvSettings.getX(), pvSettings.getY());
			PhotoDimension crop = new PhotoDimension(pvSettings.getWidth(), pvSettings.getHeight());
			PhotoDimension originalDimension = new PhotoDimension(putil.getWidth(), putil.getHeight());
			PhotoDimension workbenchDimension;
			if (putil.getHeight() > putil.getWidth()) {
				workbenchDimension = new PhotoDimension(photoUploadAPIConfig.getWorkbenchWidth() * putil.getWidth() / putil.getHeight(),
						photoUploadAPIConfig.getWorkbenchWidth());
			} else {
				workbenchDimension = new PhotoDimension(photoUploadAPIConfig.getWorkbenchWidth(), photoUploadAPIConfig.getWorkbenchWidth() * putil.getHeight()
						/ putil.getWidth());
			}
			PhotoDimension xy = move.getRelationTo(workbenchDimension, originalDimension);
			PhotoDimension wh = crop.getRelationTo(workbenchDimension, originalDimension);

			debug("originalDimension:" + originalDimension);
			debug("workbenchDimension:" + workbenchDimension);
			debug("move:" + move);
			debug("crop:" + crop);
			debug("Crop:" + xy.w + "," + xy.h + "," + wh.w + "," + wh.h);

			putil.crop(xy.w, xy.h, wh.w, wh.h);
		}

		// scale photo if needed
		if (resized){
            int height = putil.getHeight();
            int width = putil.getWidth();

            switch (CroppingType.valueOf(croppingType)){
                case HEIGHT:
                    putil.scale((int)((double)size/height*width), size);
                    break;
                case NATURAL_HEIGHT:
                    height = heightsize&&height>size)
                        putil.scale(size);
            }
        }

		// if blur param is present or photo should be blurred for user we have to blur image
		if (blurred)
			putil.blur();


		// caching changed photo
		debug("Storing changed photo: " + resultPhotoPath);
		File cachedPhoto = new File(resultPhotoPath);
		putil.write(photoAPIConfig.getJpegQuality(), cachedPhoto);
	}

	/**
	 * Returns {@link PhotoAO} by given photo id, or {@code null} if photo can't be found.
	 *
	 * @param photoId
	 * 		id of the photo
	 * @return {@link PhotoAO}, or {@code null}
	 */
	private PhotoAO getPhoto(final long photoId) {
		try {
			return photoAPI.getPhoto(photoId);
		} catch (PhotoNotFoundPhotoAPIException e) {
			LOGGER.warn("doGet(req, resp) fail. Photo[" + photoId + "] not found.");
		} catch (PhotoAPIException e) {
			LOGGER.warn("doGet(req, resp) fail. " + INTERNAL_SERVER_ERROR_MSG);
		}

		return null;
	}
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy