You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@isis.apache.org by ah...@apache.org on 2022/08/03 11:31:28 UTC

[isis] branch master updated: ISIS-3109: introduces general purpose KeyValueSessionStore

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

ahuber pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/isis.git


The following commit(s) were added to refs/heads/master by this push:
     new f317ab9a15 ISIS-3109: introduces general purpose KeyValueSessionStore
f317ab9a15 is described below

commit f317ab9a15c20c89fa7de268d93a935edafe8a24
Author: Andi Huber <ah...@apache.org>
AuthorDate: Wed Aug 3 13:31:22 2022 +0200

    ISIS-3109: introduces general purpose KeyValueSessionStore
    
    - resulting in ImpersonatedUserHolder no longer required by viewers to
    be implemented
---
 .../keyvaluestore/KeyValueSessionStore.java        | 82 ++++++++++++++++++++++
 .../services/user/ImpersonatedUserHolder.java      |  2 +-
 .../IsisModuleCoreRuntimeServices.java             |  2 +-
 .../ImpersonatedUserHolderDefault.java             | 57 ++++++---------
 .../isis/core/webapp/IsisModuleCoreWebapp.java     |  6 +-
 .../KeyValueStoreUsingHttpSession.java}            | 47 +++++++------
 .../spiimpl/SessionSubscriberForSessionLog.java    |  3 +-
 7 files changed, 135 insertions(+), 64 deletions(-)

diff --git a/api/applib/src/main/java/org/apache/isis/applib/services/keyvaluestore/KeyValueSessionStore.java b/api/applib/src/main/java/org/apache/isis/applib/services/keyvaluestore/KeyValueSessionStore.java
new file mode 100644
index 0000000000..6ea64c26f6
--- /dev/null
+++ b/api/applib/src/main/java/org/apache/isis/applib/services/keyvaluestore/KeyValueSessionStore.java
@@ -0,0 +1,82 @@
+/*
+ *  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.
+ */
+package org.apache.isis.applib.services.keyvaluestore;
+
+import java.io.Serializable;
+import java.util.Optional;
+
+import javax.servlet.http.HttpSession;
+
+import org.springframework.lang.Nullable;
+
+import lombok.NonNull;
+
+/**
+ * Defines a mechanism for viewers to store arbitrary key value pairs
+ * on a per-session basis. That is usually a {@link HttpSession}.
+ * <p>
+ * This store <i>is</i> used by the Wicket viewer. For example, the viewer
+ * remembers which time-zone the user has logged in. Or when impersonating.
+ *
+ * @since 2.0 {@index}
+ */
+public interface KeyValueSessionStore {
+
+    /**
+     * Whether a session is available, for storing/retrieving key/value pairs.
+     */
+    boolean isSessionAvailable();
+
+    /**
+     * Puts given value onto the session store, overriding any existing value.
+     * If value is null, removes the entry from the store.
+     * <p>
+     * In case there is no session store available, acts as a no-op.
+     * @param key - unique key (required)
+     * @param value - serializable value (optional)
+     */
+    void put(@NonNull String key, @Nullable Serializable value);
+
+    /**
+     * Optionally returns the value that is stored under given key,
+     * based on whether a corresponding entry exists.
+     * <p>
+     * In case there is no session store available, will return {@link Optional#empty()}.
+     */
+    <T extends Serializable>
+    Optional<T> lookupAs(@NonNull String key, @NonNull Class<T> requiredType);
+
+    /**
+     * Removes the entry from the store.
+     * <p>
+     * In case there is no session store available, acts as a no-op.
+     */
+    void clear(final @NonNull String key);
+
+    // -- SHORTCUTS
+
+    default Optional<String> lookupAsString(final @NonNull String key) {
+        return lookupAs(key, String.class);
+    }
+
+    default boolean getAsBoolean(final @NonNull String key) {
+        return lookupAs(key, Boolean.class).map(Boolean::booleanValue).orElse(false);
+    }
+
+}
diff --git a/api/applib/src/main/java/org/apache/isis/applib/services/user/ImpersonatedUserHolder.java b/api/applib/src/main/java/org/apache/isis/applib/services/user/ImpersonatedUserHolder.java
index 826966444c..f50daa97ee 100644
--- a/api/applib/src/main/java/org/apache/isis/applib/services/user/ImpersonatedUserHolder.java
+++ b/api/applib/src/main/java/org/apache/isis/applib/services/user/ImpersonatedUserHolder.java
@@ -27,7 +27,7 @@ import javax.servlet.http.HttpSession;
  * allow the current user to be temporarily impersonated.
  *
  * <p>
