RANGER-2805 : fixed for creating role with non-existing group/user failing due to concurrent threads

Signed-off-by: Mehul Parikh <mehul@apache.org>
diff --git a/security-admin/src/main/java/org/apache/ranger/biz/RoleRefUpdater.java b/security-admin/src/main/java/org/apache/ranger/biz/RoleRefUpdater.java
index bb68e32..ff8e2ba 100644
--- a/security-admin/src/main/java/org/apache/ranger/biz/RoleRefUpdater.java
+++ b/security-admin/src/main/java/org/apache/ranger/biz/RoleRefUpdater.java
@@ -20,6 +20,7 @@
 package org.apache.ranger.biz;
 
 import java.util.HashSet;
+import java.util.List;
 import java.util.Set;
 
 import org.apache.commons.collections.CollectionUtils;
@@ -29,6 +30,7 @@
 import org.apache.ranger.common.MessageEnums;
 import org.apache.ranger.common.RESTErrorUtil;
 import org.apache.ranger.common.RangerCommonEnums;
+import org.apache.ranger.common.db.RangerTransactionSynchronizationAdapter;
 import org.apache.ranger.db.RangerDaoManager;
 import org.apache.ranger.db.XXRoleRefGroupDao;
 import org.apache.ranger.db.XXRoleRefRoleDao;
@@ -38,13 +40,14 @@
 import org.apache.ranger.entity.XXRoleRefGroup;
 import org.apache.ranger.entity.XXRoleRefRole;
 import org.apache.ranger.entity.XXRoleRefUser;
+import org.apache.ranger.entity.XXTrxLog;
 import org.apache.ranger.entity.XXUser;
 import org.apache.ranger.plugin.model.RangerRole;
 import org.apache.ranger.service.RangerAuditFields;
+import org.apache.ranger.service.RangerTransactionService;
 import org.apache.ranger.service.XGroupService;
 import org.apache.ranger.service.XUserService;
 import org.apache.ranger.view.VXGroup;
-import org.apache.ranger.view.VXUser;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Component;
 
@@ -71,6 +74,15 @@
     @Autowired
     XGroupService xGroupService;
 
+    @Autowired
+    RangerTransactionSynchronizationAdapter rangerTransactionSynchronizationAdapter;
+
+	@Autowired
+	RangerTransactionService transactionService;
+
+	@Autowired
+	RangerBizUtil xaBizUtil;
+
 	public void createNewRoleMappingForRefTable(RangerRole rangerRole, Boolean createNonExistUserGroup) throws Exception {
 		if (rangerRole == null) {
 			return;
@@ -99,29 +111,40 @@
 				if (StringUtils.isBlank(roleUser)) {
 					continue;
 				}
-				VXUser vXUser = null;
+				Long userId = null;
 				XXUser xUser = daoMgr.getXXUser().findByUserName(roleUser);
 
 				if (xUser == null) {
 					if (createNonExistUserGroup) {
 						LOG.warn("User specified in role does not exist in ranger admin, creating new user, User = "
 								+ roleUser);
-						vXUser = xUserMgr.createExternalUser(roleUser);
+						// Schedule another transaction and let this transaction complete (and commit) successfully!
+						final RoleUserCreateContext roleUserCreateContext = new RoleUserCreateContext(roleUser, roleId);
+						Runnable CreateAndAssociateUser = new Runnable () {
+							@Override
+							public void run() {
+								Runnable realTask = new Runnable () {
+									@Override
+									public void run() {
+										doCreateAndAssociateRoleUser(roleUserCreateContext);
+									}
+								};
+								transactionService.scheduleToExecuteInOwnTransaction(realTask, 0L);
+							}
+                        };
+						rangerTransactionSynchronizationAdapter.executeOnTransactionCommit(CreateAndAssociateUser);
+
 					} else {
 						throw restErrorUtil.createRESTException("user with name: " + roleUser + " does not exist ",
 								MessageEnums.INVALID_INPUT_DATA);
 					}
 				}else {
-					 vXUser = xUserService.populateViewBean(xUser);
+					userId = xUser.getId();
 				}
 
-				XXRoleRefUser xRoleRefUser = rangerAuditFields.populateAuditFieldsForCreate(new XXRoleRefUser());
-
-				xRoleRefUser.setRoleId(roleId);
-				xRoleRefUser.setUserId(vXUser.getId());
-				xRoleRefUser.setUserName(roleUser);
-				xRoleRefUser.setUserType(0);
-				daoMgr.getXXRoleRefUser().create(xRoleRefUser);
+				if(null != userId) {
+					userRoleAssociation(roleId,userId,roleUser);
+				}
 			}
 		}
 
@@ -131,7 +154,7 @@
 				if (StringUtils.isBlank(roleGroup)) {
 					continue;
 				}
-				VXGroup vXGroup = null;
+				Long groupId = null;
 				XXGroup xGroup = daoMgr.getXXGroup().findByGroupName(roleGroup);
 
 				if (xGroup == null) {
@@ -140,23 +163,36 @@
 								+ roleGroup);
 						VXGroup vxGroupNew = new VXGroup();
 						vxGroupNew.setName(roleGroup);
+						vxGroupNew.setDescription(roleGroup);
 						vxGroupNew.setGroupSource(RangerCommonEnums.GROUP_EXTERNAL);
-						vXGroup = xUserMgr.createXGroup(vxGroupNew);
+						// Schedule another transaction and let this transaction complete (and commit) successfully!
+						final RoleGroupCreateContext roleGroupCreateContext = new RoleGroupCreateContext(vxGroupNew, roleId);
+
+						Runnable createAndAssociateRoleGroup = new Runnable() {
+                            @Override
+                            public void run() {
+                                Runnable realTask = new Runnable() {
+                                    @Override
+                                    public void run() {
+                                        doCreateAndAssociateRoleGroup(roleGroupCreateContext);
+                                    }
+                                };
+                                transactionService.scheduleToExecuteInOwnTransaction(realTask, 0L);
+                            }
+                        };
+						rangerTransactionSynchronizationAdapter.executeOnTransactionCommit(createAndAssociateRoleGroup);
+
 					} else {
 						throw restErrorUtil.createRESTException("group with name: " + roleGroup + " does not exist ",
 								MessageEnums.INVALID_INPUT_DATA);
 					}
 				}else {
-					vXGroup = xGroupService.populateViewBean(xGroup);
+					groupId = xGroup.getId();
 				}
 
-				XXRoleRefGroup xRoleRefGroup = rangerAuditFields.populateAuditFieldsForCreate(new XXRoleRefGroup());
-
-				xRoleRefGroup.setRoleId(roleId);
-				xRoleRefGroup.setGroupId(vXGroup.getId());
-				xRoleRefGroup.setGroupName(roleGroup);
-				xRoleRefGroup.setGroupType(0);
-				daoMgr.getXXRoleRefGroup().create(xRoleRefGroup);
+				if(null != groupId) {
+					groupRoleAssociation(roleId, groupId, roleGroup);
+				}
 			}
 		}
 
@@ -210,4 +246,174 @@
 		}
 		return true;
 	}
+
+	public void groupRoleAssociation(Long roleId, Long groupId, String groupName) {
+		if(LOG.isDebugEnabled()) {
+			LOG.debug("===> groupRoleAssociation()");
+		}
+
+		XXRoleRefGroup xRoleRefGroup = rangerAuditFields.populateAuditFieldsForCreate(new XXRoleRefGroup());
+		xRoleRefGroup.setRoleId(roleId);
+		xRoleRefGroup.setGroupId(groupId);
+		xRoleRefGroup.setGroupName(groupName);
+		xRoleRefGroup.setGroupType(0);
+		daoMgr.getXXRoleRefGroup().create(xRoleRefGroup);
+	}
+
+	private static final class RoleGroupCreateContext {
+		final VXGroup group;
+		final Long roleId;
+
+		RoleGroupCreateContext(VXGroup group, Long roleId) {
+			this.group = group;
+			this.roleId = roleId;
+		}
+
+		@Override
+		public String toString() {
+			return "{group=" + group + ", roleId=" + roleId + "}";
+		}
+	}
+
+	void doCreateAndAssociateRoleGroup(final RoleGroupCreateContext context) {
+		if (LOG.isDebugEnabled()) {
+			LOG.debug("===> doCreateAndAssociateRoleGroup()");
+		}
+		XXGroup xGroup = daoMgr.getXXGroup().findByGroupName(context.group.getName());
+
+		if (xGroup != null) {
+			groupRoleAssociation(context.roleId, xGroup.getId(), context.group.getName());
+		} else {
+			try {
+				// Create group
+				VXGroup vXGroup = xGroupService.createXGroupWithOutLogin(context.group);
+				if (null != vXGroup) {
+					List<XXTrxLog> trxLogList = xGroupService.getTransactionLog(vXGroup, "create");
+					xaBizUtil.createTrxLog(trxLogList);
+				}
+			} catch (Exception exception) {
+				LOG.error("Failed to create Group or to associate group and role, RoleGroupContext:[" + context + "]",
+						exception);
+			} finally {
+				// This transaction may still fail at commit time because another transaction
+				// has already created the group
+				// So, associate the group to role in a different transaction
+				Runnable associateRoleGroup = new Runnable() {
+                    @Override
+					public void run() {
+						Runnable realTask = new Runnable() {
+							@Override
+							public void run() {
+								doAssociateRoleGroup(context);
+							}
+						};
+						transactionService.scheduleToExecuteInOwnTransaction(realTask, 0L);
+					}
+                };
+				rangerTransactionSynchronizationAdapter.executeOnTransactionCompletion(associateRoleGroup);
+			}
+		}
+	}
+
+	void doAssociateRoleGroup(final RoleGroupCreateContext context) {
+		if(LOG.isDebugEnabled()) {
+			LOG.debug("===> doAssociateRoleGroup()");
+		}
+		XXGroup xGroup = daoMgr.getXXGroup().findByGroupName(context.group.getName());
+
+		if (xGroup == null) {
+			LOG.error("No Group created!! Irrecoverable error! RoleGroupContext:[" + context + "]");
+		} else {
+			try {
+				groupRoleAssociation(context.roleId, xGroup.getId(), context.group.getName());
+			} catch (Exception exception) {
+				LOG.error("Failed to associate group and role, RoleGroupContext:[" + context + "]", exception);
+			}
+		}
+	}
+
+	private static final class RoleUserCreateContext {
+		final String userName;
+		final Long roleId;
+
+		RoleUserCreateContext(String userName, Long roleId) {
+			this.userName = userName;
+			this.roleId = roleId;
+		}
+
+		@Override
+		public String toString() {
+			return "{userName=" + userName + ", roleId=" + roleId + "}";
+		}
+	}
+
+	public void userRoleAssociation(Long roleId, Long userId, String userName) {
+		if(LOG.isDebugEnabled()) {
+			LOG.debug("===> userRoleAssociation()");
+		}
+		XXRoleRefUser xRoleRefUser = rangerAuditFields.populateAuditFieldsForCreate(new XXRoleRefUser());
+		xRoleRefUser.setRoleId(roleId);
+		xRoleRefUser.setUserId(userId);
+		xRoleRefUser.setUserName(userName);
+		xRoleRefUser.setUserType(0);
+		daoMgr.getXXRoleRefUser().create(xRoleRefUser);
+		if(LOG.isDebugEnabled()) {
+			LOG.debug("<=== userRoleAssociation()");
+		}
+	}
+
+	void doCreateAndAssociateRoleUser(final RoleUserCreateContext context) {
+		if (LOG.isDebugEnabled()) {
+			LOG.debug("===> doCreateAndAssociateRoleUser()");
+		}
+		XXUser xUser = daoMgr.getXXUser().findByUserName(context.userName);
+
+		if (xUser != null) {
+			userRoleAssociation(context.roleId, xUser.getId(), context.userName);
+		} else {
+			try {
+				// Create External user
+				xUserMgr.createServiceConfigUser(context.userName);
+			} catch (Exception exception) {
+				LOG.error("Failed to create User or to associate user and role, RoleUserContext:[" + context + "]",
+						exception);
+			} finally {
+				// This transaction may still fail at commit time because another transaction
+				// has already created the user
+				// So, associate the user to role in a different transaction
+				Runnable associateRoleUser = new Runnable() {
+					@Override
+					public void run() {
+						Runnable realTask = new Runnable() {
+							@Override
+							public void run() {
+								doAssociateRoleUser(context);
+							}
+						};
+						transactionService.scheduleToExecuteInOwnTransaction(realTask, 0L);
+					}
+                };
+				rangerTransactionSynchronizationAdapter.executeOnTransactionCompletion(associateRoleUser);
+			}
+		}
+
+	}
+
+	void doAssociateRoleUser(final RoleUserCreateContext context) {
+		if(LOG.isDebugEnabled()) {
+			LOG.debug("===> doAssociateRoleUser()");
+		}
+		XXUser xUser = daoMgr.getXXUser().findByUserName(context.userName);
+
+		if (xUser == null) {
+			LOG.error("No User created!! Irrecoverable error! RoleUserContext:[" + context + "]");
+		} else {
+			try {
+				userRoleAssociation(context.roleId, xUser.getId(), context.userName);
+			} catch (Exception exception) {
+				LOG.error("Failed to associate user and role, RoleUserContext:[" + context + "]", exception);
+			}
+		}
+	}
+
 }