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}