- *     The intention is that viewers provide an implementation of this service..
+ *     The intention is that viewers provide an implementation of this service.
  *     Note that the Wicket viewer <i>does</i> implement this service and
  *     uses an {@link HttpSession}; this will have the side-effect
  * </p>
diff --git a/core/runtimeservices/src/main/java/org/apache/isis/core/runtimeservices/IsisModuleCoreRuntimeServices.java b/core/runtimeservices/src/main/java/org/apache/isis/core/runtimeservices/IsisModuleCoreRuntimeServices.java
index b973496825..516960719d 100644
--- a/core/runtimeservices/src/main/java/org/apache/isis/core/runtimeservices/IsisModuleCoreRuntimeServices.java
+++ b/core/runtimeservices/src/main/java/org/apache/isis/core/runtimeservices/IsisModuleCoreRuntimeServices.java
@@ -40,6 +40,7 @@ import org.apache.isis.core.runtimeservices.homepage.HomePageResolverServiceDefa
 import org.apache.isis.core.runtimeservices.i18n.po.TranslationServicePo;
 import org.apache.isis.core.runtimeservices.i18n.po.TranslationServicePoMenu;
 import org.apache.isis.core.runtimeservices.icons.ObjectIconServiceDefault;
+import org.apache.isis.core.runtimeservices.impersonation.ImpersonatedUserHolderDefault;
 import org.apache.isis.core.runtimeservices.interaction.InteractionDtoFactoryDefault;
 import org.apache.isis.core.runtimeservices.jaxb.JaxbServiceDefault;
 import org.apache.isis.core.runtimeservices.locale.LanguageProviderDefault;
@@ -66,7 +67,6 @@ import org.apache.isis.core.runtimeservices.spring.SpringBeansService;
 import org.apache.isis.core.runtimeservices.transaction.TransactionServiceSpring;
 import org.apache.isis.core.runtimeservices.urlencoding.UrlEncodingServiceWithCompression;
 import org.apache.isis.core.runtimeservices.user.ImpersonateMenuAdvisorDefault;
-import org.apache.isis.core.runtimeservices.user.ImpersonatedUserHolderDefault;
 import org.apache.isis.core.runtimeservices.userreg.EmailNotificationServiceDefault;
 import org.apache.isis.core.runtimeservices.wrapper.WrapperFactoryDefault;
 import org.apache.isis.core.runtimeservices.xml.XmlServiceDefault;
diff --git a/core/runtimeservices/src/main/java/org/apache/isis/core/runtimeservices/user/ImpersonatedUserHolderDefault.java b/core/runtimeservices/src/main/java/org/apache/isis/core/runtimeservices/impersonation/ImpersonatedUserHolderDefault.java
similarity index 56%
rename from core/runtimeservices/src/main/java/org/apache/isis/core/runtimeservices/user/ImpersonatedUserHolderDefault.java
rename to core/runtimeservices/src/main/java/org/apache/isis/core/runtimeservices/impersonation/ImpersonatedUserHolderDefault.java
index 9847fd4581..111d09b9fe 100644
--- a/core/runtimeservices/src/main/java/org/apache/isis/core/runtimeservices/user/ImpersonatedUserHolderDefault.java
+++ b/core/runtimeservices/src/main/java/org/apache/isis/core/runtimeservices/impersonation/ImpersonatedUserHolderDefault.java
@@ -16,71 +16,54 @@
  *  specific language governing permissions and limitations
  *  under the License.
  */
