You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@geode.apache.org by go...@apache.org on 2017/11/10 17:58:44 UTC

[geode] branch whitelist_wip updated: Adding configuration parameters to toggle whitelist behavior of serializable classes.

This is an automated email from the ASF dual-hosted git repository.

gosullivan pushed a commit to branch whitelist_wip
in repository https://gitbox.apache.org/repos/asf/geode.git


The following commit(s) were added to refs/heads/whitelist_wip by this push:
     new 026ae92  Adding configuration parameters to toggle whitelist behavior of serializable classes.
026ae92 is described below

commit 026ae92fe2055c09762978696d53d9cd2679bd60
Author: Galen O'Sullivan <go...@pivotal.io>
AuthorDate: Thu Nov 9 17:02:56 2017 -0800

    Adding configuration parameters to toggle whitelist behavior of serializable classes.
    
    Open question about removing DataCommandRequest.java toData and fromData
    
    Signed-off-by: Brian Rowe <br...@pivotal.io>
---
 .../geode/distributed/ConfigurationProperties.java |  27 ++++
 .../internal/AbstractDistributionConfig.java       |   3 +
 .../distributed/internal/DistributionConfig.java   |  48 +++++++
 .../internal/DistributionConfigImpl.java           |  26 +++-
 .../geode/internal/InternalDataSerializer.java     |  40 +++---
 .../internal/cli/domain/DataCommandRequest.java    |  33 -----
 .../AnalyzeSerializablesJUnitTest.java             |   9 +-
 .../internal/DistributionConfigJUnitTest.java      |   6 +-
 ...alDataSerializerSerializationWhitelistTest.java | 140 +++++++++++++++++++++
 9 files changed, 272 insertions(+), 60 deletions(-)

diff --git a/geode-core/src/main/java/org/apache/geode/distributed/ConfigurationProperties.java b/geode-core/src/main/java/org/apache/geode/distributed/ConfigurationProperties.java
index aae7d8a..37f780c 100644
--- a/geode-core/src/main/java/org/apache/geode/distributed/ConfigurationProperties.java
+++ b/geode-core/src/main/java/org/apache/geode/distributed/ConfigurationProperties.java
@@ -2070,4 +2070,31 @@ public interface ConfigurationProperties {
    * <U>Since</U>: Geode 1.0
    */
   String SSL_WEB_SERVICE_REQUIRE_AUTHENTICATION = "ssl-web-require-authentication";
+  /**
+   * The static String definition of the <i>"validate-serializable-objects"</i> property
+   *
+   * <U>Description</U>If true checks incoming java serializable objects against a filter (allows
+   * internal Geode classes and any others provided in the serializable-object-filter property).
+   * </p>
+   * <U>Default</U>: "false"
+   * </p>
+   * <U>Since</U>: Geode 1.4
+   */
+  String VALIDATE_SERIALIZABLE_OBJECTS = "validate-serializable-objects";
+  /**
+   * The static String definition of the <i>"serializable-object-filter"</i> property
+   *
+   * <U>Description</U>A user provided whitelist of objects that the system will allow to serialize.
+   *
+   * <p>
+   * See java.io.ObjectInputFilter.Config for details on the syntax for creating filters.
+   * https://docs.oracle.com/javase/9/docs/api/java/io/ObjectInputFilter.Config.html
+   * </p>
+   * </p>
+   * <U>Default</U>: "!*"
+   * </p>
+   * <U>Since</U>: Geode 1.4
+   *
+   */
+  String SERIALIZABLE_OBJECT_FILTER = "serializable-object-filter";
 }
diff --git a/geode-core/src/main/java/org/apache/geode/distributed/internal/AbstractDistributionConfig.java b/geode-core/src/main/java/org/apache/geode/distributed/internal/AbstractDistributionConfig.java
index bfc5e1d..e143fed 100644
--- a/geode-core/src/main/java/org/apache/geode/distributed/internal/AbstractDistributionConfig.java
+++ b/geode-core/src/main/java/org/apache/geode/distributed/internal/AbstractDistributionConfig.java
@@ -1220,6 +1220,9 @@ public abstract class AbstractDistributionConfig extends AbstractConfig
     m.put(SSL_DEFAULT_ALIAS, "The default certificate alias to be used in a multi-key keystore");
     m.put(SSL_WEB_SERVICE_REQUIRE_AUTHENTICATION,
         "This property determines is the HTTP service with use mutual ssl authentication.");
