You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@pivot.apache.org by tv...@apache.org on 2009/08/04 16:18:06 UTC

svn commit: r800814 - /incubator/pivot/trunk/web/src/org/apache/pivot/web/server/QueryServlet.java

Author: tvolkert
Date: Tue Aug  4 14:18:05 2009
New Revision: 800814

URL: http://svn.apache.org/viewvc?rev=800814&view=rev
Log:
PIVOT-172 :: Change QueryServlet to use ThreadLocal storage where appropriate

Modified:
    incubator/pivot/trunk/web/src/org/apache/pivot/web/server/QueryServlet.java

Modified: incubator/pivot/trunk/web/src/org/apache/pivot/web/server/QueryServlet.java
URL: http://svn.apache.org/viewvc/incubator/pivot/trunk/web/src/org/apache/pivot/web/server/QueryServlet.java?rev=800814&r1=800813&r2=800814&view=diff
==============================================================================
--- incubator/pivot/trunk/web/src/org/apache/pivot/web/server/QueryServlet.java (original)
+++ incubator/pivot/trunk/web/src/org/apache/pivot/web/server/QueryServlet.java Tue Aug  4 14:18:05 2009
@@ -84,33 +84,102 @@
     }
 
     private boolean authenticationRequired = false;
-    private Credentials credentials = null;
-
-    private String hostname;
-    private String contextPath;
-    private String queryPath;
-    private int port;
-    private boolean secure;
-    private Method method;
-
     private boolean determineContentLength = false;
 
-    private QueryDictionary parameters = new QueryDictionary();
-    private QueryDictionary requestHeaders = new QueryDictionary();
-    private QueryDictionary responseHeaders = new QueryDictionary();
-
     private Serializer<?> serializer = new JSONSerializer();
 