-package org.apache.isis.core.runtimeservices.user;
+package org.apache.isis.core.runtimeservices.impersonation;
 
 import java.util.Optional;
 
+import javax.inject.Inject;
 import javax.inject.Named;
 
-import org.springframework.beans.factory.annotation.Qualifier;
-import org.springframework.stereotype.Service;
+import org.springframework.stereotype.Component;
 
 import org.apache.isis.applib.annotation.PriorityPrecedence;
+import org.apache.isis.applib.services.keyvaluestore.KeyValueSessionStore;
 import org.apache.isis.applib.services.user.ImpersonatedUserHolder;
 import org.apache.isis.applib.services.user.UserMemento;
 import org.apache.isis.core.runtimeservices.IsisModuleCoreRuntimeServices;
 
-/**
- * Used by the framework's default implementation of {@link org.apache.isis.applib.services.user.UserService} to
- * allow the current user to be temporarily impersonated.
- *
- * <p>
- *     The intention is that viewers provide an implementation of this service..
- *     Note that the Wicket viewer <i>does</i> implement this service and
- *     uses an {@link javax.servlet.http.HttpSession}; this will have the
- *     side-effect of making REST API potentially non stateful.
- * </p>
- *
- * <p>
- *     The default implementation does <i>not</i> support impersonation.
- * </p>
- */
-@Service
+@Component
 @Named(IsisModuleCoreRuntimeServices.NAMESPACE + ".ImpersonatedUserHolderDefault")
-@javax.annotation.Priority(PriorityPrecedence.LAST)
-@Qualifier("Default")
+@javax.annotation.Priority(PriorityPrecedence.MIDPOINT)
 public class ImpersonatedUserHolderDefault implements ImpersonatedUserHolder {
 
-    /**
-     * Returns <code>false</code>, as this implementation does <i>not</i>
-     * support impersonation.
-     */
+    private static final String SESSION_KEY_IMPERSONATED_USER =
+            ImpersonatedUserHolderDefault.class.getName() + "#userMemento";
+
+    @Inject private Optional<KeyValueSessionStore> keyValueSessionStore;
+
     @Override
     public boolean supportsImpersonation() {
-        return false;
+        return keyValueSessionStore
+            .map(KeyValueSessionStore::isSessionAvailable)
+            .orElse(false);
     }
 
-    /**
-     * Simply throws an exception.
-     */
     @Override
     public void setUserMemento(final UserMemento userMemento) {
-        throw new RuntimeException("This implementation does not support impersonation");
+        keyValueSessionStore
+            .ifPresent(store->store.put(SESSION_KEY_IMPERSONATED_USER, userMemento));
     }
 
-    /**
-     * Simply returns an empty Optional.
-     */
     @Override
     public Optional<UserMemento> getUserMemento() {
-        return Optional.empty();
+        return keyValueSessionStore
+            .flatMap(store->store.lookupAs(SESSION_KEY_IMPERSONATED_USER, UserMemento.class));
     }
 
-    /**
-     * No-op
-     */
     @Override
     public void clearUserMemento() {
+        keyValueSessionStore
+            .ifPresent(store->store.clear(SESSION_KEY_IMPERSONATED_USER));
     }
 
 }
diff --git a/core/webapp/src/main/java/org/apache/isis/core/webapp/IsisModuleCoreWebapp.java b/core/webapp/src/main/java/org/apache/isis/core/webapp/IsisModuleCoreWebapp.java
index 25d869c9fb..e11ab5530d 100644
--- a/core/webapp/src/main/java/org/apache/isis/core/webapp/IsisModuleCoreWebapp.java
+++ b/core/webapp/src/main/java/org/apache/isis/core/webapp/IsisModuleCoreWebapp.java
@@ -30,7 +30,7 @@ import org.apache.isis.core.interaction.session.MessageBroker;
 import org.apache.isis.core.runtime.IsisModuleCoreRuntime;
 import org.apache.isis.core.webapp.confmenu.ConfigurationViewServiceDefault;
 import org.apache.isis.core.webapp.health.HealthIndicatorUsingHealthCheckService;
