001: /*
002: * Copyright 2007 Google Inc.
003: *
004: * Licensed under the Apache License, Version 2.0 (the "License"); you may not
005: * use this file except in compliance with the License. You may obtain a copy of
006: * the License at
007: *
008: * http://www.apache.org/licenses/LICENSE-2.0
009: *
010: * Unless required by applicable law or agreed to in writing, software
011: * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
012: * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
013: * License for the specific language governing permissions and limitations under
014: * the License.
015: */
016: package com.google.gwt.user.server.rpc;
017:
018: import com.google.gwt.user.client.rpc.IncompatibleRemoteServiceException;
019: import com.google.gwt.user.client.rpc.SerializationException;
020:
021: import java.io.IOException;
022: import java.io.InputStream;
023: import java.net.MalformedURLException;
024: import java.net.URL;
025: import java.text.ParseException;
026: import java.util.HashMap;
027: import java.util.Map;
028:
029: import javax.servlet.ServletContext;
030: import javax.servlet.http.HttpServlet;
031: import javax.servlet.http.HttpServletRequest;
032: import javax.servlet.http.HttpServletResponse;
033:
034: /**
035: * The servlet base class for your RPC service implementations that
036: * automatically deserializes incoming requests from the client and serializes
037: * outgoing responses for client/server RPCs.
038: */
039: public class RemoteServiceServlet extends HttpServlet implements
040: SerializationPolicyProvider {
041:
042: private final ThreadLocal<HttpServletRequest> perThreadRequest = new ThreadLocal<HttpServletRequest>();
043:
044: private final ThreadLocal<HttpServletResponse> perThreadResponse = new ThreadLocal<HttpServletResponse>();
045:
046: /**
047: * A cache of moduleBaseURL and serialization policy strong name to
048: * {@link SerializationPolicy}.
049: */
050: private final Map<String, SerializationPolicy> serializationPolicyCache = new HashMap<String, SerializationPolicy>();
051:
052: /**
053: * The default constructor.
054: */
055: public RemoteServiceServlet() {
056: }
057:
058: /**
059: * Standard HttpServlet method: handle the POST.
060: *
061: * This doPost method swallows ALL exceptions, logs them in the
062: * ServletContext, and returns a GENERIC_FAILURE_MSG response with status code
063: * 500.
064: */
065: @Override
066: public final void doPost(HttpServletRequest request,
067: HttpServletResponse response) {
068: try {
069: // Store the request & response objects in thread-local storage.
070: //
071: perThreadRequest.set(request);
072: perThreadResponse.set(response);
073:
074: // Read the request fully.
075: //
076: String requestPayload = RPCServletUtils
077: .readContentAsUtf8(request);
078:
079: // Let subclasses see the serialized request.
080: //
081: onBeforeRequestDeserialized(requestPayload);
082:
083: // Invoke the core dispatching logic, which returns the serialized
084: // result.
085: //
086: String responsePayload = processCall(requestPayload);
087:
088: // Let subclasses see the serialized response.
089: //
090: onAfterResponseSerialized(responsePayload);
091:
092: // Write the response.
093: //
094: writeResponse(request, response, responsePayload);
095: return;
096: } catch (Throwable e) {
097: // Give a subclass a chance to either handle the exception or rethrow it
098: //
099: doUnexpectedFailure(e);
100: } finally {
101: // null the thread-locals to avoid holding request/response
102: //
103: perThreadRequest.set(null);
104: perThreadResponse.set(null);
105: }
106: }
107:
108: public final SerializationPolicy getSerializationPolicy(
109: String moduleBaseURL, String strongName) {
110:
111: SerializationPolicy serializationPolicy = getCachedSerializationPolicy(
112: moduleBaseURL, strongName);
113: if (serializationPolicy != null) {
114: return serializationPolicy;
115: }
116:
117: serializationPolicy = doGetSerializationPolicy(
118: getThreadLocalRequest(), moduleBaseURL, strongName);
119:
120: if (serializationPolicy == null) {
121: // Failed to get the requested serialization policy; use the default
122: getServletContext()
123: .log(
124: "WARNING: Failed to get the SerializationPolicy '"
125: + strongName
126: + "' for module '"
127: + moduleBaseURL
128: + "'; a legacy, 1.3.3 compatible, serialization policy will be used. You may experience SerializationExceptions as a result.");
129: serializationPolicy = RPC.getDefaultSerializationPolicy();
130: }
131:
132: // This could cache null or an actual instance. Either way we will not
133: // attempt to lookup the policy again.
134: putCachedSerializationPolicy(moduleBaseURL, strongName,
135: serializationPolicy);
136:
137: return serializationPolicy;
138: }
139:
140: /**
141: * Process a call originating from the given request. Uses the
142: * {@link RPC#invokeAndEncodeResponse(Object, java.lang.reflect.Method, Object[])}
143: * method to do the actual work.
144: * <p>
145: * Subclasses may optionally override this method to handle the payload in any
146: * way they desire (by routing the request to a framework component, for
147: * instance). The {@link HttpServletRequest} and {@link HttpServletResponse}
148: * can be accessed via the {@link #getThreadLocalRequest()} and
149: * {@link #getThreadLocalResponse()} methods.
150: * </p>
151: * This is public so that it can be unit tested easily without HTTP.
152: *
153: * @param payload the UTF-8 request payload
154: * @return a string which encodes either the method's return, a checked
155: * exception thrown by the method, or an
156: * {@link IncompatibleRemoteServiceException}
157: * @throws SerializationException if we cannot serialize the response
158: * @throws UnexpectedException if the invocation throws a checked exception
159: * that is not declared in the service method's signature
160: * @throws RuntimeException if the service method throws an unchecked
161: * exception (the exception will be the one thrown by the service)
162: */
163: public String processCall(String payload)
164: throws SerializationException {
165: try {
166: RPCRequest rpcRequest = RPC.decodeRequest(payload, this
167: .getClass(), this );
168: return RPC.invokeAndEncodeResponse(this , rpcRequest
169: .getMethod(), rpcRequest.getParameters(),
170: rpcRequest.getSerializationPolicy());
171: } catch (IncompatibleRemoteServiceException ex) {
172: getServletContext()
173: .log(
174: "An IncompatibleRemoteServiceException was thrown while processing this call.",
175: ex);
176: return RPC.encodeResponseForFailure(null, ex);
177: }
178: }
179:
180: /**
181: * Gets the {@link SerializationPolicy} for given module base URL and strong
182: * name if there is one.
183: *
184: * Override this method to provide a {@link SerializationPolicy} using an
185: * alternative approach.
186: *
187: * @param request the HTTP request being serviced
188: * @param moduleBaseURL as specified in the incoming payload
189: * @param strongName a strong name that uniquely identifies a serialization
190: * policy file
191: * @return a {@link SerializationPolicy} for the given module base URL and
192: * strong name, or <code>null</code> if there is none
193: */
194: protected SerializationPolicy doGetSerializationPolicy(
195: HttpServletRequest request, String moduleBaseURL,
196: String strongName) {
197: // The request can tell you the path of the web app relative to the
198: // container root.
199: String contextPath = request.getContextPath();
200:
201: String modulePath = null;
202: if (moduleBaseURL != null) {
203: try {
204: modulePath = new URL(moduleBaseURL).getPath();
205: } catch (MalformedURLException ex) {
206: // log the information, we will default
207: getServletContext()
208: .log(
209: "Malformed moduleBaseURL: "
210: + moduleBaseURL, ex);
211: }
212: }
213:
214: SerializationPolicy serializationPolicy = null;
215:
216: /*
217: * Check that the module path must be in the same web app as the servlet
218: * itself. If you need to implement a scheme different than this, override
219: * this method.
220: */
221: if (modulePath == null || !modulePath.startsWith(contextPath)) {
222: String message = "ERROR: The module path requested, "
223: + modulePath
224: + ", is not in the same web application as this servlet, "
225: + contextPath
226: + ". Your module may not be properly configured or your client and server code maybe out of date.";
227: getServletContext().log(message);
228: } else {
229: // Strip off the context path from the module base URL. It should be a
230: // strict prefix.
231: String contextRelativePath = modulePath
232: .substring(contextPath.length());
233:
234: String serializationPolicyFilePath = SerializationPolicyLoader
235: .getSerializationPolicyFileName(contextRelativePath
236: + strongName);
237:
238: // Open the RPC resource file read its contents.
239: InputStream is = getServletContext().getResourceAsStream(
240: serializationPolicyFilePath);
241: try {
242: if (is != null) {
243: try {
244: serializationPolicy = SerializationPolicyLoader
245: .loadFromStream(is);
246: } catch (ParseException e) {
247: getServletContext().log(
248: "ERROR: Failed to parse the policy file '"
249: + serializationPolicyFilePath
250: + "'", e);
251: } catch (ClassNotFoundException e) {
252: getServletContext()
253: .log(
254: "ERROR: Could not find class '"
255: + e.getMessage()
256: + "' listed in the serialization policy file '"
257: + serializationPolicyFilePath
258: + "'"
259: + "; your server's classpath may be misconfigured",
260: e);
261: } catch (IOException e) {
262: getServletContext().log(
263: "ERROR: Could not read the policy file '"
264: + serializationPolicyFilePath
265: + "'", e);
266: }
267: } else {
268: String message = "ERROR: The serialization policy file '"
269: + serializationPolicyFilePath
270: + "' was not found; did you forget to include it in this deployment?";
271: getServletContext().log(message);
272: }
273: } finally {
274: if (is != null) {
275: try {
276: is.close();
277: } catch (IOException e) {
278: // Ignore this error
279: }
280: }
281: }
282: }
283:
284: return serializationPolicy;
285: }
286:
287: /**
288: * Override this method to control what should happen when an exception
289: * escapes the {@link #processCall(String)} method. The default implementation
290: * will log the failure and send a generic failure response to the client.<p/>
291: *
292: * An "expected failure" is an exception thrown by a service method that is
293: * declared in the signature of the service method. These exceptions are
294: * serialized back to the client, and are not passed to this method. This
295: * method is called only for exceptions or errors that are not part of the
296: * service method's signature, or that result from SecurityExceptions,
297: * SerializationExceptions, or other failures within the RPC framework.<p/>
298: *
299: * Note that if the desired behavior is to both send the GENERIC_FAILURE_MSG
300: * response AND to rethrow the exception, then this method should first send
301: * the GENERIC_FAILURE_MSG response itself (using getThreadLocalResponse), and
302: * then rethrow the exception. Rethrowing the exception will cause it to
303: * escape into the servlet container.
304: *
305: * @param e the exception which was thrown
306: */
307: protected void doUnexpectedFailure(Throwable e) {
308: ServletContext servletContext = getServletContext();
309: RPCServletUtils.writeResponseForUnexpectedFailure(
310: servletContext, getThreadLocalResponse(), e);
311: }
312:
313: /**
314: * Gets the <code>HttpServletRequest</code> object for the current call. It
315: * is stored thread-locally so that simultaneous invocations can have
316: * different request objects.
317: */
318: protected final HttpServletRequest getThreadLocalRequest() {
319: return perThreadRequest.get();
320: }
321:
322: /**
323: * Gets the <code>HttpServletResponse</code> object for the current call. It
324: * is stored thread-locally so that simultaneous invocations can have
325: * different response objects.
326: */
327: protected final HttpServletResponse getThreadLocalResponse() {
328: return perThreadResponse.get();
329: }
330:
331: /**
332: * Override this method to examine the serialized response that will be
333: * returned to the client. The default implementation does nothing and need
334: * not be called by subclasses.
335: */
336: protected void onAfterResponseSerialized(String serializedResponse) {
337: }
338:
339: /**
340: * Override this method to examine the serialized version of the request
341: * payload before it is deserialized into objects. The default implementation
342: * does nothing and need not be called by subclasses.
343: */
344: protected void onBeforeRequestDeserialized(String serializedRequest) {
345: }
346:
347: /**
348: * Determines whether the response to a given servlet request should or should
349: * not be GZIP compressed. This method is only called in cases where the
350: * requestor accepts GZIP encoding.
351: * <p>
352: * This implementation currently returns <code>true</code> if the response
353: * string's estimated byte length is longer than 256 bytes. Subclasses can
354: * override this logic.
355: * </p>
356: *
357: * @param request the request being served
358: * @param response the response that will be written into
359: * @param responsePayload the payload that is about to be sent to the client
360: * @return <code>true</code> if responsePayload should be GZIP compressed,
361: * otherwise <code>false</code>.
362: */
363: protected boolean shouldCompressResponse(
364: HttpServletRequest request, HttpServletResponse response,
365: String responsePayload) {
366: return RPCServletUtils
367: .exceedsUncompressedContentLengthLimit(responsePayload);
368: }
369:
370: private SerializationPolicy getCachedSerializationPolicy(
371: String moduleBaseURL, String strongName) {
372: synchronized (serializationPolicyCache) {
373: return serializationPolicyCache.get(moduleBaseURL
374: + strongName);
375: }
376: }
377:
378: private void putCachedSerializationPolicy(String moduleBaseURL,
379: String strongName, SerializationPolicy serializationPolicy) {
380: synchronized (serializationPolicyCache) {
381: serializationPolicyCache.put(moduleBaseURL + strongName,
382: serializationPolicy);
383: }
384: }
385:
386: private void writeResponse(HttpServletRequest request,
387: HttpServletResponse response, String responsePayload)
388: throws IOException {
389: boolean gzipEncode = RPCServletUtils
390: .acceptsGzipEncoding(request)
391: && shouldCompressResponse(request, response,
392: responsePayload);
393:
394: RPCServletUtils.writeResponse(getServletContext(), response,
395: responsePayload, gzipEncode);
396: }
397: }
|