You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@isis.apache.org by da...@apache.org on 2021/06/11 08:40:25 UTC

[isis] branch ISIS-2726 updated: ISIS-2726: improves docs for UserService

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

danhaywood pushed a commit to branch ISIS-2726
in repository https://gitbox.apache.org/repos/asf/isis.git


The following commit(s) were added to refs/heads/ISIS-2726 by this push:
     new cbddfec  ISIS-2726: improves docs for UserService
cbddfec is described below

commit cbddfec0e4b2a751ae6546129300e5b97f0405a1
Author: danhaywood <da...@haywood-associates.co.uk>
AuthorDate: Fri Jun 11 09:40:00 2021 +0100

    ISIS-2726: improves docs for UserService
    
    removes UserServiceDefault
---
 .../org/apache/isis/applib/IsisModuleApplib.java   |   3 +
 .../org/apache/isis/applib/clock/VirtualClock.java |   2 -
 .../isis/applib/services/user/UserService.java     | 197 ++++++++++++++-------
 .../IsisModuleCoreRuntimeServices.java             |   2 -
 .../runtimeservices/user/UserServiceDefault.java   | 101 -----------
 .../shiro/ShiroSecmanLdap_restfulStressTest.java   |   2 +-
 .../isis/testdomain/jpa/JpaInventoryResource.java  |   2 +-
 7 files changed, 141 insertions(+), 168 deletions(-)

diff --git a/api/applib/src/main/java/org/apache/isis/applib/IsisModuleApplib.java b/api/applib/src/main/java/org/apache/isis/applib/IsisModuleApplib.java
index a307456..fd03241 100644
--- a/api/applib/src/main/java/org/apache/isis/applib/IsisModuleApplib.java
+++ b/api/applib/src/main/java/org/apache/isis/applib/IsisModuleApplib.java
@@ -56,6 +56,7 @@ import org.apache.isis.applib.services.sudo.SudoService;
 import org.apache.isis.applib.services.user.ImpersonateMenu;
 import org.apache.isis.applib.services.user.RoleMemento;
 import org.apache.isis.applib.services.user.UserMemento;
+import org.apache.isis.applib.services.user.UserService;
 import org.apache.isis.schema.IsisModuleSchema;
 
 /**
@@ -99,8 +100,10 @@ import org.apache.isis.schema.IsisModuleSchema;
         LayoutServiceMenu.class,
         ImpersonateMenu.class,
         MetaModelServiceMenu.class,
+        UserService.class,
         ApplicationFeatureMenu.class,
 
+
         // @Service(s)
         CommandDtoProcessorServiceIdentity.class,
         CommandLogger.class,
diff --git a/api/applib/src/main/java/org/apache/isis/applib/clock/VirtualClock.java b/api/applib/src/main/java/org/apache/isis/applib/clock/VirtualClock.java
index 0bbdc4c..28e18e7 100644
--- a/api/applib/src/main/java/org/apache/isis/applib/clock/VirtualClock.java
+++ b/api/applib/src/main/java/org/apache/isis/applib/clock/VirtualClock.java
@@ -60,8 +60,6 @@ public interface VirtualClock extends Serializable {
      *
      * @apiNote This is a universal time difference, that does not depend on
      * where you are (eg. your current timezone), just on when you are.
-     *
-     * @see {@link Instant}
      */
     Instant now();
 
diff --git a/api/applib/src/main/java/org/apache/isis/applib/services/user/UserService.java b/api/applib/src/main/java/org/apache/isis/applib/services/user/UserService.java
index 100126c..9e1c3c8 100644
--- a/api/applib/src/main/java/org/apache/isis/applib/services/user/UserService.java
+++ b/api/applib/src/main/java/org/apache/isis/applib/services/user/UserService.java
@@ -22,144 +22,219 @@ import java.util.List;
 import java.util.Optional;
 
 import javax.annotation.Nullable;