-import org.apache.isis.core.webapp.impersonation.ImpersonatedUserHolderUsingHttpSession;
+import org.apache.isis.core.webapp.keyvaluestore.KeyValueStoreUsingHttpSession;
 import org.apache.isis.core.webapp.modules.logonlog.WebModuleLogOnExceptionLogger;
 import org.apache.isis.core.webapp.modules.templresources.WebModuleTemplateResources;
 import org.apache.isis.core.webapp.webappctx.IsisWebAppContextInitializer;
@@ -48,7 +48,7 @@ import org.apache.isis.core.webapp.webappctx.IsisWebAppContextInitializer;
         // @Component's
 
         HealthIndicatorUsingHealthCheckService.class,
-        ImpersonatedUserHolderUsingHttpSession.class,
+        KeyValueStoreUsingHttpSession.class,
 
         // (not annotated)
         IsisWebAppContextInitializer.class,
@@ -65,7 +65,7 @@ public class IsisModuleCoreWebapp {
     }
 
     /**
-     * for implementation of {@link ImpersonatedUserHolderUsingHttpSession}, using {@link org.springframework.web.context.request.RequestContextHolder}.
+     * for implementation of {@link KeyValueStoreUsingHttpSession}, using {@link org.springframework.web.context.request.RequestContextHolder}.
      *
      * @see org.springframework.web.context.request.RequestContextHolder
      * @see <a href="https://stackoverflow.com/a/44830684/56880">https://stackoverflow.com/a/44830684/56880</a>
diff --git a/core/webapp/src/main/java/org/apache/isis/core/webapp/impersonation/ImpersonatedUserHolderUsingHttpSession.java b/core/webapp/src/main/java/org/apache/isis/core/webapp/keyvaluestore/KeyValueStoreUsingHttpSession.java
similarity index 61%
rename from core/webapp/src/main/java/org/apache/isis/core/webapp/impersonation/ImpersonatedUserHolderUsingHttpSession.java
rename to core/webapp/src/main/java/org/apache/isis/core/webapp/keyvaluestore/KeyValueStoreUsingHttpSession.java
index 7f7db8e6f4..790da66298 100644
--- a/core/webapp/src/main/java/org/apache/isis/core/webapp/impersonation/ImpersonatedUserHolderUsingHttpSession.java
+++ b/core/webapp/src/main/java/org/apache/isis/core/webapp/keyvaluestore/KeyValueStoreUsingHttpSession.java
@@ -16,68 +16,75 @@
  *  specific language governing permissions and limitations
  *  under the License.
  */
-package org.apache.isis.core.webapp.impersonation;
+package org.apache.isis.core.webapp.keyvaluestore;
 
+import java.io.Serializable;
 import java.util.Optional;
 
 import javax.inject.Named;
 import javax.servlet.http.HttpSession;
 
+import org.springframework.lang.Nullable;
 import org.springframework.stereotype.Component;
 import org.springframework.web.context.request.RequestContextHolder;
 import org.springframework.web.context.request.ServletRequestAttributes;
 
 import org.apache.isis.applib.annotation.PriorityPrecedence;
-import org.apache.isis.applib.services.user.ImpersonatedUserHolder;
-import org.apache.isis.applib.services.user.UserMemento;
+import org.apache.isis.applib.services.keyvaluestore.KeyValueSessionStore;
+
+import lombok.NonNull;
 
 /**
- * Implementation that supports impersonation, using the {@link HttpSession}
- * to store the value.
+ * Implementation that uses the {@link HttpSession}
+ * to store key/value pairs.
  *
  * @since 2.0 {@index}
  */
 @Component
-@Named("isis.webapp.ImpersonatedUserHolderUsingHttpSession")
+@Named("isis.webapp.KeyValueStoreUsingHttpSession")
 @javax.annotation.Priority(PriorityPrecedence.MIDPOINT)
