XhrFileUploadServlet.java

package sk.iway.iwcm.components.upload;

import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.commons.lang.RandomStringUtils;
import sk.iway.iwcm.*;
import sk.iway.iwcm.common.DocTools;
import sk.iway.iwcm.i18n.Prop;
import sk.iway.iwcm.io.IwcmFile;
import sk.iway.iwcm.system.spring.events.WebjetEvent;
import sk.iway.iwcm.system.spring.events.WebjetEventType;
import sk.iway.iwcm.system.stripes.MultipartWrapper;

import javax.servlet.ServletException;
import javax.servlet.annotation.MultipartConfig;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.*;
import java.io.*;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

@MultipartConfig
@WebServlet("/XhrFileUpload")
public class XhrFileUploadServlet extends HttpServlet
{
	private static final long serialVersionUID = 1L;
	private static final Map<String,PathHolder> temporary = new ConcurrentHashMap<>();

	private static final String ALLOWED_EXTENSIONS = "doc docx xls xlsx xml ppt pptx pdf jpeg jpg bmp tiff psd zip rar png mp4";

	@Override
	protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
	{
		Prop prop = Prop.getInstance();
		try {
			String name = request.getParameter("name");
			Logger.debug(XhrFileUploadServlet.class, "doPost, name from parameter: "+name);

			//dropzone.js kompatibilita
			Part filePart = request.getPart("file");

			if (filePart == null) {
				XhrFileUploadResponse xhrFileUploadResponse = new XhrFileUploadResponse();
				xhrFileUploadResponse.setError(prop.getText("components.forum.new.upload_not_allowed_filetype"));
				xhrFileUploadResponse.setFile("");
				xhrFileUploadResponse.setSuccess(false);
				setResponse(response, xhrFileUploadResponse);
				return;
			}

			if (filePart.getSubmittedFileName() != null) {
				name = filePart.getSubmittedFileName();
			}

			Logger.debug(XhrFileUploadServlet.class, "doPost, name from filePart: "+name);

			String extension = FileTools.getFileExtension(name);

			Logger.debug(XhrFileUploadServlet.class, "doPost, name="+name+", extension="+extension);
			List<String> allowedExtensions = Tools.getStringListValue(Tools.getTokens(Constants.getString("xhrFileUploadAllowedExtensions", ALLOWED_EXTENSIONS), " "));
			if (!allowedExtensions.contains("*") && !allowedExtensions.contains(extension)) {
				Logger.debug(XhrFileUploadServlet.class, "doPost, extension not allowed: "+extension);
				XhrFileUploadResponse xhrFileUploadResponse = new XhrFileUploadResponse();
				xhrFileUploadResponse.setError(prop.getText("components.forum.new.upload_not_allowed_filetype"));
				xhrFileUploadResponse.setFile(name);
				xhrFileUploadResponse.setSuccess(false);
				setResponse(response, xhrFileUploadResponse);
				return;
			}

			int chunk = Tools.getIntValue(request.getParameter("chunk"), 0);
			int chunks = Tools.getIntValue(request.getParameter("chunks"), 0);

			//dropzone.js kompatibilita
			if (request.getParameter("dzchunkindex") != null) chunk = Tools.getIntValue(request.getParameter("dzchunkindex"), 0);
			if (request.getParameter("dztotalchunkcount") != null) chunks = Tools.getIntValue(request.getParameter("dztotalchunkcount"), 0);

			Logger.debug(XhrFileUploadServlet.class, "doPost, chunk="+chunk+", chunks="+chunks);

			HttpSession session = request.getSession();
			PartialUploadHolder holder = (PartialUploadHolder) session.getAttribute("partialUploadFile-" + name);
			if (holder == null || chunk == 0) {
				holder = new PartialUploadHolder(chunks, name);
				session.setAttribute("partialUploadFile-" + name, holder);
			}
			boolean isLast = false;
			if (holder.getPartPaths().size() + 1 == holder.getChunks() || holder.getChunks() == 0) {
				//je to posledny alebo jediny chunk
				isLast = true;
			}

			String suffix = FileTools.getFileExtension(name);

			//zapisem data do docasneho suboru
			File tempUploadFile = File.createTempFile(name, "." + suffix);
			FileOutputStream tempFos = new FileOutputStream(tempUploadFile);
			InputStream tempIs = filePart.getInputStream();
			int read = 0;
			byte[] bytes = new byte[1024];
			while ((read = tempIs.read(bytes)) != -1) {
				tempFos.write(bytes, 0, read);
			}

			tempIs.close();
			tempFos.close();

			String filePartName = tempUploadFile.getAbsolutePath();//filePart.get//getName();
			holder.getPartPaths().add(chunk, filePartName);

			XhrFileUploadResponse xhrFileUploadResponse = new XhrFileUploadResponse();
			xhrFileUploadResponse.setSuccess(true);
			xhrFileUploadResponse.setChunkUploaded(chunk);
			xhrFileUploadResponse.setSize(new File(filePartName).length());

			if (isLast) {
				session.removeAttribute("partialUploadFile-" + name);
				// mam posledny, spojim ich do jedneho

				FileOutputStream fos;
				FileInputStream fis;
				byte[] fileBytes;
				String random = RandomStringUtils.random(15, true, true);
				File tempfile = null;
				try {
					tempfile = File.createTempFile(random, "." + suffix);
					fos = new FileOutputStream(tempfile, true);
					for (String chunkPath : holder.getPartPaths()) {
						File inputFile = new File(chunkPath);
						fis = new FileInputStream(inputFile);
						fileBytes = new byte[(int) inputFile.length()];
						fis.read(fileBytes, 0, (int) inputFile.length());
						fos.write(fileBytes);
						fos.flush();
						fileBytes = null;
						fis.close();
						fis = null;
						inputFile.delete(); //NOSONAR
					}
					fos.close();
					fos = null;

					WebjetEvent<File> fileWebjetEvent = new WebjetEvent<>(tempfile, WebjetEventType.ON_XHR_FILE_UPLOAD);
					fileWebjetEvent.publishEvent();

					if (request.getAttribute("xhrError") != null) {
						Logger.debug(XhrFileUploadServlet.class, "doPost, xhrError, name="+name);
						xhrFileUploadResponse = new XhrFileUploadResponse();
						xhrFileUploadResponse.setFile(name);
						xhrFileUploadResponse.setSuccess(false);
						xhrFileUploadResponse.setError((String) request.getAttribute("xhrError"));
						setResponse(response, xhrFileUploadResponse);
						return;
					}

					Logger.debug(XhrFileUploadServlet.class, "doPost, success, name="+name+", key="+random);

					xhrFileUploadResponse.putName(name);
					xhrFileUploadResponse.putKey(random);

					temporary.put(random, new PathHolder(name, tempfile.getAbsolutePath(), Tools.getNow()));
				} catch (IOException ioe) {
					sk.iway.iwcm.Logger.error(ioe);
				}
			} else {
				MultipartWrapper.slowdownUpload();
			}

			setResponse(response, xhrFileUploadResponse);
		}
		catch (Exception ex) {
			/*
			if (ex.getClass().isAssignableFrom(IOException.class) && ex.getMessage().contains("Unexpected EOF")) {
				Logger.warn(XhrFileUploadServlet.class, "User cancelled upload");
				Logger.debug(XhrFileUploadServlet.class, ex.getMessage());
				return;
			}
			 */
			Logger.warn(XhrFileUploadServlet.class, ex.getMessage());
			sk.iway.iwcm.Logger.error(ex);

			XhrFileUploadResponse xhrFileUploadResponse = new XhrFileUploadResponse();
			xhrFileUploadResponse.setError(prop.getText("components.docman.error.db"));
			xhrFileUploadResponse.setSuccess(false);
			setResponse(response, xhrFileUploadResponse);
		}
	}