+    m.put(VALIDATE_SERIALIZABLE_OBJECTS,
+        "If true checks incoming java serializable objects against a filter");
+    m.put(SERIALIZABLE_OBJECT_FILTER, "The filter to check incoming java serializables against");
 
     dcAttDescriptions = Collections.unmodifiableMap(m);
 
diff --git a/geode-core/src/main/java/org/apache/geode/distributed/internal/DistributionConfig.java b/geode-core/src/main/java/org/apache/geode/distributed/internal/DistributionConfig.java
index 958ea15..200828c 100644
--- a/geode-core/src/main/java/org/apache/geode/distributed/internal/DistributionConfig.java
+++ b/geode-core/src/main/java/org/apache/geode/distributed/internal/DistributionConfig.java
@@ -4864,6 +4864,54 @@ public interface DistributionConfig extends Config, LogConfig {
    */
   boolean DEFAULT_SSL_WEB_SERVICE_REQUIRE_AUTHENTICATION = false;
 
+  /**
+   * Returns the value of the {@link ConfigurationProperties#VALIDATE_SERIALIZABLE_OBJECTS} property
+   */
+  @ConfigAttributeGetter(name = VALIDATE_SERIALIZABLE_OBJECTS)
+  boolean getValidateSerializableObjects();
+
+  /**
+   * Sets the value of the {@link ConfigurationProperties#VALIDATE_SERIALIZABLE_OBJECTS} property
+   */
+  @ConfigAttributeSetter(name = VALIDATE_SERIALIZABLE_OBJECTS)
+  void setValidateSerializableObjects(boolean value);
+
+  /**
+   * The name of the {@link ConfigurationProperties#VALIDATE_SERIALIZABLE_OBJECTS} property
+   */
+  @ConfigAttribute(type = Boolean.class)
+  String VALIDATE_SERIALIZABLE_OBJECTS_NAME = VALIDATE_SERIALIZABLE_OBJECTS;
+
+  /**
+   * The default value of the {@link ConfigurationProperties#VALIDATE_SERIALIZABLE_OBJECTS}
+   * property.
+   */
+  boolean DEFAULT_VALIDATE_SERIALIZABLE_OBJECTS = false;
+
+  /**
+   * Returns the value of the {@link ConfigurationProperties#SERIALIZABLE_OBJECT_FILTER} property
+   */
+  @ConfigAttributeGetter(name = SERIALIZABLE_OBJECT_FILTER)
+  String getSerializableObjectFilter();
+
+  /**
+   * Sets the value of the {@link ConfigurationProperties#SERIALIZABLE_OBJECT_FILTER} property
+   */
+  @ConfigAttributeSetter(name = SERIALIZABLE_OBJECT_FILTER)
+  void setSerializableObjectFilter(String value);
+
+  /**
+   * The name of the {@link ConfigurationProperties#SERIALIZABLE_OBJECT_FILTER} property
+   */
+  @ConfigAttribute(type = String.class)
+  String SERIALIZABLE_OBJECT_FILTER_NAME = SERIALIZABLE_OBJECT_FILTER;
+
+  /**
+   * The default value of the {@link ConfigurationProperties#SERIALIZABLE_OBJECT_FILTER} property.
+   * Current value is a pattern for rejecting everything <code>"!*"</code>
+   */
+  String DEFAULT_SERIALIZABLE_OBJECT_FILTER = "!*";
+
   // *************** Initializers to gather all the annotations in this class
   // ************************
 
diff --git a/geode-core/src/main/java/org/apache/geode/distributed/internal/DistributionConfigImpl.java b/geode-core/src/main/java/org/apache/geode/distributed/internal/DistributionConfigImpl.java
index abaa83c..502884a 100644
--- a/geode-core/src/main/java/org/apache/geode/distributed/internal/DistributionConfigImpl.java
+++ b/geode-core/src/main/java/org/apache/geode/distributed/internal/DistributionConfigImpl.java
@@ -627,6 +627,9 @@ public class DistributionConfigImpl extends AbstractDistributionConfig implement
 
   protected String userCommandPackages = DEFAULT_USER_COMMAND_PACKAGES;
 
+  private boolean validateSerializableObjects = DEFAULT_VALIDATE_SERIALIZABLE_OBJECTS;
+  private String serializableObjectFilter = DEFAULT_SERIALIZABLE_OBJECT_FILTER;
+
   /**
    * "off-heap-memory-size" with value of "" or "<size>[g|m]"
    */
@@ -841,7 +844,8 @@ public class DistributionConfigImpl extends AbstractDistributionConfig implement
     this.sslDefaultAlias = other.getSSLDefaultAlias();
     this.sslWebServiceRequireAuthentication = other.getSSLWebRequireAuthentication();
 
-
+    this.validateSerializableObjects = other.getValidateSerializableObjects();
+    this.serializableObjectFilter = other.getSerializableObjectFilter();
   }
 
   /**
@@ -2853,6 +2857,26 @@ public class DistributionConfigImpl extends AbstractDistributionConfig implement
     this.sslWebServiceRequireAuthentication = requiresAuthenatication;
   }
 
+  @Override
+  public boolean getValidateSerializableObjects() {
+    return validateSerializableObjects;
+  }
+
+  @Override
+  public void setValidateSerializableObjects(boolean value) {
+    this.validateSerializableObjects = value;
+  }
+
+  @Override
+  public String getSerializableObjectFilter() {
+    return serializableObjectFilter;
+  }
+
+  @Override
+  public void setSerializableObjectFilter(String value) {
+    this.serializableObjectFilter = value;
+  }
+
   /////////////////////// Utility Methods ///////////////////////
 
 
diff --git a/geode-core/src/main/java/org/apache/geode/internal/InternalDataSerializer.java b/geode-core/src/main/java/org/apache/geode/internal/InternalDataSerializer.java
index b591cd4..6e0b241 100644
--- a/geode-core/src/main/java/org/apache/geode/internal/InternalDataSerializer.java
+++ b/geode-core/src/main/java/org/apache/geode/internal/InternalDataSerializer.java
@@ -78,7 +78,6 @@ import java.io.DataInput;
 import java.io.DataOutput;
 import java.io.EOFException;
 import java.io.File;
-import java.io.FileReader;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.InputStreamReader;
@@ -140,12 +139,23 @@ public abstract class InternalDataSerializer extends DataSerializer implements D
    * serialization.
    */
   private static final Map<String, DataSerializer> classesToSerializers = new ConcurrentHashMap<>();
