io.vertigo.struts2.impl.multipartrequest.Servlet3MultiPartRequest.java Source code

Java tutorial

Introduction

Here is the source code for io.vertigo.struts2.impl.multipartrequest.Servlet3MultiPartRequest.java

Source

/**
 * vertigo - simple java starter
 *
 * Copyright (C) 2013-2018, KleeGroup, direction.technique@kleegroup.com (http://www.kleegroup.com)
 * KleeGroup, Centre d'affaire la Boursidiere - BP 159 - 92357 Le Plessis Robinson Cedex - France
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package io.vertigo.struts2.impl.multipartrequest;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.file.Files;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.Part;

import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.FileItemHeaders;
import org.apache.commons.fileupload.FileUploadBase;
import org.apache.commons.fileupload.FileUploadException;
import org.apache.commons.fileupload.ParameterParser;
import org.apache.commons.fileupload.disk.DiskFileItem;
import org.apache.commons.io.IOUtils;
import org.apache.struts2.dispatcher.multipart.JakartaMultiPartRequest;

import io.vertigo.lang.VSystemException;
import io.vertigo.util.StringUtil;

/**
 * Gestion du multipart dans le cas ou sa gestion est dlgue au moteur de Servlet dans la norme Servlet 3.
 * Elle rpond aux besoin de Struts pour lire les champs ainsi que les fichiers transmis.
 * Supporte les configurations standard struts.multipart.maxSize et struts.multipart.saveDir.
 * Sous Tomcat, ncessite d'ajouter la configuration allowCasualMultipartParsing="true" dans le contexte.<br>
 * <br>
 * Pour activer cette classe, ajouter la configuration suivante dans le struts.xml :
 *
 * <pre>
 * {@code
 * <bean type="org.apache.struts2.dispatcher.multipart.MultiPartRequest"
 *       class="io.vertigo.struts2.impl.multipartrequest.Servlet3MultiPartRequest"
 *       name="S3MultipartParser" scope="prototype"/>
 * <constant name="struts.multipart.parser" value="S3MultipartParser"/>
 * }
 * </pre>
 *
 * @author skerdudou
 */
public final class Servlet3MultiPartRequest extends JakartaMultiPartRequest {

    @Override
    protected List<FileItem> parseRequest(final HttpServletRequest servletRequest, final String saveDir)
            throws FileUploadException {
        // gestion de la configuration struts.multipart.maxSize
        if (maxSize >= 0) {
            final int requestSize = servletRequest.getContentLength();
            if (requestSize != -1 && requestSize > maxSize) {
                throw new FileUploadBase.SizeLimitExceededException(String.format(
                        "the request was rejected because its size (%s) exceeds the configured maximum (%s)",
                        Long.valueOf(requestSize), Long.valueOf(maxSize)), requestSize, maxSize);
            }
        }
        // gestion du contenu de la requete
        try {
            final Collection<Part> parts = servletRequest.getParts();
            final List<FileItem> ret = new ArrayList<>(parts.size());
            for (final Part part : parts) {
                ret.add(new PartFileItem(part, saveDir));
            }
            return ret;
        } catch (IOException | ServletException e) {
            throw new FileUploadException("Impossible de rcuprer les fichiers de la requte en mode Servlet 3",
                    e);
        }
    }

    /**
     * Cette classe interne doit hriter de DiskFileItem car Struts va le caster dans ce type ensuite.
     *
     * @author skerdudou
     */
    private static final class PartFileItem extends DiskFileItem {

        private final transient Part part;
        private final File storeLocation;

        /**
         * serialVersionUID.
         */
        private static final long serialVersionUID = -2535633405526276127L;

        public PartFileItem(final Part part, final String saveDir) {
            super(part.getName(), part.getContentType(), getFilename(part) == null, getFilename(part), -1,
                    new File(saveDir));

            this.part = part;

            if (isFormField() || StringUtil.isEmpty(getName())) {
                // si on est sur un champ (et donc pas sur un fichier upload) ou si on est sur une entre de fichier vide
                // => on a pas besoin d'un File, on reste donc juste avec l'inputStream du Part sans crer le fichier temporaire
                storeLocation = null;
            } else {
                // si c'est un fichier upload on le persiste  un endroit connu pour qu'il soit pris en charge par Struts
                // car il est impossible de rcuprer le chemin du fichier pris en charge par le moteur de servlet
                // en passant par la mthode write de l'objet Part, on s'assure de la performance car le fichier sera dplac si possible et non recopi
                try {
                    storeLocation = getTempFile();
                    part.write(storeLocation.getAbsolutePath());
                } catch (final IOException e) {
                    throw new VSystemException("Impossible de copier sur disque le fichier upload", e);
                }
            }

            // ajout des headers du Part
            setHeaders(new PartFileItemHeaders(part));
        }

        @Override
        public File getStoreLocation() {
            return storeLocation;
        }

        @Override
        public boolean isInMemory() {
            return storeLocation == null;
        }

        @Override
        public long getSize() {
            return part.getSize();
        }

        @Override
        public OutputStream getOutputStream() throws IOException {
            // Inutile dans le cas o l'on utilise cette classe de gestion du multipart
            // utilis  la base par le FileItemFactory que l'on utilise pas
            throw new UnsupportedOperationException("Objet en lecture seule");
        }

        @Override
        public InputStream getInputStream() throws IOException {
            return part.getInputStream();
        }

        @Override
        public byte[] get() {
            try {
                return IOUtils.toByteArray(getInputStream());
            } catch (final IOException e) {
                throw new VSystemException("Impossible de lire le fichier", e);
            }
        }

        @Override
        public void delete() {
            try {
                if (storeLocation != null) {
                    Files.delete(storeLocation.toPath());
                }
                part.delete();
            } catch (final IOException e) {
                throw new VSystemException("Impossible de supprimer le fichier", e);
            }
        }

        // A partir de Tomcat8, on peux passer en servlet 3.1 et utiliser la mthode getSubmittedFileName() de l'objet Part
        // en attendant, il faut parser le Content-Disposition  la main, code copi de librairies existantes (mais mthodes prives)
        private static String getFilename(final Part part) {
            String fileName = null;
            final String cd = part.getHeader("Content-Disposition");
            if (cd != null) {
                final String cdl = cd.toLowerCase(Locale.ENGLISH);
                if (cdl.startsWith("form-data") || cdl.startsWith("attachment")) {
                    final ParameterParser paramParser = new ParameterParser();
                    paramParser.setLowerCaseNames(true);
                    // Parameter parser can handle null input
                    final Map<String, String> params = paramParser.parse(cd, ';');
                    if (params.containsKey("filename")) {
                        fileName = params.get("filename");
                        if (fileName != null) {
                            fileName = fileName.trim();
                        } else {
                            // Even if there is no value, the parameter is present,
                            // so we return an empty file name rather than no file
                            // name.
                            fileName = "";
                        }
                    }
                }
            }
            return fileName;
        }
    }

    /**
     * Mise en conformit des accss aux headers du Part sur l'interface FileItemHeaders.
     *
     * @author skerdudou
     */
    private static final class PartFileItemHeaders implements FileItemHeaders {

        private final Part part;

        private PartFileItemHeaders(final Part part) {
            this.part = part;
        }

        @Override
        public Iterator<String> getHeaders(final String name) {
            return part.getHeaders(name).iterator();
        }

        @Override
        public Iterator<String> getHeaderNames() {
            return part.getHeaderNames().iterator();
        }

        @Override
        public String getHeader(final String name) {
            return part.getHeader(name);
        }
    }
}