DownloadServlet.java

/*
 * This file is part of Waarp Project (named also Waarp or GG).
 *
 *  Copyright (c) 2019, Waarp SAS, and individual contributors by the @author
 *  tags. See the COPYRIGHT.txt in the distribution for a full listing of
 * individual contributors.
 *
 *  All Waarp Project is free software: you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or (at your
 * option) any later version.
 *
 * Waarp 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.
 *
 *  You should have received a copy of the GNU General Public License along with
 * Waarp . If not, see <http://www.gnu.org/licenses/>.
 */

package org.waarp.http.protocol.servlet;

import org.waarp.common.database.exception.WaarpDatabaseException;
import org.waarp.common.guid.LongUuid;
import org.waarp.common.logging.WaarpLogger;
import org.waarp.common.logging.WaarpLoggerFactory;
import org.waarp.common.utility.WaarpSystemUtil;
import org.waarp.http.protocol.HttpDownloadSession;

import javax.servlet.ServletException;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

/**
 * Download Servlet: enables downloading file from Web Browser to final user
 */
public class DownloadServlet extends AbstractServlet {
  private static final long serialVersionUID = 2002L;
  public static final String FILENAME = "filename";
  public static final String IDENTIFIER = "identifier";
  private static final WaarpLogger logger =
      WaarpLoggerFactory.getLogger(DownloadServlet.class);
  public static final String X_HASH_SHA_256 = "X-Hash-Sha-256";

  @Override
  protected final void doPost(final HttpServletRequest request,
                              final HttpServletResponse response) {
    doGet(request, response);
  }

  @Override
  protected final void doHead(final HttpServletRequest request,
                              final HttpServletResponse response) {
    final Map<String, String> arguments = new HashMap<String, String>();
    final Enumeration<String> names = request.getParameterNames();
    while (names.hasMoreElements()) {
      final String name = names.nextElement();
      arguments.put(name, request.getParameter(name));
    }
    final String filename = getFilename(arguments);
    logger.debug("RECVHEAD: {}", filename);
    try {
      final HttpDownloadSession session =
          getDownloadSession(arguments, filename, true);//NOSONAR
      logger.debug("RECVHEAD SESSION: {}", session);
      logger.debug("Check on going");
      response.setHeader("Expires", "0");
      response.setHeader("Cache-Control",
                         "must-revalidate, post-check=0, " + "pre-check=0");
      if (session == null) {
        logger.debug("Not found");
        response.setStatus(404);
      } else if (session.isTransmitting()) {
        logger.debug("On going");
        response.setStatus(202);
      } else if (session.isFinished()) {
        logger.debug("Done");
        response.setHeader(X_HASH_SHA_256, session.getComputedHadh());
        response.setStatus(200);
      }
    } catch (final ServletException e) {
      logger.error(e.getMessage());
      response.setStatus(400);
    }
  }

  @Override
  protected final void doGet(final HttpServletRequest request,
                             final HttpServletResponse response) {
    final ExecutorService executor = Executors.newSingleThreadExecutor();
    try {
      final Map<String, String> arguments = new HashMap<String, String>();
      final Enumeration<String> names = request.getParameterNames();
      while (names.hasMoreElements()) {
        final String name = names.nextElement();
        arguments.put(name, request.getParameter(name));
      }
      final String filename = getFilename(arguments);
      logger.debug("RECVGET: {}", filename);
      try {
        final HttpDownloadSession session =
            getDownloadSession(arguments, filename, false);
        logger.debug("SESSION: {}", session);
        final Callable<String> hashCompute = new Callable<String>() {
          @Override
          public final String call() {
            return session.getHash();
          }
        };
        final Future<String> future = executor.submit(hashCompute);
        response.setHeader("Content-Disposition",
                           "attachment; filename=\"" + session.getFinalName() +
                           "\"");
        response.setHeader("Content-Description", "File Transfer");
        response.setHeader("Content-Type", "application/octet-stream");
        response.setHeader("Content-Transfer-Encoding", "binary");
        response.setHeader("Expires", "0");
        response.setHeader("Cache-Control",
                           "must-revalidate, post-check=0, " + "pre-check=0");
        // Used by javascript downloader
        final Cookie cookie = new Cookie("fileDownload", "true");
        cookie.setHttpOnly(true);
        cookie.setSecure(true);
        response.addCookie(cookie);
        response.setHeader("Content-Length",
                           Long.toString(session.getFileSize()));
        String hash = null;
        try {
          hash = future.get();
        } catch (final InterruptedException e) {//NOSONAR
          logger.debug(e);
        } catch (final ExecutionException e) {
          logger.debug(e);
        }
        if (hash != null) {
          response.setHeader(X_HASH_SHA_256, hash);

        }
        try {
          if (session.tryWrite(response.getOutputStream())) {
            session.downloadFinished();
            logger.info("Download OK: {}", session);
            response.setStatus(HttpServletResponse.SC_OK);
          } else {
            logger.info("NOT FOUND: {}", session);
            response.setStatus(HttpServletResponse.SC_NOT_FOUND);
          }
        } catch (final IOException e) {
          logger.error("Error: {} {}", session, e.getMessage());
          response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
        }
      } catch (final ServletException e) {
        logger.error(e.getMessage());
        response.setStatus(400);
      }
    } finally {
      executor.shutdown();
    }
  }

  private String getFilename(final Map<String, String> arguments) {
    return arguments.get(FILENAME);
  }

  private HttpDownloadSession getDownloadSession(
      final Map<String, String> arguments, final String filename,
      final boolean check) throws ServletException {
    final String rulename = arguments.get(RULENAME);
    if (rulename == null) {
      throw new ServletException(INVALID_REQUEST_PARAMS);
    }
    String identifier = arguments.get(IDENTIFIER);
    if (identifier == null) {
      identifier = Long.toString(LongUuid.getLongUuid());
    }
    String comment = arguments.get(COMMENT);
    if (comment == null) {
      comment = "Web Download " + identifier;
    }

    try {
      final HttpAuthent authent =
          (HttpAuthent) WaarpSystemUtil.newInstance(authentClass);
      authent.initializeAuthent(arguments);
      if (check) {
        try {
          return new HttpDownloadSession(identifier, authent);
        } catch (final WaarpDatabaseException e) {
          logger.debug(e);
          return null;
        }
      }
      return new HttpDownloadSession(filename, rulename, identifier, comment,
                                     authent);
    } catch (final IllegalArgumentException e) {
      throw new ServletException(
          INVALID_REQUEST_PARAMS + ": " + e.getMessage());
    } catch (final InvocationTargetException e) {
      throw new ServletException(
          INVALID_REQUEST_PARAMS + ": " + e.getMessage());
    }
  }
}