Class AdminUserPersistenceHandler
- java.lang.Object
-
- com.broadleafcommerce.auth.user.listener.AuthDataUpdatePersistenceHandler
-
- com.broadleafcommerce.auth.user.listener.OperationAwarePersistenceHandler
-
- com.broadleafcommerce.auth.user.listener.AdminUserPersistenceHandler
-
- All Implemented Interfaces:
com.broadleafcommerce.common.messaging.PersistenceHandler
public class AdminUserPersistenceHandler extends OperationAwarePersistenceHandler
Handles messages from the Persistence channel forAdminUser
data to updateUser
. This is designed to perform a replacement of existing data within this service from the admin user service.- Author:
- Samarth Dhruva (samarthd)
-
-
Constructor Summary
Constructors Constructor Description AdminUserPersistenceHandler(UserService<User> userService, AuthorizedClientService<AuthorizedClient> clientService, com.fasterxml.jackson.databind.ObjectMapper mapper, com.broadleafcommerce.common.extension.TypeFactory typeFactory, UserLockoutService userLockoutService)
-
Method Summary
All Methods Instance Methods Concrete Methods Deprecated Methods Modifier and Type Method Description protected boolean
arePermissionsEqual(Set<UserPermissionRef> newPermissions, Set<UserPermissionRef> existingPermissions)
protected boolean
areRolesEqual(Set<UserRoleRef> newRoles, Set<UserRoleRef> existingRoles)
protected void
attemptCreateOrThrow(@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.protected void
attemptReplaceOrThrow(@NonNull String id, @NonNull User replacement, @NonNull Instant changeTimestamp)
We cannot perform a normal "create or update" save operation here due to concurrency concerns.protected User
buildUserForCreate(AdminUser request, Instant lastUpdated)
protected AdminUser
deserialize(com.fasterxml.jackson.databind.JsonNode jsonNode)
protected Optional<User>
findExistingAdminUser(String serviceId)
protected String
getAuthorizationServerId(AdminUser request)
protected AuthorizedClientService<AuthorizedClient>
getClientService()
String[]
getSupportedSimpleTypeNames()
protected com.broadleafcommerce.common.extension.TypeFactory
getTypeFactory()
protected UserLockoutService
getUserLockoutService()
protected UserService<User>
getUserService()
void
hook(String entityJson)
protected void
logReceiptOfPersistenceMessage(@NonNull com.broadleafcommerce.data.tracking.core.type.OperationType operationType, @NonNull Instant changeTimestamp, @NonNull String entityId)
protected boolean
mapUpdatesFromRequest(AdminUser request, User target)
Perform any necessary updates of the admin user.protected void
modifyForArchival(User target)
Mimic whatUserService.archive(String)
would do to mark this record as archived.protected void
performArchivalReplacementIfEligible(@NonNull User existing, @NonNull Instant changeTimestamp)
protected void
performReplacementIfEligible(@NonNull User existing, @NonNull AdminUser request, @NonNull Instant changeTimestamp)
protected void
processCreateOperation(@NonNull String entityId, @NonNull Instant changeTimestamp, @NonNull com.fasterxml.jackson.databind.JsonNode entityJson)
Invoked if the persistence message had anOperationType
ofOperationType.CREATE
.protected void
processDeleteOperation(@NonNull String entityId, @NonNull Instant changeTimestamp, @NonNull com.fasterxml.jackson.databind.JsonNode entityJson)
Archives the given entity.protected void
processUpdateOperation(@NonNull String entityId, @NonNull Instant changeTimestamp, @NonNull com.fasterxml.jackson.databind.JsonNode entityJson)
Invoked if the persistence message had anOperationType
ofOperationType.UPDATE
.protected UserPermissionRef
toAuthPermissionRef(AdminPermissionRef adminPermissionRef)
Deprecated.Since 1.7.0.protected UserPermissionRef
toAuthPermissionRef(String id)
protected RestrictedPermission
toAuthRestrictedPermission(AdminRestrictedPermission adminRestrictedPermission)
protected RestrictedRole
toAuthRestrictedRole(AdminRestrictedRole adminRestrictedRole)
protected Restriction
toAuthRestriction(AdminRestriction adminRestriction)
protected Restriction
toAuthRestriction(String restrictionType, Set<String> restrictionTargets)
protected UserRoleRef
toAuthRoleRef(AdminRoleRef adminRoleRef)
Deprecated.Since 1.7.0.protected UserRoleRef
toAuthRoleRef(String id)
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.protected boolean
updateUserInfo(AdminUser request, User user)
protected boolean
updateUserRestrictedPermissions(AdminUser request, User user)
protected boolean
updateUserRestrictedRoles(AdminUser request, User user)
protected boolean
updateUserRestrictions(AdminUser request, User user)
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.protected boolean
updateUserStatus(AdminUser request, User user)
protected boolean
updateUserTenantRestrictions(AdminUser request, User user)
-
Methods inherited from class com.broadleafcommerce.auth.user.listener.OperationAwarePersistenceHandler
getChangeTimestamp, getEntityIdOrThrow, getNonNullField, getValidOperationTypeOrThrow, processStateChange, processStateChange
-
Methods inherited from class com.broadleafcommerce.auth.user.listener.AuthDataUpdatePersistenceHandler
getObjectMapper, handle, isNodeNull, isValidType
-
-
-
-
Constructor Detail
-
AdminUserPersistenceHandler
public AdminUserPersistenceHandler(UserService<User> userService, AuthorizedClientService<AuthorizedClient> clientService, com.fasterxml.jackson.databind.ObjectMapper mapper, com.broadleafcommerce.common.extension.TypeFactory typeFactory, UserLockoutService userLockoutService)
-
-
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 anOperationType
ofOperationType.CREATE
.- Specified by:
processCreateOperation
in classOperationAwarePersistenceHandler
- 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
-
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 usertarget
- 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
andUser.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 (inprocessCreateOperation(String, Instant, JsonNode)
,processUpdateOperation(String, Instant, JsonNode)
, orprocessDeleteOperation(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-nullUser.serviceId
,User.type
, andUser.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 anOperationType
ofOperationType.UPDATE
.- Specified by:
processUpdateOperation
in classOperationAwarePersistenceHandler
- 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 (inprocessCreateOperation(String, Instant, JsonNode)
,processUpdateOperation(String, Instant, JsonNode)
, orprocessDeleteOperation(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
- theUser.getId()
of the entity to replacereplacement
- the replacement entitychangeTimestamp
- 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 classOperationAwarePersistenceHandler
- 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 whatUserService.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 datauser
- 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.Since 1.7.0. UsetoAuthPermissionRef(String)
-
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 datauser
- 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. UsetoAuthRoleRef(String)
-
toAuthRoleRef
protected UserRoleRef toAuthRoleRef(String id)
-
updateUserTenantRestrictions
protected boolean updateUserTenantRestrictions(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)
-
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()
-
-