Implement RPC blacklists.

Review by: scottb,spoon



git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@5324 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/dev/core/src/com/google/gwt/core/ext/linker/impl/StandardConfigurationProperty.java b/dev/core/src/com/google/gwt/core/ext/linker/impl/StandardConfigurationProperty.java
index ef29748..d035406 100644
--- a/dev/core/src/com/google/gwt/core/ext/linker/impl/StandardConfigurationProperty.java
+++ b/dev/core/src/com/google/gwt/core/ext/linker/impl/StandardConfigurationProperty.java
@@ -33,8 +33,12 @@
     name = p.getName();
     values = p.getValues();
     
-    if (values == null || values.size() == 0) {
-      throw new IllegalArgumentException("values is null or empty");
+    if (values == null) {
+      throw new IllegalArgumentException("values is null");
+    }
+    if (!p.allowsMultipleValues() && values.size() != 1) {
+      throw new IllegalArgumentException(
+          "p is single-valued but values.size != 1");
     }
   }
 
diff --git a/user/src/com/google/gwt/user/RemoteService.gwt.xml b/user/src/com/google/gwt/user/RemoteService.gwt.xml
index 31baf7c..7e89fc1 100644
--- a/user/src/com/google/gwt/user/RemoteService.gwt.xml
+++ b/user/src/com/google/gwt/user/RemoteService.gwt.xml
@@ -35,6 +35,17 @@
     -->
     <define-configuration-property name="gwt.elideTypeNamesFromRPC" is-multi-valued="false" />
     <set-configuration-property name="gwt.elideTypeNamesFromRPC" value="false" />
+    
+  <!--
+      Contains regular expressions, optionally prefixed with '+' or '-'.
+      Each type being considered for serialization is tested against the
+      list of expressions in order, and if there is a match it is added to the
+      blacklist (if the prefix is '-' or no prefix is present), or removed (if
+      the prefix is '+').  If multiple entries in the list match a supplied
+      class, then the last one 'wins.'  For generic types, the regular
+      expression is applied to just the base class's fully qualified name.
+  -->
+  <define-configuration-property name="rpc.blacklist" is-multi-valued="true" />
 
 	<generate-with class="com.google.gwt.user.rebind.rpc.ServiceInterfaceProxyGenerator">
 		<when-type-assignable class="com.google.gwt.user.client.rpc.RemoteService"/>
