package kr.wisestone.owl.service.impl; import com.google.common.collect.Lists; import kr.wisestone.owl.constant.MsgConstants; import kr.wisestone.owl.domain.*; import kr.wisestone.owl.domain.enumType.CustomFieldType; import kr.wisestone.owl.domain.enumType.IssueHistoryType; import kr.wisestone.owl.exception.OwlRuntimeException; import kr.wisestone.owl.mapper.IssueCustomFieldValueMapper; import kr.wisestone.owl.repository.IssueCustomFieldValueRepository; import kr.wisestone.owl.service.*; import kr.wisestone.owl.util.ConvertUtil; import kr.wisestone.owl.util.MapUtil; import kr.wisestone.owl.vo.CustomFieldVo; import kr.wisestone.owl.vo.IssueCustomFieldValueVo; import kr.wisestone.owl.web.condition.IssueCondition; import kr.wisestone.owl.web.condition.IssueCustomFieldValueCondition; import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.util.*; @Service public class IssueCustomFieldValueServiceImpl extends AbstractServiceImpl> implements IssueCustomFieldValueService { private static final Logger log = LoggerFactory.getLogger(IssueCustomFieldValueServiceImpl.class); @Autowired private IssueCustomFieldValueRepository issueCustomFieldValueRepository; @Autowired private CustomFieldService customFieldService; @Autowired private IssueHistoryService issueHistoryService; @Autowired private IssueCustomFieldValueMapper issueCustomFieldValueMapper; @Autowired private IssueTypeCustomFieldService issueTypeCustomFieldService; @Autowired private UserService userService; @Override protected JpaRepository getRepository() { return this.issueCustomFieldValueRepository; } // 이슈에서 사용되는 사용자 정의 필드 값을 업데이트한다. @Override @Transactional public void modifyIssueCustomFieldValue(Issue issue, List> issueCustomFields) { // 전체 이슈에 사용되는 사용자 정의 필드 값 삭제 issue.getIssueCustomFieldValues().clear(); List issueCustomFieldValues = Lists.newArrayList(); for (Map map : issueCustomFields) { Map result = new HashMap<>(); // customFieldVo 에서 사용자 정의 필드와 이슈 유형에 연결된 사용자 정의 필드 정보를 가져온다. boolean useCustomField = this.getCustomFieldAndIssueTypeCustomField(map, issue, result); if (useCustomField) { List useValues = MapUtil.getStrings(map, "useValues"); if (useValues != null) { for (String useValue : useValues) { if (!StringUtils.isEmpty(useValue)) { // Xss 공격 방어를 위해 script 공백으로 치환 IssueCustomFieldValue issueCustomFieldValue = new IssueCustomFieldValue(issue, (CustomField) result.get("customField"), (IssueTypeCustomField) result.get("issueTypeCustomField"), useValue); issueCustomFieldValues.add(issueCustomFieldValue); } } } } } if (issueCustomFieldValues.size() > 0) { this.issueCustomFieldValueRepository.saveAll(issueCustomFieldValues); } } // customFieldVo 에서 사용자 정의 필드와 이슈 유형에 연결된 사용자 정의 필드 정보를 가져온다. @Override public boolean getCustomFieldAndIssueTypeCustomField(Map map, Issue issue, Map result) { Map customFieldMap = (Map) MapUtil.getObject(map, "customFieldVo"); CustomField customField = this.customFieldService.getCustomField(MapUtil.getLong(customFieldMap, "id")); IssueTypeCustomField issueTypeCustomField = this.issueTypeCustomFieldService.findByProjectIdAndIssueTypeIdAndCustomFieldId(issue.getProject().getId(), issue.getIssueType().getId(), customField.getId()); if (issueTypeCustomField == null) { // 설정된 사용자 정의 필드가 없을때 return false; } result.put("customField", customField); result.put("issueTypeCustomField", issueTypeCustomField); return true; } // 이슈에 연결된 사용자 정의 필드 값을 조회한다. @Override @Transactional(readOnly = true) public List findByIssueId(Long issueId) { List issueCustomFieldValueVos = Lists.newArrayList(); List issueCustomFieldValues = this.issueCustomFieldValueRepository.findByIssueId(issueId); for (IssueCustomFieldValue issueCustomFieldValue : issueCustomFieldValues) { IssueCustomFieldValueVo issueCustomFieldValueVo = ConvertUtil.copyProperties(issueCustomFieldValue, IssueCustomFieldValueVo.class); CustomFieldVo customFieldVo = ConvertUtil.copyProperties(issueCustomFieldValue.getCustomField(), CustomFieldVo.class); customFieldVo.setCustomFieldType(issueCustomFieldValue.getCustomField().getCustomFieldType().toString()); issueCustomFieldValueVo.setCustomFieldVo(customFieldVo); issueCustomFieldValueVos.add(issueCustomFieldValueVo); } return issueCustomFieldValueVos; } // 사용자 정의 필드 옵션 값이 변경되었을 때 사용자 정의 필드 값을 사용하는 이슈에서 해당 값이 존재하는지 확인하고 없어졌으면 삭제해준다. @Override @Transactional public void checkExistIssueCustomFieldValue(CustomField customField, List values, CustomFieldType oldCustomFieldType) { List removeIssueCustomFieldValues = Lists.newArrayList(); Map issueMaps = new HashMap<>(); // 이력을 남길 이슈 추출 // 이슈에서 사용되는 사용자 정의 필드 값 정보를 추출한다. this.saveOriginalIssueCustomFieldValues(customField, values, issueMaps, removeIssueCustomFieldValues); // 삭제할 값 삭제 if (removeIssueCustomFieldValues.size() > 0) { this.issueCustomFieldValueRepository.deleteInBatch(removeIssueCustomFieldValues); // 삭제 후 남은 이슈 사용자 정의 필드 값 정보를 issueMaps 에 저장한다. this.saveNewIssueCustomFieldValues(customField, issueMaps); // 변경된 이슈 사용자 정의 필드 값 정보를 히스토리로 남긴다. this.saveIssueCustomFieldValueHistory(issueMaps, customField, oldCustomFieldType); } } // 이슈에서 사용되는 사용자 정의 필드 값 정보를 추출한다. private void saveOriginalIssueCustomFieldValues(CustomField customField, List values, Map issueMaps, List removeIssueCustomFieldValues) { // 해당 사용자 정의 필드를 사용하고 있는 이슈 정보를 추출한다. List issueCustomFieldValues = this.findByCustomFieldId(customField); // 각 이슈의 이전 값 추출 for (IssueCustomFieldValue issueCustomFieldValue : issueCustomFieldValues) { Boolean existValue = false; for (String value : values) { if (issueCustomFieldValue.getUseValue().equals(value)) { existValue = true; break; } } if (!existValue) { // 삭제할 값 removeIssueCustomFieldValues.add(issueCustomFieldValue); } String issueId = issueCustomFieldValue.getIssue().getId().toString(); if (MapUtil.getObject(issueMaps, issueId) == null) { Map issue = new HashMap<>(); issue.put("issue", issueCustomFieldValue.getIssue()); issue.put("oldValues", Lists.newArrayList(issueCustomFieldValue.getUseValue())); issue.put("newValues", Lists.newArrayList()); issueMaps.put(issueId, issue); } else { Map issue = (Map) issueMaps.get(issueId); List oldValues = (List) MapUtil.getObject(issue, "oldValues"); oldValues.add(issueCustomFieldValue.getUseValue()); issue.put("oldValues", oldValues); } } } // 삭제 후 남은 이슈 사용자 정의 필드 값 정보를 issueMaps 에 저장한다. private void saveNewIssueCustomFieldValues(CustomField customField, Map issueMaps) { // 삭제 후 다시 이슈 사용자 정의 필드 값을 조회한다. List newIssueCustomFieldValues = this.findByCustomFieldId(customField); for (IssueCustomFieldValue newIssueCustomFieldValue : newIssueCustomFieldValues) { String issueId = newIssueCustomFieldValue.getIssue().getId().toString(); if (MapUtil.getObject(issueMaps, issueId) != null) { Map issue = (Map) issueMaps.get(issueId); List newValues = (List) MapUtil.getObject(issue, "newValues"); newValues.add(newIssueCustomFieldValue.getUseValue()); issue.put("newValues", newValues); } } } // 변경된 이슈 사용자 정의 필드 값 정보를 히스토리로 남긴다. private void saveIssueCustomFieldValueHistory(Map issueMaps, CustomField customField, CustomFieldType oldCustomFieldType) { Iterator iterator = issueMaps.keySet().iterator(); while (iterator.hasNext()) { StringBuilder issueChangeDescription = new StringBuilder(); Map issueMap = (Map) issueMaps.get(iterator.next()); Issue issue = (Issue) MapUtil.getObject(issueMap, "issue"); List oldValues = (List) MapUtil.getObject(issueMap, "oldValues"); List newValues = (List) MapUtil.getObject(issueMap, "newValues"); // 배열에 있는 값을 보기 좋게 추출한다. StringBuilder oldValueString = this.setValueString(oldValues); StringBuilder newValueString = this.setValueString(newValues); // 이슈 사용자 정의 필드 값이 변경되었는지 확인한다. if (this.checkChangeIssueCustomFieldValues(oldValueString, newValueString)) { continue; } // 값이 비었을 때 값이 없다는 글자를 남겨준다. this.setEmptyString(oldValueString, oldCustomFieldType); // 값이 비었을 때 값이 없다는 글자를 남겨준다. this.setEmptyString(newValueString, oldCustomFieldType); // 이전에 문자열필드였다가 다른 필드로 변경한 경우나 이전에 문자열 필드가 아니었다가 문자열 필드로 바뀐 경우 if (CustomFieldType.INPUT.equals(oldCustomFieldType) && !customField.getCustomFieldType().equals(CustomFieldType.INPUT) || !CustomFieldType.INPUT.equals(oldCustomFieldType) && customField.getCustomFieldType().equals(CustomFieldType.INPUT)) { // 사용자 정의 필드 유형이 변경되어 이슈에서 사용된 옵션 값이 삭제되었다고 저장한다. this.issueHistoryService.recodeChangeCustomFieldType(customField, oldValueString.toString(), issueChangeDescription); } else { // 사용자 정의 필드의 옵션 값이 삭제되어 이슈에서 사용된 값이 삭제될 경우 변경 정보를 저장한다. this.issueHistoryService.recodeRemoveCustomFieldOptionValue(customField, oldValueString.toString(), newValueString.toString(), issueChangeDescription); } this.issueHistoryService.addIssueHistory(issue, IssueHistoryType.MODIFY, issueChangeDescription.toString()); } } // 배열에 있는 값을 보기 좋게 추출한다. private StringBuilder setValueString(List values) { StringBuilder stringBuilder = new StringBuilder(); for (String value : values) { stringBuilder.append(value); stringBuilder.append(", "); } return stringBuilder; } // 값이 비었을 때 값이 없다는 글자를 남겨준다. private void setEmptyString(StringBuilder stringBuilder, CustomFieldType customFieldType) { if (stringBuilder.toString().equals("")) { if (CustomFieldType.INPUT.equals(customFieldType)) { stringBuilder.append("입력한 값이 없습니다."); } else { stringBuilder.append("선택한 값이 없습니다."); } } } // 이슈 사용자 정의 필드 값이 변경되었는지 확인한다. private boolean checkChangeIssueCustomFieldValues(StringBuilder oldValueString, StringBuilder newValueString) { if (oldValueString.toString().equals(newValueString.toString())) { return true; } return false; } // 사용자 정의 필드를 사용하고 있는 값 정보를 추출한다. @Override @Transactional(readOnly = true) public List findByCustomFieldId(CustomField customField) { return this.issueCustomFieldValueRepository.findByCustomFieldId(customField.getId()); } // 프로젝트에 사용자 정의 필드 연결이 해제될 경우 삭제한다. @Override @Transactional public void removeIssueCustomFieldValue(Long issueTypeCustomFieldId) { this.issueCustomFieldValueMapper.deleteIssueCustomFieldValue(issueTypeCustomFieldId); } // 사용자 정의 필드 값을 저장한 이슈를 찾는다. @Override @Transactional(readOnly = true) public boolean find(IssueCondition condition, Set issueIds) { boolean customFieldSearch = false; int firstCount = 0; Set tempIssueIds = new HashSet<>(); for (Map customField : condition.getIssueCustomFields()) { IssueCustomFieldValueCondition issueCustomFieldValueCondition = IssueCustomFieldValueCondition.make(customField); if (issueCustomFieldValueCondition.getUseValues().size() > 0 || !StringUtils.isEmpty(issueCustomFieldValueCondition.getUseValue())) { issueCustomFieldValueCondition.setWorkspaceId(this.userService.getUser(this.webAppUtil.getLoginId()).getLastWorkspaceId()); issueCustomFieldValueCondition.setCustomFieldId(MapUtil.getLong(customField, "id")); // 사용자 정의 필드 값 검색 시작 customFieldSearch = true; Map result = new HashMap<>(); switch (CustomFieldType.valueOf(issueCustomFieldValueCondition.getCustomFieldType())) { case INPUT: case NUMBER: case DATETIME: case IP_ADDRESS: case EMAIL: case SITE: case TEL: result = this.issueCustomFieldValueMapper.findLikeUseValue(issueCustomFieldValueCondition); break; case MULTI_SELECT: case SINGLE_SELECT: result = this.issueCustomFieldValueMapper.findByUseValue(issueCustomFieldValueCondition); break; } String issueIdList = MapUtil.getString(result, "issueIds"); if (!StringUtils.isEmpty(issueIdList)) { Set results = new HashSet<>(Arrays.asList(issueIdList.split(","))); if (firstCount > 0) { Set commonKeys = new HashSet<>(tempIssueIds); commonKeys.retainAll(results); tempIssueIds = commonKeys; } else { tempIssueIds = results; } } firstCount++; } } issueIds.addAll(tempIssueIds); return customFieldSearch; } @Override public Map find(IssueCustomFieldValueCondition issueCustomFieldValueCondition) { if (issueCustomFieldValueCondition.getUseValues().size() > 0 || !StringUtils.isEmpty(issueCustomFieldValueCondition.getUseValue())) { issueCustomFieldValueCondition.setWorkspaceId(this.userService.getUser(this.webAppUtil.getLoginId()).getLastWorkspaceId()); // 사용자 정의 필드 값 검색 시작 Map result = new HashMap<>(); switch (CustomFieldType.valueOf(issueCustomFieldValueCondition.getCustomFieldType())) { case INPUT: result = this.issueCustomFieldValueMapper.findLikeUseValue(issueCustomFieldValueCondition); break; case MULTI_SELECT: case SINGLE_SELECT: result = this.issueCustomFieldValueMapper.findByUseValue(issueCustomFieldValueCondition); break; } return result; } return null; } // 이슈에서 저장한 사용자 정의 필드 값을 조회한다. @Override @Transactional(readOnly = true) public List> findInIssueIds(IssueCondition issueCondition) { return this.issueCustomFieldValueMapper.findInIssueIds(issueCondition); } // 이슈에서 저장된 해당 사용자 정의 필드 값을 모두 삭제한다. @Override @Transactional public void removeIssueCustomFieldValuesByCustomFieldId(CustomField customField) { // 사용자 정의 필드를 사용하고 있는 값 정보를 추출한다. List issueCustomFieldValues = this.findByCustomFieldId(customField); List ids = Lists.newArrayList(); for (IssueCustomFieldValue issueCustomFieldValue : issueCustomFieldValues) { ids.add(issueCustomFieldValue.getId()); } if (ids.size() > 0) { this.issueCustomFieldValueMapper.deleteByIssueCustomFieldValueId(ids); } } }