	private void setResponse(HttpServletResponse response, XhrFileUploadResponse xhrFileUploadResponse) {
		try {
			ObjectMapper mapper = new ObjectMapper();
			response.setContentType("application/json");
			response.setCharacterEncoding("UTF-8");
			response.getWriter().write(mapper.writeValueAsString(xhrFileUploadResponse));
		} catch (IOException e) {
			sk.iway.iwcm.Logger.error(e);
		}
	}


	/**
	 * presunie uploadnuty subor
	 *
	 * @param fileKey - identifikator suboru
	 * @param dir - cielovy adresar
	 * @return - nazov suboru
	 * @throws IOException
	 */
	@SuppressWarnings("java:S1130")
	public static String moveFile(String fileKey, String dir) throws IOException
	{
		if (temporary.containsKey(fileKey))
		{
		    IwcmFile dirFile = new IwcmFile(dir);
		    String dirVirtualPath = dirFile.getVirtualPath();

			String originalFilename = temporary.get(fileKey).getFileName();

			if (dirVirtualPath.startsWith("/images") || dirVirtualPath.startsWith("/files"))
            {
                originalFilename = DB.internationalToEnglish(originalFilename);
                originalFilename = DocTools.removeCharsDir(originalFilename, true).toLowerCase();
            }

			String filename = originalFilename;
			IwcmFile file = new IwcmFile(temporary.get(fileKey).getTempPath());
			if (!file.exists()) return null;
			IwcmFile dest = new IwcmFile(dir, originalFilename);
			int counter = 1;
			while (dest.exists())
			{
				filename = originalFilename + "-" + counter;
				if (originalFilename.contains("."))	filename = originalFilename.substring(0, originalFilename.lastIndexOf(".")) + "-" + counter + originalFilename.substring(originalFilename.lastIndexOf("."));
				dest = new IwcmFile(dir, filename);
				counter++;
			}

			Logger.debug(XhrFileUploadServlet.class, "moveFile, Moving file "+file.getAbsolutePath()+", to "+dest.getAbsolutePath());
			FileTools.moveFile(file, dest);
			Adminlog.add(Adminlog.TYPE_FILE_UPLOAD, "File upload (xhr), path=" + dest.getVirtualPath(), -1, 1);
			return filename;
		}
		return null;
	}