diff --git a/user/src/com/google/gwt/user/rebind/rpc/BlacklistTypeFilter.java b/user/src/com/google/gwt/user/rebind/rpc/BlacklistTypeFilter.java
new file mode 100644
index 0000000..239d1f5
--- /dev/null
+++ b/user/src/com/google/gwt/user/rebind/rpc/BlacklistTypeFilter.java
@@ -0,0 +1,140 @@
+/*
+ * 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.rebind.rpc;
+
+import com.google.gwt.core.ext.BadPropertyValueException;
+import com.google.gwt.core.ext.ConfigurationProperty;
+import com.google.gwt.core.ext.PropertyOracle;
+import com.google.gwt.core.ext.TreeLogger;
+import com.google.gwt.core.ext.UnableToCompleteException;
+import com.google.gwt.core.ext.typeinfo.JClassType;
+import com.google.gwt.core.ext.typeinfo.JRealClassType;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.regex.Pattern;
+import java.util.regex.PatternSyntaxException;
+
+class BlacklistTypeFilter implements TypeFilter {
+  
+  private List<Boolean> includeType;
+  private TreeLogger logger;
+  private List<Pattern> typePatterns;
+  private List<String> values;
+  
+  public BlacklistTypeFilter(TreeLogger logger, PropertyOracle propertyOracle)
+      throws UnableToCompleteException {
+    this.logger = logger.branch(TreeLogger.DEBUG,
+        "Analyzing RPC blacklist information");
+    try {
+      ConfigurationProperty prop
+          = propertyOracle.getConfigurationProperty("rpc.blacklist");
+
+      values = prop.getValues();
+      int size = values.size();
+      typePatterns = new ArrayList<Pattern>(size);
+      includeType = new ArrayList<Boolean>(size);
+
+      // TODO investigate grouping multiple patterns into a single regex
+      for (String regex : values) {
+        // Patterns that don't start with [+-] are considered to be [-]
+        boolean include = false;
+        // Ignore empty regexes
+        if (regex.length() == 0) {
+          logger.log(TreeLogger.ERROR, "Got empty RPC blacklist entry");
+          throw new UnableToCompleteException();
+        }
+        char c = regex.charAt(0);
+        if (c == '+' || c == '-') {
+          regex = regex.substring(1); // skip initial character
+          include = (c == '+');
+        }
+        try {
+          Pattern p = Pattern.compile(regex);
+          typePatterns.add(p);
+          includeType.add(include);
+          
+          logger.log(TreeLogger.DEBUG,
+              "Got RPC blacklist entry '" + regex + "'");
+        } catch (PatternSyntaxException e) {
+          logger.log(TreeLogger.ERROR,
+              "Got malformed RPC blacklist entry '" + regex + "'");
+          throw new UnableToCompleteException();
+        }
+      }
+    } catch (BadPropertyValueException e) {
+      logger.log(TreeLogger.DEBUG, "No RPC blacklist entries present");
+    }
+  }
+
+  public String getName() {
+    return "BlacklistTypeFilter";
+  }
+  
+
+  public boolean isAllowed(JClassType type) {
+    String name = getBaseTypeName(type);
+    // For types not handled by getBaseTypeName just return true.
+    if (name == null) {
+      return true;
+    }
+    
+    // Process patterns in reverse order for early exit
+    int size = typePatterns.size();
+    for (int idx = size - 1; idx >= 0; idx--) {
+      logger.log(TreeLogger.DEBUG, "Considering RPC rule " + values.get(idx)
+          + " for type " + name);
+      boolean include = includeType.get(idx);
+      Pattern pattern = typePatterns.get(idx);
+      if (pattern.matcher(name).matches()) {
+        if (include) {
+          logger.log(TreeLogger.DEBUG, "Whitelisting type " + name
+              + " according to rule " + values.get(idx));
+          return true;
+        } else {
+          logger.log(TreeLogger.DEBUG, "Blacklisting type " + name
+              + " according to rule " + values.get(idx));
+          return false;
+        }
+      }
+    }
+    
+    // Type does not match any pattern, pass it through
+    return true;
+  }
+
+  /**
+   * Returns a simple qualified name for simple types, including classes and
+   * interfaces, parameterized, and raw types.  Null is returned for other types
+   * such as arrays and type parameters (e.g., 'E' in java.util.List<E>) because
+   * filtering is meaningless for such types.
+   */
+  private String getBaseTypeName(JClassType type) {
+    JClassType baseType = null;
+    
+    if (type instanceof JRealClassType) {
+      baseType = type;
+    } else if (type.isParameterized() != null) {
+      baseType = type.isParameterized().getBaseType();
+    } else if (type.isRawType() != null) {
+      baseType = type.isRawType();
+    }
+    
+    return baseType == null ? null : baseType.getQualifiedSourceName();
+  }
+}
+
diff --git a/user/src/com/google/gwt/user/rebind/rpc/ProxyCreator.java b/user/src/com/google/gwt/user/rebind/rpc/ProxyCreator.java
index 84a06bb..346e2cd 100644
--- a/user/src/com/google/gwt/user/rebind/rpc/ProxyCreator.java
+++ b/user/src/com/google/gwt/user/rebind/rpc/ProxyCreator.java
@@ -234,11 +234,17 @@
 
     final PropertyOracle propertyOracle = context.getPropertyOracle();
     
+    // Load the blacklist/whitelist
+    TypeFilter blacklistTypeFilter = new BlacklistTypeFilter(logger, propertyOracle);
+
     // Determine the set of serializable types
     SerializableTypeOracleBuilder typesSentFromBrowserBuilder = new SerializableTypeOracleBuilder(
         logger, propertyOracle, typeOracle);
+    typesSentFromBrowserBuilder.setTypeFilter(blacklistTypeFilter);
     SerializableTypeOracleBuilder typesSentToBrowserBuilder = new SerializableTypeOracleBuilder(
         logger, propertyOracle, typeOracle);
+    typesSentToBrowserBuilder.setTypeFilter(blacklistTypeFilter);
+
     try {
       addRequiredRoots(logger, typeOracle, typesSentFromBrowserBuilder);
       addRequiredRoots(logger, typeOracle, typesSentToBrowserBuilder);
@@ -267,9 +273,25 @@
     // output.
     OutputStream pathInfo = context.tryCreateResource(logger,
         serviceIntf.getQualifiedSourceName() + ".rpc.log");
+    PrintWriter writer = new PrintWriter(pathInfo);
+    
     typesSentFromBrowserBuilder.setLogOutputStream(pathInfo);
-    SerializableTypeOracle typesSentFromBrowser = typesSentFromBrowserBuilder.build(logger);
-    SerializableTypeOracle typesSentToBrowser = typesSentToBrowserBuilder.build(logger);
+    typesSentToBrowserBuilder.setLogOutputStream(pathInfo);
+    
+    writer.write("====================================\n");
+    writer.write("Types potentially sent from browser:\n");
+    writer.write("====================================\n\n");
+    writer.flush();
+    SerializableTypeOracle typesSentFromBrowser
+        = typesSentFromBrowserBuilder.build(logger);
+    
+    writer.write("===================================\n");
+    writer.write("Types potentially sent from server:\n");
+    writer.write("===================================\n\n");
+    writer.flush();
+    SerializableTypeOracle typesSentToBrowser
+        = typesSentToBrowserBuilder.build(logger);
+    
     if (pathInfo != null) {
       context.commitResource(logger, pathInfo).setPrivate(true);
     }