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";