+import javax.inject.Inject;
+import javax.inject.Named;
 
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.context.annotation.Primary;
+import org.springframework.core.annotation.Order;
+import org.springframework.stereotype.Service;
+
+import org.apache.isis.applib.annotation.OrderPrecedence;
 import org.apache.isis.applib.services.iactnlayer.InteractionContext;
+import org.apache.isis.applib.services.iactnlayer.InteractionLayerTracker;
 import org.apache.isis.applib.services.sudo.SudoService;
 import org.apache.isis.commons.internal.exceptions._Exceptions;
 
+import lombok.RequiredArgsConstructor;
+import lombok.val;
+
 /**
  * Allows the domain object to obtain the identity of the user interacting with
  * said object.
  *
  * <p>
  * If {@link SudoService} has been used to temporarily override the user and/or
- * roles, then this service will report the overridden values instead.
+ * roles, then this service will report the overridden values instead.  This is within the context of a thread.
+ * </p>
+ *
+ * <p>
+ * In addition, if impersonation has been invoked through the {@link ImpersonateMenu}, then this service
+ * will report the impersonated user, with the companion {@link ImpersonatedUserHolder} taking responsibilty for
+ * remembering the impersonated user over multiple (http) requests, eg using an http session.  It's important to note
+ * that under these circumstances the user reported by this service (the &quot;effective&quot; user) will <i>not</i> be
+ * the same as the user held in the {@link InteractionContext}, as obtained by
+ * {@link InteractionLayerTracker#currentInteractionContext() InteractionLayerTracker} (the &quot;real&quot; user).
  * </p>
  *
+ * @see org.apache.isis.applib.services.iactnlayer.InteractionLayerTracker
+ * @see org.apache.isis.applib.services.iactnlayer.InteractionContext
+ * @see SudoService
+ * @see ImpersonateMenu
+ * @see ImpersonatedUserHolder
+ *
  * @since 1.x revised in 2.0 {@index}
  */
