org.primeframework.mvc.parameter.RequestBodyWorkflow.java Source code

Java tutorial

Introduction

Here is the source code for org.primeframework.mvc.parameter.RequestBodyWorkflow.java

Source

/*
 * Copyright (c) 2012, Inversoft Inc., All Rights Reserved
 *
 * 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 org.primeframework.mvc.parameter;

import com.google.inject.Inject;
import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.FileItemFactory;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.ServletFileUpload;
import org.primeframework.mvc.PrimeException;
import org.primeframework.mvc.parameter.fileupload.FileInfo;
import org.primeframework.mvc.util.IteratorEnumeration;
import org.primeframework.mvc.util.RequestKeys;
import org.primeframework.mvc.workflow.Workflow;
import org.primeframework.mvc.workflow.WorkflowChain;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * This workflow handles providing access to parameters inside the request body when the container doesn't parse them.
 * Some containers don't parse request bodies for parameters when the method is not POST. They do this because that is
 * the defined behavior in the Servlet specification under section SRV 3.1.
 *
 * @author Brian Pontarelli
 */
@SuppressWarnings("unchecked")
public class RequestBodyWorkflow implements Workflow {
    private final HttpServletRequest request;

    @Inject
    public RequestBodyWorkflow(HttpServletRequest request) {
        this.request = request;
    }

    @Override
    public void perform(WorkflowChain workflowChain) throws IOException, ServletException {
        // Let the container parse out the GET and POST parameters by calling the getParameterMap method
        // After this call, if the method is GET with any content-type or POST with the content-type as
        // x-www-form-urlencoded the InputStream will be empty
        Map<String, String[]> parameters = request.getParameterMap();

        Map<String, List<String>> parsedParameters = null;
        String contentType = request.getContentType();
        if (contentType != null) {
            contentType = contentType.toLowerCase();
            if (contentType.startsWith("multipart/")) {
                FilesAndParameters filesAndParameters = handleFiles();
                request.setAttribute(RequestKeys.FILE_ATTRIBUTE, filesAndParameters.files);
                parsedParameters = filesAndParameters.parameters;
            } else if (contentType.startsWith("application/x-www-form-urlencoded")) {
                parsedParameters = parse(request.getInputStream(), request.getContentLength(),
                        request.getCharacterEncoding());
            }
        }

        if (parsedParameters != null && parsedParameters.size() > 0) {
            HttpServletRequestWrapper wrapper = (HttpServletRequestWrapper) request;
            HttpServletRequest previous = (HttpServletRequest) wrapper.getRequest();
            HttpServletRequest newRequest = new ParameterHttpServletRequestWrapper(previous,
                    combine(parameters, parsedParameters));
            wrapper.setRequest(newRequest);
        }

        workflowChain.continueWorkflow();
    }

    private void addParam(Map<String, List<String>> parsedParameters, String key, byte[] str, int start, int length,
            String encoding, boolean decode) throws IOException {
        if (key == null) {
            throw new IOException("Invalid HTTP URLEncoded request body");
        }

        String value = toParameterString(encoding, str, start, length, decode);
        List<String> list = parsedParameters.get(key);
        if (list == null) {
            list = new ArrayList<String>();
            parsedParameters.put(key, list);
        }

        list.add(value);
    }

    private Map<String, String[]> combine(Map<String, String[]> original, Map<String, List<String>> parsed) {
        Map<String, String[]> map = new HashMap<String, String[]>();
        for (String key : original.keySet()) {
            String[] originalValues = original.get(key);
            List<String> parsedValues = parsed.remove(key);

            String[] newValues = new String[originalValues.length
                    + (parsedValues == null ? 0 : parsedValues.size())];
            System.arraycopy(originalValues, 0, newValues, 0, originalValues.length);

            if (parsedValues != null && parsedValues.size() > 0) {
                int index = originalValues.length;
                for (String parsedValue : parsedValues) {
                    newValues[index++] = parsedValue;
                }
            }

            map.put(key, newValues);
        }

        for (String key : parsed.keySet()) {
            List<String> parsedValues = parsed.get(key);
            map.put(key, parsedValues.toArray(new String[parsedValues.size()]));
        }

        return map;
    }

