You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@pivot.apache.org by gb...@apache.org on 2010/01/29 22:44:53 UTC

svn commit: r904637 - in /pivot/trunk: web-server/src/org/apache/pivot/web/server/ web/src/org/apache/pivot/web/ web/test/org/apache/pivot/web/test/

Author: gbrown
Date: Fri Jan 29 21:44:52 2010
New Revision: 904637

URL: http://svn.apache.org/viewvc?rev=904637&view=rev
Log:
Add caseSensitiveKeys flag to QueryDictionary (HTTP headers are not case-sensitive); update QueryServlet API for consistency with Query API.

Removed:
    pivot/trunk/web-server/src/org/apache/pivot/web/server/ClientException.java
Modified:
    pivot/trunk/web-server/src/org/apache/pivot/web/server/QueryServlet.java
    pivot/trunk/web/src/org/apache/pivot/web/Query.java
    pivot/trunk/web/src/org/apache/pivot/web/QueryDictionary.java
    pivot/trunk/web/test/org/apache/pivot/web/test/QueryDictionaryTest.java

Modified: pivot/trunk/web-server/src/org/apache/pivot/web/server/QueryServlet.java
URL: http://svn.apache.org/viewvc/pivot/trunk/web-server/src/org/apache/pivot/web/server/QueryServlet.java?rev=904637&r1=904636&r2=904637&view=diff
==============================================================================
--- pivot/trunk/web-server/src/org/apache/pivot/web/server/QueryServlet.java (original)
+++ pivot/trunk/web-server/src/org/apache/pivot/web/server/QueryServlet.java Fri Jan 29 21:44:52 2010
@@ -26,140 +26,62 @@
 import java.net.URLDecoder;
 import java.util.Enumeration;
 
-import javax.security.auth.login.LoginException;
 import javax.servlet.ServletException;
 import javax.servlet.http.HttpServlet;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 
+import org.apache.pivot.collections.HashMap;
+import org.apache.pivot.serialization.CSVSerializer;
+import org.apache.pivot.serialization.JSONSerializer;
 import org.apache.pivot.serialization.SerializationException;
 import org.apache.pivot.serialization.Serializer;
-import org.apache.pivot.util.Base64;
+import org.apache.pivot.web.Query;
 import org.apache.pivot.web.QueryDictionary;
