All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.directwebremoting.dwrp.Batch Maven / Gradle / Ivy

package org.directwebremoting.dwrp;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.StringTokenizer;

import javax.servlet.http.HttpServletRequest;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.directwebremoting.Container;
import org.directwebremoting.WebContextFactory;
import org.directwebremoting.extend.FormField;
import org.directwebremoting.extend.ProtocolConstants;
import org.directwebremoting.extend.ServerException;
import org.directwebremoting.util.LocalUtil;

/**
 * A Batch is a request from the client.
 * This can be either a from a call ({@link CallBatch}) or an active reverse
 * ajax poll ({@link PollBatch})
 * @author Joe Walker [joe at getahead dot ltd dot uk]
 */
public class Batch
{
    /**
     * Initialize the batch from an {@link HttpServletRequest}
     * @param request ...
     * @throws ServerException ...
     */
    public Batch(HttpServletRequest request) throws ServerException
    {
        get = "GET".equals(request.getMethod());
        if (get)
        {
            extraParameters = parseGet(request);
        }
        else
        {
            extraParameters = parsePost(request);
        }
        parseParameters();
    }

    /**
     * Initialize the batch from a set of pre-parsed parameters
     * @param allParameters ...
     * @param get ...
     */
    public Batch(Map allParameters, boolean get)
    {
        this.extraParameters = allParameters;
        this.get = get;
        if (log.isDebugEnabled())
        {
            this.parametersDebug = allParameters.toString();
        }
        parseParameters();
    }

    private void parseParameters()
    {
        // Extract the batch id
        batchId = extractParameter(ProtocolConstants.INBOUND_KEY_BATCHID, THROW);
        if (!LocalUtil.isLetterOrDigitOrUnderline(batchId))
        {
            throw new SecurityException("Batch IDs must be a number");
        }

        // Extract the instance id
        instanceId = extractParameter(ProtocolConstants.INBOUND_KEY_INSTANCEID, THROW);
        if (!LocalUtil.isLetterOrDigitOrUnderline(instanceId))
        {
            throw new SecurityException("Batch instance IDs must be a number");
        }

        // Extract scriptSessionId
        scriptSessionId = extractParameter(ProtocolConstants.INBOUND_KEY_SCRIPT_SESSIONID, THROW);
        if (scriptSessionId.contains("/"))
        {
            dwrSessionId = scriptSessionId.substring(0, scriptSessionId.indexOf('/'));
        }
        else
        {
            dwrSessionId = "";
        }

        // Extract reverse ajax index (if present)
        String nextReverseAjaxIndexStr = extractParameter(ProtocolConstants.INBOUND_KEY_NEXT_REVERSE_AJAX_INDEX, null);
        if (nextReverseAjaxIndexStr != null)
        {
            nextReverseAjaxIndex = Long.parseLong(nextReverseAjaxIndexStr);
        }

        // Extract document domain (if present)
        documentDomain = extractParameter(ProtocolConstants.INBOUND_KEY_DOCUMENT_DOMAIN, null);

        page = LocalUtil.urlDecode(extractParameter(ProtocolConstants.INBOUND_KEY_PAGE, THROW));
    }

    /**
     * Extract a parameter and ensure it is in the request.
     * This is needed to cope with Jetty continuations that are not real
     * continuations.
     * @param paramName The name of the parameter sent
     * @param defaultValue ...
     * @return The found value
     */
    protected String extractParameter(String paramName, String defaultValue)
    {
        FormField formField = extraParameters.remove(paramName);
        if (formField != null)
        {
            return formField.getString();
        }

        if (defaultValue == THROW)
        {
            if (log.isDebugEnabled())
            {
                log.debug("Failed to find parameter: " + paramName + " in batch parameters:\n" + parametersDebug);
            }
            else
            {
                log.error("Failed to find parameter: " + paramName + ", enable debug logging to see more info.");
            }
            throw new IllegalArgumentException("Failed to find parameter: " + paramName + " (check server log for more info).");
        }
        else
        {
            return defaultValue;
        }
    }

