org.springframework.integration.http.DefaultInboundRequestMapper.java Source code

Java tutorial

Introduction

Here is the source code for org.springframework.integration.http.DefaultInboundRequestMapper.java

Source

/*
 * Copyright 2002-2010 the original author or authors.
 *
 * 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.springframework.integration.http;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.servlet.ServletRequest;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.integration.Message;
import org.springframework.integration.support.MessageBuilder;
import org.springframework.util.FileCopyUtils;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.multipart.MultipartException;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.multipart.MultipartHttpServletRequest;
import org.springframework.web.multipart.MultipartResolver;

/**
 * Default implementation of {@link InboundRequestMapper} for inbound HttpServletRequests.
 * The request will be mapped according to the following rules:
 * <ul>
 * <li>For a GET request or a POST request with a Content-Type of
 * "application/x-www-form-urlencoded", the parameter Map will be copied as the
 * payload. The map will be an instance of {@link MultiValueMap} where the keys are
 * Strings and the values are Lists of Strings. Those Lists are populated from the
 * String array values of the original request parameter Map as described for the
 * {@link ServletRequest#getParameterMap()} method.</li>
 * <li>If a MultipartResolver has been provided, and a multipart request is
 * detected, the multipart file content will be converted to String for any
 * "text" content type, or byte arrays otherwise.</li>
 * <li>For other request types, the request body will be used as the payload
 * and the type will depend on the Content-Type header value. If it begins with
 * "text", a String will be created. If the Content-Type is
 * "application/x-java-serialized-object", the request body will be expected to
 * contain a Serializable Object, and that will be used as the message payload.
 * Otherwise, the payload will be a byte array.</li>
 * </ul>
 * In all cases, the original request headers will be passed in the
 * MessageHeaders. Likewise, the following headers will be added:
 * <ul>
 *   <li>{@link HttpHeaders#REQUEST_URL}</li>
 *   <li>{@link HttpHeaders#REQUEST_METHOD}</li>
 *   <li>{@link HttpHeaders#USER_PRINCIPAL} (if available)</li>
 * </ul>
 * 
 * @author Mark Fisher
 * @author Oleg Zhurakousky
 * @since 1.0.2
 */
public class DefaultInboundRequestMapper implements InboundRequestMapper {

    private final Log logger = LogFactory.getLog(getClass());

    private volatile MultipartResolver multipartResolver;

    private volatile String multipartCharset = null;

    private volatile boolean copyUploadedFiles;

    /**
     * Specify the {@link MultipartResolver} to use when checking requests.
     * If no resolver is provided, this mapper will not support multipart
     * requests. 
     */
    public void setMultipartResolver(MultipartResolver multipartResolver) {
        this.multipartResolver = multipartResolver;
    }

    /**
     * Specify the charset name to use when converting multipart file content
     * into Strings.
     */
    public void setMultipartCharset(String multipartCharset) {
        this.multipartCharset = multipartCharset;
    }

    /**
     * Specify whether uploaded multipart files should be copied to a temporary
     * file on the server. If this is set to 'true', the payload map will
     * contain a File instance as the value for each multipart file entry.
     * Otherwise the uploaded file's content will be converted to either a
     * String or byte array based on the content-type (String for "text/*" and
     * byte array otherwise). The default value is false.
     */
    public void setCopyUploadedFiles(boolean copyUploadedFiles) {
        this.copyUploadedFiles = copyUploadedFiles;
    }

    public Message<?> toMessage(HttpServletRequest request) throws Exception {
        try {
            request = this.checkMultipart(request);
            Object payload = createPayloadFromRequest(request);
            MessageBuilder<?> builder = MessageBuilder.withPayload(payload);
            this.populateHeaders(request, builder);
            return builder.build();
        } finally {
            this.cleanupMultipart(request);
        }
    }