+import org.apache.pivot.web.QueryException;
 
 /**
- * Abstract base class for web query servlets. It is the server counterpart to
- * {@link org.apache.pivot.web.Query}.
+ * Abstract base class for query servlets.
  */
 public abstract class QueryServlet extends HttpServlet {
-    static final long serialVersionUID = -646654620936816287L;
+    private static final long serialVersionUID = 0;
 
-    /**
-     * Supported HTTP methods.
-     */
-    public enum Method {
-        GET,
-        POST,
-        PUT,
-        DELETE;
-    }
-
-    /**
-     * User credentials, which will be made availale if the servlet's
-     * <tt>authenticationRequired</tt> flag is set to <tt>true</tt>.
-     */
-    public static final class Credentials {
-        private String username;
-        private String password;
-
-        private Credentials(String username, String password) {
-            this.username = username;
-            this.password = password;
-        }
-
-        public String getUsername() {
-            return username;
-        }
-
-        public String getPassword() {
-            return password;
-        }
-    }
-
-    private boolean authenticationRequired = false;
     private boolean determineContentLength = false;
-
-    private transient ThreadLocal<Credentials> credentials = new ThreadLocal<Credentials>();
+    private HashMap<String, Class<? extends Serializer<?>>> serializerTypes
+        = new HashMap<String, Class<? extends Serializer<?>>>();
 
     private transient ThreadLocal<String> hostname = new ThreadLocal<String>();
-    private transient ThreadLocal<String> contextPath = new ThreadLocal<String>();
-    private transient ThreadLocal<String> queryPath = new ThreadLocal<String>();
     private transient ThreadLocal<Integer> port = new ThreadLocal<Integer>();
+    private transient ThreadLocal<String> servletPath = new ThreadLocal<String>();
     private transient ThreadLocal<Boolean> secure = new ThreadLocal<Boolean>();
-    private transient ThreadLocal<Method> method = new ThreadLocal<Method>();
 
     private transient ThreadLocal<QueryDictionary> parameters = new ThreadLocal<QueryDictionary>();
     private transient ThreadLocal<QueryDictionary> requestHeaders = new ThreadLocal<QueryDictionary>();
     private transient ThreadLocal<QueryDictionary> responseHeaders = new ThreadLocal<QueryDictionary>();
 
-    private static final String BASIC_AUTHENTICATION_TAG = "Basic";
-    private static final String HTTP_PROTOCOL = "http";
-    private static final String HTTPS_PROTOCOL = "https";
-    private static final String URL_ENCODING = "UTF-8";
+    public static final String HTTP_PROTOCOL = "http";
+    public static final String HTTPS_PROTOCOL = "https";
+    public static final String URL_ENCODING = "UTF-8";
+
+    public static final String ACTION_HEADER = "Action";
+    public static final String CONTENT_TYPE_HEADER = "Content-Type";
+    public static final String CONTENT_LENGTH_HEADER = "Content-Length";
+    public static final String LOCATION_HEADER = "Location";
 
-    /**
-     * Tells whether this servlet is configured to always determine the content
-     * length of outgoing responses and set the <tt>Content-Length</tt> HTTP
-     * response header accordingly. If this flag is <tt>false</tt>, it is up to
-     * the servlet's discretion as to when to set the <tt>Content-Length</tt>
-     * header (it will do so if it is trivially easy). If this is set to
-     * <tt>true</tt>, it will force the servlet to always set the header, but
-     * doing so will incur a performance penalty, as the servlet will be unable
-     * to stream the response directly to the HTTP output stream as it gets
-     * serialized.
-     */
-    public boolean isDetermineContentLength() {
-        return determineContentLength;
-    }
-
-    /**
-     * Sets the value of the <tt>determineContentLength</tt> flag.
-     *
-     * @see #isDetermineContentLength()
-     */
-    public void setDetermineContentLength(boolean determineContentLength) {
-        this.determineContentLength = determineContentLength;
-    }
+    @Override
+    public void init() throws ServletException {
+        super.init();
 
-    /**
-     * Tells whether or not this servlet will require authentication data. If
-     * set to <tt>true</tt>, and un-authenticated requests are received, the
-     * servlet will automatically respond with a request for authentication.
-     */
-    public boolean isAuthenticationRequired() {
-        return authenticationRequired;
-    }
+        // TODO Read determineContentLength and MIME type-serializer class mapping
+        // from init params
 
-    /**
-     * Sets whether or not this servlet will require authentication data. If
-     * set to <tt>true</tt>, and un-authenticated requests are received, the
-     * servlet will automatically respond with a request for authentication.
-     */
-    public void setAuthenticationRequired(boolean authenticationRequired) {
-        this.authenticationRequired = authenticationRequired;
+        serializerTypes.put(JSONSerializer.MIME_TYPE, JSONSerializer.class);
+        serializerTypes.put(CSVSerializer.MIME_TYPE, CSVSerializer.class);
 
-        if (!authenticationRequired) {
-            credentials.remove();
-        }
+        System.out.println(serializerTypes);
     }
 
     /**
-     * Creates a new serializer capable of serializing the objects that this
-     * servlet reads and writes. For <tt>GET</tt> requests, this serializer
-     * will be used to write the response back to the client. For <tt>PUT</tt>
-     * and <tt>POST</tt> requests, this serializer will be used to read the
-     * object passed by the client.
-     * <p>
-     * <b>Note</b>: Since this servlet may be accessed by multiple threads,
-     * subclasses should <b>not</b> re-use servlets unless they do so in a
-     * thread-safe manner.
-     *
-     * @return
-     * A new serializer
-     */
-    protected abstract Serializer<?> newSerializer();
-
-    /**
      * Gets the host name that was requested.
      */
     public String getHostname() {
@@ -167,26 +89,6 @@
     }
 
     /**
-     * Returns the portion of the request URI that indicates the context of the
-     * request. The context path always comes first in a request URI. The path
-     * starts with a "/" character but does not end with a "/" character. For
-     * servlets in the default (root) context, this method returns "".
-     */
-    public String getContextPath() {
-        return contextPath.get();
-    }
-
-    /**
-     * Returns the portion of the request URI that occurs after the context
-     * path but preceding the query string. It will start with a "/" character.
-     * For servlets in the default (root) context, this method returns the full
-     * path.
-     */
-    public String getQueryPath() {
-        return queryPath.get();
-    }
-
-    /**
      * Returns the Internet Protocol (IP) port number of the interface on which
      * the request was received.
      */
@@ -195,6 +97,13 @@
     }
 
     /**
+     * Returns the portion of the request URL representing the servlet path.
+     */
+    public String getServletPath() {
+        return servletPath.get();
+    }
+
+    /**
      * Tells whether the request has been ecrypted over HTTPS.
      */
     public boolean isSecure() {
@@ -209,22 +118,6 @@
     }
 
     /**
-     * Gets the HTTP method with which the current request was made.
-     */
-    public Method getMethod() {
-        return method.get();
-    }
-
-    /**
-     * Gets the authentication credentials that were extracted from the
-     * request. These are only available if the <tt>authenticationRequired</tt>
-     * flag is set to <tt>true</tt>.
-     */
-    public Credentials getCredentials() {
-        return credentials.get();
-    }
-
-    /**
      * Returns the servlet's parameter dictionary, which holds the values
      * passed in the HTTP request query string.
      */
@@ -249,151 +142,72 @@
     }
 
     /**
-     * Called when an HTTP GET is received. This base method throws
-     * <tt>UnsupportedOperationException</tt>, which will cause an HTTP 405
-     * (method not allowed) to be sent in the response. Subclasses should
-     * override this method if they wish to support GET requests.
-     * <p>
-     * Request parameters, and request/response headers are available to
-     * subclasses via the corresponding query dictionary.
-     *
-     * @return
-     * The object that was retrieved via the GET request. This object will be
-     * serialized by this servlet's serializer before being included in the
-     * HTTP response
-     *
-     * @throws ServletException
-     * If the server encounters an error while processing the request.
+     * Handles an HTTP GET request. The default implementation throws an HTTP
+     * 405 query exception.
      *
-     * @throws ClientException
-     * If the client request is invalid in any way. This will cause the client
-     * to receive an HTTP 400 (bad request) response.
+     * @param path
      *
-     * @throws UnsupportedOperationException
-     * If HTTP <tt>GET</tt> is not supported by the servlet.
+     * @return
+     * The result of the GET.
      *
-     * @see #getParameters()
-     * @see #getRequestHeaders()
-     * @see #getResponseHeaders()
+     * @throws QueryException
      */
