GEODE-10093 - Fixed attr issue in Delta Session (#7405) (#7412)

* GEODE-10093 - Fixed attr issue in Delta Session

(cherry picked from commit e040759cd1e42df377501cd423967d549ce2bfab)
diff --git a/extensions/geode-modules-test/src/main/java/org/apache/geode/modules/session/catalina/AbstractDeltaSessionIntegrationTest.java b/extensions/geode-modules-test/src/main/java/org/apache/geode/modules/session/catalina/AbstractDeltaSessionIntegrationTest.java
index 5008b17..ee27e1b 100644
--- a/extensions/geode-modules-test/src/main/java/org/apache/geode/modules/session/catalina/AbstractDeltaSessionIntegrationTest.java
+++ b/extensions/geode-modules-test/src/main/java/org/apache/geode/modules/session/catalina/AbstractDeltaSessionIntegrationTest.java
@@ -17,8 +17,12 @@
 
 import static org.apache.geode.cache.RegionShortcut.PARTITION;
 import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.verifyNoMoreInteractions;
 import static org.mockito.Mockito.when;
@@ -35,11 +39,15 @@
 import org.junit.Rule;
 import org.junit.Test;
 import org.mockito.ArgumentCaptor;
+import org.mockito.InOrder;
+import org.mockito.Mockito;
 
 import org.apache.geode.cache.Region;
 import org.apache.geode.internal.util.BlobHelper;
 import org.apache.geode.modules.session.catalina.callback.SessionExpirationCacheListener;
+import org.apache.geode.modules.session.catalina.internal.DeltaSessionDestroyAttributeEvent;
 import org.apache.geode.modules.session.catalina.internal.DeltaSessionStatistics;
+import org.apache.geode.modules.session.catalina.internal.DeltaSessionUpdateAttributeEvent;
 import org.apache.geode.test.junit.rules.ServerStarterRule;
 
 public abstract class AbstractDeltaSessionIntegrationTest<DeltaSessionManagerT extends DeltaSessionManager<?>, DeltaSessionT extends DeltaSession> {
@@ -107,4 +115,94 @@
     verifyNoMoreInteractions(listener);
     assertThat(event.getValue().getValue()).isEqualTo(value1);
   }
+
+  @Test
+  public void setNewAttributeWithNullValueInvokesRemove() {
+    final HttpSessionAttributeListener listener = mock(HttpSessionAttributeListener.class);
+    when(context.getApplicationEventListeners()).thenReturn(new Object[] {listener});
+    when(manager.isBackingCacheAvailable()).thenReturn(true);
+
+    final DeltaSessionT session = spy(newSession(manager));
+    session.setId(KEY, false);
+    session.setValid(true);
+    session.setOwner(manager);
+
+    final String name = "attribute";
+    final Object nullValue = null;
+
+    session.setAttribute(name, nullValue);
+    assertThat(session.getAttributes().size()).isEqualTo(0);
+
+    verify(session).queueAttributeEvent(any(DeltaSessionDestroyAttributeEvent.class), anyBoolean());
+    verify(session, times(0)).queueAttributeEvent(any(DeltaSessionUpdateAttributeEvent.class),
+        anyBoolean());
+    verify(session).removeAttribute(eq(name));
+  }
+
+  @Test
+  public void setExistingAttributeWithNullValueInvokesRemove() {
+    final HttpSessionAttributeListener listener = mock(HttpSessionAttributeListener.class);
+    when(context.getApplicationEventListeners()).thenReturn(new Object[] {listener});
+    when(manager.isBackingCacheAvailable()).thenReturn(true);
+
+    final DeltaSessionT session = spy(newSession(manager));
+    session.setId(KEY, false);
+    session.setValid(true);
+    session.setOwner(manager);
+
+    final String name = "attribute";
+    final Object value = "value";
+    final Object nullValue = null;
+
+    session.setAttribute(name, value);
+    assertThat(session.getAttributes().size()).isEqualTo(1);
+
+    session.setAttribute(name, nullValue);
+    assertThat(session.getAttributes().size()).isEqualTo(0);
+
+
+    InOrder inOrder = Mockito.inOrder(session);
+    inOrder.verify(session).queueAttributeEvent(any(DeltaSessionUpdateAttributeEvent.class),
+        anyBoolean());
+    inOrder.verify(session).removeAttribute(eq(name));
+    inOrder.verify(session).queueAttributeEvent(any(DeltaSessionDestroyAttributeEvent.class),
+        anyBoolean());
+  }
+
+  @Test
+  public void getAttributeWithNullValueReturnsNull() throws IOException, ClassNotFoundException {
+    final HttpSessionAttributeListener listener = mock(HttpSessionAttributeListener.class);
+    when(context.getApplicationEventListeners()).thenReturn(new Object[] {listener});
+    when(manager.isBackingCacheAvailable()).thenReturn(true);
+
+    final DeltaSessionT session = spy(newSession(manager));
+    session.setId(KEY, false);
+    session.setValid(true);
+    session.setOwner(manager);
+
+    final String name = "attribute";
+    final Object value = null;
+
+    final byte[] serializedValue1 = BlobHelper.serializeToBlob(value);
+    // simulates initial deserialized state with serialized attribute values.
+    session.getAttributes().put(name, serializedValue1);
+
+    assertThat(session.getAttribute(name)).isNull();
+  }
+
+  @Test
+  public void getAttributeWithNullNameReturnsNull() throws IOException, ClassNotFoundException {
+    final HttpSessionAttributeListener listener = mock(HttpSessionAttributeListener.class);
+    when(context.getApplicationEventListeners()).thenReturn(new Object[] {listener});
+    when(manager.isBackingCacheAvailable()).thenReturn(true);
+
+    final DeltaSessionT session = spy(newSession(manager));
+    session.setId(KEY, false);
+    session.setValid(true);
+    session.setOwner(manager);
+
+    final String name = null;
+
+    assertThat(session.getAttribute(name)).isNull();
+  }
 }
