package ru.hflabs.rcd.service.document.field; import com.google.common.base.Function; import com.google.common.base.Predicate; import com.google.common.collect.*; import org.apache.lucene.document.DocumentStoredFieldVisitor; import org.apache.lucene.search.IndexSearcher; import org.apache.lucene.search.ReferenceManager; import org.springframework.util.CollectionUtils; import org.springframework.util.StringUtils; import ru.hflabs.rcd.event.modify.ChangeEvent; import ru.hflabs.rcd.exception.search.document.UnknownFieldException; import ru.hflabs.rcd.lucene.criteria.LuceneCriteriaHolder; import ru.hflabs.rcd.model.criteria.FilterCriteria; import ru.hflabs.rcd.model.document.*; import ru.hflabs.rcd.model.path.MetaFieldNamedPath; import ru.hflabs.rcd.service.document.DocumentServiceTemplate; import ru.hflabs.rcd.service.document.IFieldService; import ru.hflabs.rcd.service.document.IMetaFieldService; import ru.hflabs.util.core.EqualsUtil; import ru.hflabs.util.lucene.LuceneQueryCallback; import ru.hflabs.util.lucene.LuceneQueryDescriptor; import ru.hflabs.util.lucene.LuceneQueryUtil; import ru.hflabs.util.spring.Assert; import javax.swing.*; import java.io.IOException; import java.util.*; import static ru.hflabs.rcd.accessor.Accessors.META_FIELD_TO_FIELD_INJECTOR; import static ru.hflabs.rcd.accessor.Accessors.linkRelative; import static ru.hflabs.rcd.model.CriteriaUtils.*; import static ru.hflabs.rcd.model.ModelUtils.ID_FUNCTION; import static ru.hflabs.rcd.model.ModelUtils.extractFieldsFromRecords; import static ru.hflabs.rcd.service.ServiceUtils.*; /** * Класс <class>FieldService</class> реализует сервис работы со значениями полей записи * * @author Nazin Alexander */ public class FieldService extends DocumentServiceTemplate<Field> implements IFieldService { /** Сервис работы с МЕТА-полями */ private IMetaFieldService metaFieldService; public FieldService() { super(Field.class); } public void setMetaFieldService(IMetaFieldService metaFieldService) { this.metaFieldService = metaFieldService; } @Override protected Collection<Field> injectTransitiveDependencies(Collection<Field> objects) { return super.injectTransitiveDependencies(injectRelations(objects, metaFieldService)); } @Override public Field findUniqueByRelativeId(String relativeId, String name, boolean fillTransitive, boolean quietly) { Field result = findUniqueDocumentBy(this, createCriteriaByRelative(Field.META_FIELD_ID, relativeId, Field.NAME, name), fillTransitive); if (result == null && !quietly) { throw new UnknownFieldException(name); } return result; } @Override public Collection<Field> findAllByRelativeId(String relativeId, String searchQuery, boolean fillTransitive) { Assert.isTrue(StringUtils.hasText(relativeId), "Relative ID must not be NULL or EMPTY"); return findAllByCriteria(createCriteriaByIDs(Field.META_FIELD_ID, relativeId).injectSearch(searchQuery), fillTransitive); } @Override public Collection<Field> findByNames(String relativeId, Set<String> names, boolean fillTransitive) { Assert.isTrue(StringUtils.hasText(relativeId), "Relative ID must not be NULL or EMPTY"); return findAllByCriteria(createCriteriaByRelative(Field.META_FIELD_ID, relativeId, Field.NAME, names), fillTransitive); } @Override public Collection<Field> findByValues(String relativeId, Set<String> values, boolean fillTransitive) { Assert.isTrue(StringUtils.hasText(relativeId), "Relative ID must not be NULL or EMPTY"); final MetaField metaField = metaFieldService.findByID(relativeId, fillTransitive, false); Collection<Field> result = findAllByCriteria( createCriteriaByRelative(Field.META_FIELD_ID, relativeId, Field.VALUE, values), false ); return fillTransitive ? Lists.newArrayList(Collections2.transform(result, new Function<Field, Field>() { @Override public Field apply(Field input) { return linkRelative(metaField, input); } })) : result; } @Override public Collection<Field> findAllByMetaFields(Collection<String> metaFieldIDs, boolean fillTransitive) { if (!CollectionUtils.isEmpty(metaFieldIDs)) { return findAllByCriteria(createCriteriaByIDs(Field.META_FIELD_ID, metaFieldIDs), fillTransitive); } return Collections.emptyList(); } @Override public boolean isFieldExist(String metaFieldId, String value) { return countByCriteria(createCriteriaByRelative(Field.META_FIELD_ID, metaFieldId, Field.VALUE, value)) != 0; } @Override public boolean isFieldUnique(Field field) { Assert.notNull(field, "Field must not be NULL"); FilterCriteria criteria = createCriteriaByRelative( Field.META_FIELD_ID, field.getMetaFieldId(), Field.VALUE, field.getValue() ).injectCount(2); Collection<Field> result = findByCriteria(criteria, false).getResult(); if (result.size() == 1) { return EqualsUtil.equals(extractSingleDocument(result).getId(), field.getId()); } else { return result.isEmpty(); } } @Override public boolean isFieldsUnique(String metaFieldId) { // Проверяем, что поля существуют final LuceneCriteriaHolder criteria = criteriaBuilder.createCriteria( retrieveTargetClass(), createCriteriaByIDs(Field.META_FIELD_ID, metaFieldId).injectSort(Field.VALUE, SortOrder.ASCENDING) ); final int totalCount = queryProvider.executeCountByCriteria(criteria); if (totalCount <= 0) { return true; } // Выполняем итерирование значений полей пока не дойдем до конца или не встретим дублирующегося значения UniqueFieldHandler uniqueFieldHandler = new UniqueFieldHandler(); try { LuceneQueryUtil.query( binderTransformer, new LuceneQueryCallback() { @Override public ReferenceManager<IndexSearcher> getSearcherManager() { return refreshSearcherManager(false); } @Override public LuceneQueryDescriptor getQueryDescriptor() { return new LuceneQueryDescriptor(criteria.buildQuery(), criteria.buildSort(), criteria.buildFilter(), 0, totalCount); } @Override public DocumentStoredFieldVisitor createStoredFieldVisitor() { return new DocumentStoredFieldVisitor(); } }, uniqueFieldHandler ); return uniqueFieldHandler.isUnique(); } catch (IOException ex) { throw new RuntimeException(String.format("Can't find unique fields '%s' index. Cause by: %s", retrieveTargetClassName(), ex.getMessage()), ex); } } @Override public Collection<DocumentContext> findDocumentContexts(Set<MetaFieldNamedPath> namedPath) { Map<MetaFieldNamedPath, MetaField> metaFields = metaFieldService.findMetaFieldByNamedPath(namedPath, false); Map<String, MetaField> id2metaFields = Maps.uniqueIndex(metaFields.values(), ID_FUNCTION); Collection<Field> fields = findAllByMetaFields(id2metaFields.keySet(), false); ImmutableList.Builder<DocumentContext> result = ImmutableList.builder(); for (Field field : fields) { field = META_FIELD_TO_FIELD_INJECTOR.inject(field, id2metaFields.get(field.getMetaFieldId())); result.add(Contexts.createDocumentContext(field)); } return result.build(); } @Override protected void handleOtherCreateEvent(ChangeEvent event) { if (Record.class.equals(event.getChangedClass())) { create(extractFieldsFromRecords(event.getChanged(Record.class)), true); } } @Override protected void handleOtherUpdateEvent(ChangeEvent event) { if (Record.class.isAssignableFrom(event.getChangedClass())) { update(extractFieldsFromRecords(event.getChanged(Record.class)), true); } } @Override protected void handleOtherCloseEvent(ChangeEvent event) { if (MetaField.class.equals(event.getChangedClass())) { closeByCriteria(createCriteriaByDocumentIDs(Field.META_FIELD_ID, event.getChanged(MetaField.class))); } else if (Record.class.equals(event.getChangedClass())) { Collection<String> fieldIDs = Sets.newHashSet(Collections2.transform(extractFieldsFromRecords(event.getChanged(Record.class)), ID_FUNCTION)); closeByCriteria(createCriteriaByIDs(Field.PRIMARY_KEY, fieldIDs)); } } /** * Класс <class>UniqueFieldHandler</class> реализует предикат уникальных значений полей * * @author Nazin Alexander */ private static class UniqueFieldHandler implements Predicate<Field> { /** Предыдущее значение поля */ private String previousValue; /** Флаг уникальности */ private boolean isUnique; private UniqueFieldHandler() { this.previousValue = UUID.randomUUID().toString(); this.isUnique = true; } public boolean isUnique() { return isUnique; } @Override public boolean apply(Field input) { isUnique = !EqualsUtil.equals(previousValue, input.getValue()); previousValue = input.getValue(); return isUnique; } } }