    /**
     * Convert the request into a multipart request to make multiparts available.
     * If no multipart resolver is set, simply use the existing request.
     * @param request current HTTP request
     * @return the processed request (multipart wrapper if necessary)
     * @see MultipartResolver#resolveMultipart
     */
    private HttpServletRequest checkMultipart(HttpServletRequest request) throws MultipartException {
        if (this.multipartResolver != null && this.multipartResolver.isMultipart(request)) {
            if (request instanceof MultipartHttpServletRequest) {
                logger.debug("Request is already a MultipartHttpServletRequest");
            } else {
                return this.multipartResolver.resolveMultipart(request);
            }
        }
        return request;
    }

    /**
     * Clean up any resources used by the given multipart request (if any).
     * @param request current HTTP request
     * @see MultipartResolver#cleanupMultipart
     */
    private void cleanupMultipart(HttpServletRequest request) {
        if (this.multipartResolver != null && request instanceof MultipartHttpServletRequest) {
            this.multipartResolver.cleanupMultipart((MultipartHttpServletRequest) request);
        }
    }

    private Object createPayloadFromRequest(HttpServletRequest request) throws Exception {
        Object payload = null;
        String contentType = request.getContentType() != null ? request.getContentType() : "";
        if (request instanceof MultipartHttpServletRequest) {
            payload = this.createPayloadFromMultipartRequest((MultipartHttpServletRequest) request);
        } else if (contentType.startsWith("multipart/form-data")) {
            throw new IllegalArgumentException("Content-Type of 'multipart/form-data' requires a MultipartResolver."
                    + " Try configuring a MultipartResolver within the ApplicationContext.");
        } else if (request.getMethod().equals("GET")) {
            if (logger.isDebugEnabled()) {
                logger.debug("received GET request, using parameter map as payload");
            }
            payload = this.createPayloadFromParameterMap(request);
        } else if (contentType.startsWith("application/x-www-form-urlencoded")) {
            if (logger.isDebugEnabled()) {
                logger.debug("received " + request.getMethod()
                        + " request with form data, using parameter map as payload");
            }
            payload = createPayloadFromParameterMap(request);
        } else if (contentType.startsWith("text")) {
            if (logger.isDebugEnabled()) {
                logger.debug("received " + request.getMethod() + " request, creating payload with text content");
            }
            payload = createPayloadFromTextContent(request);
        } else if (contentType.startsWith("application/x-java-serialized-object")) {
            payload = createPayloadFromSerializedObject(request);
        } else {
            payload = createPayloadFromInputStream(request);
        }
        return payload;
    }

    @SuppressWarnings("unchecked")
    private Object createPayloadFromMultipartRequest(MultipartHttpServletRequest multipartRequest) {
        Map<String, Object> payloadMap = new HashMap<String, Object>(multipartRequest.getParameterMap());
        Map<String, MultipartFile> fileMap = multipartRequest.getFileMap();
        for (Map.Entry<String, MultipartFile> entry : fileMap.entrySet()) {
            MultipartFile multipartFile = entry.getValue();
            if (multipartFile.isEmpty()) {
                continue;
            }
            try {
                if (this.copyUploadedFiles) {
                    File tmpFile = File.createTempFile("si_", null);
                    multipartFile.transferTo(tmpFile);
                    payloadMap.put(entry.getKey(), tmpFile);
                    if (logger.isDebugEnabled()) {
                        logger.debug("copied uploaded file [" + multipartFile.getOriginalFilename()
                                + "] to temporary file [" + tmpFile.getAbsolutePath() + "]");
                    }
                } else if (multipartFile.getContentType() != null
                        && multipartFile.getContentType().startsWith("text")) {
                    String multipartFileAsString = this.multipartCharset != null
                            ? new String(multipartFile.getBytes(), this.multipartCharset)
                            : new String(multipartFile.getBytes());
                    payloadMap.put(entry.getKey(), multipartFileAsString);
                } else {
                    payloadMap.put(entry.getKey(), multipartFile.getBytes());
                }
            } catch (IOException e) {
                throw new IllegalArgumentException("Cannot read contents of multipart file", e);
            }
        }
        return Collections.unmodifiableMap(payloadMap);
    }