diff --git a/extensions/geode-modules/src/main/java/org/apache/geode/modules/session/catalina/DeltaSession.java b/extensions/geode-modules/src/main/java/org/apache/geode/modules/session/catalina/DeltaSession.java
index e745ffe..9fe63bc 100644
--- a/extensions/geode-modules/src/main/java/org/apache/geode/modules/session/catalina/DeltaSession.java
+++ b/extensions/geode-modules/src/main/java/org/apache/geode/modules/session/catalina/DeltaSession.java
@@ -254,7 +254,7 @@
 
     synchronized (changeLock) {
       // Serialize the value
-      byte[] serializedValue = serialize(value);
+      final byte[] serializedValue = value == null ? null : serialize(value);
 
       // Store the attribute locally
       if (preferDeserializedForm) {
@@ -266,7 +266,9 @@
         super.setAttribute(name, serializedValue, true);
       }
 
-      if (serializedValue == null) {
+      // super.setAttribute above performed a removeAttribute for a value which was null once
+      // deserialized.
+      if (value == null) {
         return;
       }
 
@@ -332,6 +334,9 @@
 
   @Override
   public Object getAttribute(String name) {
+    if (name == null) {
+      return null;
+    }
     checkBackingCacheAvailable();
     Object value = deserializeAttribute(name, super.getAttribute(name), preferDeserializedForm);
 
@@ -353,6 +358,10 @@
     if (value instanceof byte[]) {
       try {
         final Object deserialized = BlobHelper.deserializeBlob((byte[]) value);
+        if (deserialized == null) {
+          removeAttributeInternal(name, false);
+          return null;
+        }
         if (store) {
           setAttributeInternal(name, deserialized);
         }
@@ -478,7 +487,7 @@
     }
   }
 
-  private void queueAttributeEvent(DeltaSessionAttributeEvent event,
+  void queueAttributeEvent(DeltaSessionAttributeEvent event,
       boolean checkAddToCurrentGatewayDelta) {
     // Add to current gateway delta if necessary
     if (checkAddToCurrentGatewayDelta) {