package ru.hflabs.rcd.service.document; import com.google.common.base.Function; import com.google.common.base.Predicate; import com.google.common.collect.*; import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; import org.springframework.util.CollectionUtils; import ru.hflabs.rcd.event.modify.ChangeEvent; import ru.hflabs.rcd.model.Essence; import ru.hflabs.rcd.model.Historical; import ru.hflabs.rcd.model.ModelUtils; import ru.hflabs.rcd.model.change.*; import ru.hflabs.rcd.model.criteria.FilterActivity; import ru.hflabs.rcd.model.criteria.FilterCriteria; import ru.hflabs.rcd.service.IDocumentService; import ru.hflabs.rcd.service.IHistoryService; import ru.hflabs.rcd.service.IValidateService; import ru.hflabs.util.core.EqualsUtil; import ru.hflabs.util.core.Pair; import ru.hflabs.util.lucene.LuceneModifierUtil; import ru.hflabs.util.spring.Assert; import java.util.*; import static ru.hflabs.rcd.model.CriteriaUtils.createCriteriaByIDs; import static ru.hflabs.rcd.model.ModelUtils.ID_FUNCTION; import static ru.hflabs.rcd.service.ServiceUtils.checkFoundDocuments; import static ru.hflabs.rcd.service.ServiceUtils.publishChangeEvent; /** * Класс <class>DocumentServiceTemplate</class> реализует базовый сервис работы с документами * * @author Nazin Alexander */ public class DocumentServiceTemplate<E extends Essence & Historical> extends FilterDocumentServiceTemplate<E> implements IDocumentService<E> { /** Сервис работы с историей документов */ protected IHistoryService historyService; /** Сервис валидации документов при создании */ protected IValidateService<E> createValidator; /** Сервис валидации документов при обновлении */ protected IValidateService<E> updateValidator; /** Сервис валидации документов при закрытии */ protected IValidateService<E> closeValidator; public DocumentServiceTemplate(Class<E> targetClass) { super(targetClass); } public void setHistoryService(IHistoryService historyService) { this.historyService = historyService; } public void setCreateValidator(IValidateService<E> createValidator) { this.createValidator = createValidator; } public void setUpdateValidator(IValidateService<E> updateValidator) { this.updateValidator = updateValidator; } public void setCloseValidator(IValidateService<E> closeValidator) { this.closeValidator = closeValidator; } /** * Выполняет оповещение слушателей об изменениях * * @param descriptor дескриптор изменений * @param types целевые типы событий */ protected void doPublishEvent(HistoryBuilder<E> descriptor, ChangeType... types) { publishChangeEvent(eventPublisher, this, descriptor, types); } @Override protected Collection<E> handleSelfRestoreEvent(Collection<E> changed) { doModify("restore", LuceneModifierUtil.createUpdateCallback(indexManager, binderTransformer, changed)); return changed; } @Override protected Collection<E> handleSelfCloseEvent(Collection<E> changed) { doModify("close", LuceneModifierUtil.createUpdateCallback(indexManager, binderTransformer, changed)); return changed; } /** * Выполняет заполнение истории для документа * * @param object целевой документ * @return Возвращает модифицированный документ с заполненной историей */ protected E injectHistory(E object) { if (object != null) { object.setHistory(historyService.findByID(object.getHistoryId(), false, false)); } return object; } /** * Выполняет заполнение истории документов * * @param objects коллекцию документов * @return Возвращает коллекцию документов с заполненной историей */ protected Collection<E> injectHistory(Collection<E> objects) { if (!CollectionUtils.isEmpty(objects)) { // Формируем карту соответствий идентификатора истории к объекту Map<String, E> historyId2objects = Maps.uniqueIndex(objects, ModelUtils.HISTORY_ID_FUNCTION); // Заполняем историю для документов Collection<History> histories = historyService.findByIDs(historyId2objects.keySet(), false, false); for (History history : histories) { historyId2objects.get(history.getId()).setHistory(history); } } return objects; } @Override protected Collection<E> injectTransitiveDependencies(Collection<E> objects) { return super.injectTransitiveDependencies(injectHistory(objects)); } /** * Выполняет создание документов * * @param objects коллекция документов для создания * @param validateService сервис валидации перед создаеним * @return Возвращает коллекцию созданных документов */ @Transactional(propagation = Propagation.MANDATORY, rollbackFor = Throwable.class) protected Collection<E> doCreate(Collection<E> objects, IValidateService<E> validateService) { final HistoryBuilder<E> changeDescriptor = new HistoryBuilder<E>(retrieveTargetClass()); for (E object : objects) { object = (validateService != null) ? validateService.validate(object) : object; changeDescriptor.addChange(historyService.createChangeHistory(null, object)); } doPublishEvent(changeDescriptor); return changeDescriptor.getEssences(); } @Override @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Throwable.class) public Collection<E> create(Collection<E> objects, boolean needValidation) { if (!CollectionUtils.isEmpty(objects)) { return doCreate(objects, needValidation ? createValidator : null); } else { return Collections.emptyList(); } } /** * Выполняет обновление документов * * @param newObjects коллекцию документов для обновления * @param oldObjects коллекция существующих документов * @param validateService сервис валидации перед обновлением * @return Возвращает обновленную коллекцию документов */ @Transactional(propagation = Propagation.MANDATORY, rollbackFor = Throwable.class) protected Collection<E> doUpdate(Map<String, E> newObjects, Map<String, E> oldObjects, IValidateService<E> validateService) { final Collection<E> toSelfUpdate = Lists.newArrayList(); final HistoryBuilder<E> changeDescriptor = new HistoryBuilder<E>(retrieveTargetClass()); for (Map.Entry<String, E> entry : newObjects.entrySet()) { E oldValue = oldObjects.get(entry.getKey()); Assert.notNull(oldValue, String.format("Can't find '%s' with ID '%s'", retrieveTargetClassName(), entry.getKey())); E newValue = (validateService != null) ? validateService.validate(entry.getValue()) : entry.getValue(); Pair<ChangeType, E> change = historyService.createChangeHistory(oldValue, newValue); // Если персистентные поля не изменились, то проверяем транзитивные поля if (ChangeType.SKIP.equals(change.first) && !EqualsUtil.equals(oldValue, newValue)) { toSelfUpdate.add(change.second); } changeDescriptor.addChange(change); } doPublishEvent(changeDescriptor); if (!CollectionUtils.isEmpty(toSelfUpdate)) { handleSelfUpdateEvent(toSelfUpdate); } return changeDescriptor.getEssences(); } @Override @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Throwable.class) public Collection<E> update(Collection<E> newObjects, Collection<E> oldObjects, boolean needValidation) { return !CollectionUtils.isEmpty(newObjects) ? doUpdate(Maps.uniqueIndex(newObjects, ID_FUNCTION), Maps.uniqueIndex(oldObjects, ID_FUNCTION), needValidation ? updateValidator : null) : Collections.<E>emptyList(); } @Override @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Throwable.class) public Collection<E> update(Collection<E> objects, boolean needValidation) { return !CollectionUtils.isEmpty(objects) ? update(objects, findByIDs(Sets.newHashSet(Collections2.transform(objects, ID_FUNCTION)), true, false), needValidation) : Collections.<E>emptyList(); } @Override @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Throwable.class) public Collection<E> closeByCriteria(FilterCriteria criteria) { return doClose(findAllByCriteria(criteria, false), null); } /** * Выполняет закрытие документов * * @param existed коллекция существующих документов * @param validator сервис валидации перед закрытием * @return Возвращает обновленную коллекцию документов */ @Transactional(propagation = Propagation.MANDATORY, rollbackFor = Throwable.class) protected Collection<E> doClose(Collection<E> existed, IValidateService<E> validator) { final HistoryBuilder<E> changeDescriptor = new HistoryBuilder<>(retrieveTargetClass()); for (E object : existed) { object = (validator != null) ? validator.validate(object) : object; changeDescriptor.addChange(historyService.createChangeHistory(object, null)); } doPublishEvent(changeDescriptor); return changeDescriptor.getEssences(); } @Override @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Throwable.class) public Collection<E> close(Collection<E> objects, boolean needValidation) { return !CollectionUtils.isEmpty(objects) ? doClose(objects, needValidation ? closeValidator : null) : Collections.<E>emptyList(); } @Override @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Throwable.class) public void closeByIDs(Set<String> ids) { if (!CollectionUtils.isEmpty(ids)) { doClose(findAllByCriteria(createCriteriaByIDs(E.PRIMARY_KEY, ids), false), closeValidator); } } /** * Выполняет переоткрытие документов * * @param restoreDate дата, на которую производится восстановление объектов * @param closedObjects коллекция документов для переоткрытия * @return Возвращает актуальные документы */ @Transactional(propagation = Propagation.MANDATORY, rollbackFor = Throwable.class) protected Collection<E> doReopen(Date restoreDate, Collection<E> closedObjects) { ChangeSet<E> changeSet = new ChangeSet<>(retrieveTargetClass(), ChangeType.RESTORE, ChangeMode.DEFAULT); for (E object : closedObjects) { E validated = createValidator.validate(object); changeSet.appendChange(historyService.createRestoreHistory(validated)); } eventPublisher.publishEvent(new ChangeEvent(this, changeSet)); return changeSet.getChanged(); } @Override @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Throwable.class) public Collection<E> reopen(Set<String> ids) { Assert.isTrue(!CollectionUtils.isEmpty(ids), "Restore IDs must not be NULL or EMPTY"); // Получаем коллекцию восстаналиваемых документов Collection<E> documents = findAllByCriteria( createCriteriaByIDs(E.PRIMARY_KEY, ids).injectActivity(FilterActivity.ALL), true ); checkFoundDocuments(retrieveTargetClass(), ids, documents, false); // Отбираем те документы, для которых последним событием было закрытие Collection<E> closedDocuments = Collections2.filter(documents, new Predicate<E>() { @Override public boolean apply(E input) { return ChangeType.CLOSE.equals(input.getChangeType()); } }); // Группируем документы по целевой дате восстановления Map<Date, Collection<E>> date2documents = Multimaps.index(closedDocuments, new Function<E, Date>() { @Override public Date apply(E input) { return input.getChangeDate(); } }).asMap(); ImmutableList.Builder<E> resultBuilder = ImmutableList.builder(); // Выполняем восстановление документов по датам for (Map.Entry<Date, Collection<E>> entry : date2documents.entrySet()) { resultBuilder.addAll(doReopen(entry.getKey(), entry.getValue())); } // Возвращаем результирующую коллекцию throw new UnsupportedOperationException("Not implement yet."); } }