+    private transient ThreadLocal<Credentials> credentials = new ThreadLocal<Credentials>();
+
+    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<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";
 
     /**
+     * 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;
+    }
+
+    /**
+     * 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;
+    }
+
+    /**
+     * 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;
+
+        if (!authenticationRequired) {
+            credentials = null;
+        }
+    }
+
+    /**
+     * Returns the serializer used to stream the value passed to or from the
+     * web query. By default, an instance of {@link JSONSerializer} is used.
+     */
+    public Serializer<?> getSerializer() {
+        return serializer;
+    }
+
+    /**
+     * Sets the serializer used to stream the value passed to or from the
+     * web query.
+     *
+     * @param serializer
+     * The serializer (must be non-null).
+     */
+    public void setSerializer(Serializer<?> serializer) {
+        if (serializer == null) {
+            throw new IllegalArgumentException("serializer is null.");
+        }
+
+        this.serializer = serializer;
+    }
+
+    /**
      * Gets the host name that was requested.
      */
     public String getHostname() {
-        return hostname;
+        return hostname.get();
     }
 
     /**
@@ -120,7 +189,7 @@
      * servlets in the default (root) context, this method returns "".
      */
     public String getContextPath() {
-        return contextPath;
+        return contextPath.get();
     }
 
     /**
@@ -130,7 +199,7 @@
      * path.
      */
     public String getQueryPath() {
-        return queryPath;
+        return queryPath.get();
     }
 
     /**
@@ -138,14 +207,14 @@
      * the request was received.
      */
     public int getPort() {
-        return port;
+        return port.get();
     }
 
     /**
      * Tells whether the request has been ecrypted over HTTPS.
      */
     public boolean isSecure() {
-        return secure;
+        return secure.get();
     }
 
     /**
@@ -159,53 +228,7 @@
      * Gets the HTTP method with which the current request was made.
      */
     public Method getMethod() {
-        return method;
-    }
-
-    /**
-     * 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;
-    }
-
-    /**
-     * 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;
-    }
-
-    /**
-     * 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;
-
-        if (!authenticationRequired) {
-            credentials = null;
-        }
+        return method.get();
     }
 
     /**
@@ -214,7 +237,7 @@
      * flag is set to <tt>true</tt>.
      */
     public Credentials getCredentials() {
-        return credentials;
+        return credentials.get();
     }
 
     /**
@@ -222,7 +245,7 @@
      * passed in the HTTP request query string.
      */
     public QueryDictionary getParameters() {
-        return parameters;
+        return parameters.get();
     }
 
     /**
@@ -230,7 +253,7 @@
      * request headers.
      */
     public QueryDictionary getRequestHeaders() {
-        return requestHeaders;
+        return requestHeaders.get();
     }
 
     /**
@@ -238,55 +261,89 @@
      * response headers that will be sent back to the client.
      */
     public QueryDictionary getResponseHeaders() {
-        return responseHeaders;
-    }
-
-    /**
-     * Returns the serializer used to stream the value passed to or from the
-     * web query. By default, an instance of {@link JSONSerializer} is used.
-     */
-    public Serializer<?> getSerializer() {
-        return serializer;
-    }
-
-    /**
-     * Sets the serializer used to stream the value passed to or from the
-     * web query.
-     *
-     * @param serializer
-     * The serializer (must be non-null).
-     */
-    public void setSerializer(Serializer<?> serializer) {
-        if (serializer == null) {
-            throw new IllegalArgumentException("serializer is null.");
-        }
-
-        this.serializer = serializer;
+        return responseHeaders.get();
     }
 
     /**
+     * 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
+     *
+     * @see #getParameters()
+     * @see #getRequestHeaders()
+     * @see #getResponseHeaders()
      */
     protected Object doGet() throws ServletException {
         throw new UnsupportedOperationException();
     }
 
     /**
+     * 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.
      *
+     * @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
+     *
+     * @see #getParameters()
+     * @see #getRequestHeaders()
+     * @see #getResponseHeaders()
      */
     protected URL doPost(Object value) throws ServletException {
         throw new UnsupportedOperationException();
     }
 
     /**
+     * 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
+     *
+     * @see #getParameters()
+     * @see #getRequestHeaders()
+     * @see #getResponseHeaders()
      */
     protected void doPut(Object value) throws ServletException {
         throw new UnsupportedOperationException();
     }
 
     /**
+     * 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.
      *
+     * @see #getParameters()
+     * @see #getRequestHeaders()
+     * @see #getResponseHeaders()
      */
     protected void doDelete() throws ServletException {
         throw new UnsupportedOperationException();
@@ -299,7 +356,7 @@
      * 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 authorization
+     * 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
@@ -316,92 +373,104 @@
     @SuppressWarnings("unchecked")
     protected void service(HttpServletRequest request, HttpServletResponse response)
         throws IOException, ServletException {
-        boolean proceed = true;
-
-        if (authenticationRequired) {
-            String authorization = request.getHeader("Authorization");
+        try {
+            parameters.set(new QueryDictionary());
+            requestHeaders.set(new QueryDictionary());
+            responseHeaders.set(new QueryDictionary());
 
-            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(":");
+            boolean proceed = true;
 
-                String username = credentialsPair.length > 0 ? credentialsPair[0] : "";
-                String password = credentialsPair.length > 1 ? credentialsPair[1] : "";
+            if (authenticationRequired) {
+                String authorization = request.getHeader("Authorization");
 
-                if (credentials == null) {
-                    credentials = new Credentials(username, password);
+                if (authorization == null) {
+                    proceed = false;
+                    doUnauthorized(request, response);
                 } else {
-                    credentials.username = username;
-                    credentials.password = password;
-                }
-            }
-        }
+                    String encodedCredentials = authorization.substring
+                        (BASIC_AUTHENTICATION_TAG.length() + 1);
+                    String decodedCredentials = new String(Base64.decode(encodedCredentials));
+                    String[] credentialsPair = decodedCredentials.split(":");
 
-        if (proceed) {
-            // Extract our location context
-            try {
-                URL url = new URL(request.getRequestURL().toString());
-
-                hostname = url.getHost();
-                contextPath = request.getContextPath();
-                queryPath = request.getRequestURI();
-                port = request.getLocalPort();
-                secure = url.getProtocol().equalsIgnoreCase(HTTPS_PROTOCOL);
-                method = Method.valueOf(request.getMethod().toUpperCase());
+                    String username = credentialsPair.length > 0 ? credentialsPair[0] : "";
+                    String password = credentialsPair.length > 1 ? credentialsPair[1] : "";
 
-                if (queryPath.startsWith(contextPath)) {
-                    queryPath = queryPath.substring(contextPath.length());
+                    credentials.set(new Credentials(username, password));
                 }
-            } catch (MalformedURLException ex) {
-                throw new ServletException(ex);
             }
 
-            // Clear out any remnants of the previous service
-            parameters.clear();
-            requestHeaders.clear();
-            responseHeaders.clear();
-
-            // Copy the query string into our arguments dictionary
-            String queryString = request.getQueryString();
-            if (queryString != null) {
-                String[] pairs = queryString.split("&");
+            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);
+                }
+
+                // 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);
 
-                    parameters.add(key, value);
+                        parametersDictionary.add(key, value);
+                    }
                 }
-            }
 
-            // Copy the request headers into our request properties dictionary
-            Enumeration<String> headerNames = request.getHeaderNames();
-            while (headerNames.hasMoreElements()) {
-                String headerName = headerNames.nextElement();
-                String headerValue = request.getHeader(headerName);
+                // 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);
 
-                requestHeaders.add(headerName, headerValue);
-            }
+                    requestHeaderDictionary.add(headerName, headerValue);
+                }
 
