From 1972cb51d135442e30b288eb13d7a0e859fb753e Mon Sep 17 00:00:00 2001 From: Gerard Keiser Date: Fri, 27 Jun 2025 11:58:53 -0500 Subject: [PATCH 1/2] Replace servlet context logging with JUL logging in com.google.gwt.user.server.rpc --- .../rpc/AbstractRemoteServiceServlet.java | 10 ++-- .../gwt/user/server/rpc/RPCServletUtils.java | 32 +++++++++--- .../user/server/rpc/RemoteServiceServlet.java | 50 +++++++++++-------- .../server/rpc/RemoteServiceServletTest.java | 9 ++-- 4 files changed, 59 insertions(+), 42 deletions(-) diff --git a/user/src/com/google/gwt/user/server/rpc/AbstractRemoteServiceServlet.java b/user/src/com/google/gwt/user/server/rpc/AbstractRemoteServiceServlet.java index a21e4fb7a3f..bca9fc8d276 100644 --- a/user/src/com/google/gwt/user/server/rpc/AbstractRemoteServiceServlet.java +++ b/user/src/com/google/gwt/user/server/rpc/AbstractRemoteServiceServlet.java @@ -19,7 +19,6 @@ import java.io.IOException; -import javax.servlet.ServletContext; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; @@ -41,9 +40,8 @@ public AbstractRemoteServiceServlet() { * 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. + * This doPost method swallows ALL exceptions, logs them, + * and returns a GENERIC_FAILURE_MSG response with status code 500. */ @Override public final void doPost(HttpServletRequest request, @@ -106,9 +104,7 @@ protected void doUnexpectedFailure(Throwable e) { */ throw new RuntimeException("Unable to report failure", e); } - ServletContext servletContext = getServletContext(); - RPCServletUtils.writeResponseForUnexpectedFailure(servletContext, - getThreadLocalResponse(), e); + RPCServletUtils.writeResponseForUnexpectedFailure(getThreadLocalResponse(), e); } /** diff --git a/user/src/com/google/gwt/user/server/rpc/RPCServletUtils.java b/user/src/com/google/gwt/user/server/rpc/RPCServletUtils.java index 8a2a50fb012..c205a88b66d 100644 --- a/user/src/com/google/gwt/user/server/rpc/RPCServletUtils.java +++ b/user/src/com/google/gwt/user/server/rpc/RPCServletUtils.java @@ -24,6 +24,8 @@ import java.nio.charset.StandardCharsets; import java.util.Locale; import java.util.concurrent.ConcurrentHashMap; +import java.util.logging.Level; +import java.util.logging.Logger; import java.util.zip.GZIPOutputStream; import javax.servlet.ServletContext; @@ -37,6 +39,8 @@ */ public class RPCServletUtils { + private static final Logger logger = Logger.getLogger(RPCServletUtils.class.getName()); + public static final String CHARSET_UTF8_NAME = "UTF-8"; /** @@ -319,12 +323,19 @@ public static boolean shouldGzipResponseContent(HttpServletRequest request, && exceedsUncompressedContentLengthLimit(responseContent); } + @Deprecated + public static void writeResponse(ServletContext servletContext, + HttpServletResponse response, String responseContent, boolean gzipResponse) + throws IOException { + writeResponse(response, responseContent, gzipResponse); + } + + /** * Write the response content into the {@link HttpServletResponse}. If * gzipResponse is true, the response content will * be gzipped prior to being written into the response. * - * @param servletContext servlet context for this response * @param response response instance * @param responseContent a string containing the response content * @param gzipResponse if true the response content will be gzip @@ -332,7 +343,7 @@ public static boolean shouldGzipResponseContent(HttpServletRequest request, * @throws IOException if reading, writing, or closing the response's output * stream fails */ - public static void writeResponse(ServletContext servletContext, + public static void writeResponse( HttpServletResponse response, String responseContent, boolean gzipResponse) throws IOException { @@ -363,7 +374,7 @@ public static void writeResponse(ServletContext servletContext, } if (caught != null) { - servletContext.log("Unable to compress response", caught); + logger.log(Level.SEVERE, "Unable to compress response", caught); response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); return; } @@ -378,18 +389,23 @@ public static void writeResponse(ServletContext servletContext, response.getOutputStream().write(responseBytes); } + @Deprecated + public static void writeResponseForUnexpectedFailure( + ServletContext servletContext, HttpServletResponse response, + Throwable failure) { + writeResponseForUnexpectedFailure(response, failure); + } + /** * Called when the servlet itself has a problem, rather than the invoked * third-party method. It writes a simple 500 message back to the client. * - * @param servletContext * @param response * @param failure */ public static void writeResponseForUnexpectedFailure( - ServletContext servletContext, HttpServletResponse response, - Throwable failure) { - servletContext.log("Exception while dispatching incoming RPC call", failure); + HttpServletResponse response, Throwable failure) { + logger.log(Level.SEVERE, "Exception while dispatching incoming RPC call", failure); // Send GENERIC_FAILURE_MSG with 500 status. // @@ -403,7 +419,7 @@ public static void writeResponseForUnexpectedFailure( response.getWriter().write(GENERIC_FAILURE_MSG); } } catch (IOException ex) { - servletContext.log( + logger.log(Level.SEVERE, "respondWithUnexpectedFailure failed while sending the previous failure to the client", ex); } diff --git a/user/src/com/google/gwt/user/server/rpc/RemoteServiceServlet.java b/user/src/com/google/gwt/user/server/rpc/RemoteServiceServlet.java index 1bd73e142ef..d25733d9df8 100644 --- a/user/src/com/google/gwt/user/server/rpc/RemoteServiceServlet.java +++ b/user/src/com/google/gwt/user/server/rpc/RemoteServiceServlet.java @@ -30,6 +30,8 @@ import java.text.ParseException; import java.util.HashMap; import java.util.Map; +import java.util.logging.Level; +import java.util.logging.Logger; import javax.servlet.ServletConfig; import javax.servlet.ServletException; @@ -45,6 +47,8 @@ public class RemoteServiceServlet extends AbstractRemoteServiceServlet implements SerializationPolicyProvider { + private static final Logger logger = Logger.getLogger(RemoteServiceServlet.class.getName()); + /** * Loads a serialization policy stored as a servlet resource in the same * ServletContext as this servlet. Returns null if not found. @@ -62,7 +66,7 @@ static SerializationPolicy loadSerializationPolicy(HttpServlet servlet, modulePath = new URL(moduleBaseURL).getPath(); } catch (MalformedURLException ex) { // log the information, we will default - servlet.log("Malformed moduleBaseURL: " + moduleBaseURL, ex); + logger.log(Level.SEVERE, "Malformed moduleBaseURL: " + moduleBaseURL, ex); } } @@ -74,12 +78,12 @@ static SerializationPolicy loadSerializationPolicy(HttpServlet servlet, * this method. */ if (modulePath == null || !modulePath.startsWith(contextPath)) { - String message = "ERROR: The module path requested, " + String message = "The module path requested, " + modulePath + ", is not in the same web application as this servlet, " + contextPath + ". Your module may not be properly configured or your client and server code maybe out of date."; - servlet.log(message); + logger.log(Level.SEVERE, message); } else { // Strip off the context path from the module base URL. It should be a // strict prefix. @@ -98,11 +102,13 @@ static SerializationPolicy loadSerializationPolicy(HttpServlet servlet, null); if (serializationPolicy.hasClientFields()) { if (ENABLE_ENHANCED_CLASSES) { - servlet.log("WARNING: Service deserializes enhanced JPA/JDO classes, which is " + + logger.log(Level.WARNING, + "Service deserializes enhanced JPA/JDO classes, which is " + "unsafe. See https://github.com/gwtproject/gwt/issues/9709 for more " + "detail on the vulnerability that this presents."); } else { - servlet.log("ERROR: Service deserializes enhanced JPA/JDO classes, which is " + + logger.log(Level.SEVERE, + "Service deserializes enhanced JPA/JDO classes, which is " + "unsafe. Review build logs to see which classes are affected, or set " + ENABLE_GWT_ENHANCED_CLASSES_PROPERTY + " to true to allow using this " + "service. See https://github.com/gwtproject/gwt/issues/9709 for more " + @@ -111,17 +117,19 @@ static SerializationPolicy loadSerializationPolicy(HttpServlet servlet, } } } catch (ParseException e) { - servlet.log("ERROR: Failed to parse the policy file '" + logger.log(Level.SEVERE, + "Failed to parse the policy file '" + serializationPolicyFilePath + "'", e); } catch (IOException e) { - servlet.log("ERROR: Could not read the policy file '" + logger.log(Level.SEVERE, + "Could not read the policy file '" + serializationPolicyFilePath + "'", e); } } else { - String message = "ERROR: The serialization policy file '" + String message = "The serialization policy file '" + serializationPolicyFilePath + "' was not found; did you forget to include it in this deployment?"; - servlet.log(message); + logger.log(Level.SEVERE, message); } } finally { if (is != null) { @@ -260,8 +268,8 @@ public final SerializationPolicy getSerializationPolicy(String moduleBaseURL, if (serializationPolicy == null) { // Failed to get the requested serialization policy; use the default - log( - "WARNING: Failed to get the SerializationPolicy '" + logger.log(Level.WARNING, + "Failed to get the SerializationPolicy '" + strongName + "' for module '" + moduleBaseURL @@ -311,7 +319,8 @@ public String processCall(String payload) throws SerializationException { try { rpcRequest = RPC.decodeRequest(payload, delegate.getClass(), this); } catch (IncompatibleRemoteServiceException ex) { - log( + logger.log( + Level.SEVERE, "An IncompatibleRemoteServiceException was thrown while processing this call.", ex); return RPC.encodeResponseForFailedRequest(null, ex); @@ -350,12 +359,13 @@ public String processCall(RPCRequest rpcRequest) throws SerializationException { rpcRequest.getParameters(), rpcRequest.getSerializationPolicy(), rpcRequest.getFlags()); } catch (IncompatibleRemoteServiceException ex) { - log( + logger.log(Level.SEVERE, "An IncompatibleRemoteServiceException was thrown while processing this call.", ex); return RPC.encodeResponseForFailedRequest(rpcRequest, ex); } catch (RpcTokenException tokenException) { - log("An RpcTokenException was thrown while processing this call.", + logger.log(Level.SEVERE, + "An RpcTokenException was thrown while processing this call.", tokenException); return RPC.encodeResponseForFailedRequest(rpcRequest, tokenException); } @@ -364,9 +374,8 @@ public String processCall(RPCRequest rpcRequest) throws SerializationException { /** * Standard HttpServlet method: handle the POST. * - * This doPost method swallows ALL exceptions, logs them in the - * ServletContext, and returns a GENERIC_FAILURE_MSG response with status code - * 500. + * This doPost method swallows ALL exceptions, logs them, + * and returns a GENERIC_FAILURE_MSG response with status code 500. * * @throws ServletException * @throws SerializationException @@ -469,12 +478,12 @@ protected SerializationPolicy loadPolicyFromCodeServer(String url) { @Override public void logInfo(String message) { - RemoteServiceServlet.this.log(message); + logger.log(Level.INFO, message); } @Override public void logError(String message, Throwable throwable) { - RemoteServiceServlet.this.log(message, throwable); + logger.log(Level.SEVERE, message, throwable); } }; return CODE_SERVER_CLIENT.loadPolicy(url, adapter); @@ -541,7 +550,6 @@ private void writeResponse(HttpServletRequest request, boolean gzipEncode = RPCServletUtils.acceptsGzipEncoding(request) && shouldCompressResponse(request, response, responsePayload); - RPCServletUtils.writeResponse(getServletContext(), response, - responsePayload, gzipEncode); + RPCServletUtils.writeResponse(response, responsePayload, gzipEncode); } } diff --git a/user/test/com/google/gwt/user/server/rpc/RemoteServiceServletTest.java b/user/test/com/google/gwt/user/server/rpc/RemoteServiceServletTest.java index 0cd88ac5454..a6d3a6c89ac 100644 --- a/user/test/com/google/gwt/user/server/rpc/RemoteServiceServletTest.java +++ b/user/test/com/google/gwt/user/server/rpc/RemoteServiceServletTest.java @@ -96,7 +96,6 @@ public String getServletName() { } private class MockServletContext implements ServletContext { - private String messageLogged; public MockServletContext() { } @@ -190,7 +189,7 @@ public void log(String arg0) { } public void log(String arg0, Throwable arg1) { - messageLogged = arg0; + throw new UnsupportedOperationException(); } public void removeAttribute(String arg0) { @@ -329,7 +328,6 @@ public InputStream getResourceAsStream(String resource) { SerializationPolicy serializationPolicy = rss.doGetSerializationPolicy( mockRequest, "http://www.google.com/MyModule", "12345"); assertNull(serializationPolicy); - assertNotNull(mockContext.messageLogged); } /** @@ -337,8 +335,8 @@ public InputStream getResourceAsStream(String resource) { * {@link com.google.gwt.user.server.rpc.RemoteServiceServlet#doGetSerializationPolicy(javax.servlet.http.HttpServletRequest, java.lang.String, java.lang.String)}. * * This method tests that if the module path is in a different context than - * the RemoteServiceServlet which is processing the request, a message will be - * logged and null is returned for the SerializationPolicy. + * the RemoteServiceServlet which is processing the request, + * null is returned for the SerializationPolicy. */ public void testDoGetSerializationPolicy_ModuleInSeparateServlet() throws ServletException { @@ -353,7 +351,6 @@ public void testDoGetSerializationPolicy_ModuleInSeparateServlet() mockRequest.contextPath = "/foo"; SerializationPolicy serializationPolicy = rss.doGetSerializationPolicy( mockRequest, "http://www.google.com/MyModule", ""); - assertNotNull(mockContext.messageLogged); assertNull(serializationPolicy); } From 7593341df758a7ce2dd157a5bc29fc45cc13523d Mon Sep 17 00:00:00 2001 From: gkeiser Date: Thu, 25 Sep 2025 15:14:46 -0500 Subject: [PATCH 2/2] Add RPC logging facade backed by service loader --- maven/lib-gwt.sh | 2 +- user/build.xml | 19 ++++ ...gwt.user.server.rpc.logging.LoggerProvider | 4 + .../gwt/user/server/rpc/RPCServletUtils.java | 13 +-- .../user/server/rpc/RemoteServiceServlet.java | 56 +++++----- .../server/rpc/SerializationPolicyClient.java | 37 +++---- .../server/rpc/logging/JulLoggerProvider.java | 43 ++++++++ .../server/rpc/logging/Log4jProvider.java | 25 +++++ .../user/server/rpc/logging/LogManager.java | 102 ++++++++++++++++++ .../gwt/user/server/rpc/logging/Logger.java | 44 ++++++++ .../server/rpc/logging/LoggerDelegate.java | 13 +++ .../server/rpc/logging/LoggerProvider.java | 15 +++ .../rpc/logging/PlatformLoggerProvider.java | 21 ++++ .../server/rpc/logging/ReflectiveLogger.java | 65 +++++++++++ .../rpc/logging/ReflectiveLoggerProvider.java | 39 +++++++ .../logging/ServletContextLoggerProvider.java | 47 ++++++++ .../server/rpc/RemoteServiceServletTest.java | 12 ++- 17 files changed, 497 insertions(+), 60 deletions(-) create mode 100644 user/src/META-INF/services/com.google.gwt.user.server.rpc.logging.LoggerProvider create mode 100644 user/src/com/google/gwt/user/server/rpc/logging/JulLoggerProvider.java create mode 100644 user/src/com/google/gwt/user/server/rpc/logging/Log4jProvider.java create mode 100644 user/src/com/google/gwt/user/server/rpc/logging/LogManager.java create mode 100644 user/src/com/google/gwt/user/server/rpc/logging/Logger.java create mode 100644 user/src/com/google/gwt/user/server/rpc/logging/LoggerDelegate.java create mode 100644 user/src/com/google/gwt/user/server/rpc/logging/LoggerProvider.java create mode 100644 user/src/com/google/gwt/user/server/rpc/logging/PlatformLoggerProvider.java create mode 100644 user/src/com/google/gwt/user/server/rpc/logging/ReflectiveLogger.java create mode 100644 user/src/com/google/gwt/user/server/rpc/logging/ReflectiveLoggerProvider.java create mode 100644 user/src/com/google/gwt/user/server/rpc/logging/ServletContextLoggerProvider.java diff --git a/maven/lib-gwt.sh b/maven/lib-gwt.sh index 24b1386b4b6..113f1ed13a4 100644 --- a/maven/lib-gwt.sh +++ b/maven/lib-gwt.sh @@ -86,7 +86,7 @@ function maven-gwt() { zip -q $GWT_EXTRACT_DIR/gwt-user.jar --copy --out $GWT_EXTRACT_DIR/gwt-user-trimmed.jar \ "com/google/gwt/*" "com/google/web/bindery/*" "javaemul/*" \ "javax/validation/*" "org/hibernate/validator/*" \ - "org/w3c/flute/*" + "org/w3c/flute/*" "META-INF/services/*" mv $GWT_EXTRACT_DIR/gwt-user-trimmed.jar $GWT_EXTRACT_DIR/gwt-user.jar for i in $gwtLibs diff --git a/user/build.xml b/user/build.xml index 2af156012ff..9a6c3ba1b94 100755 --- a/user/build.xml +++ b/user/build.xml @@ -163,6 +163,24 @@ + + + + + + + + + + + + + + + + + + @@ -178,6 +196,7 @@ + diff --git a/user/src/META-INF/services/com.google.gwt.user.server.rpc.logging.LoggerProvider b/user/src/META-INF/services/com.google.gwt.user.server.rpc.logging.LoggerProvider new file mode 100644 index 00000000000..aecd0eb215d --- /dev/null +++ b/user/src/META-INF/services/com.google.gwt.user.server.rpc.logging.LoggerProvider @@ -0,0 +1,4 @@ +com.google.gwt.user.server.rpc.logging.JulLoggerProvider +com.google.gwt.user.server.rpc.logging.Log4jProvider +com.google.gwt.user.server.rpc.logging.PlatformLoggerProvider +# No no-args constructor: com.google.gwt.user.server.rpc.logging.ServletContextLoggerProvider diff --git a/user/src/com/google/gwt/user/server/rpc/RPCServletUtils.java b/user/src/com/google/gwt/user/server/rpc/RPCServletUtils.java index c205a88b66d..cf514e39d6c 100644 --- a/user/src/com/google/gwt/user/server/rpc/RPCServletUtils.java +++ b/user/src/com/google/gwt/user/server/rpc/RPCServletUtils.java @@ -16,6 +16,9 @@ package com.google.gwt.user.server.rpc; +import com.google.gwt.user.server.rpc.logging.LogManager; +import com.google.gwt.user.server.rpc.logging.Logger; + import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; @@ -24,8 +27,6 @@ import java.nio.charset.StandardCharsets; import java.util.Locale; import java.util.concurrent.ConcurrentHashMap; -import java.util.logging.Level; -import java.util.logging.Logger; import java.util.zip.GZIPOutputStream; import javax.servlet.ServletContext; @@ -39,7 +40,7 @@ */ public class RPCServletUtils { - private static final Logger logger = Logger.getLogger(RPCServletUtils.class.getName()); + private static final Logger logger = LogManager.getLogger(RPCServletUtils.class); public static final String CHARSET_UTF8_NAME = "UTF-8"; @@ -374,7 +375,7 @@ public static void writeResponse( } if (caught != null) { - logger.log(Level.SEVERE, "Unable to compress response", caught); + logger.error("Unable to compress response", caught); response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); return; } @@ -405,7 +406,7 @@ public static void writeResponseForUnexpectedFailure( */ public static void writeResponseForUnexpectedFailure( HttpServletResponse response, Throwable failure) { - logger.log(Level.SEVERE, "Exception while dispatching incoming RPC call", failure); + logger.error("Exception while dispatching incoming RPC call", failure); // Send GENERIC_FAILURE_MSG with 500 status. // @@ -419,7 +420,7 @@ public static void writeResponseForUnexpectedFailure( response.getWriter().write(GENERIC_FAILURE_MSG); } } catch (IOException ex) { - logger.log(Level.SEVERE, + logger.error( "respondWithUnexpectedFailure failed while sending the previous failure to the client", ex); } diff --git a/user/src/com/google/gwt/user/server/rpc/RemoteServiceServlet.java b/user/src/com/google/gwt/user/server/rpc/RemoteServiceServlet.java index d25733d9df8..ba2b6e459d9 100644 --- a/user/src/com/google/gwt/user/server/rpc/RemoteServiceServlet.java +++ b/user/src/com/google/gwt/user/server/rpc/RemoteServiceServlet.java @@ -22,6 +22,8 @@ import com.google.gwt.user.client.rpc.IncompatibleRemoteServiceException; import com.google.gwt.user.client.rpc.RpcTokenException; import com.google.gwt.user.client.rpc.SerializationException; +import com.google.gwt.user.server.rpc.logging.LogManager; +import com.google.gwt.user.server.rpc.logging.Logger; import java.io.IOException; import java.io.InputStream; @@ -30,8 +32,6 @@ import java.text.ParseException; import java.util.HashMap; import java.util.Map; -import java.util.logging.Level; -import java.util.logging.Logger; import javax.servlet.ServletConfig; import javax.servlet.ServletException; @@ -47,7 +47,7 @@ public class RemoteServiceServlet extends AbstractRemoteServiceServlet implements SerializationPolicyProvider { - private static final Logger logger = Logger.getLogger(RemoteServiceServlet.class.getName()); + private static final Logger logger = LogManager.getLogger(RemoteServiceServlet.class); /** * Loads a serialization policy stored as a servlet resource in the same @@ -66,7 +66,7 @@ static SerializationPolicy loadSerializationPolicy(HttpServlet servlet, modulePath = new URL(moduleBaseURL).getPath(); } catch (MalformedURLException ex) { // log the information, we will default - logger.log(Level.SEVERE, "Malformed moduleBaseURL: " + moduleBaseURL, ex); + logger.error("Malformed moduleBaseURL: " + moduleBaseURL, ex); } } @@ -83,7 +83,7 @@ static SerializationPolicy loadSerializationPolicy(HttpServlet servlet, + ", is not in the same web application as this servlet, " + contextPath + ". Your module may not be properly configured or your client and server code maybe out of date."; - logger.log(Level.SEVERE, message); + logger.error(message); } else { // Strip off the context path from the module base URL. It should be a // strict prefix. @@ -102,12 +102,12 @@ static SerializationPolicy loadSerializationPolicy(HttpServlet servlet, null); if (serializationPolicy.hasClientFields()) { if (ENABLE_ENHANCED_CLASSES) { - logger.log(Level.WARNING, + logger.warn( "Service deserializes enhanced JPA/JDO classes, which is " + "unsafe. See https://github.com/gwtproject/gwt/issues/9709 for more " + "detail on the vulnerability that this presents."); } else { - logger.log(Level.SEVERE, + logger.error( "Service deserializes enhanced JPA/JDO classes, which is " + "unsafe. Review build logs to see which classes are affected, or set " + ENABLE_GWT_ENHANCED_CLASSES_PROPERTY + " to true to allow using this " + @@ -117,11 +117,11 @@ static SerializationPolicy loadSerializationPolicy(HttpServlet servlet, } } } catch (ParseException e) { - logger.log(Level.SEVERE, + logger.error( "Failed to parse the policy file '" + serializationPolicyFilePath + "'", e); } catch (IOException e) { - logger.log(Level.SEVERE, + logger.error( "Could not read the policy file '" + serializationPolicyFilePath + "'", e); } @@ -129,7 +129,7 @@ static SerializationPolicy loadSerializationPolicy(HttpServlet servlet, String message = "The serialization policy file '" + serializationPolicyFilePath + "' was not found; did you forget to include it in this deployment?"; - logger.log(Level.SEVERE, message); + logger.error(message); } } finally { if (is != null) { @@ -190,9 +190,22 @@ public RemoteServiceServlet(Object delegate) { @Override public void init(ServletConfig config) throws ServletException { super.init(config); + String providerName = getProviderName(config); + LogManager.initialize(providerName, config.getServletContext()); codeServerPort = getCodeServerPort(); } + private String getProviderName(ServletConfig config) { + String parameterName = "gwt.rpc.logging"; + if (System.getProperty(parameterName) != null) { + return System.getProperty(parameterName); + } else if (config.getInitParameter(parameterName) != null) { + return config.getInitParameter(parameterName); + } else { + return config.getServletContext().getInitParameter(parameterName); + } + } + /** * Returns the value of the gwt.codeserver.port system property, or zero if not defined. * @@ -268,7 +281,7 @@ public final SerializationPolicy getSerializationPolicy(String moduleBaseURL, if (serializationPolicy == null) { // Failed to get the requested serialization policy; use the default - logger.log(Level.WARNING, + logger.warn( "Failed to get the SerializationPolicy '" + strongName + "' for module '" @@ -319,8 +332,7 @@ public String processCall(String payload) throws SerializationException { try { rpcRequest = RPC.decodeRequest(payload, delegate.getClass(), this); } catch (IncompatibleRemoteServiceException ex) { - logger.log( - Level.SEVERE, + logger.error( "An IncompatibleRemoteServiceException was thrown while processing this call.", ex); return RPC.encodeResponseForFailedRequest(null, ex); @@ -359,12 +371,12 @@ public String processCall(RPCRequest rpcRequest) throws SerializationException { rpcRequest.getParameters(), rpcRequest.getSerializationPolicy(), rpcRequest.getFlags()); } catch (IncompatibleRemoteServiceException ex) { - logger.log(Level.SEVERE, + logger.error( "An IncompatibleRemoteServiceException was thrown while processing this call.", ex); return RPC.encodeResponseForFailedRequest(rpcRequest, ex); } catch (RpcTokenException tokenException) { - logger.log(Level.SEVERE, + logger.error( "An RpcTokenException was thrown while processing this call.", tokenException); return RPC.encodeResponseForFailedRequest(rpcRequest, tokenException); @@ -474,19 +486,7 @@ protected String getCodeServerPolicyUrl(String strongName) { * no authentication. It should only be used during development.

*/ protected SerializationPolicy loadPolicyFromCodeServer(String url) { - SerializationPolicyClient.Logger adapter = new SerializationPolicyClient.Logger() { - - @Override - public void logInfo(String message) { - logger.log(Level.INFO, message); - } - - @Override - public void logError(String message, Throwable throwable) { - logger.log(Level.SEVERE, message, throwable); - } - }; - return CODE_SERVER_CLIENT.loadPolicy(url, adapter); + return CODE_SERVER_CLIENT.loadPolicy(url); } /** diff --git a/user/src/com/google/gwt/user/server/rpc/SerializationPolicyClient.java b/user/src/com/google/gwt/user/server/rpc/SerializationPolicyClient.java index dffc45a8841..f3a0c312fad 100644 --- a/user/src/com/google/gwt/user/server/rpc/SerializationPolicyClient.java +++ b/user/src/com/google/gwt/user/server/rpc/SerializationPolicyClient.java @@ -15,6 +15,9 @@ */ package com.google.gwt.user.server.rpc; +import com.google.gwt.user.server.rpc.logging.LogManager; +import com.google.gwt.user.server.rpc.logging.Logger; + import java.io.IOException; import java.io.InputStream; import java.net.HttpURLConnection; @@ -30,6 +33,8 @@ * (Intended only for development.) */ class SerializationPolicyClient { + + private static final Logger logger = LogManager.getLogger(SerializationPolicyClient.class); private final int connectTimeout; private final int readTimeout; @@ -43,12 +48,12 @@ class SerializationPolicyClient { this.readTimeout = readTimeoutMs; } - SerializationPolicy loadPolicy(String url, Logger logger) { + SerializationPolicy loadPolicy(String url) { URL urlObj; try { urlObj = new URL(url); } catch (MalformedURLException e) { - logger.logError("Can't parse serialization policy URL: " + url, e); + logger.error("Can't parse serialization policy URL: " + url, e); return null; } @@ -66,11 +71,11 @@ SerializationPolicy loadPolicy(String url, Logger logger) { conn.connect(); in = conn.getInputStream(); } catch (IOException e) { - logger.logError("Can't open serialization policy URL: " + url, e); + logger.error("Can't open serialization policy URL: " + url, e); return null; } - return readPolicy(in, url, logger); + return readPolicy(in, url); } /** @@ -79,34 +84,33 @@ SerializationPolicy loadPolicy(String url, Logger logger) { * @param sourceName names the source of the input stream for log messages. * @return the policy or null if unavailable. */ - private static SerializationPolicy readPolicy(InputStream in, String sourceName, - Logger logger) { + private static SerializationPolicy readPolicy(InputStream in, String sourceName) { try { List errs = new ArrayList(); SerializationPolicy policy = SerializationPolicyLoader.loadFromStream(in, errs); - logger.logInfo("Downloaded serialization policy from " + sourceName); + logger.error("Downloaded serialization policy from " + sourceName); if (!errs.isEmpty()) { - logMissingClasses(logger, errs); + logMissingClasses(errs); } return policy; } catch (ParseException e) { - logger.logError("Can't parse serialization policy from " + sourceName, e); + logger.error("Can't parse serialization policy from " + sourceName, e); return null; } catch (IOException e) { - logger.logError("Can't read serialization policy from " + sourceName, e); + logger.error("Can't read serialization policy from " + sourceName, e); return null; } finally { try { in.close(); } catch (IOException e) { - logger.logError("Can't close serialization policy url: " + sourceName, e); + logger.error("Can't close serialization policy url: " + sourceName, e); } } } - private static void logMissingClasses(Logger logger, List errs) { + private static void logMissingClasses(List errs) { StringBuilder message = new StringBuilder(); message.append("Unable to load server-side classes used by policy:\n"); @@ -118,14 +122,7 @@ private static void logMissingClasses(Logger logger, List 0) { message.append(" (omitted " + omitted + " more classes)\n"); } - logger.logInfo(message.toString()); + logger.info(message.toString()); } - /** - * Destination for the loader's log messages. - */ - interface Logger { - void logInfo(String message); - void logError(String message, Throwable throwable); - } } diff --git a/user/src/com/google/gwt/user/server/rpc/logging/JulLoggerProvider.java b/user/src/com/google/gwt/user/server/rpc/logging/JulLoggerProvider.java new file mode 100644 index 00000000000..27e3ae64f15 --- /dev/null +++ b/user/src/com/google/gwt/user/server/rpc/logging/JulLoggerProvider.java @@ -0,0 +1,43 @@ +package com.google.gwt.user.server.rpc.logging; + +import java.util.logging.Level; + +public class JulLoggerProvider implements LoggerProvider { + + public JulLoggerProvider() { } + + @Override + public LoggerDelegate createLogger(String name) { + return new JulLogger(java.util.logging.Logger.getLogger(name)); + } + + private static final class JulLogger implements LoggerDelegate { + private final java.util.logging.Logger logger; + + public JulLogger(java.util.logging.Logger logger) { + this.logger = logger; + } + + @Override + public void info(String message) { + logger.log(Level.INFO, message); + } + + @Override + public void warn(String message) { + logger.log(Level.WARNING, message); + } + + @Override + public void error(String message) { + logger.log(Level.SEVERE, message); + } + + @Override + public void error(String message, Throwable throwable) { + logger.log(Level.SEVERE, message, throwable); + } + + } + +} diff --git a/user/src/com/google/gwt/user/server/rpc/logging/Log4jProvider.java b/user/src/com/google/gwt/user/server/rpc/logging/Log4jProvider.java new file mode 100644 index 00000000000..78c9d1dec7a --- /dev/null +++ b/user/src/com/google/gwt/user/server/rpc/logging/Log4jProvider.java @@ -0,0 +1,25 @@ +package com.google.gwt.user.server.rpc.logging; + +import java.lang.reflect.Method; + +public class Log4jProvider extends ReflectiveLoggerProvider implements LoggerProvider { + + public Log4jProvider() { + try { + Class factory = Class.forName("org.apache.logging.log4j.LogManager"); + createLogger = factory.getMethod("getLogger", String.class); + Class levelClass = Class.forName("org.apache.logging.log4j.Level"); + Method levelFinder = levelClass.getMethod("valueOf", String.class); + infoLevel = levelFinder.invoke(null, "INFO"); + warnLevel = levelFinder.invoke(null, "WARN"); + errorLevel = levelFinder.invoke(null, "ERROR"); + Class loggerClass = Class.forName("org.apache.logging.log4j.Logger"); + logMessage = loggerClass.getMethod("log", levelClass, String.class); + logThrowable = loggerClass.getMethod("log", levelClass, String.class, Throwable.class); + available = true; + } catch (Exception e) { + available = false; + } + } + +} diff --git a/user/src/com/google/gwt/user/server/rpc/logging/LogManager.java b/user/src/com/google/gwt/user/server/rpc/logging/LogManager.java new file mode 100644 index 00000000000..b1130429379 --- /dev/null +++ b/user/src/com/google/gwt/user/server/rpc/logging/LogManager.java @@ -0,0 +1,102 @@ +package com.google.gwt.user.server.rpc.logging; + +import javax.servlet.ServletContext; +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicReference; + +public class LogManager { + + private static volatile List providers = null; + private static volatile ServletContext servletContext = null; + private static final ConcurrentHashMap loggers = new ConcurrentHashMap<>(); + private static final AtomicReference loggerProvider = new AtomicReference<>(); + private static final Object initLock = new Object(); + + public static Logger getLogger(Class clazz) { + return loggers.computeIfAbsent(clazz.getName(), Logger::new); + } + + static LoggerProvider getLoggerProvider() { + loggerProvider.compareAndSet(null, new JulLoggerProvider()); + LoggerProvider provider = loggerProvider.get(); + if (provider == null) { + synchronized (initLock) { + provider = loggerProvider.get(); + if (provider == null) { + initialize(); + provider = loggerProvider.get(); + } + } + } + return provider; + } + + public static void initialize() { + initialize(null, null); + } + + public static void initialize(String providerName) { + initialize(providerName, null); + } + + public static void initialize(String providerName, ServletContext servletContext) { + synchronized (initLock) { + if (servletContext != null) { + LogManager.servletContext = servletContext; + } + LoggerProvider fallback = servletContext != null ? + new ServletContextLoggerProvider(LogManager.servletContext) : + new JulLoggerProvider(); + LoggerProvider provider = chooseProvider(providerName, fallback); + setupProvider(provider); + } + } + + private static LoggerProvider chooseProvider(String name, LoggerProvider fallback) { + if (providers == null) { + providers = loadAllProviders(); + } + for (LoggerProvider provider : providers) { + if (provider.isDefault() || provider.getClass().getName().equals(name)) { + return provider; + } + } + return fallback; + } + + private static void setupProvider(LoggerProvider provider) { + LoggerProvider currentProvider = loggerProvider.get(); + String currentProviderName = currentProvider != null ? currentProvider.getClass().getName() : null; + if (!provider.getClass().getName().equals(currentProviderName)) { + loggerProvider.set(provider); + loggers.clear(); + } + } + + private static List loadAllProviders() { + List providers = new ArrayList<>(); + Set loaded = new HashSet<>(); + ClassLoader[] loaders = new ClassLoader[] { + Thread.currentThread().getContextClassLoader(), + LogManager.class.getClassLoader(), + ClassLoader.getSystemClassLoader() + }; + + for (ClassLoader loader : loaders) { + if (loader == null) { + continue; + } + ServiceLoader loaderService = ServiceLoader.load(LoggerProvider.class, loader); + for (LoggerProvider provider : loaderService) { + if (provider.isAvailable() && !loaded.contains(provider.getClass().getName())) { + loaded.add(provider.getClass().getName()); + providers.add(provider); + } + } + } + + return providers; + } + +} diff --git a/user/src/com/google/gwt/user/server/rpc/logging/Logger.java b/user/src/com/google/gwt/user/server/rpc/logging/Logger.java new file mode 100644 index 00000000000..e03714a135c --- /dev/null +++ b/user/src/com/google/gwt/user/server/rpc/logging/Logger.java @@ -0,0 +1,44 @@ +package com.google.gwt.user.server.rpc.logging; + +import java.util.concurrent.atomic.AtomicReference; + +public class Logger { + + private final AtomicReference delegate = new AtomicReference<>(); + private final String name; + + public Logger(String name) { + this.name = name; + } + + public void info(String message) { + getDelegate().info(message); + } + + public void warn(String message) { + getDelegate().warn(message); + } + + public void error(String message) { + getDelegate().error(message); + } + + public void error(String message, Throwable throwable) { + getDelegate().error(message, throwable); + } + + private LoggerDelegate getDelegate() { + LoggerDelegate delegate = this.delegate.get(); + if (delegate == null) { + delegate = createDelegate(); + } + return delegate; + } + + private LoggerDelegate createDelegate() { + LoggerProvider provider = LogManager.getLoggerProvider(); + LoggerDelegate newDelegate = provider.createLogger(name); + return delegate.compareAndSet(null, newDelegate) ? newDelegate : delegate.get(); + } + +} diff --git a/user/src/com/google/gwt/user/server/rpc/logging/LoggerDelegate.java b/user/src/com/google/gwt/user/server/rpc/logging/LoggerDelegate.java new file mode 100644 index 00000000000..b679b99a6bc --- /dev/null +++ b/user/src/com/google/gwt/user/server/rpc/logging/LoggerDelegate.java @@ -0,0 +1,13 @@ +package com.google.gwt.user.server.rpc.logging; + +public interface LoggerDelegate { + + void info(String message); + + void warn(String message); + + void error(String message); + + void error(String message, Throwable throwable); + +} diff --git a/user/src/com/google/gwt/user/server/rpc/logging/LoggerProvider.java b/user/src/com/google/gwt/user/server/rpc/logging/LoggerProvider.java new file mode 100644 index 00000000000..5e9e36595ac --- /dev/null +++ b/user/src/com/google/gwt/user/server/rpc/logging/LoggerProvider.java @@ -0,0 +1,15 @@ +package com.google.gwt.user.server.rpc.logging; + +public interface LoggerProvider { + + LoggerDelegate createLogger(String name); + + default boolean isDefault() { + return false; + } + + default boolean isAvailable() { + return true; + } + +} diff --git a/user/src/com/google/gwt/user/server/rpc/logging/PlatformLoggerProvider.java b/user/src/com/google/gwt/user/server/rpc/logging/PlatformLoggerProvider.java new file mode 100644 index 00000000000..9d5df21b8a5 --- /dev/null +++ b/user/src/com/google/gwt/user/server/rpc/logging/PlatformLoggerProvider.java @@ -0,0 +1,21 @@ +package com.google.gwt.user.server.rpc.logging; + +public class PlatformLoggerProvider extends ReflectiveLoggerProvider implements LoggerProvider { + + public PlatformLoggerProvider() { + try { + createLogger = System.class.getMethod("getLogger", String.class); + Class levelClass = Class.forName("java.lang.System$Logger$Level"); + infoLevel = getLogLevel(levelClass, "INFO"); + warnLevel = getLogLevel(levelClass, "WARNING"); + errorLevel = getLogLevel(levelClass, "ERROR"); + Class loggerClass = Class.forName("java.lang.System$Logger"); + logMessage = loggerClass.getMethod("log", levelClass, String.class); + logThrowable = loggerClass.getMethod("log", levelClass, String.class, Throwable.class); + available = true; + } catch (Exception e) { + available = false; + } + } + +} diff --git a/user/src/com/google/gwt/user/server/rpc/logging/ReflectiveLogger.java b/user/src/com/google/gwt/user/server/rpc/logging/ReflectiveLogger.java new file mode 100644 index 00000000000..70d5856a330 --- /dev/null +++ b/user/src/com/google/gwt/user/server/rpc/logging/ReflectiveLogger.java @@ -0,0 +1,65 @@ +package com.google.gwt.user.server.rpc.logging; + +import java.lang.reflect.Method; + +class ReflectiveLogger implements LoggerDelegate { + + private final Object logger; + private final Object infoLevel; + private final Object warnLevel; + private final Object errorLevel; + private final Method logMessage; + private final Method logThrowable; + + public ReflectiveLogger(Object platformLogger, Object infoLevel, Object warnLevel, Object errorLevel, Method logMessage, Method logThrowable) { + this.logger = platformLogger; + this.infoLevel = infoLevel; + this.warnLevel = warnLevel; + this.errorLevel = errorLevel; + this.logMessage = logMessage; + this.logThrowable = logThrowable; + } + + @Override + public void info(String message) { + try { + logMessage.invoke(logger, infoLevel, message); + } catch (Exception e) { + System.err.println("Failed to log message:" + message); + e.printStackTrace(); + } + } + + @Override + public void warn(String message) { + try { + logMessage.invoke(logger, warnLevel, message); + } catch (Exception e) { + System.err.println("Failed to log message:" + message); + e.printStackTrace(); + } + } + + @Override + public void error(String message) { + try { + logMessage.invoke(logger, errorLevel, message); + } catch (Exception e) { + System.err.println("Failed to log message:" + message); + e.printStackTrace(); + } + } + + @Override + public void error(String message, Throwable throwable) { + try { + logThrowable.invoke(logger, errorLevel, message, throwable); + } catch (Exception e) { + System.err.println("Failed to log message:" + message); + e.printStackTrace(); + System.err.println("Original exception:"); + throwable.printStackTrace(); + } + } + +} diff --git a/user/src/com/google/gwt/user/server/rpc/logging/ReflectiveLoggerProvider.java b/user/src/com/google/gwt/user/server/rpc/logging/ReflectiveLoggerProvider.java new file mode 100644 index 00000000000..f3566c79cfa --- /dev/null +++ b/user/src/com/google/gwt/user/server/rpc/logging/ReflectiveLoggerProvider.java @@ -0,0 +1,39 @@ +package com.google.gwt.user.server.rpc.logging; + +import java.lang.reflect.Method; + +abstract class ReflectiveLoggerProvider implements LoggerProvider { + + protected Method createLogger; + protected Object infoLevel; + protected Object warnLevel; + protected Object errorLevel; + protected Method logMessage; + protected Method logThrowable; + protected boolean available; + + @Override + public LoggerDelegate createLogger(String name) { + try { + Object logger = createLogger.invoke(null, name); + return new ReflectiveLogger(logger, infoLevel, warnLevel, errorLevel, logMessage, logThrowable); + } catch (Exception e) { + throw new IllegalStateException(getClass() + " is not available on this platform", e); + } + } + + @Override + public boolean isAvailable() { + return available; + } + + protected Object getLogLevel(Class levelClass, String levelName) { + for (Object level : levelClass.getEnumConstants()) { + if (level.toString().equals(levelName)) { + return level; + } + } + return null; + } + +} diff --git a/user/src/com/google/gwt/user/server/rpc/logging/ServletContextLoggerProvider.java b/user/src/com/google/gwt/user/server/rpc/logging/ServletContextLoggerProvider.java new file mode 100644 index 00000000000..1be4257454a --- /dev/null +++ b/user/src/com/google/gwt/user/server/rpc/logging/ServletContextLoggerProvider.java @@ -0,0 +1,47 @@ +package com.google.gwt.user.server.rpc.logging; + +import javax.servlet.ServletContext; + +public class ServletContextLoggerProvider implements LoggerProvider { + + private final ServletContext servletContext; + + public ServletContextLoggerProvider(ServletContext servletContext) { + this.servletContext = servletContext; + } + + @Override + public LoggerDelegate createLogger(String name) { + return new ServletContextLogger(servletContext); + } + + private static final class ServletContextLogger implements LoggerDelegate { + + private final ServletContext servletContext; + + public ServletContextLogger(ServletContext servletContext) { + this.servletContext = servletContext; + } + + @Override + public void info(String message) { + servletContext.log(message); + } + + @Override + public void warn(String message) { + servletContext.log("WARNING: " + message); + } + + @Override public void error(String message) { + servletContext.log("ERROR: " + message); + } + + @Override + public void error(String message, Throwable throwable) { + servletContext.log(message, throwable); + } + + } + +} diff --git a/user/test/com/google/gwt/user/server/rpc/RemoteServiceServletTest.java b/user/test/com/google/gwt/user/server/rpc/RemoteServiceServletTest.java index a6d3a6c89ac..4e015044fa1 100644 --- a/user/test/com/google/gwt/user/server/rpc/RemoteServiceServletTest.java +++ b/user/test/com/google/gwt/user/server/rpc/RemoteServiceServletTest.java @@ -79,7 +79,7 @@ public MockServletConfig(ServletContext context) { } public String getInitParameter(String arg0) { - throw new UnsupportedOperationException(); + return null; } public Enumeration getInitParameterNames() { @@ -96,6 +96,7 @@ public String getServletName() { } private class MockServletContext implements ServletContext { + private String messageLogged; public MockServletContext() { } @@ -117,7 +118,7 @@ public String getContextPath() { } public String getInitParameter(String arg0) { - throw new UnsupportedOperationException(); + return null; } public Enumeration getInitParameterNames() { @@ -189,7 +190,7 @@ public void log(String arg0) { } public void log(String arg0, Throwable arg1) { - throw new UnsupportedOperationException(); + messageLogged = arg0; } public void removeAttribute(String arg0) { @@ -328,6 +329,7 @@ public InputStream getResourceAsStream(String resource) { SerializationPolicy serializationPolicy = rss.doGetSerializationPolicy( mockRequest, "http://www.google.com/MyModule", "12345"); assertNull(serializationPolicy); + assertNotNull(mockContext.messageLogged); } /** @@ -335,8 +337,8 @@ public InputStream getResourceAsStream(String resource) { * {@link com.google.gwt.user.server.rpc.RemoteServiceServlet#doGetSerializationPolicy(javax.servlet.http.HttpServletRequest, java.lang.String, java.lang.String)}. * * This method tests that if the module path is in a different context than - * the RemoteServiceServlet which is processing the request, - * null is returned for the SerializationPolicy. + * the RemoteServiceServlet which is processing the request, a message will be + * logged and null is returned for the SerializationPolicy. */ public void testDoGetSerializationPolicy_ModuleInSeparateServlet() throws ServletException {