-    protected Object doGet() throws ServletException, ClientException {
-        throw new UnsupportedOperationException();
+    public Object doGet(String path) throws QueryException {
+        throw new QueryException(Query.Status.METHOD_NOT_ALLOWED);
     }
 
     /**
-     * Called when an HTTP POST is received. This base method throws
-     * <tt>UnsupportedOperationException</tt>, which will cause an HTTP 405
-     * (method not allowed) to be sent in the response. Subclasses should
-     * override this method if they wish to support POST requests.
-     * <p>
-     * Request parameters, and request/response headers are available to
-     * subclasses via the corresponding query dictionary.
+     * Handles an HTTP POST request. The default implementation throws an HTTP
+     * 405 query exception.
      *
+     * @param path
      * @param value
-     * The object that is being posted by the client. This object will have
-     * been de-serialized from within the request by this servlet's serializer
      *
      * @return
-     * The URL identifying the location of the object that was posted. The
-     * semantics of this URL are up to the subclass to define
-     *
-     * @throws ServletException
-     * If the server encounters an error while processing the request.
-     *
-     * @throws ClientException
-     * If the client request is invalid in any way. This will cause the client
-     * to receive an HTTP 400 (bad request) response.
-     *
-     * @throws UnsupportedOperationException
-     * If HTTP <tt>POST</tt> is not supported by the servlet.
+     * A URL containing the location of the created resource.
      *
-     * @see #getParameters()
-     * @see #getRequestHeaders()
-     * @see #getResponseHeaders()
+     * @throws QueryException
      */
-    protected URL doPost(Object value) throws ServletException, ClientException {
-        throw new UnsupportedOperationException();
+    public URL doPost(String path, Object value) throws QueryException {
+        throw new QueryException(Query.Status.METHOD_NOT_ALLOWED);
     }
 
     /**
-     * Called when an HTTP PUT is received. This base method throws
-     * <tt>UnsupportedOperationException</tt>, which will cause an HTTP 405
-     * (method not allowed) to be sent in the response. Subclasses should
-     * override this method if they wish to support PUT requests.
-     * <p>
-     * Request parameters, and request/response headers are available to
-     * subclasses via the corresponding query dictionary.
-     *
-     * @param value
-     * The object that is being updated by the client. This object will have
-     * been de-serialized from within the request by this servlet's serializer
-     *
-     * @throws ServletException
-     * If the server encounters an error while processing the request.
-     *
-     * @throws ClientException
-     * If the client request is invalid in any way. This will cause the client
-     * to receive an HTTP 400 (bad request) response.
+     * Handles an HTTP POST/Action request. The default implementation throws an HTTP
+     * 405 query exception.
      *
-     * @throws UnsupportedOperationException
-     * If HTTP <tt>PUT</tt> is not supported by the servlet.
+     * @param path
+     * @param action
      *
-     * @see #getParameters()
-     * @see #getRequestHeaders()
-     * @see #getResponseHeaders()
+     * @throws QueryException
      */
-    protected void doPut(Object value) throws ServletException, ClientException {
-        throw new UnsupportedOperationException();
+    public void doPostAction(String path, String action) throws QueryException {
+        throw new QueryException(Query.Status.METHOD_NOT_ALLOWED);
     }
 
     /**
-     * Called when an HTTP DELETE is received. This base method throws
-     * <tt>UnsupportedOperationException</tt>, which will cause an HTTP 405
-     * (method not allowed) to be sent in the response. Subclasses should
-     * override this method if they wish to support DELETE requests.
-     * <p>
-     * Request parameters, and request/response headers are available to
-     * subclasses via the corresponding query dictionary.
+     * Handles an HTTP GET request. The default implementation throws an HTTP
+     * 405 query exception.
      *
-     * @throws ServletException
-     * If the server encounters an error while processing the request.
-     *
-     * @throws ClientException
-     * If the client request is invalid in any way. This will cause the client
-     * to receive an HTTP 400 (bad request) response.
-     *
-     * @throws UnsupportedOperationException
-     * If HTTP <tt>DELETE</tt> is not supported by the servlet.
+     * @param path
+     * @param value
      *
-     * @see #getParameters()
-     * @see #getRequestHeaders()
-     * @see #getResponseHeaders()
+     * @throws QueryException
      */
-    protected void doDelete() throws ServletException, ClientException {
-        throw new UnsupportedOperationException();
+    public void doPut(String path, Object value) throws QueryException {
+        throw new QueryException(Query.Status.METHOD_NOT_ALLOWED);
     }
 
     /**
-     * Authorizes the current request, and throws a <tt>LoginException</tt> if
-     * the request is not authorized. This method will only be called if the
-     * <tt>authenticationRequired</tt> flag is set to <tt>true</tt>. Subclasses
-     * wishing to authorize the authenticated user credentials may override
-     * this method to perform that authorization. On the other hand, the
-     * <tt>authorize</tt> method of <tt>QueryServlet</tt> does nothing, so
-     * subclasses that wish to authenticate the request but not authorize
-     * it may simply not override this method.
-     * <p>
-     * This method is guaranteed to be called after the arguments and request
-     * properties have been made available.
+     * Handles an HTTP GET request. The default implementation throws an HTTP
+     * 405 query exception.
      *
-     * @throws ServletException
-     * If a servlet exception is thrown.
+     * @param path
      *
-     * @throws LoginException
-     * If the request is not authorized.
+     * @throws QueryException
      */
-    protected void authorize() throws ServletException, LoginException {
-        // No-op
+    public void doDelete(String path) throws QueryException {
+        throw new QueryException(Query.Status.METHOD_NOT_ALLOWED);
     }
 
     @Override
@@ -401,100 +215,54 @@
     protected void service(HttpServletRequest request, HttpServletResponse response)
         throws IOException, ServletException {
         try {
-            parameters.set(new QueryDictionary());
-            requestHeaders.set(new QueryDictionary());
-            responseHeaders.set(new QueryDictionary());
-
-            boolean proceed = true;
-
-            if (authenticationRequired) {
-                String authorization = request.getHeader("Authorization");
-
-                if (authorization == null) {
-                    proceed = false;
-                    doUnauthorized(request, response);
-                } else {
-                    String encodedCredentials = authorization.substring
-                        (BASIC_AUTHENTICATION_TAG.length() + 1);
-                    String decodedCredentials = new String(Base64.decode(encodedCredentials));
-                    String[] credentialsPair = decodedCredentials.split(":");
-
-                    String username = credentialsPair.length > 0 ? credentialsPair[0] : "";
-                    String password = credentialsPair.length > 1 ? credentialsPair[1] : "";
-
-                    credentials.set(new Credentials(username, password));
-                }
+            try {
+                URL url = new URL(request.getRequestURL().toString());
+                hostname.set(url.getHost());
+                port.set(request.getLocalPort());
+                servletPath.set(request.getServletPath());
+                secure.set(url.getProtocol().equalsIgnoreCase(HTTPS_PROTOCOL));
+            } catch (MalformedURLException exception) {
+                throw new ServletException(exception);
             }
 
-            if (proceed) {
-                // Extract our location context
-                try {
-                    URL url = new URL(request.getRequestURL().toString());
-                    String requestURI = request.getRequestURI();
-                    String requestContext = request.getContextPath();
-
-                    hostname.set(url.getHost());
-                    contextPath.set(requestContext);
-                    queryPath.set(requestURI);
-                    port.set(request.getLocalPort());
-                    secure.set(url.getProtocol().equalsIgnoreCase(HTTPS_PROTOCOL));
-                    method.set(Method.valueOf(request.getMethod().toUpperCase()));
-
-                    if (requestURI.startsWith(requestContext)) {
-                        queryPath.set(requestURI.substring(requestContext.length()));
-                    }
-                } catch (MalformedURLException exception) {
-                    throw new ServletException(exception);
-                }
+            parameters.set(new QueryDictionary(true));
+            requestHeaders.set(new QueryDictionary(false));
+            responseHeaders.set(new QueryDictionary(false));
+
+            // Copy the query string into the arguments dictionary
+            String queryString = request.getQueryString();
+            if (queryString != null) {
+                QueryDictionary parametersDictionary = parameters.get();
+                String[] pairs = queryString.split("&");
 
-                // Copy the query string into our arguments dictionary
-                String queryString = request.getQueryString();
-                if (queryString != null) {
-                    QueryDictionary parametersDictionary = parameters.get();
-                    String[] pairs = queryString.split("&");
+                for (int i = 0, n = pairs.length; i < n; i++) {
+                    String[] pair = pairs[i].split("=");
 
-                    for (int i = 0, n = pairs.length; i < n; i++) {
-                        String[] pair = pairs[i].split("=");
+                    String key = URLDecoder.decode(pair[0], URL_ENCODING);
+                    String value = URLDecoder.decode((pair.length > 1) ? pair[1] : "", URL_ENCODING);
 
-                        String key = URLDecoder.decode(pair[0], URL_ENCODING);
-                        String value = URLDecoder.decode((pair.length > 1) ? pair[1] : "", URL_ENCODING);
-
-                        parametersDictionary.add(key, value);
-                    }
+                    parametersDictionary.add(key, value);
                 }
+            }
 
-                // Copy the request headers into our request properties dictionary
-                QueryDictionary requestHeaderDictionary = requestHeaders.get();
-                Enumeration<String> headerNames = request.getHeaderNames();
-                while (headerNames.hasMoreElements()) {
-                    String headerName = headerNames.nextElement();
-                    String headerValue = request.getHeader(headerName);
-
-                    requestHeaderDictionary.add(headerName, headerValue);
-                }
+            // Copy the request headers into the request properties dictionary
+            QueryDictionary requestHeaderDictionary = requestHeaders.get();
+            Enumeration<String> headerNames = request.getHeaderNames();
+            while (headerNames.hasMoreElements()) {
+                String headerName = headerNames.nextElement();
+                String headerValue = request.getHeader(headerName);
 
-                if (authenticationRequired) {
-                    try {
-                        authorize();
-                    } catch (LoginException exception) {
-                        proceed = false;
-                        doForbidden(request, response);
-                    }
-                }
+                requestHeaderDictionary.add(headerName, headerValue);
             }
 
-            if (proceed) {
-                super.service(request, response);
-            }
+            // Call the base method to process the request
+            super.service(request, response);
         } finally {
-            // Clean up our thread local variables
-            credentials.remove();
+            // Clean up thread local variables
             hostname.remove();
-            contextPath.remove();
-            queryPath.remove();
             port.remove();
+            servletPath.remove();
             secure.remove();
-            method.remove();
             parameters.remove();
             requestHeaders.remove();
             responseHeaders.remove();
@@ -502,14 +270,19 @@
     }
 
     @Override
-    @SuppressWarnings("unchecked")
     protected final void doGet(HttpServletRequest request, HttpServletResponse response)
         throws IOException, ServletException {
-        Serializer<Object> serializer = (Serializer<Object>)newSerializer();
+        Serializer<Object> serializer = createSerializer(request.getHeader(CONTENT_TYPE_HEADER));
 
+        Object result = null;
         try {
-            Object result = doGet();
+            result = doGet(request.getPathInfo());
+        } catch (QueryException exception) {
+            response.setStatus(exception.getStatus());
+            response.flushBuffer();
+        }
 
+        if (!response.isCommitted()) {
             response.setStatus(200);
             setResponseHeaders(response);
             response.setContentType(serializer.getMIMEType(result));
@@ -517,20 +290,22 @@
             OutputStream responseOutputStream = response.getOutputStream();
 
             if (determineContentLength) {
-                File tempFile = File.createTempFile("pivot", null);
+                File tempFile = File.createTempFile(getClass().getName(), null);
 
-                // Serialize our result to an intermediary file
+                // Serialize the result to an intermediary file
                 FileOutputStream fileOutputStream = new FileOutputStream(tempFile);
                 try {
                     serializer.writeObject(result, fileOutputStream);
+                } catch (SerializationException exception) {
+                    throw new ServletException(exception);
                 } finally {
                     fileOutputStream.close();
                 }
 
-                // Our content length is the length of the file in bytes
-                response.setHeader("Content-Length", String.valueOf(tempFile.length()));
+                // Set the content length header
+                response.setHeader(CONTENT_LENGTH_HEADER, String.valueOf(tempFile.length()));
 
-                // Now write the contents of the file out to our response
+                // Write the contents of the file out to the response
                 FileInputStream fileInputStream = new FileInputStream(tempFile);
                 try {
                     byte[] buffer = new byte[1024];
@@ -545,61 +320,87 @@
                     fileInputStream.close();
                 }
             } else {
-                serializer.writeObject(result, responseOutputStream);
+                try {
+                    serializer.writeObject(result, responseOutputStream);
+                } catch (SerializationException exception) {
+                    throw new ServletException(exception);
+                }
             }
-        } catch (UnsupportedOperationException exception) {
-            doMethodNotAllowed(response);
-        } catch (ClientException exception) {
-            doBadRequest(response);
-        } catch (SerializationException exception) {
-            throw new ServletException(exception);
+
+            response.flushBuffer();
         }
     }
 
     @Override
     protected final void doPost(HttpServletRequest request, HttpServletResponse response)
         throws IOException, ServletException {
-        Serializer<?> serializer = newSerializer();
+        Serializer<Object> serializer = createSerializer(request.getHeader(CONTENT_TYPE_HEADER));
+        String action = request.getHeader(ACTION_HEADER);
 
-        try {
-            Object value = serializer.readObject(request.getInputStream());
+        if (action == null) {
+            Object value = null;
+            try {
+                value = serializer.readObject(request.getInputStream());
+            } catch (SerializationException exception) {
+                throw new ServletException(exception);
+            }
 
-            URL url = doPost(value);
+            URL location = null;
+            try {
+                location = doPost(request.getPathInfo(), value);
+            } catch (QueryException exception) {
+                response.setStatus(exception.getStatus());
+                response.flushBuffer();
+            }
+
+            if (!response.isCommitted()) {
+                response.setStatus(201);
+                setResponseHeaders(response);
+                response.setHeader(LOCATION_HEADER, location.toString());
+                response.setContentLength(0);
+            }
+        } else {
+            try {
+                doPostAction(request.getPathInfo(), action);
+            } catch (QueryException exception) {
+                response.setStatus(exception.getStatus());
+                response.flushBuffer();
+            }
+
+            if (!response.isCommitted()) {
+                response.setStatus(204);
+                setResponseHeaders(response);
+                response.setContentLength(0);
+            }
 
-            response.setStatus(201);
-            setResponseHeaders(response);
-            response.setHeader("Location", url.toString());
-            response.setContentLength(0);
             response.flushBuffer();
-        } catch (UnsupportedOperationException exception) {
-            doMethodNotAllowed(response);
-        } catch (ClientException exception) {
-            doBadRequest(response);
-        } catch (SerializationException exception) {
-            throw new ServletException(exception);
         }
     }
 
     @Override
     protected final void doPut(HttpServletRequest request, HttpServletResponse response)
         throws IOException, ServletException {
-        Serializer<?> serializer = newSerializer();
+        Serializer<Object> serializer = createSerializer(request.getHeader(CONTENT_TYPE_HEADER));
 
+        Object value = null;
         try {
-            Object value = serializer.readObject(request.getInputStream());
+            value = serializer.readObject(request.getInputStream());
+        } catch (SerializationException exception) {
+            throw new ServletException(exception);
+        }
 
-            doPut(value);
+        try {
+            doPut(request.getPathInfo(), value);
+        } catch (QueryException exception) {
+            response.setStatus(exception.getStatus());
+            response.flushBuffer();
+        }
 
+        if (!response.isCommitted()) {
             response.setStatus(204);
             setResponseHeaders(response);
             response.setContentLength(0);
             response.flushBuffer();
-        } catch (UnsupportedOperationException exception) {
-            doMethodNotAllowed(response);
-        } catch (ClientException exception) {
-            doBadRequest(response);
-        } catch (SerializationException exception) {
-            throw new ServletException(exception);
         }
     }
 
@@ -607,63 +408,66 @@
     protected final void doDelete(HttpServletRequest request, HttpServletResponse response)
         throws IOException, ServletException {
         try {
-            doDelete();
+            doDelete(request.getPathInfo());
+        } catch (QueryException exception) {
+            response.setStatus(exception.getStatus());
+            response.flushBuffer();
+        }
 
+        if (!response.isCommitted()) {
             response.setStatus(204);
             setResponseHeaders(response);
             response.setContentLength(0);
             response.flushBuffer();
-        } catch (UnsupportedOperationException exception) {
-            doMethodNotAllowed(response);
-        } catch (ClientException exception) {
-            doBadRequest(response);
         }
     }
 
     @Override
     protected final void doHead(HttpServletRequest request, HttpServletResponse response)
         throws IOException, ServletException {
-        doMethodNotAllowed(response);
+        response.setStatus(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
+        response.flushBuffer();
     }
 
     @Override
     protected final void doOptions(HttpServletRequest request, HttpServletResponse response)
         throws IOException, ServletException {
-        doMethodNotAllowed(response);
+        response.setStatus(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
+        response.flushBuffer();
     }
 
     @Override
     protected final void doTrace(HttpServletRequest request, HttpServletResponse response)
         throws IOException, ServletException {
-        doMethodNotAllowed(response);
-    }
-
-    private void doUnauthorized(HttpServletRequest request, HttpServletResponse response)
-        throws IOException {
-        response.setHeader("WWW-Authenticate", "BASIC realm=\""
-            + request.getServletPath() +"\"");
-        response.setStatus(401);
-        response.setContentLength(0);
+        response.setStatus(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
         response.flushBuffer();
     }
 
-    private void doForbidden(HttpServletRequest request, HttpServletResponse response)
-        throws IOException {
-        response.setStatus(403);
-        response.setContentLength(0);
-        response.flushBuffer();
-    }
+    @SuppressWarnings("unchecked")
+    private Serializer<Object> createSerializer(String mimeType)
+        throws ServletException {
+        if (mimeType == null) {
+            mimeType = JSONSerializer.MIME_TYPE;
+        } else {
+            mimeType = mimeType.substring(0, mimeType.indexOf(';'));
+            mimeType = mimeType.trim();
+        }
 
-    private void doMethodNotAllowed(HttpServletResponse response) throws IOException {
-        response.setStatus(405);
-        response.setContentLength(0);
-        response.flushBuffer();
-    }
+        Class<? extends Serializer<?>> serializerType = serializerTypes.get(mimeType);
+        if (serializerType == null) {
+            throw new ServletException("A serializer for " + mimeType + " not found.");
+        }
 
-    private void doBadRequest(HttpServletResponse response) throws IOException {
-        response.setStatus(400);
-        response.setContentLength(0);
-        response.flushBuffer();
+        Serializer<Object> serializer = null;
+        try {
+            serializer = (Serializer<Object>)serializerType.newInstance();
+        } catch (InstantiationException exception) {
+            throw new ServletException(exception);
+        } catch (IllegalAccessException exception) {
+            throw new ServletException(exception);
+        }
+
+        return serializer;
     }
 
     private void setResponseHeaders(HttpServletResponse response) {

Modified: pivot/trunk/web/src/org/apache/pivot/web/Query.java
URL: http://svn.apache.org/viewvc/pivot/trunk/web/src/org/apache/pivot/web/Query.java?rev=904637&r1=904636&r2=904637&view=diff
==============================================================================
--- pivot/trunk/web/src/org/apache/pivot/web/Query.java (original)
+++ pivot/trunk/web/src/org/apache/pivot/web/Query.java Fri Jan 29 21:44:52 2010
@@ -139,9 +139,9 @@
     private HostnameVerifier hostnameVerifier = null;
     private Proxy proxy = null;
 
-    private QueryDictionary parameters = new QueryDictionary();
-    private QueryDictionary requestHeaders = new QueryDictionary();
-    private QueryDictionary responseHeaders = new QueryDictionary();
+    private QueryDictionary parameters = new QueryDictionary(true);
+    private QueryDictionary requestHeaders = new QueryDictionary(false);
+    private QueryDictionary responseHeaders = new QueryDictionary(false);
     private int status = 0;
 
     private volatile long bytesExpected = -1;

Modified: pivot/trunk/web/src/org/apache/pivot/web/QueryDictionary.java
URL: http://svn.apache.org/viewvc/pivot/trunk/web/src/org/apache/pivot/web/QueryDictionary.java?rev=904637&r1=904636&r2=904637&view=diff
==============================================================================
--- pivot/trunk/web/src/org/apache/pivot/web/QueryDictionary.java (original)
+++ pivot/trunk/web/src/org/apache/pivot/web/QueryDictionary.java Fri Jan 29 21:44:52 2010
@@ -28,27 +28,46 @@
  * multiple values to be set against a given key.
  */
 public final class QueryDictionary implements Dictionary<String, String>, Iterable<String> {
-    private HashMap<String, ArrayList<String>> map =  new HashMap<String, ArrayList<String>>();
+    private boolean caseSensitiveKeys;
+    private HashMap<String, ArrayList<String>> map = new HashMap<String, ArrayList<String>>();
+
+    public QueryDictionary(boolean caseSensitiveKeys) {
+        this.caseSensitiveKeys = caseSensitiveKeys;
+    }
 
     @Override
     public String get(String key) {
+        if (caseSensitiveKeys) {
+            key = key.toLowerCase();
+        }
+
         ArrayList<String> list = map.get(key);
         if (list != null && list.getLength() > 0) {
             return list.get(0);
         }
+
         return null;
     }
 
     public String get(String key, int index) {
+        if (caseSensitiveKeys) {
+            key = key.toLowerCase();
+        }
+
         ArrayList<String> list = map.get(key);
         if (list == null || list.getLength() <= index) {
             throw new IndexOutOfBoundsException();
         }
+
         return list.get(index);
     }
 
     @Override
     public String put(String key, String value) {
+        if (caseSensitiveKeys) {
+            key = key.toLowerCase();
+        }
+
         ArrayList<String> list = new ArrayList<String>();
         list.add(value);
 
@@ -61,6 +80,10 @@
     }
 
     public int add(String key, String value) {
+        if (caseSensitiveKeys) {
+            key = key.toLowerCase();
+        }
+
         ArrayList<String> list = map.get(key);
         if (list == null) {
             put(key, value);
@@ -72,6 +95,10 @@
     }
 
     public void insert(String key, String value, int index) {
+        if (caseSensitiveKeys) {
+            key = key.toLowerCase();
+        }
+
         ArrayList<String> list = map.get(key);
 
         // e.g if index = 0 and length = 0, throw an exception
@@ -84,6 +111,10 @@
 
     @Override
     public String remove(String key) {
+        if (caseSensitiveKeys) {
+            key = key.toLowerCase();
+        }
+
         ArrayList<String> list = map.remove(key);
         if (list != null && list.getLength() > 0) {
             return list.get(0);
@@ -93,10 +124,15 @@
     }
 
     public String remove(String key, int index) {
+        if (caseSensitiveKeys) {
+            key = key.toLowerCase();
+        }
+
         ArrayList<String> list = map.get(key);
         if (list == null || list.getLength() <= index) {
             throw new IndexOutOfBoundsException();
         }
+
         return list.get(index);
     }
 
@@ -106,11 +142,19 @@
 
     @Override
     public boolean containsKey(String key) {
+        if (caseSensitiveKeys) {
+            key = key.toLowerCase();
+        }
+
         return map.containsKey(key);
     }
 
 
     public int getLength(String key) {
+        if (caseSensitiveKeys) {
+            key = key.toLowerCase();
+        }
+
         ArrayList<String> list = map.get(key);
         if (list == null) {
             return 0;

Modified: pivot/trunk/web/test/org/apache/pivot/web/test/QueryDictionaryTest.java
URL: http://svn.apache.org/viewvc/pivot/trunk/web/test/org/apache/pivot/web/test/QueryDictionaryTest.java?rev=904637&r1=904636&r2=904637&view=diff
==============================================================================
--- pivot/trunk/web/test/org/apache/pivot/web/test/QueryDictionaryTest.java (original)
+++ pivot/trunk/web/test/org/apache/pivot/web/test/QueryDictionaryTest.java Fri Jan 29 21:44:52 2010
@@ -31,7 +31,7 @@
 public class QueryDictionaryTest {
     @Test(expected=IndexOutOfBoundsException.class)
     public void testQueryDictionary() {
-        QueryDictionary dict = new QueryDictionary();
+        QueryDictionary dict = new QueryDictionary(true);
 
         assertNull(dict.get("key"));