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)
  • Constructor Details

  • Method Details

    • 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
    • deserialize

      protected AdminUser deserialize(com.fasterxml.jackson.databind.JsonNode jsonNode) throws IOException
      Throws:
      IOException
    • 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
    • arePermissionsEqual

      protected boolean arePermissionsEqual(Set<UserPermissionRef> newPermissions, Set<UserPermissionRef> existingPermissions)
    • toAuthPermissionRef

      @Deprecated protected UserPermissionRef toAuthPermissionRef(AdminPermissionRef adminPermissionRef)
      Deprecated.
    • toAuthPermissionRef

      protected UserPermissionRef toAuthPermissionRef(String id)
    • 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
    • areRolesEqual

      protected boolean areRolesEqual(Set<UserRoleRef> newRoles, Set<UserRoleRef> existingRoles)
    • toAuthRoleRef

      @Deprecated protected UserRoleRef toAuthRoleRef(AdminRoleRef adminRoleRef)
      Deprecated.
      Since 1.7.0. Use toAuthRoleRef(String)
    • toAuthRoleRef

      protected UserRoleRef toAuthRoleRef(String id)
    • 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)
    • toAuthRestriction

      protected Restriction toAuthRestriction(AdminRestriction adminRestriction)
    • updateUserRestrictedRoles

      protected boolean updateUserRestrictedRoles(AdminUser request, User user)
    • toAuthRestrictedRole

      protected RestrictedRole toAuthRestrictedRole(AdminRestrictedRole adminRestrictedRole)
    • updateUserRestrictedPermissions

      protected boolean updateUserRestrictedPermissions(AdminUser request, User user)
    • toAuthRestrictedPermission

      protected RestrictedPermission toAuthRestrictedPermission(AdminRestrictedPermission adminRestrictedPermission)
    • toAuthRestriction

      protected Restriction toAuthRestriction(String restrictionType, Set<String> restrictionTargets)
    • getAuthorizationServerId

      protected String getAuthorizationServerId(AdminUser request)
    • getSupportedSimpleTypeNames

      public String[] getSupportedSimpleTypeNames()
    • getUserService

      protected UserService<User> getUserService()
    • getClientService

      protected AuthorizedClientService<AuthorizedClient> getClientService()
    • getTypeFactory

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

      protected UserLockoutService getUserLockoutService()