+  private static final String SANCTIONED_SERIALIZABLES_DEPENDENCIES_PATTERN =
+      "java.**;javax.management.**" + ";javax.print.attribute.EnumSyntax" // used for some *old*
+                                                                          // enums
+          + ";antlr.**" // query AST objects
+          + ";org.apache.commons.modeler.AttributeInfo" // old Admin API
+          + ";org.apache.commons.modeler.FeatureInfo" // old Admin API
+          + ";org.apache.commons.modeler.ManagedBean" // old Admin API
+          + ";";
+
+
+  private static ObjectInputFilter defaultSerializationFilter =
+      ObjectInputFilter.Config.createFilter("**");
 
   /**
    * A deserialization filter for ObjectInputStreams
    */
-  private static ObjectInputFilter serializationFilter =
-      ObjectInputFilter.Config.createFilter("**");
+  private static ObjectInputFilter serializationFilter = defaultSerializationFilter;
 
   private static final String serializationVersionTxt =
       System.getProperty(DistributionConfig.GEMFIRE_PREFIX + "serializationVersion");
@@ -209,29 +219,21 @@ public abstract class InternalDataSerializer extends DataSerializer implements D
    */
   public static void initialize(DistributionConfig distributionConfig,
       Collection<DistributedSystemService> services) {
-
-    String configSerializationSpec = ""; // distributionConfig.getSerializationSpecification()
-    // if (configSerializationSpec == null || configSerializationSpec.trim().length() == 0) {
-    // return;
-    // }
-
-    String serializationFilterSpec =
-        "java.**;javax.management.**;javax.print.attribute.EnumSyntax" + ";antlr.**" // query AST
-                                                                                     // objects
-            + ";org.apache.commons.modeler.AttributeInfo" // old Admin API
-            + ";org.apache.commons.modeler.FeatureInfo" // old Admin API
-            + ";org.apache.commons.modeler.ManagedBean" // old Admin API
-            + ";" + configSerializationSpec + ";!*"; // everything else is black-listed
-
-    if (serializationFilterSpec != null) {
+    if (distributionConfig.getValidateSerializableObjects()) {
       if (!ClassUtils.isClassAvailable("sun.misc.ObjectInputFilter")) {
         throw new GemFireConfigException(
             "A serialization filter has been specified but this version of Java does not support serialization filters - sun.misc.ObjectInputFilter is not available");
       }
-      createSerializationFilter(serializationFilterSpec, services);
+      createSerializationFilter(SANCTIONED_SERIALIZABLES_DEPENDENCIES_PATTERN
+          + distributionConfig.getSerializableObjectFilter() + ";!*", services);
+    } else {
+      clearSerializationFilter();
     }
   }
 
+  private static void clearSerializationFilter() {
+    serializationFilter = defaultSerializationFilter;
+  }
 
   private static void createSerializationFilter(String serializationFilterSpec,
       Collection<DistributedSystemService> services) {
diff --git a/geode-core/src/main/java/org/apache/geode/management/internal/cli/domain/DataCommandRequest.java b/geode-core/src/main/java/org/apache/geode/management/internal/cli/domain/DataCommandRequest.java
index 1b7557b..9c59bdb 100644
--- a/geode-core/src/main/java/org/apache/geode/management/internal/cli/domain/DataCommandRequest.java
+++ b/geode-core/src/main/java/org/apache/geode/management/internal/cli/domain/DataCommandRequest.java
@@ -194,37 +194,4 @@ public class DataCommandRequest implements /* Data */ Serializable {
   public void setPrincipal(Object principal) {
     this.principal = principal;
   }
-
-  // @Override
-  public void toData(DataOutput out) throws IOException {
-    DataSerializer.writeString(command, out);
-    DataSerializer.writeString(key, out);
-    DataSerializer.writeString(value, out);
-    DataSerializer.writeBoolean(putIfAbsent, out);
-    DataSerializer.writeString(keyClass, out);
-    DataSerializer.writeString(valueClass, out);
-    DataSerializer.writeString(regionName, out);
-    DataSerializer.writeString(removeAllKeys, out);
-    DataSerializer.writeBoolean(recursive, out);
-    DataSerializer.writeBoolean(loadOnCacheMiss, out);
-    DataSerializer.writeObject(principal, out);
-  }
-
-  // @Override
-  public void fromData(DataInput in) throws IOException, ClassNotFoundException {
-    command = DataSerializer.readString(in);
-    key = DataSerializer.readString(in);
-    value = DataSerializer.readString(in);
-    putIfAbsent = DataSerializer.readBoolean(in);
-    keyClass = DataSerializer.readString(in);
-    valueClass = DataSerializer.readString(in);
-    regionName = DataSerializer.readString(in);
-    removeAllKeys = DataSerializer.readString(in);
-    recursive = DataSerializer.readBoolean(in);
-    loadOnCacheMiss = DataSerializer.readBoolean(in);
-    principal = DataSerializer.readObject(in);
-  }
-
-
-
 }
diff --git a/geode-core/src/test/java/org/apache/geode/codeAnalysis/AnalyzeSerializablesJUnitTest.java b/geode-core/src/test/java/org/apache/geode/codeAnalysis/AnalyzeSerializablesJUnitTest.java
index 0ea8c69..2df8cf3 100644
--- a/geode-core/src/test/java/org/apache/geode/codeAnalysis/AnalyzeSerializablesJUnitTest.java
+++ b/geode-core/src/test/java/org/apache/geode/codeAnalysis/AnalyzeSerializablesJUnitTest.java
@@ -60,10 +60,10 @@ import sun.reflect.ReflectionFactory;
 
 import org.apache.geode.CancelException;
 import org.apache.geode.DataSerializer;
-import org.apache.geode.ForcedDisconnectException;
 import org.apache.geode.codeAnalysis.decode.CompiledClass;
 import org.apache.geode.codeAnalysis.decode.CompiledField;
 import org.apache.geode.codeAnalysis.decode.CompiledMethod;
+import org.apache.geode.distributed.ConfigurationProperties;
 import org.apache.geode.distributed.internal.DistributedSystemService;
 import org.apache.geode.distributed.internal.DistributionConfig;
 import org.apache.geode.distributed.internal.DistributionConfigImpl;
@@ -191,9 +191,10 @@ public class AnalyzeSerializablesJUnitTest {
   @Test
   public void excludedClassesExistAndDoNotDeserialize() throws Exception {
     List<String> excludedClasses = loadExcludedClasses(getResourceAsFile(EXCLUDED_CLASSES_TXT));
-    DistributionConfig distributionConfig = new DistributionConfigImpl(new Properties());
-    InternalDataSerializer.initialize(distributionConfig,
-        new ArrayList<DistributedSystemService>());
+    Properties properties = new Properties();
+    properties.setProperty(ConfigurationProperties.VALIDATE_SERIALIZABLE_OBJECTS, "true");
+    DistributionConfig distributionConfig = new DistributionConfigImpl(properties);
+    InternalDataSerializer.initialize(distributionConfig, new ArrayList<>());
 
     for (String filePath : excludedClasses) {
       String className = filePath.replaceAll("/", ".");
diff --git a/geode-core/src/test/java/org/apache/geode/distributed/internal/DistributionConfigJUnitTest.java b/geode-core/src/test/java/org/apache/geode/distributed/internal/DistributionConfigJUnitTest.java
index 7e5f83f..4b96bf2 100644
--- a/geode-core/src/test/java/org/apache/geode/distributed/internal/DistributionConfigJUnitTest.java
+++ b/geode-core/src/test/java/org/apache/geode/distributed/internal/DistributionConfigJUnitTest.java
@@ -99,7 +99,7 @@ public class DistributionConfigJUnitTest {
   @Test
   public void testGetAttributeNames() {
     String[] attNames = AbstractDistributionConfig._getAttNames();
-    assertEquals(attNames.length, 157);
+    assertEquals(attNames.length, 159);
 
     List boolList = new ArrayList();
     List intList = new ArrayList();
@@ -133,9 +133,9 @@ public class DistributionConfigJUnitTest {
 
     // TODO - This makes no sense. One has no idea what the correct expected number of attributes
     // are.
-    assertEquals(29, boolList.size());
+    assertEquals(30, boolList.size());
     assertEquals(33, intList.size());
-    assertEquals(86, stringList.size());
+    assertEquals(87, stringList.size());
     assertEquals(5, fileList.size());
     assertEquals(4, otherList.size());
   }
diff --git a/geode-core/src/test/java/org/apache/geode/internal/InternalDataSerializerSerializationWhitelistTest.java b/geode-core/src/test/java/org/apache/geode/internal/InternalDataSerializerSerializationWhitelistTest.java
new file mode 100644
index 0000000..1064e2f
--- /dev/null
+++ b/geode-core/src/test/java/org/apache/geode/internal/InternalDataSerializerSerializationWhitelistTest.java
@@ -0,0 +1,140 @@
+package org.apache.geode.internal;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import java.io.ByteArrayInputStream;
+import java.io.DataInputStream;
+import java.io.IOException;
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.Properties;
+
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.experimental.categories.Category;
+
+import org.apache.geode.DataSerializer;
+import org.apache.geode.distributed.internal.DistributionConfig;
+import org.apache.geode.distributed.internal.DistributionConfigImpl;
+import org.apache.geode.test.junit.categories.UnitTest;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more contributor license
+ * agreements. See the NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The ASF licenses this file to You 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.
+ */
+@Category(UnitTest.class)
+public class InternalDataSerializerSerializationWhitelistTest {
+  private HeapDataOutputStream outputStream;
+  private Object testSerializable;
+  private Properties properties;
+
+  @Before
+  public void setUp() {
+    outputStream = new HeapDataOutputStream(Version.CURRENT);
+    testSerializable = new TestSerializable();
+    properties = new Properties();
+  }
+
+  @AfterClass
+  public static void clearDataSerializerFilter() {
+    InternalDataSerializer.initialize(new DistributionConfigImpl(new Properties()),
+        new ArrayList<>());
+  }
+
+  @Test
+  public void distributionConfigDefaults() {
+    DistributionConfigImpl distributionConfig = new DistributionConfigImpl(new Properties());
+
+    assertFalse(distributionConfig.getValidateSerializableObjects());
+    assertEquals("!*", distributionConfig.getSerializableObjectFilter());
+  }
+
+  @Test
+  public void canSerializeWhenFilterIsDisabled() throws Exception {
+    trySerializingTestObject(new Properties());
+  }
+
+  @Test(expected = java.io.InvalidClassException.class)
+  public void notWhitelistedWithFilterCannotSerialize() throws Exception {
+    properties.setProperty(DistributionConfig.VALIDATE_SERIALIZABLE_OBJECTS_NAME, "true");
+
+    trySerializingTestObject(properties);
+  }
+
+  @Test
+  public void whitelistedWithFilterCanSerialize() throws Exception {
+    properties.setProperty(DistributionConfig.VALIDATE_SERIALIZABLE_OBJECTS_NAME, "true");
+    properties.setProperty(DistributionConfig.SERIALIZABLE_OBJECT_FILTER_NAME,
+        TestSerializable.class.getName());
+
+    trySerializingTestObject(properties);
+  }
+
+  @Test(expected = java.io.InvalidClassException.class)
+  public void whitelistedWithNonlMatchingFilterCannotSerialize() throws Exception {
+    trySerializingWithFilter("RabidMonkeyTurnip");
+  }
+
+  @Test(expected = java.io.InvalidClassException.class)
+  public void whitelistedWithPartialMatchingFilterCannotSerialize() throws Exception {
+    trySerializingWithFilter("TestSerializable"); // Not fully qualified class name
+  }
+
+  @Test(expected = java.io.InvalidClassException.class)
+  public void whitelistedWithEmptyFilterCannotSerialize() throws Exception {
+    trySerializingWithFilter("");
+  }
+
+  @Test(expected = java.io.InvalidClassException.class)
+  public void whitelistedWithIncorrectPathFilterCannotSerialize() throws Exception {
+    trySerializingWithFilter(
+        "org.apache.commons.InternalDataSerializerSerializationWhitelistTest$TestSerializable");
+  }
+
+  @Test(expected = java.io.InvalidClassException.class)
+  public void whitelistedWithWildcardPathFilterCannotSerialize() throws Exception {
+    trySerializingWithFilter("org.apache.*");
+  }
+
+  @Test
+  public void whitelistedWithWildcardSubpathFilterCanSerialize() throws Exception {
+    trySerializingWithFilter("org.apache.**");
+  }
+
+  private void trySerializingWithFilter(String filter) throws Exception {
+    properties.setProperty(DistributionConfig.VALIDATE_SERIALIZABLE_OBJECTS_NAME, "true");
+    properties.setProperty(DistributionConfig.SERIALIZABLE_OBJECT_FILTER_NAME, filter);
+
+    trySerializingTestObject(properties);
+  }
+
+  private void trySerializingTestObject(Properties properties)
+      throws IOException, ClassNotFoundException {
+    DistributionConfig distributionConfig = new DistributionConfigImpl(properties);
+    InternalDataSerializer.initialize(distributionConfig, new ArrayList<>());
+
+    DataSerializer.writeObject(testSerializable, outputStream);
+
+    // if this throws, we're good!
+    DataSerializer
+        .readObject(new DataInputStream(new ByteArrayInputStream(outputStream.toByteArray())));
+  }
+
+  private static class TestSerializable implements Serializable {
+
+  }
+
+}

-- 
To stop receiving notification emails like this one, please contact
['"commits@geode.apache.org" <co...@geode.apache.org>'].