    @SuppressWarnings("unchecked")
    private Object createPayloadFromParameterMap(HttpServletRequest request) {
        return new UnmodifiableRequestParameterMap(request.getParameterMap());
    }

    private Object createPayloadFromTextContent(HttpServletRequest request) throws IOException {
        String charset = request.getCharacterEncoding() != null ? request.getCharacterEncoding() : "utf-8";
        return new String(FileCopyUtils.copyToByteArray(request.getInputStream()), charset);
    }

    private Object createPayloadFromSerializedObject(HttpServletRequest request) {
        try {
            return new ObjectInputStream(request.getInputStream()).readObject();
        } catch (Exception e) {
            throw new IllegalArgumentException("failed to deserialize Object in request", e);
        }
    }

    private byte[] createPayloadFromInputStream(HttpServletRequest request) throws Exception {
        InputStream stream = request.getInputStream();
        int length = request.getContentLength();
        if (length == -1) {
            throw new ResponseStatusCodeException(HttpServletResponse.SC_LENGTH_REQUIRED);
        }
        if (logger.isDebugEnabled()) {
            logger.debug("received " + request.getMethod() + " request, "
                    + "creating byte array payload with content lenth: " + length);
        }
        byte[] bytes = new byte[length];
        stream.read(bytes, 0, length);
        return bytes;
    }

    private void populateHeaders(HttpServletRequest request, MessageBuilder<?> builder) {
        Enumeration<?> headerNames = request.getHeaderNames();
        if (headerNames != null) {
            while (headerNames.hasMoreElements()) {
                String headerName = (String) headerNames.nextElement();
                Enumeration<?> headerEnum = request.getHeaders(headerName);
                if (headerEnum != null) {
                    List<Object> headers = new ArrayList<Object>();
                    while (headerEnum.hasMoreElements()) {
                        headers.add(headerEnum.nextElement());
                    }
                    if (headers.size() == 1) {
                        builder.setHeader(headerName, headers.get(0));
                    } else if (headers.size() > 1) {
                        builder.setHeader(headerName, headers);
                    }
                }
            }
        }
        builder.setHeader(HttpHeaders.REQUEST_URL, request.getRequestURL().toString());
        builder.setHeader(HttpHeaders.REQUEST_METHOD, request.getMethod());
        builder.setHeader(HttpHeaders.USER_PRINCIPAL, request.getUserPrincipal());
    }

    /**
     * Map class that extends {@link LinkedMultiValueMap} and implements Serializable.
     * The contents of the map are unmodifiable, so calling any modification operation
     * (e.g. put, add, or remove) will result in an UnsupportedOperationException. 
     */
    @SuppressWarnings("serial")
    private static class UnmodifiableRequestParameterMap extends LinkedMultiValueMap<String, String>
            implements Serializable { // TODO: in 3.0.1 LMVM implements Serializable

        UnmodifiableRequestParameterMap(Map<String, String[]> parameters) {
            for (Map.Entry<String, String[]> entry : parameters.entrySet()) {
                super.put(entry.getKey(), Arrays.asList(entry.getValue()));
            }
        }

        @Override
        public void add(String key, String value) {
            throw new UnsupportedOperationException();
        }

        @Override
        public void clear() {
            throw new UnsupportedOperationException();
        }

        @Override
        public List<String> put(String key, List<String> value) {
            throw new UnsupportedOperationException();
        }

        @Override
        public void putAll(Map<? extends String, ? extends List<String>> m) {
            throw new UnsupportedOperationException();
        }

        @Override
        public List<String> remove(Object key) {
            throw new UnsupportedOperationException();
        }

        @Override
        public void set(String key, String value) {
            throw new UnsupportedOperationException();
        }

        @Override
        public void setAll(Map<String, String> values) {
            throw new UnsupportedOperationException();
        }

        @Override
        public Map<String, String> toSingleValueMap() {
            return Collections.unmodifiableMap(super.toSingleValueMap());
        }

    }

}