-            if (authenticationRequired) {
-                try {
-                    authorize();
-                } catch (LoginException ex) {
-                    proceed = false;
-                    doForbidden(request, response);
+                if (authenticationRequired) {
+                    try {
+                        authorize();
+                    } catch (LoginException exception) {
+                        proceed = false;
+                        doForbidden(request, response);
+                    }
                 }
             }
-        }
 
-        if (proceed) {
-            super.service(request, response);
+            if (proceed) {
+                super.service(request, response);
+            }
+        } finally {
+            // Clean up our thread local variables
+            credentials.remove();
+            hostname.remove();
+            contextPath.remove();
+            queryPath.remove();
+            port.remove();
+            secure.remove();
+            method.remove();
+            parameters.remove();
+            requestHeaders.remove();
+            responseHeaders.remove();
         }
     }
 
@@ -451,10 +520,10 @@
             } else {
                 serializer.writeObject(result, responseOutputStream);
             }
-        } catch (UnsupportedOperationException ex) {
+        } catch (UnsupportedOperationException exception) {
             doMethodNotAllowed(response);
-        } catch (SerializationException ex) {
-            throw new ServletException(ex);
+        } catch (SerializationException exception) {
+            throw new ServletException(exception);
         }
     }
 
@@ -471,10 +540,10 @@
             response.setHeader("Location", url.toString());
             response.setContentLength(0);
             response.flushBuffer();
-        } catch (UnsupportedOperationException ex) {
+        } catch (UnsupportedOperationException exception) {
             doMethodNotAllowed(response);
-        } catch (SerializationException ex) {
-            throw new ServletException(ex);
+        } catch (SerializationException exception) {
+            throw new ServletException(exception);
         }
     }
 
@@ -490,10 +559,10 @@
             setResponseHeaders(response);
             response.setContentLength(0);
             response.flushBuffer();
-        } catch (UnsupportedOperationException ex) {
+        } catch (UnsupportedOperationException exception) {
             doMethodNotAllowed(response);
-        } catch (SerializationException ex) {
-            throw new ServletException(ex);
+        } catch (SerializationException exception) {
+            throw new ServletException(exception);
         }
     }
 
@@ -507,7 +576,7 @@
             setResponseHeaders(response);
             response.setContentLength(0);
             response.flushBuffer();
-        } catch (UnsupportedOperationException ex) {
+        } catch (UnsupportedOperationException exception) {
             doMethodNotAllowed(response);
         }
     }
@@ -565,9 +634,11 @@
      *
      */
     private void setResponseHeaders(HttpServletResponse response) {
-        for (String key : responseHeaders) {
-            for (int i = 0, n = responseHeaders.getLength(key); i < n; i++) {
-                response.addHeader(key, responseHeaders.get(key, i));
+        QueryDictionary responseHeaderDictionary = responseHeaders.get();
+
+        for (String key : responseHeaderDictionary) {
+            for (int i = 0, n = responseHeaderDictionary.getLength(key); i < n; i++) {
+                response.addHeader(key, responseHeaderDictionary.get(key, i));
             }
         }
     }