package ru.hflabs.rcd.service.document.recodeRuleSet;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Sets;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import ru.hflabs.rcd.event.modify.ChangeEvent;
import ru.hflabs.rcd.exception.constraint.rule.AttachedRecodeRuleException;
import ru.hflabs.rcd.exception.search.rule.UnknownRecodeRuleSetException;
import ru.hflabs.rcd.exception.search.rule.UnknownRuleSetNameException;
import ru.hflabs.rcd.model.Essence;
import ru.hflabs.rcd.model.Identifying;
import ru.hflabs.rcd.model.change.ChangeType;
import ru.hflabs.rcd.model.criteria.FilterCriteria;
import ru.hflabs.rcd.model.criteria.FilterCriteriaValue;
import ru.hflabs.rcd.model.document.Dictionary;
import ru.hflabs.rcd.model.document.Field;
import ru.hflabs.rcd.model.document.Group;
import ru.hflabs.rcd.model.document.MetaField;
import ru.hflabs.rcd.model.path.DirectionNamedPath;
import ru.hflabs.rcd.model.path.MetaFieldNamedPath;
import ru.hflabs.rcd.model.rule.RecodeRule;
import ru.hflabs.rcd.model.rule.RecodeRuleSet;
import ru.hflabs.rcd.service.document.DocumentServiceTemplate;
import ru.hflabs.rcd.service.document.IDictionaryService;
import ru.hflabs.rcd.service.document.IFieldService;
import ru.hflabs.rcd.service.document.IMetaFieldService;
import ru.hflabs.rcd.service.rule.IRecodeRuleService;
import ru.hflabs.rcd.service.rule.IRecodeRuleSetService;
import ru.hflabs.rcd.term.Condition;
import ru.hflabs.util.spring.Assert;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Set;
import static ru.hflabs.rcd.accessor.Accessors.FROM_SET_INJECTOR;
import static ru.hflabs.rcd.accessor.Accessors.TO_SET_INJECTOR;
import static ru.hflabs.rcd.model.CriteriaUtils.createCriteriaByDocumentIDs;
import static ru.hflabs.rcd.model.CriteriaUtils.createCriteriaByIDs;
import static ru.hflabs.rcd.model.ModelUtils.validateDictionaryNamedPath;
import static ru.hflabs.rcd.model.change.Predicates.CHANGE_NAME_PREDICATE;
import static ru.hflabs.rcd.service.ServiceUtils.*;
/**
* Класс <class>RecodeRuleSetService</class> реализует сервис работы с наборами правил перекодирования
*
* @author Nazin Alexander
*/
public class RecodeRuleSetService extends DocumentServiceTemplate<RecodeRuleSet> implements IRecodeRuleSetService {
/** Сервис работы со справочниками */
private IDictionaryService dictionaryService;
/** Сервис работы с МЕТА-полями */
private IMetaFieldService metaFieldService;
/** Сервис работы со значениями полей */
private IFieldService fieldService;
/** Сервис работы с правилами перекодирования */
private IRecodeRuleService recodeRuleService;
public RecodeRuleSetService() {
super(RecodeRuleSet.class);
}
public void setDictionaryService(IDictionaryService dictionaryService) {
this.dictionaryService = dictionaryService;
}
public void setMetaFieldService(IMetaFieldService metaFieldService) {
this.metaFieldService = metaFieldService;
}
public void setFieldService(IFieldService fieldService) {
this.fieldService = fieldService;
}
public void setRecodeRuleService(IRecodeRuleService recodeRuleService) {
this.recodeRuleService = recodeRuleService;
}
@Override
protected Collection<RecodeRuleSet> injectTransitiveDependencies(Collection<RecodeRuleSet> objects) {
return super.injectTransitiveDependencies(injectRuleRelations(injectRelations(objects, fieldService), metaFieldService, FROM_SET_INJECTOR, TO_SET_INJECTOR));
}
@Override
public RecodeRuleSet findUniqueByNamedPath(String path, boolean quietly) {
Assert.isTrue(StringUtils.hasText(path), "RecodeRuleSet name must not be NULL or EMPTY");
RecodeRuleSet result = findUniqueDocumentBy(this, createCriteriaByIDs(RecodeRuleSet.NAME, path), true);
if (result == null && !quietly) {
throw new UnknownRuleSetNameException(path);
}
return result;
}
@Override
public RecodeRuleSet findRecodeRuleSetByNamedPath(MetaFieldNamedPath fromPath, MetaFieldNamedPath toPath, boolean fillTransitive, boolean quietly) {
Collection<RecodeRuleSet> ruleSets = findRecodeRuleSetByNamedPath(
Arrays.asList(new DirectionNamedPath<>(fromPath, toPath)),
fillTransitive);
// Выполняем поиск набора правил
RecodeRuleSet ruleSet = extractSingleDocument(ruleSets, null);
// Проверяем, что набор правил найден
if (ruleSet != null) {
return ruleSet;
} else if (!quietly) {
// Пытаемся определить причину отсутствия набора правил, опираясь на то, что не найден один из справочников
dictionaryService.findUniqueByNamedPath(fromPath, false);
dictionaryService.findUniqueByNamedPath(toPath, false);
// Справочники найдены, но для них не составлен набор правил
throw new UnknownRecodeRuleSetException(fromPath, toPath);
} else {
return null;
}
}
@Override
public Collection<RecodeRuleSet> findRecodeRuleSetByNamedPath(Collection<DirectionNamedPath<MetaFieldNamedPath>> rulePaths, boolean fillTransitive) {
ImmutableMap.Builder<String, FilterCriteriaValue<?>> builder = ImmutableMap.builder();
// Формируем критерий для каждого именованного пути
for (DirectionNamedPath<MetaFieldNamedPath> path : rulePaths) {
validateDictionaryNamedPath(path.first);
validateDictionaryNamedPath(path.second);
ImmutableMap.Builder<String, FilterCriteriaValue<?>> clauseBuilder = ImmutableMap.<String, FilterCriteriaValue<?>>builder()
.put(RecodeRuleSet.FROM_GROUP_NAME, new FilterCriteriaValue.StringValue(path.first.getGroupName()))
.put(RecodeRuleSet.FROM_DICTIONARY_NAME, new FilterCriteriaValue.StringValue(path.first.getDictionaryName()))
.put(RecodeRuleSet.TO_GROUP_NAME, new FilterCriteriaValue.StringValue(path.second.getGroupName()))
.put(RecodeRuleSet.TO_DICTIONARY_NAME, new FilterCriteriaValue.StringValue(path.second.getDictionaryName()));
// Добавляем фильтры по МЕТА-полям
clauseBuilder = StringUtils.hasText(path.first.getFieldName()) ?
clauseBuilder.put(RecodeRuleSet.FROM_FIELD_NAME, new FilterCriteriaValue.StringValue(path.first.getFieldName())) :
clauseBuilder;
clauseBuilder = StringUtils.hasText(path.second.getFieldName()) ?
clauseBuilder.put(RecodeRuleSet.TO_FIELD_NAME, new FilterCriteriaValue.StringValue(path.second.getFieldName())) :
clauseBuilder;
builder.put(path.toString(), new FilterCriteriaValue.ClauseValue(clauseBuilder.build()).injectCondition(Condition.OR));
}
// Формируем критерий поиска
FilterCriteria filterCriteria = new FilterCriteria()
.injectCount(rulePaths.size() + 1)
.injectFilters(builder.build());
// Выполняем поиск наборов правил
return findByCriteria(filterCriteria, fillTransitive).getResult();
}
/**
* Проверяет, что для справочника заданы все перекодировки для каждого набора правил
*
* @param dictionary проверяемый справочник
* @param recodeRuleSets проверяемые наборы перекодировок
* @return Возвращает флаг проверки
*/
private boolean isDictionaryUnmatched(Dictionary dictionary, Collection<RecodeRuleSet> recodeRuleSets) {
// Получаем первичное МЕТА-поле
MetaField primaryMetaField = metaFieldService.findPrimaryMetaField(dictionary.getId(), false, false);
// Получаем количество записей справочника
int recordsCount = fieldService.countByCriteria(
createCriteriaByIDs(Field.META_FIELD_ID, primaryMetaField.getId())
);
// Для каждого набора проверяем, что количество перекодировок соответствует количеству записей справочника
for (RecodeRuleSet ruleSet : recodeRuleSets) {
// Если не задано правило перекодирования по умолчанию, то проверяем, что
// количество записей справочника равно количеству перекодировок
if (!StringUtils.hasText(ruleSet.getDefaultFieldId())) {
int recodesCount = recodeRuleService.countByCriteria(
createCriteriaByIDs(RecodeRule.RECODE_RULE_SET_ID, ruleSet.getId())
);
if (recordsCount != recodesCount) {
return true;
}
}
}
// Для всех наборов заданы все правила
return false;
}
@Override
public Set<Dictionary> findUnmatchedDictionaries(String groupId, boolean fillTransitive) {
Assert.isTrue(StringUtils.hasText(groupId), "Group id must not be NULL or EMPTY");
final Collection<Dictionary> dictionaries = dictionaryService.findAllByRelativeId(groupId, null, fillTransitive);
final Set<Dictionary> result = Sets.newHashSet();
// Выполняет итерирование всех справочников для указанной группы
for (Dictionary dictionary : dictionaries) {
// Получаем наборы перекодировок для справочника, где он является источником и назначением
Collection<RecodeRuleSet> fromRecodeRuleSets = findAllByCriteria(createCriteriaByIDs(RecodeRuleSet.FROM_DICTIONARY_ID, dictionary.getId()), false);
Collection<RecodeRuleSet> toRecodeRuleSets = findAllByCriteria(createCriteriaByIDs(RecodeRuleSet.TO_DICTIONARY_ID, dictionary.getId()), false);
// Проверяем, что справочник участвует в перекодировках
if (CollectionUtils.isEmpty(fromRecodeRuleSets) && CollectionUtils.isEmpty(toRecodeRuleSets)) {
result.add(dictionary);
} else if (!CollectionUtils.isEmpty(fromRecodeRuleSets) && isDictionaryUnmatched(dictionary, fromRecodeRuleSets)) {
result.add(dictionary);
}
}
return result;
}
/**
* Выполняет актуализацию наборов правил перекодирования
*
* @param service сервис актуализации
* @param changed коллекция изменившихся зависимостей
* @param fieldName названия поля для поиска существующих правил
*/
private <T extends Essence> Collection<RecodeRuleSet> doUpdateByDependencies(RecodeRuleSetActualizeService<T> service, Collection<T> changed, String fieldName) {
// Получаем существующие наборы
Collection<RecodeRuleSet> existedRules = findAllByCriteria(createCriteriaByDocumentIDs(fieldName, changed), true);
// Выполняем актуализацию
Collection<RecodeRuleSet> updatedRules = updateRulesByDependencies(service, changed, existedRules);
// Выполняем обновление
return update(updatedRules, existedRules, false);
}
@SuppressWarnings("unchecked")
@Override
@Transactional(propagation = Propagation.MANDATORY, rollbackFor = Throwable.class)
public <T> Collection<RecodeRuleSet> modifyByDependencies(Class<T> dependencyClass, Collection<T> dependencies) {
if (!CollectionUtils.isEmpty(dependencies)) {
if (Group.class.equals(dependencyClass)) {
return doUpdateByDependencies(
RecodeRuleSetActualizeService.BY_GROUP,
(Collection<Group>) dependencies,
Dictionary.GROUP_ID
);
} else if (Dictionary.class.equals(dependencyClass)) {
return doUpdateByDependencies(
RecodeRuleSetActualizeService.BY_DICTIONARY,
(Collection<Dictionary>) dependencies,
MetaField.DICTIONARY_ID
);
} else if (MetaField.class.equals(dependencyClass)) {
return doUpdateByDependencies(
RecodeRuleSetActualizeService.BY_META_FIELD,
(Collection<MetaField>) dependencies,
RecodeRuleSet.FIELD_ID
);
}
}
return Collections.emptyList();
}
/**
* Выполняет проверку привязанных документов к существующим правилам перекодирования
*
* @param changeType тип изменений
* @param changeClass целевой класс документов
* @param targetField проверяемое поля правил перекодирования
* @param documents коллекция проверяемых документов
* @throws AttachedRecodeRuleException Исключительная ситуация
*/
private <T extends Identifying> void checkAttachedRules(ChangeType changeType, Class<T> changeClass, String targetField, Collection<T> documents) throws AttachedRecodeRuleException {
if (!CollectionUtils.isEmpty(documents)) {
int existedRulesCount = countByCriteria(createCriteriaByDocumentIDs(targetField, documents));
Assert.isTrue(
existedRulesCount == 0,
String.format("Can't %s %s. Cause by: found %d attached recode rule sets",
changeType.name().toLowerCase(),
changeClass.getSimpleName(),
existedRulesCount
),
AttachedRecodeRuleException.class
);
}
}
@Override
protected void handleOtherUpdateEvent(ChangeEvent event) {
if (Group.class.equals(event.getChangedClass())) {
modifyByDependencies(Group.class, event.getChangedByPredicate(Group.class, CHANGE_NAME_PREDICATE));
} else if (Dictionary.class.equals(event.getChangedClass())) {
modifyByDependencies(Dictionary.class, event.getChangedByPredicate(Dictionary.class, CHANGE_NAME_PREDICATE));
} else if (MetaField.class.equals(event.getChangedClass())) {
modifyByDependencies(MetaField.class, event.getChangedByPredicate(MetaField.class, CHANGE_NAME_PREDICATE));
}
}
@Override
protected void handleOtherCloseEvent(ChangeEvent event) {
if (MetaField.class.equals(event.getChangedClass())) {
checkAttachedRules(event.getChangeType(), MetaField.class, RecodeRuleSet.FIELD_ID, event.getChanged(MetaField.class));
} else if (Field.class.equals(event.getChangedClass())) {
checkAttachedRules(event.getChangeType(), Field.class, RecodeRuleSet.DEFAULT_FIELD_ID, event.getChanged(Field.class));
}
}
}