| /* |
| * Copyright 2009 Google Inc. |
| * |
| * 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 com.google.gwt.user.server.rpc; |
| |
| import static com.google.gwt.user.client.rpc.RpcRequestBuilder.STRONG_NAME_HEADER; |
| |
| import java.io.IOException; |
| |
| import javax.servlet.ServletContext; |
| import javax.servlet.ServletException; |
| import javax.servlet.http.HttpServlet; |
| import javax.servlet.http.HttpServletRequest; |
| import javax.servlet.http.HttpServletResponse; |
| |
| /** |
| * An abstract base class containing utility methods. |
| */ |
| public abstract class AbstractRemoteServiceServlet extends HttpServlet { |
| |
| protected transient ThreadLocal<HttpServletRequest> perThreadRequest; |
| protected transient ThreadLocal<HttpServletResponse> perThreadResponse; |
| |
| public AbstractRemoteServiceServlet() { |
| super(); |
| } |
| |
| /** |
| * Standard HttpServlet method: handle the POST. Delegates to |
| * {@link #processPost(HttpServletRequest, HttpServletResponse)}. |
| * |
| * This doPost method swallows ALL exceptions, logs them in the |
| * ServletContext, and returns a GENERIC_FAILURE_MSG response with status code |
| * 500. |
| */ |
| @Override |
| public final void doPost(HttpServletRequest request, |
| HttpServletResponse response) { |
| // Ensure the thread-local data fields have been initialized |
| |
| try { |
| // Store the request & response objects in thread-local storage. |
| // |
| synchronized (this) { |
| validateThreadLocalData(); |
| perThreadRequest.set(request); |
| perThreadResponse.set(response); |
| } |
| |
| processPost(request, response); |
| |
| } catch (Throwable e) { |
| // Give a subclass a chance to either handle the exception or rethrow it |
| // |
| doUnexpectedFailure(e); |
| } finally { |
| // null the thread-locals to avoid holding request/response |
| // |
| perThreadRequest.set(null); |
| perThreadResponse.set(null); |
| } |
| } |
| |
| /** |
| * Override this method to control what should happen when an exception |
| * escapes the {@link #doPost} method. The default implementation will log the |
| * failure and send a generic failure response to the client. |
| * <p> |
| * An "expected failure" is an exception thrown by a service method that is |
| * declared in the signature of the service method. These exceptions are |
| * serialized back to the client, and are not passed to this method. This |
| * method is called only for exceptions or errors that are not part of the |
| * service method's signature, or that result from SecurityExceptions, |
| * SerializationExceptions, or other failures within the RPC framework. |
| * <p> |
| * Note that if the desired behavior is to both send the GENERIC_FAILURE_MSG |
| * response AND to rethrow the exception, then this method should first send |
| * the GENERIC_FAILURE_MSG response itself (using getThreadLocalResponse), and |
| * then rethrow the exception. Rethrowing the exception will cause it to |
| * escape into the servlet container. |
| * |
| * @param e the exception which was thrown |
| */ |
| protected void doUnexpectedFailure(Throwable e) { |
| try { |
| getThreadLocalResponse().reset(); |
| } catch (IllegalStateException ex) { |
| /* |
| * If we can't reset the request, the only way to signal that something |
| * has gone wrong is to throw an exception from here. It should be the |
| * case that we call the user's implementation code before emitting data |
| * into the response, so the only time that gets tripped is if the object |
| * serialization code blows up. |
| */ |
| throw new RuntimeException("Unable to report failure", e); |
| } |
| ServletContext servletContext = getServletContext(); |
| RPCServletUtils.writeResponseForUnexpectedFailure(servletContext, |
| getThreadLocalResponse(), e); |
| } |
| |
| /** |
| * Returns the strong name of the permutation, as reported by the client that |
| * issued the request, or <code>null</code> if it could not be determined. |
| * This information is encoded in the |
| * {@value com.google.gwt.user.client.rpc.RpcRequestBuilder#STRONG_NAME_HEADER} |
| * HTTP header. |
| */ |
| protected final String getPermutationStrongName() { |
| return getThreadLocalRequest().getHeader(STRONG_NAME_HEADER); |
| } |
| |
| /** |
| * Gets the <code>HttpServletRequest</code> object for the current call. It is |
| * stored thread-locally so that simultaneous invocations can have different |
| * request objects. |
| */ |
| protected final HttpServletRequest getThreadLocalRequest() { |
| synchronized (this) { |
| validateThreadLocalData(); |
| return perThreadRequest.get(); |
| } |
| } |
| |
| /** |
| * Gets the <code>HttpServletResponse</code> object for the current call. It |
| * is stored thread-locally so that simultaneous invocations can have |
| * different response objects. |
| */ |
| protected final HttpServletResponse getThreadLocalResponse() { |
| synchronized (this) { |
| validateThreadLocalData(); |
| return perThreadResponse.get(); |
| } |
| } |
| |
| /** |
| * Override this method to examine the deserialized version of the request |
| * before the call to the servlet method is made. The default implementation |
| * does nothing and need not be called by subclasses. |
| * |
| * @param rpcRequest |
| */ |
| protected void onAfterRequestDeserialized(RPCRequest rpcRequest) { |
| } |
| |
| /** |
| * Called by {@link #doPost} for type-specific processing of the request. |
| * Because <code>doPost</code> swallows all <code>Throwables</code>, this |
| * method may throw any exception the implementor wishes. |
| */ |
| protected abstract void processPost(HttpServletRequest request, |
| HttpServletResponse response) throws Throwable; |
| |
| /** |
| * Override this method in order to control the parsing of the incoming |
| * request. For example, you may want to bypass the check of the Content-Type |
| * and character encoding headers in the request, as some proxies re-write the |
| * request headers. Note that bypassing these checks may expose the servlet to |
| * some cross-site vulnerabilities. Your implementation should comply with the |
| * HTTP/1.1 specification, which includes handling both requests which include |
| * a Content-Length header and requests utilizing <code>Transfer-Encoding: |
| * chuncked</code>. |
| * |
| * @param request the incoming request |
| * @return the content of the incoming request encoded as a string. |
| */ |
| protected String readContent(HttpServletRequest request) |
| throws ServletException, IOException { |
| return RPCServletUtils.readContentAsGwtRpc(request); |
| } |
| |
| /** |
| * Initializes the perThreadRequest and perThreadResponse fields if they are |
| * null. This will occur the first time they are accessed after an instance of |
| * this class is constructed or deserialized. This method should be called |
| * from within a 'synchronized(this) {}' block in order to ensure that only |
| * one thread creates the objects. |
| */ |
| private void validateThreadLocalData() { |
| if (perThreadRequest == null) { |
| perThreadRequest = new ThreadLocal<HttpServletRequest>(); |
| } |
| if (perThreadResponse == null) { |
| perThreadResponse = new ThreadLocal<HttpServletResponse>(); |
| } |
| } |
| } |