    /**
     * Handles parsing the multi-part body to pull out the files and the parameters.
     *
     * @return The files and the parameters.
     */
    private FilesAndParameters handleFiles() {
        FilesAndParameters filesAndParameters = new FilesAndParameters();
        FileItemFactory factory = new DiskFileItemFactory();
        ServletFileUpload upload = new ServletFileUpload(factory);
        try {
            List<FileItem> items = upload.parseRequest(request);
            for (FileItem item : items) {
                String name = item.getFieldName();
                if (item.isFormField()) {
                    String value = item.getString();
                    List<String> list = filesAndParameters.parameters.get(name);
                    if (list == null) {
                        list = new ArrayList<String>();
                        filesAndParameters.parameters.put(name, list);
                    }

                    list.add(value);
                } else {
                    String fileName = item.getName();

                    // Handle lame ass IE issues with file names
                    if (fileName.contains(":\\")) {
                        int index = fileName.lastIndexOf("\\");
                        fileName = fileName.substring(index + 1);
                    }

                    String contentType = item.getContentType();
                    File file = File.createTempFile("prime", "fileupload");
                    item.write(file);

                    // Handle when the user doesn't provide a file at all
                    if (file.length() == 0 || fileName == null || contentType == null) {
                        continue;
                    }

                    List<FileInfo> list = filesAndParameters.files.get(name);
                    if (list == null) {
                        list = new ArrayList<FileInfo>();
                        filesAndParameters.files.put(name, list);
                    }

                    list.add(new FileInfo(file, fileName, contentType));
                }
            }
        } catch (Exception e) {
            throw new PrimeException("Unable to handle file uploads", e);
        }

        return filesAndParameters;
    }

    /**
     * Parses the HTTP request body for URL encoded parameters.
     *
     * @param inputStream   The input stream to read from.
     * @param contentLength The estimated length of the content.
     * @param encoding      The encoding header.
     * @return The parameter map.
     * @throws IOException If the read failed.
     */
    private Map<String, List<String>> parse(InputStream inputStream, int contentLength, String encoding)
            throws IOException {
        if (contentLength == 0) {
            return null;
        }

        if (encoding == null) {
            encoding = "UTF-8";
        }

        if (contentLength < 0) {
            contentLength = 1024;
        }

        byte[] readBuffer = new byte[contentLength];

        int read;
        int length = 0;
        while ((read = inputStream.read(readBuffer, length, readBuffer.length - length)) >= 0) {
            length += read;
            if (length == readBuffer.length) {
                byte[] expandedBuffer = new byte[readBuffer.length + 1024];
                System.arraycopy(readBuffer, 0, expandedBuffer, 0, length);
                readBuffer = expandedBuffer;
            }
        }

        if (length == 0) {
            return null;
        }

        Map<String, List<String>> parsedParameters = new HashMap<String, List<String>>();
        int start = 0;
        int index = 0;
        String key = null;
        boolean decode = false;
        for (; index < length; index++) {
            byte c = readBuffer[index];
            switch (c) {
            case '=':
                key = toParameterString(encoding, readBuffer, start, index - start, decode);
                start = index + 1;
                decode = false;
                break;
            case '&':
                addParam(parsedParameters, key, readBuffer, start, index - start, encoding, decode);
                key = null;
                start = index + 1;
                decode = false;
                break;
            case '+':
            case '%':
                decode = true;
            }
        }

        addParam(parsedParameters, key, readBuffer, start, index - start, encoding, decode);
        return parsedParameters;
    }

    private String toParameterString(String encoding, byte[] readBuffer, int start, int length, boolean decode)
            throws UnsupportedEncodingException {
        if (length == 0) {
            return "";
        }

        String key = new String(readBuffer, start, length, encoding);
        if (decode) {
            key = URLDecoder.decode(key, encoding);
        }
        return key;
    }

    private static class FilesAndParameters {
        public final Map<String, List<FileInfo>> files = new HashMap<String, List<FileInfo>>();

        public final Map<String, List<String>> parameters = new HashMap<String, List<String>>();
    }

    private static class ParameterHttpServletRequestWrapper extends HttpServletRequestWrapper {
        private final Map<String, String[]> parameters;

        public ParameterHttpServletRequestWrapper(HttpServletRequest previous, Map<String, String[]> parameters) {
            super(previous);
            this.parameters = Collections.unmodifiableMap(parameters);
        }

        @Override
        public String getParameter(String s) {
            return (parameters.get(s) != null) ? parameters.get(s)[0] : null;
        }

        @Override
        public Map getParameterMap() {
            return parameters;
        }

        @Override
        public Enumeration getParameterNames() {
            return new IteratorEnumeration(parameters.keySet().iterator());
        }

        @Override
        public String[] getParameterValues(String s) {
            return parameters.get(s);
        }
    }
}