package ru.hflabs.rcd.model; import com.google.common.base.Function; import com.google.common.base.Functions; import com.google.common.base.Predicate; import com.google.common.collect.*; import org.apache.commons.lang.StringUtils; import org.springframework.util.CollectionUtils; import ru.hflabs.rcd.accessor.Accessors; import ru.hflabs.rcd.model.document.Dictionary; import ru.hflabs.rcd.model.document.*; import ru.hflabs.rcd.model.notification.Notification; import ru.hflabs.rcd.model.path.DictionaryNamedPath; import ru.hflabs.rcd.model.path.DirectionNamedPath; import ru.hflabs.rcd.model.path.FieldNamedPath; import ru.hflabs.rcd.model.path.MetaFieldNamedPath; import ru.hflabs.rcd.model.rule.Rule; import ru.hflabs.util.core.EqualsUtil; import ru.hflabs.util.core.MD5; import ru.hflabs.util.core.date.DateInterval; import ru.hflabs.util.spring.Assert; import java.util.*; import static ru.hflabs.rcd.accessor.Accessors.META_FIELD_TO_FIELD_INJECTOR; import static ru.hflabs.rcd.accessor.Accessors.injectId; /** * Класс <class>ModelUtils</class> реализует вспомогательные методы для работы с моделью * * @see Function * @see Accessors */ public abstract class ModelUtils { /** Функция выделения/установки первичного ключа документа */ public static final Function<Identifying, String> ID_FUNCTION = new Function<Identifying, String>() { @Override public String apply(Identifying input) { return input != null ? input.getId() : null; } }; /** Функция выделения идентификатора истории документа */ public static final Function<Historical, String> HISTORY_ID_FUNCTION = new Function<Historical, String>() { @Override public String apply(Historical input) { return input != null ? input.getHistoryId() : null; } }; /** Функция выделения идентификатора связанной сущности */ public static final Function<ManyToOne<?>, String> RELATIVE_ID_FUNCTION = new Function<ManyToOne<?>, String>() { @Override public String apply(ManyToOne<?> input) { return input != null ? input.getRelativeId() : null; } }; /** Сервис доступа к значению поля */ public static final Function<Field, String> FIELD_VALUE = new Function<Field, String>() { @Override public String apply(Field input) { return input != null ? input.getValue() : null; } }; /** Сервис доступа к идентификатору поля источника */ public static final Function<Rule<?, ?, ?>, String> FROM_RULE_FIELD_ID = new Function<Rule<?, ?, ?>, String>() { @Override public String apply(Rule<?, ?, ?> input) { return input.getFromFieldId(); } }; /** Сервис доступа к идентификатору поля назначения */ public static final Function<Rule<?, ?, ?>, String> TO_RULE_FIELD_ID = new Function<Rule<?, ?, ?>, String>() { @Override public String apply(Rule<?, ?, ?> input) { return input.getToFieldId(); } }; /** Функция выделения имени документа */ public static final Function<Named, String> NAME_FUNCTION = new Function<Named, String>() { @Override public String apply(Named input) { return input != null ? input.getName() : null; } }; /** Функция преобразования строки в нижний регистр */ public static final Function<String, String> LOWER_CASE_FUNCTION = new Function<String, String>() { @Override public String apply(String input) { return input != null ? input.toLowerCase() : null; } }; /** Формируем функцию преоразования имени в нижний регистр */ public static final Function<Named, String> LOWER_CASE_NAME_FUNCTION = Functions.compose(LOWER_CASE_FUNCTION, NAME_FUNCTION); protected ModelUtils() { // embedded constructor } @SuppressWarnings("unchecked") public static <T extends Identifying> Function<T, String> idFunction() { return (Function<T, String>) ID_FUNCTION; } @SuppressWarnings("unchecked") public static <T extends Named> Function<T, String> nameFunction() { return (Function<T, String>) NAME_FUNCTION; } @SuppressWarnings("unchecked") public static <T extends Named> Function<T, String> lowerNameFunction() { return (Function<T, String>) LOWER_CASE_NAME_FUNCTION; } /** * @param metaFields коллекция МЕТА-полей * @return Возвращает отсортированную коллекцию МЕТА-полей */ public static List<MetaField> sortMetaFieldsByOrdinal(Collection<MetaField> metaFields) { List<MetaField> sortedMetaFields = Lists.newArrayList(metaFields); Collections.sort(sortedMetaFields, Comparators.META_FIELD_ORDINAL_COMPARATOR); return sortedMetaFields; } /** * @param metaFields коллекция предендентов * @return Возвращает найденное первичное МЕТА-поле или <code>NULL</code> */ public static MetaField retrievePrimaryMetaField(Collection<MetaField> metaFields) { if (!CollectionUtils.isEmpty(metaFields)) { final Collection<MetaField> fields = Collections2.filter(metaFields, new Predicate<MetaField>() { @Override public boolean apply(MetaField input) { return input.isFlagEstablished(MetaField.FLAG_PRIMARY); } }); if (fields.size() == 1) { return fields.iterator().next(); } } // Первичное МЕТА-поле не найдено return null; } /** * @param metaFields коллекция предендентов * @return Возвращает <b>НЕ скрытые</b> МЕТА-поля */ public static Collection<MetaField> retrieveNotHiddenMetaFields(Collection<MetaField> metaFields) { return !CollectionUtils.isEmpty(metaFields) ? Collections2.filter(metaFields, new Predicate<MetaField>() { @Override public boolean apply(MetaField input) { return !input.isFlagEstablished(MetaField.FLAG_HIDDEN); } }) : Collections.<MetaField>emptyList(); } /** * Выполняет удаление скрытых МЕТА-полей из записей * * @param records коллекция записей * @return Возвращает модифицированные записи */ public static Collection<Record> removeHiddenMetaFields(Collection<Record> records) { ImmutableList.Builder<Record> result = ImmutableList.builder(); for (Record record : records) { Map<String, Field> filtered = Maps.newLinkedHashMap(Maps.filterEntries(record.getFields(), new Predicate<Map.Entry<String, Field>>() { @Override public boolean apply(Map.Entry<String, Field> input) { Field field = input.getValue(); if (field != null) { MetaField metaField = META_FIELD_TO_FIELD_INJECTOR.apply(input.getValue()); if (metaField != null) { return !metaField.isFlagEstablished(MetaField.FLAG_HIDDEN); } } return true; } })); record.setFields(filtered); result.add(record); } return result.build(); } /** * Создает коллекцию записей справочника по МЕТА-полям с заполненными значениями * * @param dictionaryId идентификатор справочника * @param metaFields коллекция МЕТА-полей справочника с заполненными {@link Field значениями} * @return Возвращает сформированные записи */ public static Collection<Record> createRecords(String dictionaryId, Collection<MetaField> metaFields) { Map<String, Record> id2record = new LinkedHashMap<>(); for (MetaField metaField : sortMetaFieldsByOrdinal(metaFields)) { String metaFieldName = metaField.getName(); Collection<Field> fields = metaField.getDescendants(); if (!CollectionUtils.isEmpty(fields)) { for (Field field : fields) { String recordId = NAME_FUNCTION.apply(field); Record record = id2record.get(recordId); if (record == null) { record = injectId(new Record(), recordId); record.setDictionaryId(dictionaryId); } Map<String, Field> recordFields = record.getFields(); if (recordFields == null) { recordFields = new LinkedHashMap<>(); } recordFields.put(metaFieldName, field); record.injectFields(recordFields); id2record.put(recordId, record); } } } return Lists.newArrayList(id2record.values()); } /** * Создает коллекцию записей по МЕТА-полям и значениям справочника * * @param dictionaryId идентификатор справочника * @param metaFields МЕТА-поля справочника * @param fields значения полей записи справочника * @return Возвращает сформированные записи */ public static Collection<Record> createRecords(final String dictionaryId, Collection<MetaField> metaFields, Collection<Field> fields) { List<MetaField> sortedMetaFields = sortMetaFieldsByOrdinal(Collections2.filter(metaFields, new Predicate<MetaField>() { @Override public boolean apply(MetaField input) { return EqualsUtil.equals(dictionaryId, input.getDictionaryId()); } })); Map<String, Collection<Field>> row2fields = Multimaps.index(fields, NAME_FUNCTION).asMap(); ImmutableList.Builder<Record> resultBuilder = ImmutableList.builder(); for (Map.Entry<String, Collection<Field>> entry : row2fields.entrySet()) { Map<String, Field> metaFieldId2Field = Maps.uniqueIndex(entry.getValue(), new Function<Field, String>() { @Override public String apply(Field input) { return input.getMetaFieldId(); } }); Map<String, Field> content = new LinkedHashMap<String, Field>(sortedMetaFields.size()); for (MetaField metaField : sortedMetaFields) { content.put(metaField.getName(), metaFieldId2Field.get(metaField.getId())); } Record record = new Record().injectFields(content); record.setId(entry.getKey()); record.setDictionaryId(dictionaryId); resultBuilder.add(record); } return Lists.newArrayList(resultBuilder.build()); } /** * Создает коллекцию записей по МЕТА-полям и значениям полей справочников * * @param metaFields МЕТА-поля справочников * @param fields значения полей записи справочников * @return Возвращает сформированные записи */ public static List<Record> createRecords(Collection<MetaField> metaFields, Collection<Field> fields) { Map<String, Collection<MetaField>> dictionaryId2MetaFields = Multimaps.index(metaFields, new Function<MetaField, String>() { @Override public String apply(MetaField input) { return input.getDictionaryId(); } }).asMap(); ImmutableList.Builder<Record> resultBuilder = ImmutableList.builder(); for (Map.Entry<String, Collection<MetaField>> entry : dictionaryId2MetaFields.entrySet()) { final Set<String> metaFieldIDs = Sets.newHashSet(Collections2.transform(entry.getValue(), ID_FUNCTION)); Collection<Field> targetFields = Collections2.filter(fields, new Predicate<Field>() { @Override public boolean apply(Field input) { return metaFieldIDs.contains(input.getMetaFieldId()); } }); resultBuilder.addAll(createRecords(entry.getKey(), entry.getValue(), targetFields)); } return Lists.newArrayList(resultBuilder.build()); } /** * Выполняет извлечение значений полей МЕТА-полей * * @param metaFields коллекция МЕТА-полей * @return Возвращает коллекцию значений полей */ public static Collection<Field> extractFieldsFromMetaFields(Collection<MetaField> metaFields) { ImmutableList.Builder<Field> fields = ImmutableList.builder(); for (MetaField metaField : metaFields) { fields.addAll(metaField.getDescendants()); } return fields.build(); } /** * Выполняет извлечение значений полей из записей * * @param records коллекция записей справочника * @return Возвращает коллекцию значений полей */ public static Collection<Field> extractFieldsFromRecords(Collection<Record> records) { ImmutableList.Builder<Field> fields = ImmutableList.builder(); for (Record object : records) { if (!CollectionUtils.isEmpty(object.getFields())) { fields.addAll(object.getFields().values()); } } return Lists.newArrayList(fields.build()); } /** * Выполняет формирование коллекци групп справочников из справочников * * @param dictionaries коллекция справочников с заполненными группами * @return Возвращает коллекцию групп справочников */ public static Collection<Group> extractGroups(Collection<Dictionary> dictionaries) { Map<Group, Collection<Dictionary>> group2dictionaries = Multimaps.index(dictionaries, Accessors.GROUP_TO_DICTIONARY_INJECTOR).asMap(); for (Map.Entry<Group, Collection<Dictionary>> entry : group2dictionaries.entrySet()) { entry.getKey().setDescendants(entry.getValue()); } return Lists.newArrayList(group2dictionaries.keySet()); } /** * Рассчитывает и возвращает идентификатор записи на основе значения первичного ключа * * @param field значение первичного ключа записи * @return Возвращает идентификатор записи */ public static String createRecordId(Field field) { return MD5.asHex(StringUtils.lowerCase(formatFieldValue(field))); } /** * Выполняет форматирование значения поля * * @param field значение поля * @return Возвращает отформатированное значение или <code>NULL</code>, если поле имеет пустую длинну */ public static String formatFieldValue(Field field) { return !StringUtils.isEmpty(field.getValue()) ? field.getValue() : null; } /** * Выпоняет установку битов в значение <code>1</code> * * @param current текущие значение * @param target целевые значения * @return Возвращает модифицированное значение */ public static int establishBits(int current, int... target) { int result = current; for (int f : target) { result = result | f; } return result; } /** * Выполняет установку битов в значение <code>0</code> * * @param current текущие значение * @param target целевые значения * @return Возвращает модифицированное значение */ public static int resetBits(int current, int... target) { int result = current; for (int f : target) { result = result & ~f; } return result; } /** * Выполняет установку бита в указанное значение * * @param value значение * @param current текущие значение битов * @param targetBit целевой бит * @return Возвращает модифицированное значение */ public static int changeBit(boolean value, int current, int targetBit) { return value ? establishBits(current, targetBit) : resetBits(current, targetBit); } /** * Проверяет и возвращает <code>TRUE</code>, если целевой бит установлен в <code>1</code> * * @param current текущее значение битов * @param targetBit проверяемый бит * @return Возвращает флаг проверки */ public static boolean isBitEstablished(int current, int targetBit) { return (current & targetBit) == targetBit; } /** * Проверяет в возвращает <code>TRUE</code>, если указанный объект обладает указанными правами * * @param target проверяемый объект * @param targetPermission проверяемые наборы прав * @return Возвращает флаг проверки */ public static <T extends Permissioned> boolean hasPermission(T target, int targetPermission) { return target != null && isBitEstablished(target.getPermissions(), targetPermission); } /** * Создает и возвращает именованный путь для справочника * * @param dictionary целевой справочник * @return Возвращает именованный путь справочника */ public static DictionaryNamedPath createDictionaryNamedPath(Dictionary dictionary) { Assert.notNull(dictionary, "Dictionary must not be NULL"); Assert.notNull(dictionary.getRelative(), "Group must not be NULL"); return new DictionaryNamedPath(dictionary.getRelative().getName(), dictionary.getName()); } /** * Создает и возвращает именованный путь для МЕТА-поля * * @param metaField целевое МЕТА-поле * @return Возвращает именованный путь МЕТА-поля */ public static MetaFieldNamedPath createMetaFieldNamedPath(MetaField metaField) { Assert.notNull(metaField, "Meta field must not be NULL"); return new MetaFieldNamedPath(createDictionaryNamedPath(metaField.getRelative()), metaField.getName()); } /** * Создает и возвращает именованный путь значения поля * * @param field целевое значение поля * @return Возвращает именованный поть значения поля */ public static FieldNamedPath createFieldNamedPath(Field field) { return field != null ? new FieldNamedPath(createMetaFieldNamedPath(field.getRelative()), field.getValue()) : null; } /** * Создает и возвращает ключ, уникально идентифицирующий правило * * @param rule правило * @return Возвращает уникальный ключ правила */ public static <NP extends MetaFieldNamedPath> DirectionNamedPath<NP> createRulePath(Rule<NP, ?, ?> rule) { Assert.notNull(rule.getFromNamedPath(), String.format("From path must not be NULL")); Assert.notNull(rule.getToNamedPath(), String.format("To path must not be NULL")); return new DirectionNamedPath<>(rule.getFromNamedPath(), rule.getToNamedPath()); } /** * Создает коллекцию именованных путей по коллекции правил * * @param rules коллекция правил * @return Возвращает коллекцию именованных путей */ public static <NP extends MetaFieldNamedPath, R extends Rule<NP, ?, R>> Set<NP> createNamedPath(Collection<R> rules) { ImmutableSet.Builder<NP> builder = ImmutableSet.builder(); for (Rule<NP, ?, ?> rule : rules) { builder.add(rule.getFromNamedPath()); builder.add(rule.getToNamedPath()); } return builder.build(); } /** * Выполняет валидацию именованного пути справочника на предмет заполненности полей * * @param path именованный путь * @throws IllegalArgumentException Исключительная ситуация валидации */ public static DictionaryNamedPath validateDictionaryNamedPath(DictionaryNamedPath path) throws IllegalArgumentException { Assert.notNull(path, "Named path must not be NULL"); Assert.isTrue(StringUtils.isNotEmpty(path.getGroupName()), "Group name must not be NULL or EMPTY"); Assert.isTrue(StringUtils.isNotEmpty(path.getDictionaryName()), "Dictionary name must not be NULL or EMPTY"); return path; } /** * Выполняет валидацию именованного пути МЕТА-поля на предмет заполненности полей * * @param path именованный путь * @throws IllegalArgumentException Исключительная ситуация валидации */ public static MetaFieldNamedPath validateMetaFieldNamedPath(MetaFieldNamedPath path) throws IllegalArgumentException { validateDictionaryNamedPath(path); Assert.isTrue(StringUtils.isNotEmpty(path.getFieldName()), "Field name must not be NULL or EMPTY"); return path; } /** * Возвращает интервал дат начала и окончания агрегации событий * * @param notifications коллекция событий * @return Возвращает интервал дат */ public static DateInterval createNotificationInterval(Collection<Notification> notifications) { Date minStartDate = null; Date maxEndDate = null; for (Notification notification : notifications) { if (minStartDate == null || minStartDate.after(notification.getStartDate())) { minStartDate = notification.getStartDate(); } if (maxEndDate == null || maxEndDate.before(notification.getEndDate())) { maxEndDate = notification.getEndDate(); } } return new DateInterval(minStartDate, maxEndDate); } }