-public class ImpersonatedUserHolderUsingHttpSession implements ImpersonatedUserHolder {
-
-    private static final String HTTP_SESSION_KEY_IMPERSONATED_USER =
-            ImpersonatedUserHolderUsingHttpSession.class.getName() + "#userMemento";
+public class KeyValueStoreUsingHttpSession implements KeyValueSessionStore {
 
     @Override
-    public boolean supportsImpersonation() {
+    public boolean isSessionAvailable() {
         return httpSession().isPresent();
     }
 
     @Override
-    public void setUserMemento(final UserMemento userMemento) {
+    public void put(final @NonNull String key, final @Nullable Serializable value) {
+        if(value==null) {
+            clear(key);
+            return;
+        }
         httpSession()
         .ifPresent(session->
-            session.setAttribute(HTTP_SESSION_KEY_IMPERSONATED_USER, userMemento));
+            session.setAttribute(key, value));
     }
 
     @Override
-    public Optional<UserMemento> getUserMemento() {
+    public <T extends Serializable>
+    Optional<T> lookupAs(final @NonNull String key, final @NonNull Class<T> requiredType) {
         return httpSession()
-            .map(session->session.getAttribute(HTTP_SESSION_KEY_IMPERSONATED_USER))
-            .filter(UserMemento.class::isInstance)
-            .map(UserMemento.class::cast);
+                .map(session->session.getAttribute(key))
+                .filter(requiredType::isInstance)
+                .map(requiredType::cast);
     }
 
     @Override
-    public void clearUserMemento() {
+    public void clear(final @NonNull String key) {
         httpSession()
         .ifPresent(session->
-            session.removeAttribute(HTTP_SESSION_KEY_IMPERSONATED_USER));
+            session.removeAttribute(key));
     }
 
+    // -- HELPER
+
     private static Optional<HttpSession> httpSession() {
         return Optional.ofNullable(RequestContextHolder.getRequestAttributes())
                 .filter(ServletRequestAttributes.class::isInstance)
                 .map(ServletRequestAttributes.class::cast)
                 .map(ServletRequestAttributes::getRequest)
-                .map(x -> x.getSession(false));
+                .map(x -> x.getSession(false)); // asks for session without side-effects
     }
 
 }
diff --git a/extensions/security/sessionlog/applib/src/main/java/org/apache/isis/extensions/sessionlog/applib/spiimpl/SessionSubscriberForSessionLog.java b/extensions/security/sessionlog/applib/src/main/java/org/apache/isis/extensions/sessionlog/applib/spiimpl/SessionSubscriberForSessionLog.java
index 34e8c131ca..65cf1317c0 100644
--- a/extensions/security/sessionlog/applib/src/main/java/org/apache/isis/extensions/sessionlog/applib/spiimpl/SessionSubscriberForSessionLog.java
+++ b/extensions/security/sessionlog/applib/src/main/java/org/apache/isis/extensions/sessionlog/applib/spiimpl/SessionSubscriberForSessionLog.java
@@ -40,7 +40,6 @@ import org.apache.isis.extensions.sessionlog.applib.dom.SessionLogEntryRepositor
 
 import lombok.RequiredArgsConstructor;
 import lombok.val;
-import lombok.extern.log4j.Log4j2;
 
 /**
  * Implementation of the Isis {@link SessionSubscriber} creates a log
@@ -52,7 +51,7 @@ import lombok.extern.log4j.Log4j2;
 @Named(SessionSubscriberForSessionLog.LOGICAL_TYPE_NAME)
 @Priority(PriorityPrecedence.LATE)
 @Qualifier("sessionlog")
-@Log4j2
+//@Log4j2
 public class SessionSubscriberForSessionLog implements SessionSubscriber {
 
     static final String LOGICAL_TYPE_NAME = IsisModuleExtSessionLogApplib.NAMESPACE + ".SessionLoggingServiceDefault";