    /**
     * Parse an HTTP POST request to fill out the scriptName, methodName and
     * paramList properties. This method should not fail unless it will not
     * be possible to return any sort of error to the user. Failure cases should
     * be handled by the checkParams() method.
     * @param req The original browser's request
     * @return The equivalent of HttpServletRequest.getParameterMap() for now
     * @throws ServerException If reading from the request body stream fails
     */
    private Map parsePost(HttpServletRequest req) throws ServerException
    {
        Map paramMap;

        if (isMultipartContent(req))
        {
            paramMap = UPLOADER.parseRequest(req);
        }
        else
        {
            paramMap = parseBasicPost(req);
        }

        // If there is only 1 param then this must be a broken Safari.
        if (paramMap.size() == 1)
        {
            parseBrokenMacPost(paramMap);
        }

        return paramMap;
    }

    /**
     * Utility method that determines whether the request contains multipart
     * content.
     * @param request The servlet request to be evaluated. Must be non-null.
     * @return true if the request is multipart, false otherwise.
     */
    public static boolean isMultipartContent(HttpServletRequest request)
    {
        if (!"post".equals(request.getMethod().toLowerCase()))
        {
            return false;
        }

        String contentType = request.getContentType();
        if (contentType == null)
        {
            return false;
        }

        if (contentType.toLowerCase().startsWith("multipart/"))
        {
            return true;
        }

        return false;
    }

    /**
     * The default parse case for a normal form submit
     * @param req The http request
     * @return a map of parsed parameters
     * @throws ServerException
     */
    private Map parseBasicPost(HttpServletRequest req) throws ServerException
    {
        Map paramMap;
        paramMap = new HashMap();
        StringBuilder debugBuf = new StringBuilder();

        BufferedReader in = null;
        try
        {
            String charEncoding = req.getCharacterEncoding();
            if (charEncoding != null)
            {
                in = new BufferedReader(new InputStreamReader(req.getInputStream(), charEncoding));
            }
            else
            {
                in = new BufferedReader(new InputStreamReader(req.getInputStream()));
            }

            while (true)
            {
                String line = in.readLine();

                if (line == null)
                {
                    if (paramMap.isEmpty())
                    {
                        // Normally speaking we should just bail out, but if
                        // we are using DWR with Acegi without ActiveX on IE,
                        // then Acegi 'fixes' the parameters for us.
                        Enumeration en = req.getParameterNames();
                        while (en.hasMoreElements())
                        {
                            String name = en.nextElement();
                            paramMap.put(name, new FormField(req.getParameter(name)));
                        }
                    }

                    break;
                }

                if (log.isDebugEnabled())
                {
                    debugBuf.append(line);
                    debugBuf.append("\n");
                }

                if (line.indexOf('&') != -1)
                {
                    // If there are any &'s then this must be iframe post and all the
                    // parameters have got dumped on one line, split with &
                    log.debug("Using iframe POST mode");
                    StringTokenizer st = new StringTokenizer(line, "&");
                    while (st.hasMoreTokens())
                    {
                        String part = st.nextToken();
                        part = LocalUtil.urlDecode(part);

                        parsePostLine(part, paramMap);
                    }
                }
                else
                {
                    // Hooray, this is a normal one!
                    parsePostLine(line, paramMap);
                }
            }
        }
        catch (Exception ex)
        {
            throw new ServerException("Failed to read input", ex);
        }
        finally
        {
            if (log.isDebugEnabled())
            {
                parametersDebug = debugBuf.toString();
            }

            if (in != null)
            {
                try
                {
                    in.close();
                }
                catch (IOException ex)
                {
                    // Ignore
                }
            }
        }
        return paramMap;
    }

    /**
     * All the parameters have got dumped on one line split with \n
     * See: http://bugzilla.opendarwin.org/show_bug.cgi?id=3565
     *      https://dwr.dev.java.net/issues/show_bug.cgi?id=93
     *      http://jira.atlassian.com/browse/JRA-8354
     *      http://developer.apple.com/internet/safari/uamatrix.html
     * @param paramMap The broken parsed parameter
     */
    private static void parseBrokenMacPost(Map paramMap)
    {
        // This looks like a broken Mac where the line endings are confused
        log.debug("Using Broken Safari POST mode");

        // Iterators insist that we call hasNext() before we start
        Iterator> it = paramMap.entrySet().iterator();
        if (!it.hasNext())
        {
            throw new IllegalStateException("No entries in non empty map!");
        }

        // So get the first
        Map.Entry entry = it.next();
        String key = entry.getKey();
        String value = entry.getValue().getString();
        String line = key + ProtocolConstants.INBOUND_DECL_SEPARATOR + value;

        StringTokenizer st = new StringTokenizer(line, "\n");
        while (st.hasMoreTokens())
        {
            String part = st.nextToken();
            part = LocalUtil.urlDecode(part);

            parsePostLine(part, paramMap);
        }
    }

