package ru.hflabs.rcd.history; import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; import org.springframework.util.Assert; import org.springframework.util.StringUtils; import ru.hflabs.rcd.accessor.Accessors; import ru.hflabs.rcd.event.modify.ChangeEvent; import ru.hflabs.rcd.model.Essence; import ru.hflabs.rcd.model.Historical; import ru.hflabs.rcd.model.Identifying; import ru.hflabs.rcd.model.change.*; import ru.hflabs.rcd.service.*; import ru.hflabs.rcd.service.document.FilterDocumentServiceTemplate; import ru.hflabs.util.core.EqualsUtil; import ru.hflabs.util.core.Pair; import ru.hflabs.util.io.IOUtils; import ru.hflabs.util.security.SecurityUtil; import ru.hflabs.util.spring.transaction.support.TransactionUtil; import java.util.Date; import java.util.Map; /** * Класс <class>HistoryService</class> реализует сервис работы с историей документов * * @author Nazin Alexander */ public class HistoryService extends FilterDocumentServiceTemplate<History> implements IHistoryService { /** Сервис создания уникальных идентификаторов */ private ISequenceService sequenceService; /** Сервис работы с изменениями */ private IDifferenceService<Identifying> differenceService; public HistoryService() { super(History.class); } public void setSequenceService(ISequenceService sequenceService) { this.sequenceService = sequenceService; } public void setDifferenceService(IDifferenceService<Identifying> differenceService) { this.differenceService = differenceService; } /** * Определяет тип события в зависимости от состояния переданных сущностей * * @param oldEssence старый экземпляр сущности * @param newEssence новый экземпляр сущности * @return Возвращает определенный тип события */ private <E extends Identifying & Historical> ChangeType determineChangeType(E oldEssence, E newEssence) { if (oldEssence == null && newEssence != null) { return ChangeType.CREATE; } else if (oldEssence != null && newEssence != null) { if (ChangeType.CLOSE.equals(oldEssence.getChangeType())) { return ChangeType.RESTORE; } else { return EqualsUtil.equals(differenceService.createHashCode(oldEssence), differenceService.createHashCode(newEssence)) ? ChangeType.SKIP : ChangeType.UPDATE; } } else if (oldEssence != null) { if (ChangeType.CLOSE.equals(oldEssence.getChangeType())) { return ChangeType.IGNORE; } else { return ChangeType.CLOSE; } } else { throw new IllegalArgumentException("Can't determine change type: old or new essence must not be NULL"); } } /** * Выполняет формирование события истории для сущности * * @param changeType тип изменения * @param date дата события * @param author автор события * @param oldEssence существующая сущность (может быть <code>NULL</code>) * @param newEssence обновленная сущность * @return Возвращает обновленную сущность */ private <E extends Identifying & Historical> E doCreateHistory(ChangeType changeType, Date date, String author, E oldEssence, E newEssence) { Assert.notNull(newEssence, "Target history object must not be NULL"); final History history = sequenceService.fillIdentifier(new History(), true); history.setTargetId(newEssence.getId()); history.setTargetType(newEssence.getHistoryName()); history.setEventType(changeType); history.setEventDate(date); history.setEventAuthor(author); history.setDiffs(differenceService.createDiff(oldEssence, newEssence)); newEssence.setHistory(history); return newEssence; } @Override @Transactional(propagation = Propagation.MANDATORY, rollbackFor = Throwable.class) public <E extends Essence & Historical> E createRestoreHistory(E essence) { return doCreateHistory( ChangeType.RESTORE, TransactionUtil.getTransactionStartDate(), SecurityUtil.getCurrentUserName(), essence, Accessors.shallowClone(essence) ); } @Override @Transactional(propagation = Propagation.MANDATORY, rollbackFor = Throwable.class) public <E extends Essence & Historical> Pair<ChangeType, E> createChangeHistory(E oldEssence, E newEssence) { return createChangeHistory(TransactionUtil.getTransactionStartDate(), SecurityUtil.getCurrentUserName(), oldEssence, newEssence); } @Override public <E extends Essence & Historical> Pair<ChangeType, E> createChangeHistory(Date date, String author, E oldEssence, E newEssence) { Assert.notNull(date, "Change date must not be NULL"); Assert.isTrue(StringUtils.hasText(author), "Change author must not be NULL or EMPTY"); // В зависимости от типа события формируем историю модификации ChangeType changeType = determineChangeType(oldEssence, newEssence); switch (changeType) { case IGNORE: { return Pair.valueOf(changeType, oldEssence); } case SKIP: { newEssence.injectId(oldEssence.getId()); newEssence.setHistory(oldEssence.getHistory()); return Pair.valueOf(changeType, newEssence); } case CREATE: { newEssence = sequenceService.fillIdentifier(newEssence, false); // invoke UPDATE methods } case UPDATE: { // invoke RESTORE methods } case RESTORE: { return Pair.valueOf(changeType, doCreateHistory(changeType, date, author, oldEssence, newEssence)); } case CLOSE: { return Pair.valueOf(changeType, doCreateHistory(changeType, date, author, IOUtils.deepClone(oldEssence), oldEssence)); } default: { throw new UnsupportedOperationException(String.format("Change type '%s' not supported by '%s'", changeType.name(), getClass().getName())); } } } @Override public <E extends Essence & Historical> HistoryBuilder<E> createChangeSet( Class<E> targetClass, Map<String, E> existedEssences, Map<String, E> newEssences, IMergeService.Single<E> mergeService) { return createChangeSet(targetClass, TransactionUtil.getTransactionStartDate(), SecurityUtil.getCurrentUserName(), existedEssences, newEssences, mergeService); } @Override public <E extends Essence & Historical> HistoryBuilder<E> createChangeSet(Class<E> targetClass, Date date, String author, Map<String, E> existedEssences, Map<String, E> newEssences, IMergeService.Single<E> mergeService) { Assert.notNull(targetClass, "Target class must not be NULL"); Assert.notNull(mergeService, "Merge service must not be NULL"); final HistoryBuilder<E> changeDescriptor = new HistoryBuilder<E>(targetClass); // Выполняем сортировку сущностей по типам операций, опираясь на уникальность их имени for (Map.Entry<String, E> entry : newEssences.entrySet()) { final E oldValue = existedEssences.remove(entry.getKey()); final E newValue = mergeService.merge(entry.getValue(), oldValue); changeDescriptor.addChange(createChangeHistory(date, author, oldValue, newValue)); } // Для оставшихся сущностей совпадений не найдено - выполняем их закрытие for (E existed : existedEssences.values()) { changeDescriptor.addChange(createChangeHistory(date, author, existed, null)); } return changeDescriptor; } @Override protected void handleOtherChangeEvent(ChangeEvent event) { if (Historical.class.isAssignableFrom(event.getChangedClass()) && !ChangeType.SKIP.equals(event.getChangeType()) && !ChangeType.IGNORE.equals(event.getChangeType())) { ChangeSet<?> otherChangeSet = event.getChangeSet(); // Формируем набор изменений истории для создания ChangeSet<History> historyChangeSet = new ChangeSet<>( retrieveTargetClass(), ChangeType.CREATE, ChangeMode.ISOLATED, otherChangeSet.getChanges() ); // Публикуем событие создания истории документов ServiceUtils.publishChangeEvent(eventPublisher, event.getSource(), historyChangeSet); } } }