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 2020/12/03 12:48:44 UTC

[isis] branch master updated: ISIS-2464: prepare removal of Clock (in favor of VirtualClock)

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 72eddd8  ISIS-2464: prepare removal of Clock (in favor of VirtualClock)
72eddd8 is described below

commit 72eddd8b171eb14a1004b7631dfd7364312179b8
Author: Andi Huber <ah...@apache.org>
AuthorDate: Thu Dec 3 13:48:24 2020 +0100

    ISIS-2464: prepare removal of Clock (in favor of VirtualClock)
---
 .../java/org/apache/isis/applib/clock/Clock.java   |   2 +-
 .../org/apache/isis/applib/clock/VirtualClock.java | 118 +++++++++++++++++++++
 .../isis/applib/services/user/UserMemento.java     |  51 ++++-----
 .../isis/commons/internal/collections/_Lists.java  |   6 ++
 .../runtimeservices/user/UserServiceDefault.java   |   2 +-
 .../authentication/AuthenticationSession.java      |  18 +++-
 .../AuthenticationSessionAbstract.java             |  80 +++++++-------
 .../authentication/health/HealthAuthSession.java   |   3 +-
 .../authentication/standard/SimpleSession.java     |   3 +-
 9 files changed, 209 insertions(+), 74 deletions(-)

diff --git a/api/applib/src/main/java/org/apache/isis/applib/clock/Clock.java b/api/applib/src/main/java/org/apache/isis/applib/clock/Clock.java
index 37ec6a4..e29f459 100644
--- a/api/applib/src/main/java/org/apache/isis/applib/clock/Clock.java
+++ b/api/applib/src/main/java/org/apache/isis/applib/clock/Clock.java
@@ -36,7 +36,7 @@ import lombok.val;
  *
  * <p>
  * The clock is used primarily by the temporal value classes, and is accessed by
- * the NOF as a singleton. The actual implementation used can be configured at
+ * the framework as a singleton. The actual implementation used can be configured at
  * startup, but once specified the clock instance cannot be changed.
  *
  * <p>
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
new file mode 100644
index 0000000..7635625
--- /dev/null
+++ b/api/applib/src/main/java/org/apache/isis/applib/clock/VirtualClock.java
@@ -0,0 +1,118 @@
+/*
+ *  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.clock;
+
+import java.io.Serializable;
+import java.sql.Timestamp;
+import java.time.Instant;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.OffsetDateTime;
+import java.time.ZoneId;
+
+import org.joda.time.DateTime;
+import org.joda.time.DateTimeZone;
+
+import org.apache.isis.applib.services.iactn.Interaction;
+
+import lombok.NonNull;
+
+/**
+ * Works in connection with {@link InteractionFactory}, such that it allows an {@link Interaction}
+ * to run with its own simulated (or actual) time. 
+ * <p>
+ * Relates to {@link VirtualContext}
+ * 
+ * @since 2.0
+ *
+ */
+@FunctionalInterface
+public interface VirtualClock extends Serializable {
+    
+    // -- INTERFACE
+    
+    /**
+     * Returns the (virtual) time as an {@link Instant}.
+     * 
+     * @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();
+    
+    // -- FACTORIES
+    
+    static VirtualClock system() {
+        return Instant::now;
+    }
+    
+    // -- UTILITY
+    
+    /**
+     * Returns the (virtual) time as the number of milliseconds since the epoch start.
+     * 
+     * @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}
+     */
+    default long getEpochMillis() {
+        return now().toEpochMilli();
+    }
+
+    /**
+     * Returns the (virtual) time as {@link LocalDate}, using the {@link ZoneId} timezone.
+     * @param zoneId - the time-zone, which may be an offset, not null
+     */
+    default LocalDate getTimeAsLocalDate(final @NonNull ZoneId zoneId) {
+        return getTimeAsLocalDateTime(zoneId).toLocalDate();
+    }
+
+    /**
+     * Returns the (virtual) time as {@link LocalDateTime}, using the {@link ZoneId} timezone.
+     * @param zoneId - the time-zone, which may be an offset, not null
+     */
+    default LocalDateTime getTimeAsLocalDateTime(final @NonNull ZoneId zoneId) {
+        return LocalDateTime.ofInstant(now(), zoneId);
+    }
+
+    /**
+     * Returns the (virtual) time as {@link OffsetDateTime}, using the {@link ZoneId} timezone.
+     * @param zoneId - the time-zone, which may be an offset, not null
+     */
+    default OffsetDateTime getTimeAsOffsetDateTime(final @NonNull ZoneId zoneId) {
+        return OffsetDateTime.ofInstant(now(), zoneId);
+    }
+
+    default Timestamp getTimeAsJavaSqlTimestamp() {
+        return new java.sql.Timestamp(getEpochMillis());
+    }
+
+    /**
+     * Returns the time as a Joda {@link DateTime},
+     * using the {@link ZoneId#systemDefault() system default} timezone.
+     * @deprecated please migrate to java.time.* TODO provide a compatibility layer?
+     */
+    @Deprecated
+    default DateTime getTimeAsJodaDateTime() {
+        final ZoneId zoneId = ZoneId.systemDefault();
+        return new DateTime(getEpochMillis(), DateTimeZone.forID(zoneId.getId()));
+    }
+}
diff --git a/api/applib/src/main/java/org/apache/isis/applib/services/user/UserMemento.java b/api/applib/src/main/java/org/apache/isis/applib/services/user/UserMemento.java
index d90ea91..607fa44 100644
--- a/api/applib/src/main/java/org/apache/isis/applib/services/user/UserMemento.java
+++ b/api/applib/src/main/java/org/apache/isis/applib/services/user/UserMemento.java
@@ -30,7 +30,7 @@ import lombok.Getter;
 import lombok.experimental.UtilityClass;
 
 /**
- * Details, obtained from the container, about the user and his roles.
+ * Details about a user and his roles.
  * Read-only.
  */
 // tag::refguide[]