    /**
     * Sort out a single line in a POST request
     * @param line The line to parse
     * @param paramMap The map to add parsed parameters to
     */
    private static void parsePostLine(String line, Map paramMap)
    {
        if (line.length() == 0)
        {
            return;
        }

        int sep = line.indexOf(ProtocolConstants.INBOUND_DECL_SEPARATOR);
        if (sep == -1)
        {
            paramMap.put(line, null);
        }
        else
        {
            String key = line.substring(0, sep);
            String value = line.substring(sep  + ProtocolConstants.INBOUND_DECL_SEPARATOR.length());

            paramMap.put(key, new FormField(value));
        }
    }

    /**
     * Parse an HTTP GET request to fill out the scriptName, methodName and
     * paramList properties. This method should not fail unless it will not
     * be possible to return any sort of error to the user. Failure cases should
     * be handled by the checkParams() method.
     * @param req The original browser's request
     * @return Simply HttpRequest.getParameterMap() for now
     * @throws ServerException If the parsing fails
     */
    private Map parseGet(HttpServletRequest req) throws ServerException
    {
        if (log.isDebugEnabled())
        {
            parametersDebug = req.getQueryString();
        }

        Map convertedMap = new HashMap();
        Map paramMap = req.getParameterMap();

        for (Map.Entry entry : paramMap.entrySet())
        {
            String key = entry.getKey();
            String[] array = entry.getValue();

            if (array.length == 1)
            {
                convertedMap.put(key, new FormField(array[0]));
            }
            else
            {
                log.error("Multiple values for key: " + key + "in parameters:\n" + req.getQueryString());
                throw new ServerException("Multiple values for key. See console for more information");
            }
        }

        return convertedMap;
    }

    /**
     * @return the batchId
     */
    public String getBatchId()
    {
        return batchId;
    }

    /**
     * The ID of this batch from the browser
     */
    private String batchId;

    /**
     * @return the instanceId
     */
    public String getInstanceId()
    {
        return instanceId;
    }

    /**
     * The ID of the DWR instance in the browser
     */
    private String instanceId;

    /**
     * @return the nextReverseAjaxIndex
     */
    public Long getNextReverseAjaxIndex()
    {
        return nextReverseAjaxIndex;
    }

    /**
     * The next expected reverse ajax index in the browser
     */
    private Long nextReverseAjaxIndex;

    /**
     * Is this request from a GET?
     * @return true if the request is a GET request
     */
    public boolean isGet()
    {
        return get;
    }

    /**
     * Is it a GET request?
     */
    private final boolean get;

    /**
     * @return the scriptSessionId
     */
    public String getScriptSessionId()
    {
        return scriptSessionId;
    }

    /**
     * The unique ID sent to the current page
     */
    private String scriptSessionId;

    /**
     * @return the httpSessionId
     */
    public String getDwrSessionId()
    {
        return dwrSessionId;
    }

    /**
     * The unique ID sent to the browser in the session cookie
     */
    private String dwrSessionId;

    /**
     * @return the page
     */
    public String getPage()
    {
        return page;
    }

    /**
     * The page that the request was sent from
     */
    private String page;

    /**
     * @return the document domain
     */
    public String getDocumentDomain()
    {
        return documentDomain;
    }

    /**
     * The document domain in effect on the page. Should be set in iframe remoting replies.
     */
    private String documentDomain;

    /**
     * @return the spareParameters
     */
    public Map getExtraParameters()
    {
        return extraParameters;
    }

    /**
     * All the parameters sent by the browser
     */
    private final Map extraParameters;

    /**
     * Raw parameter source text used for for debug logging
     */
    private String parametersDebug = null;

    /**
     * A special marker for the default value for extractParameter
     */
    protected static final String THROW = "throw";

    /**
     * What implementation of FileUpload are we using?
     */
    private static final FileUpload UPLOADER;

    /**
     * Retrieve the File Upload implementation
     */
    static
    {
        Container container = WebContextFactory.get().getContainer();
        UPLOADER = container.getBean(FileUpload.class);
    }

    /**
     * The log stream
     */
    private static final Log log = LogFactory.getLog(Batch.class);
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy