001/** 002 * Copyright 2005-2016 The Kuali Foundation 003 * 004 * Licensed under the Educational Community License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.opensource.org/licenses/ecl2.php 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016package org.kuali.rice.kim.impl.data; 017 018import org.kuali.rice.krad.data.platform.MaxValueIncrementerFactory; 019import org.springframework.beans.factory.InitializingBean; 020import org.springframework.jdbc.core.JdbcTemplate; 021import org.springframework.jdbc.core.RowMapper; 022import org.springframework.jdbc.support.incrementer.DataFieldMaxValueIncrementer; 023 024import javax.sql.DataSource; 025import java.sql.ResultSet; 026import java.sql.SQLException; 027import java.util.ArrayList; 028import java.util.List; 029import java.util.UUID; 030 031/** 032 * An implementation of DataIntegrityService which repairs two common data issues with delegations that occurred 033 * because of bad code in KIM. 034 * 035 * The first issue is one where duplicate delegations for a given role, delegation type, and KIM type exist. This can 036 * cause issues with the role document when editing delegations. 037 * 038 * The second issue is one where delegation members point to a role member for a role that doesn't match the role of 039 * their delegation. 040 * 041 * @author Eric Westfall 042 */ 043public class DataIntegrityServiceImpl implements DataIntegrityService, InitializingBean { 044 045 private static final String DUPLICATE_DELEGATIONS = "select role_id, dlgn_typ_cd, kim_typ_id, count(*) cnt from krim_dlgn_t where actv_ind = 'Y' group by role_id, dlgn_typ_cd, kim_typ_id having cnt > 1"; 046 private static final String BAD_DELEGATION_MEMBERS = "select m.dlgn_mbr_id, m.role_mbr_id, rm.role_id, r.kim_typ_id, d.dlgn_id, d.role_id, d.dlgn_typ_cd from krim_dlgn_t d, krim_dlgn_mbr_t m, krim_role_mbr_t rm, krim_role_t r where d.actv_ind = 'Y' and d.dlgn_id=m.dlgn_id and m.role_mbr_id=rm.role_mbr_id and rm.role_id=r.role_id and d.role_id != rm.role_id"; 047 048 private static final String DUPLICATE_DELEGATION_IDS = "select dlgn_id from krim_dlgn_t where role_id = ? and dlgn_typ_cd = ? and kim_typ_id = ? and actv_ind = 'Y'"; 049 private static final String FIX_DUPLICATE_DELEGATION_ID = "update krim_dlgn_mbr_t set dlgn_id = ? where dlgn_id = ?"; 050 private static final String DELETE_DUPLICATE_DELEGATION = "delete from krim_dlgn_t where dlgn_id = ?"; 051 052 private static final String FIND_TARGET_DELEGATION = "select dlgn_id from krim_dlgn_t where role_id = ? and dlgn_typ_cd = ? and actv_ind = 'Y'"; 053 private static final String CREATE_DELEGATION = "insert into krim_dlgn_t (dlgn_id, obj_id, role_id, kim_typ_id, dlgn_typ_cd, ver_nbr, actv_ind) values (?, ?, ?, ?, ?, 1, 'Y')"; 054 private static final String FIX_BAD_DELEGATION_MEMBER = "update krim_dlgn_mbr_t set dlgn_id = ? where dlgn_mbr_id = ?"; 055 056 private DataSource dataSource; 057 private JdbcTemplate template; 058 059 @Override 060 public void afterPropertiesSet() throws Exception { 061 this.template = new JdbcTemplate(dataSource); 062 } 063 064 @Override 065 public List<String> checkIntegrity() { 066 List<String> messages = new ArrayList<>(); 067 messages.addAll(reportDuplicateDelegations(findDuplicateDelegations())); 068 messages.addAll(reportBadDelegationMembers(findBadDelegationMembers())); 069 return messages; 070 } 071 072 private List<String> reportDuplicateDelegations(List<DuplicateRoleDelegation> duplicateRoleDelegations) { 073 List<String> reports = new ArrayList<>(); 074 for (DuplicateRoleDelegation duplicateRoleDelegation : duplicateRoleDelegations) { 075 reports.add(duplicateRoleDelegation.report()); 076 } 077 return reports; 078 } 079 080 private List<String> reportBadDelegationMembers(List<BadDelegationMember> badDelegationMembers) { 081 List<String> reports = new ArrayList<>(); 082 for (BadDelegationMember badDelegationMember : badDelegationMembers) { 083 reports.add(badDelegationMember.report()); 084 } 085 return reports; 086 } 087 088 @Override 089 public List<String> repair() { 090 List<String> messages = new ArrayList<>(); 091 messages.addAll(repairDuplicateDelegations()); 092 messages.addAll(repairBadDelegationMembers()); 093 return messages; 094 } 095 096 private List<String> repairDuplicateDelegations() { 097 List<String> messages = new ArrayList<>(); 098 List<DuplicateRoleDelegation> duplicateRoleDelegations = findDuplicateDelegations(); 099 for (DuplicateRoleDelegation duplicateRoleDelegation : duplicateRoleDelegations) { 100 messages.add(repairDuplicateDelegation(duplicateRoleDelegation)); 101 } 102 return messages; 103 } 104 105 private String repairDuplicateDelegation(DuplicateRoleDelegation duplicateRoleDelegation) { 106 107 // first let's find all of the duplicate delegation ids 108 List<String> delegationIds = template.query(DUPLICATE_DELEGATION_IDS, 109 new RowMapper<String>() { 110 @Override 111 public String mapRow(ResultSet resultSet, int i) throws SQLException { 112 return resultSet.getString(1); 113 } 114 }, 115 duplicateRoleDelegation.roleId, 116 duplicateRoleDelegation.delegationTypeCode, 117 duplicateRoleDelegation.kimTypeId); 118 // we'll keep the first delegation and repoint all members to it instead, then delete the remaining 119 // duplicate delegations 120 String delegationIdToKeep = delegationIds.remove(0); 121 122 for (String delegationId : delegationIds) { 123 template.update(FIX_DUPLICATE_DELEGATION_ID, delegationIdToKeep, delegationId); 124 template.update(DELETE_DUPLICATE_DELEGATION, delegationId); 125 } 126 127 return reportRepairDuplicateDelegation(duplicateRoleDelegation, delegationIdToKeep, delegationIds); 128 } 129 130 private String reportRepairDuplicateDelegation(DuplicateRoleDelegation duplicateRoleDelegation, 131 String delegationIdToKeep, List<String> duplicateDelegationIds) { 132 StringBuilder message = new StringBuilder(); 133 message.append("Repaired duplicate delegations with roleId = ").append(duplicateRoleDelegation.roleId) 134 .append(", delegationTypeCode = ").append(duplicateRoleDelegation.delegationTypeCode) 135 .append(", and kimTypeId = ").append(duplicateRoleDelegation.kimTypeId) 136 .append(". Retained delegation with id ").append(delegationIdToKeep) 137 .append(". Deleted the following delegations and repointed their members to delegation id ") 138 .append(delegationIdToKeep).append(": [ "); 139 for (String duplicateDelegationId : duplicateDelegationIds) { 140 message.append(duplicateDelegationId).append(", "); 141 } 142 message.delete(message.length() - 2, message.length()); 143 message.append(" ]"); 144 return message.toString(); 145 } 146 147 private List<String> repairBadDelegationMembers() { 148 List<String> messages = new ArrayList<>(); 149 List<BadDelegationMember> badDelegationMembers = findBadDelegationMembers(); 150 for (BadDelegationMember badDelegationMember : badDelegationMembers) { 151 messages.add(repairBadDelegationMember(badDelegationMember)); 152 } 153 return messages; 154 } 155 156 private String repairBadDelegationMember(BadDelegationMember badDelegationMember) { 157 // first attempt to find an existing delegation for the proper role id + delegation type code 158 List<String> delegationIds = template.query(FIND_TARGET_DELEGATION, 159 new RowMapper<String>() { 160 @Override 161 public String mapRow(ResultSet resultSet, int i) throws SQLException { 162 return resultSet.getString(1); 163 } 164 }, 165 badDelegationMember.roleMemberRoleId, 166 badDelegationMember.delegationTypeCode); 167 168 // if delegationIds is empty, then we will need to manufacture a delegation, otherwise there should only be one 169 // target delegation id since we just previously ran the repair to get rid of duplicate delegations 170 String targetDelegationId; 171 boolean newDelegationCreated = false; 172 if (delegationIds.isEmpty()) { 173 targetDelegationId = getNextDelegationId(); 174 String objectId = UUID.randomUUID().toString(); 175 template.update(CREATE_DELEGATION, targetDelegationId, objectId, badDelegationMember.roleMemberRoleId, 176 badDelegationMember.roleMemberRoleKimTypeId, badDelegationMember.delegationTypeCode); 177 newDelegationCreated = true; 178 } else { 179 targetDelegationId = delegationIds.get(0); 180 } 181 // now that we have the target delegation id, let's update our delegation member and point it to the proper delegation 182 template.update(FIX_BAD_DELEGATION_MEMBER, targetDelegationId, badDelegationMember.delegationMemberId); 183 return reportRepairBadDelegationMember(badDelegationMember, targetDelegationId, newDelegationCreated); 184 } 185 186 private String reportRepairBadDelegationMember(BadDelegationMember badDelegationMember, 187 String targetDelegationId, boolean newDelegationCreated) { 188 StringBuilder message = new StringBuilder(); 189 message.append("Repaired bad delegation member ").append(badDelegationMember.toString()); 190 if (newDelegationCreated) { 191 message.append(" New delegation created with id ").append(targetDelegationId) 192 .append(" since there was no existing delegation that matched for the proper role id and delegation type."); 193 } else { 194 message.append(" Reassigned the delegation member to an existing delegation with id ") 195 .append(targetDelegationId).append(" because it matched the proper role id and delegation type."); 196 } 197 return message.toString(); 198 } 199 200 private String getNextDelegationId() { 201 DataFieldMaxValueIncrementer incrementer = MaxValueIncrementerFactory.getIncrementer(dataSource, "KRIM_DLGN_ID_S"); 202 return incrementer.nextStringValue(); 203 } 204 205 206 private List<DuplicateRoleDelegation> findDuplicateDelegations() { 207 return template.query(DUPLICATE_DELEGATIONS, new RowMapper<DuplicateRoleDelegation>() { 208 @Override 209 public DuplicateRoleDelegation mapRow(ResultSet resultSet, int i) throws SQLException { 210 return new DuplicateRoleDelegation(resultSet.getString(1), resultSet.getString(2), 211 resultSet.getString(3), resultSet.getInt(4)); 212 } 213 }); 214 } 215 216 private List<BadDelegationMember> findBadDelegationMembers() { 217 return template.query(BAD_DELEGATION_MEMBERS, new RowMapper<BadDelegationMember>() { 218 @Override 219 public BadDelegationMember mapRow(ResultSet resultSet, int i) throws SQLException { 220 return new BadDelegationMember(resultSet.getString(1), resultSet.getString(2), resultSet.getString(3), 221 resultSet.getString(4), resultSet.getString(5), resultSet.getString(6), resultSet.getString(7)); 222 } 223 }); 224 } 225 226 public void setDataSource(DataSource dataSource) { 227 this.dataSource = dataSource; 228 } 229 230 class DuplicateRoleDelegation { 231 232 final String roleId; 233 final String delegationTypeCode; 234 final String kimTypeId; 235 final int numMatching; 236 237 DuplicateRoleDelegation(String roleId, String delegationTypeCode, String kimTypeId, int numMatching) { 238 this.roleId = roleId; 239 this.delegationTypeCode = delegationTypeCode; 240 this.kimTypeId = kimTypeId; 241 this.numMatching = numMatching; 242 } 243 244 String report() { 245 return "Found duplicate role delegation " + toString(); 246 } 247 248 public String toString() { 249 return String.format("[roleId = %s, delegationTypeCode = %s, kimTypeId = %s, num of matching delegations = %d]", 250 roleId, delegationTypeCode, kimTypeId, numMatching); 251 } 252 253 254 } 255 256 class BadDelegationMember { 257 258 final String delegationMemberId; 259 final String roleMemberId; 260 final String roleMemberRoleId; 261 final String roleMemberRoleKimTypeId; 262 final String delegationId; 263 final String delegationRoleId; 264 final String delegationTypeCode; 265 266 BadDelegationMember(String delegationMemberId, String roleMemberId, String roleMemberRoleId, 267 String roleMemberRoleKimTypeId, String delegationId, String delegationRoleId, 268 String delegationTypeCode) { 269 this.delegationMemberId = delegationMemberId; 270 this.roleMemberId = roleMemberId; 271 this.roleMemberRoleId = roleMemberRoleId; 272 this.roleMemberRoleKimTypeId = roleMemberRoleKimTypeId; 273 this.delegationId = delegationId; 274 this.delegationRoleId = delegationRoleId; 275 this.delegationTypeCode = delegationTypeCode; 276 } 277 278 String report() { 279 return "Found bad delegation member " + toString(); 280 } 281 282 public String toString() { 283 return String.format("[delegationMemberId = %s, roleMemberId = %s, " + 284 "roleMemberRoleId = %s, roleMemberRoleKimTypeId = %s, delegationId = %s, " + 285 "delegationRoleId = %s, delegationTypeCode = %s]", 286 delegationMemberId, roleMemberId, roleMemberRoleId, roleMemberRoleKimTypeId, delegationId, 287 delegationRoleId, delegationTypeCode); 288 } 289 290 } 291}