	@SuppressWarnings("java:S1130")
    public static String moveAndReplaceFile(String fileKey, String dir, String fileNameParam) throws IOException
    {
        String fileName = fileNameParam;
        if (temporary.containsKey(fileKey))
        {
            IwcmFile dirFile = new IwcmFile(dir);
            String dirVirtualPath = dirFile.getVirtualPath();

            if (dirVirtualPath.startsWith("/images") || dirVirtualPath.startsWith("/files"))
            {
                fileName = DB.internationalToEnglish(fileName);
                fileName = DocTools.removeCharsDir(fileName, true).toLowerCase();
            }

            IwcmFile file = new IwcmFile(temporary.get(fileKey).getTempPath());
            if (!file.exists()) return null;
            IwcmFile dest = new IwcmFile(dir + fileName);

            if (dest.exists()) {
                dest.delete();
            }

				Logger.debug(XhrFileUploadServlet.class, "moveAndReplaceFile, Moving file "+file.getAbsolutePath()+", to "+dest.getAbsolutePath());
            FileTools.moveFile(file, dest);
            Adminlog.add(Adminlog.TYPE_FILE_UPLOAD, "File upload (xhr), path=" + dest.getVirtualPath(), -1, 1);

            return fileName;
        }
        return null;
    }

	public static boolean delete(String hash) {
		if (temporary.containsKey(hash))
		{

			IwcmFile file = new IwcmFile(temporary.get(hash).getTempPath());
			return file.delete();
		}

		return false;
	}

	/**
	 * Vrati meno suboru podla zadaneho kluca
	 * @param fileKey
	 * @return
	 */
	public static String getTempFileName(String fileKey)
	{
		if (temporary.containsKey(fileKey))
		{
			return temporary.get(fileKey).getFileName();
		}
		return null;
	}

	/**
	 * Vrati cestu k temp suboru, pozuiva sa vo FormMail na detekciu ci subor vyhovuje poziadavkam
	 * @param fileKey
	 * @return
	 */
	public static String getTempFilePath(String fileKey)
	{
		if (temporary.containsKey(fileKey))
		{
			return temporary.get(fileKey).getTempPath();
		}
		return null;
	}


	public static class PartialUploadHolder implements Serializable
	{
		private static final long serialVersionUID = 1L;
		private int chunks;
		private String name;

		private List<String> partPaths;

		public PartialUploadHolder(int chunks, String name)
		{
			this.chunks = chunks;
			this.name = name;
			partPaths = new ArrayList<>(chunks);
		}

		public int getChunks()
		{
			return chunks;
		}

		public void setChunks(int chunks)
		{
			this.chunks = chunks;
		}

		public String getName()
		{
			return name;
		}

		public void setName(String name)
		{
			this.name = name;
		}

		public List<String> getPartPaths()
		{
			return partPaths;
		}

		public void setPartPaths(List<String> partPaths)
		{
			this.partPaths = partPaths;
		}
	}


	public static class PathHolder
	{
		private String fileName;
		private String tempPath;
		private long timestamp;

		public PathHolder(String fileName, String tempPath, long timestamp)
		{
			this.fileName = fileName;
			this.timestamp = timestamp;
			this.tempPath = tempPath;
		}
		public String getFileName()
		{
			return fileName;
		}
		public void setFileName(String fileName)
		{
			this.fileName = fileName;
		}
		public long getTimestamp()
		{
			return timestamp;
		}
		public void setTimestamp(long timestamp)
		{
			this.timestamp = timestamp;
		}
		public String getTempPath()
		{
			return tempPath;
		}
		public void setTempPath(String tempPath)
		{
			this.tempPath = tempPath;
		}
	}
}