Class AdminUserPersistenceHandler

  • All Implemented Interfaces:
    com.broadleafcommerce.common.messaging.PersistenceHandler

    public class AdminUserPersistenceHandler
    extends OperationAwarePersistenceHandler
    Handles messages from the Persistence channel for AdminUser data to update User. This is designed to perform a replacement of existing data within this service from the admin user service.
    Author:
    Samarth Dhruva (samarthd)
    • Method Detail

      • hook

        @StreamListener("persistenceInputAdminUser")
        public void hook​(String entityJson)
      • processCreateOperation

        protected void processCreateOperation​(@NonNull
                                              @NonNull String entityId,
                                              @NonNull
                                              @NonNull Instant changeTimestamp,
                                              @NonNull
                                              @NonNull com.fasterxml.jackson.databind.JsonNode entityJson)
                                       throws IOException
        Description copied from class: OperationAwarePersistenceHandler
        Invoked if the persistence message had an OperationType of OperationType.CREATE.
        Specified by:
        processCreateOperation in class OperationAwarePersistenceHandler
        Parameters:
        entityId - the ID found on the persistence message. Guaranteed non-empty.
        changeTimestamp - the timestamp found on the persistence message. Guaranteed non-null.
        entityJson - the persistence message json itself. Guaranteed non-null.
        Throws:
        IOException
      • findExistingAdminUser

        protected Optional<User> findExistingAdminUser​(String serviceId)
      • buildUserForCreate

        protected User buildUserForCreate​(AdminUser request,
                                          Instant lastUpdated)
      • mapUpdatesFromRequest

        protected boolean mapUpdatesFromRequest​(AdminUser request,
                                                User target)
        Perform any necessary updates of the admin user. If no update is necessary, this will return false.
        Parameters:
        request - The updated admin user
        target - The user to apply changes to
        Returns:
        true if the user should be updated, else false
      • attemptCreateOrThrow

        protected void attemptCreateOrThrow​(@NonNull
                                            @NonNull User newUser)
        For creation, we need a guarantee that only one attempt will succeed even in the event of concurrent duplicate messages or out-of-order messages.

        Thus, we need to use a method here that will create a record with the given non-null User.serviceId and User.type with the minimum guarantee that only one record with the given values will ever be created (even if the requests are highly concurrent).

        This means that create can fail for two main reasons, either another operation beat us to it or one of the entity's references (ex: User.getRoles()) is not present. In either case, the exception thrown by this method should be bubbled up to trigger a retry. If it was due to another operation concurrently creating the record, the retry will resolve quietly after the eager fetch (in processCreateOperation(String, Instant, JsonNode), processUpdateOperation(String, Instant, JsonNode), or processDeleteOperation(String, Instant, JsonNode)) finds the now-existing record and compares against its lastUpdated. If the error was due to missing references, the retry will keep trying until the referenced records have been created.

        TODO https://github.com/BroadleafCommerce/AuthenticationServices/issues/182

        Parameters:
        newUser - the entity to create. Must have a non-null User.serviceId, User.type, and User.lastUpdated.
        Throws:
        RuntimeException - if the create failed due to a concurrent create happening, or if one of the entity's references was not found
      • logReceiptOfPersistenceMessage

        protected void logReceiptOfPersistenceMessage​(@NonNull
                                                      @NonNull com.broadleafcommerce.data.tracking.core.type.OperationType operationType,
                                                      @NonNull
                                                      @NonNull Instant changeTimestamp,
                                                      @NonNull
                                                      @NonNull String entityId)
      • processUpdateOperation

        protected void processUpdateOperation​(@NonNull
                                              @NonNull String entityId,
                                              @NonNull
                                              @NonNull Instant changeTimestamp,
                                              @NonNull
                                              @NonNull com.fasterxml.jackson.databind.JsonNode entityJson)
                                       throws IOException
        Description copied from class: OperationAwarePersistenceHandler
        Invoked if the persistence message had an OperationType of OperationType.UPDATE.
        Specified by:
        processUpdateOperation in class OperationAwarePersistenceHandler
        Parameters:
        entityId - the entity ID found on the persistence message. Guaranteed non-empty.
        changeTimestamp - the timestamp found on the persistence message. Guaranteed non-null.
        entityJson - the persistence message json itself. Guaranteed non-null.
        Throws:
        IOException
      • performReplacementIfEligible

        protected void performReplacementIfEligible​(@NonNull
                                                    @NonNull User existing,
                                                    @NonNull
                                                    @NonNull AdminUser request,
                                                    @NonNull
                                                    @NonNull Instant changeTimestamp)
      • attemptReplaceOrThrow

        protected void attemptReplaceOrThrow​(@NonNull
                                             @NonNull String id,
                                             @NonNull
                                             @NonNull User replacement,
                                             @NonNull
                                             @NonNull Instant changeTimestamp)
        We cannot perform a normal "create or update" save operation here due to concurrency concerns. We need a guarantee that only one attempt will succeed even in the event of duplicate messages or out-of-order messages. Furthermore, the end result must be atomically updating an existing record only if its lastUpdated is before the current change.

        Thus, we invoke a special method that will directly attempt an update that the data store will reject if the entity is not found or has a lastUpdated ahead of the current change.

        This means that replace can fail for two main reasons, either the user wasn't found with a valid lastUpdated or one of the entity's references (ex: User.getRoles()) is not present. In either case, the exception thrown by this method should be bubbled up to trigger a retry. If it was due to another operation concurrently modifying the record (which would change its lastUpdated), the retry will resolve quietly after the eager fetch (in processCreateOperation(String, Instant, JsonNode), processUpdateOperation(String, Instant, JsonNode), or processDeleteOperation(String, Instant, JsonNode)) finds the updated record and compares against its lastUpdated. If the error was due to missing references, the retry will keep trying until the referenced record has been created.

        TODO https://github.com/BroadleafCommerce/AuthenticationServices/issues/182

        Parameters:
        id - the User.getId() of the entity to replace
        replacement - the replacement entity
        changeTimestamp - the lastUpdated of the current change
        Throws:
        RuntimeException - if the replacement failed due to the entity not existing or having an invalid lastUpdated, or if one of the entity's references was not found
      • processDeleteOperation

        protected void processDeleteOperation​(@NonNull
                                              @NonNull String entityId,
                                              @NonNull
                                              @NonNull Instant changeTimestamp,
                                              @NonNull
                                              @NonNull com.fasterxml.jackson.databind.JsonNode entityJson)
                                       throws IOException
        Archives the given entity.

        Note that if an existing record is not found, one will be created in an archived state. The motivation for this is to protect against out-of-order messages (ex: "Delete" message on an entity arrives before the "Create"). By establishing a pre-existing record in an archived state with a timestamp, any subsequent persistence message received can be compared against this record's timestamp and appropriately discarded as outdated rather than triggering creation of a new record.

        Specified by:
        processDeleteOperation in class OperationAwarePersistenceHandler
        Parameters:
        entityId - the entity ID found on the persistence message. Guaranteed non-empty.
        changeTimestamp - the timestamp found on the persistence message. Guaranteed non-null.
        entityJson - the persistence message json itself. Guaranteed non-null.
        Throws:
        IOException
      • performArchivalReplacementIfEligible

        protected void performArchivalReplacementIfEligible​(@NonNull
                                                            @NonNull User existing,
                                                            @NonNull
                                                            @NonNull Instant changeTimestamp)
      • modifyForArchival

        protected void modifyForArchival​(User target)
        Mimic what UserService.archive(String) would do to mark this record as archived.
        Parameters:
        target - the instance to modify
      • updatePermissions

        protected boolean updatePermissions​(AdminUser request,
                                            User user)
        We will not validate these references by querying for them, as the associated permissions may not yet be synchronized to the authentication service. We trust the admin user service to have pre-validated these associations, and thus eventually no references will be broken.

        TODO https://github.com/BroadleafCommerce/AuthenticationServices/issues/182

        Parameters:
        request - the request whose permissions should be used to replace the existing data
        user - the target on which the permissions will be replaced
      • updateUserRoles

        protected boolean updateUserRoles​(AdminUser request,
                                          User user)
        We will not validate these references by querying for them, as the associated roles may not yet be synchronized to the authentication service. We trust the admin user service to have pre-validated these associations, and thus eventually no references will be broken.

        TODO https://github.com/BroadleafCommerce/AuthenticationServices/issues/182

        Parameters:
        request - the request whose roles should be used to replace the existing data
        user - the target on which the roles will be replaced
      • updateUserInfo

        protected boolean updateUserInfo​(AdminUser request,
                                         User user)
      • updateUserStatus

        protected boolean updateUserStatus​(AdminUser request,
                                           User user)
      • updateUserTenantRestrictions

        protected boolean updateUserTenantRestrictions​(AdminUser request,
                                                       User user)
      • updateUserRestrictions

        protected boolean updateUserRestrictions​(AdminUser request,
                                                 User user)
      • updateUserRestrictedRoles

        protected boolean updateUserRestrictedRoles​(AdminUser request,
                                                    User user)
      • updateUserRestrictedPermissions

        protected boolean updateUserRestrictedPermissions​(AdminUser request,
                                                          User user)
      • getAuthorizationServerId

        protected String getAuthorizationServerId​(AdminUser request)
      • getSupportedSimpleTypeNames

        public String[] getSupportedSimpleTypeNames()
      • getTypeFactory

        protected com.broadleafcommerce.common.extension.TypeFactory getTypeFactory()