@@ -59,7 +59,7 @@ public final class UserMemento {
             throw new IllegalArgumentException("Name not specified");
         }
         this.name = name;
-        this.roles.addAll(roles);
+        this.roles = Collections.unmodifiableList(new ArrayList<RoleMemento>(roles));
     }
 
     public String title() {
@@ -79,10 +79,10 @@ public final class UserMemento {
      * The roles associated with this user.
      */
     @MemberOrder(sequence = "1.1")
-    private final List<RoleMemento> roles = new ArrayList<RoleMemento>();
+    private final List<RoleMemento> roles;
     // tag::refguide[]
     public List<RoleMemento> getRoles() {
-        return Collections.unmodifiableList(roles);
+        return roles;
     }
     // end::refguide[]
     /**
@@ -99,27 +99,28 @@ public final class UserMemento {
         return name.equals(userName);
     }
 
-    /**
-     * Determines if the user fulfills the specified role.
-     *
-     * @param role  the role to search for, regular expressions are allowed
-     */
-    public boolean hasRole(final RoleMemento role) {
-        return hasRole(role.getName());
-    }
-
-    /**
-     * Determines if the user fulfills the specified role. Roles are compared
-     * lexically by role name.
-     */
-    public boolean hasRole(final String roleName) {
-        for (final RoleMemento role : roles) {
-            if (role.getName().matches(roleName)) {
-                return true;
-            }
-        }
-        return false;
-    }
+//XXX implemented as regex match, java-doc is not specific about what these methods actually do; so if in doubt, rather remove     
+//    /**
+//     * Determines if the user fulfills the specified role.
+//     *
+//     * @param role  the role to search for, regular expressions are allowed
+//     */
+//    public boolean hasRole(final RoleMemento role) {
+//        return hasRole(role.getName());
+//    }
+//
+//    /**
+//     * Determines if the user fulfills the specified role. Roles are compared
+//     * lexically by role name.
+//     */
+//    public boolean hasRole(final String roleName) {
+//        for (final RoleMemento role : roles) {
+//            if (role.getName().matches(roleName)) {
+//                return true;
+//            }
+//        }
+//        return false;
+//    }
 
     @Override
     public String toString() {
diff --git a/commons/src/main/java/org/apache/isis/commons/internal/collections/_Lists.java b/commons/src/main/java/org/apache/isis/commons/internal/collections/_Lists.java
index 0f5d8f0..3d08f03 100644
--- a/commons/src/main/java/org/apache/isis/commons/internal/collections/_Lists.java
+++ b/commons/src/main/java/org/apache/isis/commons/internal/collections/_Lists.java
@@ -117,6 +117,10 @@ public final class _Lists {
     public static <T> ArrayList<T> newArrayList() {
         return new ArrayList<T>();
     }
+    
+    public static <T> ArrayList<T> newArrayList(final int initialSize) {
+        return new ArrayList<T>(initialSize);
+    }
 
     public static <T> ArrayList<T> newArrayList(@Nullable Collection<T> collection) {
         if(collection==null) {
@@ -196,4 +200,6 @@ public final class _Lists {
         return toUnmodifiable(ArrayList::new);
     }
 
+
+
 }
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
index f9fa51d..38565b8 100644
--- 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
@@ -97,7 +97,7 @@ public class UserServiceDefault implements UserService {
 
         } else {
             return isisInteractionTracker.currentAuthenticationSession()
-            .map(AuthenticationSession::createUserMemento)
+            .map(AuthenticationSession::getUser)
             .orElseThrow(()->_Exceptions.illegalState("need an AuthenticationSession to create a UserMemento"));
         }
     }
diff --git a/core/security/src/main/java/org/apache/isis/core/security/authentication/AuthenticationSession.java b/core/security/src/main/java/org/apache/isis/core/security/authentication/AuthenticationSession.java
index afdb374..dc1a174 100644
--- a/core/security/src/main/java/org/apache/isis/core/security/authentication/AuthenticationSession.java
+++ b/core/security/src/main/java/org/apache/isis/core/security/authentication/AuthenticationSession.java
@@ -21,6 +21,8 @@ package org.apache.isis.core.security.authentication;
 
 import java.io.Serializable;
 
+import org.apache.isis.applib.clock.VirtualClock;
+import org.apache.isis.applib.services.iactn.Interaction;
 import org.apache.isis.applib.services.user.UserMemento;
 import org.apache.isis.commons.collections.Can;
 import org.apache.isis.core.security.authentication.manager.AuthenticationManager;
@@ -75,7 +77,21 @@ public interface AuthenticationSession extends Serializable {
      */
     MessageBroker getMessageBroker();
 
-    UserMemento createUserMemento();
+    /**
+     * The (programmatically) simulated (or actual) user, belonging to this session.
+     * 
+     * @apiNote immutable, allows an {@link Interaction} to (logically) run with its 
+     * own simulated (or actual) user 
+     */
+    UserMemento getUser();
+    
+    /**
+     * The (programmatically) simulated (or actual) clock, belonging to this session.
+     * 
+     * @apiNote immutable, allows an {@link Interaction} to (logically) run with its 
+     * own simulated (or actual) clock 
+     */
+    VirtualClock getClock();
 
     /**
      * To support external security mechanisms such as keycloak, where the validity of the session is defined by
diff --git a/core/security/src/main/java/org/apache/isis/core/security/authentication/AuthenticationSessionAbstract.java b/core/security/src/main/java/org/apache/isis/core/security/authentication/AuthenticationSessionAbstract.java
index e897a96..ad3ad69 100644
--- a/core/security/src/main/java/org/apache/isis/core/security/authentication/AuthenticationSessionAbstract.java
+++ b/core/security/src/main/java/org/apache/isis/core/security/authentication/AuthenticationSessionAbstract.java
@@ -21,53 +21,64 @@ package org.apache.isis.core.security.authentication;
 
 import java.io.Serializable;
 import java.util.HashMap;
-import java.util.List;
 import java.util.Map;
 import java.util.Objects;
-import java.util.Set;
 import java.util.stream.Stream;
 
+import org.apache.isis.applib.clock.VirtualClock;
 import org.apache.isis.applib.services.user.RoleMemento;
 import org.apache.isis.applib.services.user.UserMemento;
 import org.apache.isis.applib.util.ToString;
 import org.apache.isis.commons.collections.Can;
 import org.apache.isis.commons.internal.base._Strings;
 import org.apache.isis.commons.internal.collections._Lists;
-import org.apache.isis.commons.internal.collections._Sets;
 
 import lombok.Getter;
 import lombok.NonNull;
+import lombok.val;
 
-public abstract class AuthenticationSessionAbstract implements AuthenticationSession, Serializable {
+public abstract class AuthenticationSessionAbstract 
+implements AuthenticationSession, Serializable {
 
     private static final long serialVersionUID = 1L;
 
     // -- Constructor, fields
 
-    @Getter private final String userName;
-    private final Set<String> roles = _Sets.newHashSet();
-    private transient Can<String> rolesImmutable;
+    @Getter(onMethod_ = {@Override}) @NonNull 
+    private final VirtualClock clock;
+    
+    @Getter(onMethod_ = {@Override}) @NonNull 
+    private final String userName;
+    
+    @Getter(onMethod_ = {@Override}) @NonNull
+    private final Can<String> roles;
+    
+    @Getter(onMethod_ = {@Override}) @NonNull
     private final String validationCode;
 
-    private final Map<String, Object> attributeByName = new HashMap<String, Object>();
-
+    @Getter(onMethod_ = {@Override}) @NonNull
     private final MessageBroker messageBroker;
+    
+    private final Map<String, Object> attributeByName = new HashMap<String, Object>();
+    
 
     public AuthenticationSessionAbstract(
             @NonNull final String name, 
             @NonNull final String validationCode) {
-        this(name, Stream.empty(), validationCode);
+        this(VirtualClock.system(), name, Stream.empty(), validationCode);
     }
 
     public AuthenticationSessionAbstract(
+            @NonNull final VirtualClock clock,
             @NonNull final String userName, 
             @NonNull final Stream<String> roles, 
             @NonNull final String validationCode) {
-        this.userName = userName;
 
-        roles
-        .filter(_Strings::isNotEmpty)
-        .forEach(this.roles::add);
+        this.clock = clock;
+        this.userName = userName;
+        this.roles = Can.ofStream(roles
+                .filter(_Strings::isNotEmpty)
+                .distinct());
 
         this.validationCode = validationCode;
         this.messageBroker = new MessageBroker();
@@ -84,27 +95,10 @@ public abstract class AuthenticationSessionAbstract implements AuthenticationSes
     // -- Roles
 
     @Override
-    public Can<String> getRoles() {
-        if(rolesImmutable==null) { 
-            // lazy in support of serialization, 
-            // its also (effectively) thread-safe without doing any synchronization here 
-            rolesImmutable = Can.ofCollection(roles);
-        }
-        return rolesImmutable;
-    }
-
-    @Override
     public boolean hasRole(String role) {
         return roles.contains(role);
     }
 
-    // -- Validation Code
-
-    @Override
-    public String getValidationCode() {
-        return validationCode;
-    }
-
     // -- Attributes
 
     @Override
@@ -117,22 +111,20 @@ public abstract class AuthenticationSessionAbstract implements AuthenticationSes
         attributeByName.put(attributeName, attribute);
     }
 
-    // -- MessageBroker
-
-    @Override
-    public MessageBroker getMessageBroker() {
-        return messageBroker;
-    }
-
-    // -- createUserMemento
+    // -- UserMemento
 
+    private transient UserMemento user; 
+    
     @Override
-    public UserMemento createUserMemento() {
-        final List<RoleMemento> roles = _Lists.newArrayList();
-        for (final String roleName : this.roles) {
-            roles.add(new RoleMemento(roleName));
+    public UserMemento getUser() {
+        if(user==null) {
+            val roleMementos = _Lists.<RoleMemento>newArrayList(roles.size());
+            for (final String roleName : roles) {
+                roleMementos.add(new RoleMemento(roleName));
+            }
+            user = new UserMemento(getUserName(), roleMementos);
         }
-        return new UserMemento(getUserName(), roles);
+        return user;
     }
 
     // -- toString
diff --git a/core/security/src/main/java/org/apache/isis/core/security/authentication/health/HealthAuthSession.java b/core/security/src/main/java/org/apache/isis/core/security/authentication/health/HealthAuthSession.java
index 6d8c257..854b2ed 100644
--- a/core/security/src/main/java/org/apache/isis/core/security/authentication/health/HealthAuthSession.java
+++ b/core/security/src/main/java/org/apache/isis/core/security/authentication/health/HealthAuthSession.java
@@ -21,6 +21,7 @@ package org.apache.isis.core.security.authentication.health;
 
 import java.util.stream.Stream;
 
+import org.apache.isis.applib.clock.VirtualClock;
 import org.apache.isis.core.security.authentication.AuthenticationSessionAbstract;
 
 public class HealthAuthSession extends AuthenticationSessionAbstract {
@@ -32,7 +33,7 @@ public class HealthAuthSession extends AuthenticationSessionAbstract {
     private static final String CODE = "";
 
     public HealthAuthSession() {
-        super(USER_NAME, Stream.of(ROLE), CODE);
+        super(VirtualClock.system(), USER_NAME, Stream.of(ROLE), CODE);
     }
 
 
diff --git a/core/security/src/main/java/org/apache/isis/core/security/authentication/standard/SimpleSession.java b/core/security/src/main/java/org/apache/isis/core/security/authentication/standard/SimpleSession.java
index 6a1dcb9..c40b310 100644
--- a/core/security/src/main/java/org/apache/isis/core/security/authentication/standard/SimpleSession.java
+++ b/core/security/src/main/java/org/apache/isis/core/security/authentication/standard/SimpleSession.java
@@ -22,6 +22,7 @@ package org.apache.isis.core.security.authentication.standard;
 import java.util.Collection;
 import java.util.stream.Stream;
 
+import org.apache.isis.applib.clock.VirtualClock;
 import org.apache.isis.core.security.authentication.AuthenticationSessionAbstract;
 
 import static org.apache.isis.commons.internal.base._NullSafe.stream;
@@ -46,7 +47,7 @@ public class SimpleSession extends AuthenticationSessionAbstract {
     }
 
     public SimpleSession(final String userName, final Stream<String> roles, final String code) {
-        super(userName, roles, code);
+        super(VirtualClock.system(), userName, roles, code);
     }
 
     @Getter @Setter