-public interface UserService {
+@Service
+@Named("isis.applib.UserService")
+@Order(OrderPrecedence.MIDPOINT)
+@Primary
+@Qualifier("Default")
+@RequiredArgsConstructor(onConstructor_ = {@Inject})
+public class UserService {
+
+    /**
+     * Default returned from {@link #currentUserNameElseNobody()}.
+     */
+    public static final String NOBODY = "__isis_nobody";
+
+    private final InteractionLayerTracker iInteractionLayerTracker;
+    private final List<ImpersonatedUserHolder> impersonatedUserHolders;
 
-    // -- INTERFACE
 
     /**
-     * Optionally gets the details about the current user,
-     * based on whether an {@link InteractionContext} can be found with the current thread's context.
+     * Returns the details about the current user, either the &quot;effective&quot; user (if being
+     * {@link #impersonateUser(String, List) impersonated}) otherwise the &quot;real&quot; user (as obtained from
+     * the {@link InteractionContext} of the current thread).
      */
-    Optional<UserMemento> currentUser();
+    public Optional<UserMemento> currentUser() {
+        val impersonatedUserIfAny = impersonatedUserIfAny();
+        return impersonatedUserIfAny.isPresent()
+                ? impersonatedUserIfAny
+                : iInteractionLayerTracker.currentInteractionContext().map(InteractionContext::getUser);
+    }
 
     /**
-     * Gets the details about the current user.
-     * @apiNote for backward compatibility
+     * Gets the details about the {@link #currentUser()} current user, if any (and returning <code>null</code> if
+     * there is none).
      */
     @Nullable
-    default UserMemento getUser() {
+    public UserMemento getUser() {
         return currentUser().orElse(null);
     }
 
 
-
     /**
-     * Gets the details about the current user.
+     * Gets the details about the {@link #currentUser()} current user, throwing an exception if there is none.
+     *
      * @throws IllegalStateException if no {@link InteractionContext} can be found with the current thread's context.
      */
-    default UserMemento currentUserElseFail() {
+    public UserMemento currentUserElseFail() {
         return currentUser()
                 .orElseThrow(()->_Exceptions.illegalState("Current thread has no InteractionContext."));
     }
 
     /**
-     * Optionally gets the the current user's name,
-     * based on whether an {@link InteractionContext} can be found with the current thread's context.
+     * Optionally gets the {@link #currentUser() current user}'s name, obtained from {@link UserMemento}.
      */
-    default Optional<String> currentUserName() {
+    public Optional<String> currentUserName() {
         return currentUser()
                 .map(UserMemento::getName);
     }
 
     /**
-     * Returns either the current user's name or else {@literal Nobody}.
+     * Returns either the current user's name or else {@link #NOBODY}.
      */
-    default String currentUserNameElseNobody() {
+    public String currentUserNameElseNobody() {
         return currentUserName()
-                .orElse("Nobody");
+                .orElse(NOBODY);
     }
 
+
+
+
     /**
-     * Allows implementations to override the current user with another user.
-     *
-     * <p>
-     *     This is intended for non-production environments only, where it can
-     *     be invaluable (from a support perspective) to be able to quickly
-     *     use the application &quot;as if&quot; logged in as another user.
-     * </p>
+     * Whether or not the user currently reported (in {@link #currentUser()}
+     * and similar) is actually an impersonated user.
      *
+     * @see #currentUser()
      * @see #supportsImpersonation()
-     * @see #getImpersonatedUser()
-     * @see #isImpersonating()
+     * @see #impersonateUser(String, List)
+     * @see #impersonatedUserIfAny()
      * @see #stopImpersonating()
-     *
-     * @param userName
-     * @param roles
      */
-    default void impersonateUser(final String userName, final List<String> roles) {
-        throw new RuntimeException("Not implemented");
+    public boolean isImpersonating() {
+        return impersonatedUserIfAny().isPresent();
     }
 
+
+
     /**
-     * For implementations that support impersonation, this is to
-     * programmatically stop impersonating a user
+     * Whether impersonation is available for this request.
      *
      * <p>
-     *     Intended to be called at some point after
-     *     {@link #impersonateUser(String, List)} would have been called.
+     *     The typical implementation uses an HTTP session, which is not guaranteed to be available
+     *     for all viewers.  Specifically, the Wicket viewer <i>does</i> use HTTP sessions and
+     *     therefore supports impersonation, but the RestfulObjects viewer does <i>not</i>.
+     *     This means that the result of this call varies on a request-by-request basis.
      * </p>
      *
-     * @see #supportsImpersonation()
      * @see #impersonateUser(String, List)
-     * @see #getImpersonatedUser()
+     * @see #impersonatedUserIfAny()
      * @see #isImpersonating()
+     * @see #stopImpersonating()
+     *
+     * @return whether impersonation is supported in the context of this (http) request.
      */
-    default void stopImpersonating() {
-        throw new RuntimeException("Not implemented");
+    public boolean supportsImpersonation() {
+        return impersonatingHolder()
+                .isPresent();
     }
 
-    /**
-     * Whether this implementation supports impersonation.
-     *
-     * @see #impersonateUser(String, List)
-     * @see #getImpersonatedUser()
-     * @see #isImpersonating()
-     * @see #stopImpersonating()
-     */
-    default boolean supportsImpersonation() {
-        return false;
+    private Optional<ImpersonatedUserHolder> impersonatingHolder() {
+        return impersonatedUserHolders.stream()
+                .filter(ImpersonatedUserHolder::supportsImpersonation)
+                .findFirst();
     }
 
     /**
-     * The impersonated user, if it has previously been set.
+     * Allows implementations to override the current user with another user.
+     *
+     * <p>
+     *     If this service (for this request) does not
+     *     {@link #supportsImpersonation() support impersonation}, then the
+     *     request is just ignored.
+     * </p>
+     *
+     * <p>
+     *     IMPORTANT: this is intended for non-production environments only, where it can
+     *     be invaluable (from a support perspective) to be able to quickly
+     *     use the application &quot;as if&quot; logged in as another user.
+     * </p>
      *
      * @see #supportsImpersonation()
-     * @see #impersonateUser(String, List)
+     * @see #impersonatedUserIfAny()
      * @see #isImpersonating()
      * @see #stopImpersonating()
+     *
+     * @param userName - the name of the user to be impersonated
+     * @param roles - the collection of roles for the impersonated user to have.
      */
-    default Optional<UserMemento> getImpersonatedUser() {
-        return Optional.empty();
+    public void impersonateUser(final String userName, final List<String> roles) {
+        impersonatingHolder().ifPresent(x ->
+                {
+                    val userMemento = UserMemento.ofNameAndRoleNames(userName, roles)
+                            .withImpersonating(true);
+                    x.setUserMemento(userMemento);
+                }
+        );
     }
 
     /**
-     * Whether or not the user currently reported (in {@link #currentUser()}
-     * and similar) is actually an impersonated user.
+     * For implementations that support impersonation, this is to
+     * programmatically stop impersonating a user
+     *
+     * <p>
+     *     If this service (for this request) does not
+     *     {@link #supportsImpersonation() support impersonation}, then the
+     *     request is just ignored.
+     * </p>
+     *
+     * <p>
+     *     Intended to be called at some point after
+     *     {@link #impersonateUser(String, List)} would have been called.
+     * </p>
      *
-     * @see #currentUser()
      * @see #supportsImpersonation()
      * @see #impersonateUser(String, List)
-     * @see #getImpersonatedUser()
-     * @see #stopImpersonating()
+     * @see #isImpersonating()
      */
-    default boolean isImpersonating() {
-        return getImpersonatedUser().isPresent();
+    public void stopImpersonating() {
+        impersonatingHolder().ifPresent(ImpersonatedUserHolder::clearUserMemento);
+    }
+
+    private Optional<UserMemento> impersonatedUserIfAny() {
+        return impersonatingHolder().flatMap(ImpersonatedUserHolder::getUserMemento);
     }
 
 }
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 b731b99..4c94c65 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
@@ -58,7 +58,6 @@ 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.user.UserServiceDefault;
 import org.apache.isis.core.runtimeservices.userreg.EmailNotificationServiceDefault;
 import org.apache.isis.core.runtimeservices.wrapper.WrapperFactoryDefault;
 import org.apache.isis.core.runtimeservices.xml.XmlServiceDefault;
@@ -98,7 +97,6 @@ import org.apache.isis.core.runtimeservices.xmlsnapshot.XmlSnapshotServiceDefaul
         ScratchpadDefault.class,
         TransactionServiceSpring.class,
         UrlEncodingServiceWithCompression.class,
-        UserServiceDefault.class,
         WrapperFactoryDefault.class,
         XmlServiceDefault.class,
         XmlSnapshotServiceDefault.class,
diff --git a/core/runtimeservices/src/main/java/org/apache/isis/core/runtimeservices/user/UserServiceDefault.java b/core/runtimeservices/src/main/java/org/apache/isis/core/runtimeservices/user/UserServiceDefault.java
deleted file mode 100644
index 4b85432..0000000
--- a/core/runtimeservices/src/main/java/org/apache/isis/core/runtimeservices/user/UserServiceDefault.java
+++ /dev/null
@@ -1,101 +0,0 @@
-/*
- *  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.core.runtimeservices.user;
-
-import java.util.List;
-import java.util.Optional;
-
-import javax.inject.Inject;
-import javax.inject.Named;
-
-import org.springframework.beans.factory.annotation.Qualifier;
-import org.springframework.context.annotation.Primary;
-import org.springframework.core.annotation.Order;
-import org.springframework.stereotype.Service;
-
-import org.apache.isis.applib.annotation.OrderPrecedence;
-import org.apache.isis.applib.services.iactnlayer.InteractionContext;
-import org.apache.isis.applib.services.iactnlayer.InteractionLayerTracker;
-import org.apache.isis.applib.services.user.ImpersonatedUserHolder;
-import org.apache.isis.applib.services.user.UserMemento;
-import org.apache.isis.applib.services.user.UserService;
-
-import lombok.RequiredArgsConstructor;
-import lombok.val;
-
-/**
- * This default implementation delegates to {@link ImpersonatedUserHolder} to
- * hold an impersonated user (if supported).
- */
-@Service
-@Named("isis.runtimeservices.UserServiceDefault")
-@Order(OrderPrecedence.MIDPOINT)
-@Primary
-@Qualifier("Default")
-@RequiredArgsConstructor(onConstructor_ = {@Inject})
-public class UserServiceDefault implements UserService {
-
-    private final InteractionLayerTracker iInteractionLayerTracker;
-    private final List<ImpersonatedUserHolder> impersonatedUserHolders;
-
-    /**
-     * Either the current user or the one being impersonated.
-     */
-    @Override
-    public Optional<UserMemento> currentUser() {
-        Optional<UserMemento> optional = getImpersonatedUser();
-        return optional.isPresent()
-                ? optional
-                : iInteractionLayerTracker.currentInteractionContext().map(InteractionContext::getUser);
-    }
-
-    @Override
-    public boolean supportsImpersonation() {
-        return impersonatingHolder()
-                .isPresent();
-    }
-
-    private Optional<ImpersonatedUserHolder> impersonatingHolder() {
-        return impersonatedUserHolders.stream()
-                .filter(ImpersonatedUserHolder::supportsImpersonation)
-                .findFirst();
-    }
-
-    @Override
-    public void impersonateUser(final String userName, final List<String> roles) {
-        impersonatingHolder().ifPresent(x ->
-                {
-                    val userMemento = UserMemento.ofNameAndRoleNames(userName, roles)
-                                        .withImpersonating(true);
-                    x.setUserMemento(userMemento);
-                }
-        );
-    }
-
-    @Override
-    public void stopImpersonating() {
-        impersonatingHolder().ifPresent(ImpersonatedUserHolder::clearUserMemento);
-    }
-
-    @Override
-    public Optional<UserMemento> getImpersonatedUser() {
-        return impersonatingHolder().flatMap(ImpersonatedUserHolder::getUserMemento);
-    }
-
-}
diff --git a/regressiontests/incubating/src/test/java/org/apache/isis/testdomain/shiro/ShiroSecmanLdap_restfulStressTest.java b/regressiontests/incubating/src/test/java/org/apache/isis/testdomain/shiro/ShiroSecmanLdap_restfulStressTest.java
index 8e29fa5..46ba1b1 100644
--- a/regressiontests/incubating/src/test/java/org/apache/isis/testdomain/shiro/ShiroSecmanLdap_restfulStressTest.java
+++ b/regressiontests/incubating/src/test/java/org/apache/isis/testdomain/shiro/ShiroSecmanLdap_restfulStressTest.java
@@ -129,7 +129,7 @@ class ShiroSecmanLdap_restfulStressTest extends AbstractShiroTest {
 
                 assertNotNull(httpSessionInfo);
 
-                // impersonation in UserServiceDefault means that we _do_ now get
+                // impersonation in UserService means that we _do_ now get
                 // an httpSession as a side-effect
                 //assertEquals("no http-session", httpSessionInfo);
                 assertEquals("http-session attribute names: {}", httpSessionInfo);
diff --git a/regressiontests/stable/src/main/java/org/apache/isis/testdomain/jpa/JpaInventoryResource.java b/regressiontests/stable/src/main/java/org/apache/isis/testdomain/jpa/JpaInventoryResource.java
index 8406ba4..567fc4a 100644
--- a/regressiontests/stable/src/main/java/org/apache/isis/testdomain/jpa/JpaInventoryResource.java
+++ b/regressiontests/stable/src/main/java/org/apache/isis/testdomain/jpa/JpaInventoryResource.java
@@ -93,7 +93,7 @@ public class JpaInventoryResource {
     public String httpSessionInfo() {
 
         // when running with basic-auth strategy, we don't want to create HttpSessions at all
-        // however, this isn't the case if UserServiceDefault is in use, as that _dpes_
+        // however, this isn't the case if UserService is in use, as that _dpes_
         // use HttpSession to hold any impersonated user.
 
         val servletRequestAttributes =