net.n2oapp.framework.engine.data.N2oQueryProcessor Maven / Gradle / Ivy
The newest version!
package net.n2oapp.framework.engine.data;
import lombok.Setter;
import net.n2oapp.criteria.api.CollectionPage;
import net.n2oapp.criteria.api.Sorting;
import net.n2oapp.criteria.api.SortingDirection;
import net.n2oapp.criteria.dataset.DataList;
import net.n2oapp.criteria.dataset.DataSet;
import net.n2oapp.criteria.filters.Filter;
import net.n2oapp.criteria.filters.FilterReducer;
import net.n2oapp.criteria.filters.FilterType;
import net.n2oapp.criteria.filters.Result;
import net.n2oapp.framework.api.MetadataEnvironment;
import net.n2oapp.framework.api.context.ContextProcessor;
import net.n2oapp.framework.api.criteria.N2oPreparedCriteria;
import net.n2oapp.framework.api.criteria.Restriction;
import net.n2oapp.framework.api.data.*;
import net.n2oapp.framework.api.exception.N2oException;
import net.n2oapp.framework.api.metadata.aware.MetadataEnvironmentAware;
import net.n2oapp.framework.api.metadata.global.dao.invocation.N2oArgumentsInvocation;
import net.n2oapp.framework.api.metadata.global.dao.query.AbstractField;
import net.n2oapp.framework.api.metadata.global.dao.query.N2oQuery;
import net.n2oapp.framework.api.metadata.global.dao.query.field.QueryListField;
import net.n2oapp.framework.api.metadata.global.dao.query.field.QueryReferenceField;
import net.n2oapp.framework.api.metadata.global.dao.query.field.QuerySimpleField;
import net.n2oapp.framework.api.metadata.local.CompiledQuery;
import net.n2oapp.framework.api.script.ScriptProcessor;
import net.n2oapp.framework.engine.exception.N2oFoundMoreThanOneRecordException;
import net.n2oapp.framework.engine.exception.N2oRecordNotFoundException;
import net.n2oapp.framework.engine.exception.N2oSpelException;
import net.n2oapp.framework.engine.exception.N2oUniqueRequestNotFoundException;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.ArrayUtils;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import java.util.*;
import java.util.function.IntSupplier;
import java.util.stream.Collectors;
import static java.util.Objects.nonNull;
import static net.n2oapp.framework.engine.util.ArgumentsInvocationUtil.mapToArgs;
import static net.n2oapp.framework.engine.util.MappingProcessor.*;
/**
* Процессор выборок
*/
public class N2oQueryProcessor implements QueryProcessor, MetadataEnvironmentAware, ApplicationContextAware {
private static final ExpressionParser parser = new SpelExpressionParser();
private ContextProcessor contextProcessor;
private final N2oInvocationFactory invocationFactory;
@Setter
private CriteriaConstructor criteriaConstructor = new N2oCriteriaConstructor(false);
private DomainProcessor domainProcessor;
private final QueryExceptionHandler exceptionHandler;
@Setter
private ApplicationContext applicationContext;
@Setter
private boolean pageStartsWith0;
@Setter
private String ascExpression;
@Setter
private String descExpression;
public N2oQueryProcessor(N2oInvocationFactory invocationFactory,
QueryExceptionHandler exceptionHandler) {
this.invocationFactory = invocationFactory;
this.exceptionHandler = exceptionHandler;
}
@SuppressWarnings("unchecked")
public CollectionPage execute(final CompiledQuery query, final N2oPreparedCriteria criteria) {
if (criteria.getSize() == 1) {
return executeOneSizeQuery(query, criteria);
}
return executePageQuery(findListSelection(query, criteria), query, criteria);
}
@SuppressWarnings("unchecked")
public Integer executeCount(final CompiledQuery query, final N2oPreparedCriteria criteria) {
resolveRestriction(query, criteria);
addDefaultFilters(query, criteria);
N2oQuery.Selection selection = findCountSelection(query, criteria);
ActionInvocationEngine engine = invocationFactory.produce(selection.getInvocation().getClass());
Object result;
if (engine instanceof ArgumentsInvocationEngine) {
try {
result = ((ArgumentsInvocationEngine) engine).invoke(
(N2oArgumentsInvocation) selection.getInvocation(),
mapToArgs((N2oArgumentsInvocation) selection.getInvocation(),
query, criteria, criteriaConstructor, domainProcessor));
} catch (Exception e) {
throw exceptionHandler.handle(query, criteria, e);
}
} else {
Map map = new LinkedHashMap<>();
prepareMapForQuery(map, selection, query, criteria);
try {
result = engine.invoke(selection.getInvocation(), map);
} catch (Exception e) {
throw exceptionHandler.handle(query, criteria, e);
}
}
return calculateCount(result, selection.getCountMapping());
}
private Integer calculateCount(Object result, String countMapping) {
try {
return outMap(result, countMapping, Integer.class);
} catch (N2oException e) {
throw new N2oSpelException(countMapping, e);
}
}
public CollectionPage executeOneSizeQuery(CompiledQuery query, N2oPreparedCriteria criteria) {
N2oQuery.Selection selection = findUniqueSelection(query, criteria);
if (selection.getType().equals(N2oQuery.Selection.Type.unique)) {
criteria.setSize(2);
criteria.setCount(2);
}
Object result = executeQuery(selection, query, criteria);
criteria.setSize(1);
if (selection.getType().equals(N2oQuery.Selection.Type.list)) {
CollectionPage page = preparePageResult(result, query, selection, criteria);
if (CollectionUtils.isEmpty(page.getCollection())) {
throw new N2oRecordNotFoundException();
}
if (page.getCollection().size() != 1) {
throw new N2oFoundMoreThanOneRecordException();
}
return page;
} else if (selection.getType().equals(N2oQuery.Selection.Type.unique)) {
DataSet single = prepareSingleResult(result, query, selection);
if (single.isEmpty()) {
throw new N2oRecordNotFoundException();
}
return new CollectionPage<>(1, Collections.singletonList(single), criteria);
} else
throw new UnsupportedOperationException();
}
private N2oQuery.Selection findCountSelection(final CompiledQuery query, final N2oPreparedCriteria criteria) {
Set filterFields = getFilterIds(query, criteria);
N2oQuery.Selection selection = chooseSelection(query.getCounts(), filterFields, query.getId());
if (selection == null)
throw new N2oException(String.format("В %s.query.xml не найден запрос необходимый для пагинации", query.getId()));
return selection;
}
private N2oQuery.Selection findUniqueSelection(final CompiledQuery query, final N2oPreparedCriteria criteria) {
resolveRestriction(query, criteria);
addDefaultFilters(query, criteria);
Set filterFields = getFilterIds(query, criteria);
N2oQuery.Selection selection = chooseSelection(query.getUniques(), filterFields, query.getId());
if (selection != null)
return selection;
selection = chooseSelection(query.getLists(), filterFields, query.getId());
if (selection == null)
throw new N2oUniqueRequestNotFoundException(query.getId());
return selection;
}
private N2oQuery.Selection findListSelection(final CompiledQuery query, final N2oPreparedCriteria criteria) {
resolveRestriction(query, criteria);
addDefaultFilters(query, criteria);
Set filterFields = getFilterIds(query, criteria);
N2oQuery.Selection selection = chooseSelection(query.getLists(), filterFields, query.getId());
if (selection == null)
throw new N2oException(String.format("В %s.query.xml не найден запрос", query.getId()));
return selection;
}
private Set getFilterIds(CompiledQuery query, N2oPreparedCriteria criteria) {
return criteria.getRestrictions() == null ? Collections.emptySet() :
criteria.getRestrictions().stream()
.map(r -> query.getFilterFieldId(r.getFieldId(), r.getType()))
.collect(Collectors.toSet());
}
@SuppressWarnings("unchecked")
private Object executeQuery(N2oQuery.Selection selection, CompiledQuery query, N2oPreparedCriteria criteria) {
ActionInvocationEngine engine = invocationFactory.produce(selection.getInvocation().getClass());
Object result;
if (engine instanceof ArgumentsInvocationEngine) {
try {
result = ((ArgumentsInvocationEngine) engine).invoke(
(N2oArgumentsInvocation) selection.getInvocation(),
mapToArgs((N2oArgumentsInvocation) selection.getInvocation(),
query, criteria, criteriaConstructor, domainProcessor)
);
} catch (Exception e) {
throw exceptionHandler.handle(query, criteria, e);
}
} else if (engine instanceof MapInvocationEngine) {
Map map = new LinkedHashMap<>();
prepareMapForQuery(map, selection, query, criteria);
prepareMapForPage(map, criteria, pageStartsWith0);
try {
result = engine.invoke(selection.getInvocation(), map);
} catch (Exception e) {
throw exceptionHandler.handle(query, criteria, e);
}
} else
throw new UnsupportedOperationException("InvocationEngine должен быть наследован от `ArgumentsInvocationEngine` или `MapInvocationEngine`");
return result;
}
private CollectionPage executePageQuery(N2oQuery.Selection selection, CompiledQuery query, N2oPreparedCriteria criteria) {
if (criteria != null && criteria.getRestrictions() != null) {
Set restrictionFieldIds = criteria.getRestrictions().stream().map(Restriction::getFieldId).collect(Collectors.toSet());
for (String fieldId : restrictionFieldIds) {
if (reduceFiltersByField(criteria, fieldId))
return new CollectionPage<>(0, Collections.emptyList(), criteria);
}
}
Object result = executeQuery(selection, query, criteria);
CollectionPage page = preparePageResult(result, query, selection, criteria);
addIdIfNotPresent(query, page);
return page;
}
private boolean reduceFiltersByField(N2oPreparedCriteria criteria, String fieldId) {
List restrictionsByField = criteria.getRestrictions(fieldId);
if (restrictionsByField.size() > 1) {
List resFilters = new ArrayList<>();
resFilters.add(restrictionsByField.get(0));
for (int i = 1; i < restrictionsByField.size(); i++) {
boolean notMergeable = false;
for (Filter result : resFilters) {
Result reduceResult = FilterReducer.reduce(result, restrictionsByField.get(i));
if (reduceResult.isSuccess()) {
resFilters.remove(result);
resFilters.add(reduceResult.getResultFilter());
notMergeable = false;
break;
} else if (reduceResult.getType().equals(Result.Type.notMergeable)) {
notMergeable = true;
} else {
return true;
}
}
if (notMergeable) {
resFilters.add(restrictionsByField.get(i));
}
}
criteria.removeFilterForField(fieldId);
resFilters.forEach(result -> criteria.addRestriction(new Restriction(fieldId, result)));
}
return false;
}
private void addDefaultFilters(CompiledQuery query, N2oPreparedCriteria criteria) {
for (Map.Entry> entry : query.getFiltersMap().entrySet()) {
for (N2oQuery.Filter filter : entry.getValue().values()) {
if (filter.getCompiledDefaultValue() != null && !criteria.containsRestriction(entry.getKey())) {
Object value = prepareFilterValue(filter.getCompiledDefaultValue(), filter, null);
if (value != null) {
Restriction defaultRestriction = new Restriction(entry.getKey(), value, filter.getType());
criteria.addRestriction(defaultRestriction);
}
}
}
}
}
public void prepareMapForQuery(Map map, N2oQuery.Selection selection, CompiledQuery query, N2oPreparedCriteria criteria) {
map.put("select", query.getSelectExpressions());
prepareSelectKeys(map, query.getDisplayFields(), query);
List where = new ArrayList<>();
for (Restriction r : criteria.getRestrictions()) {
N2oQuery.Filter filter = query.getFiltersMap().get(r.getFieldId()).get(r.getType());
if (filter == null)
throw new N2oUniqueRequestNotFoundException(query.getId());
if (filter.getText() != null)
where.add(filter.getText());
inMap(map, r.getFieldId(), filter.getMapping(), r.getValue());
}
map.put("filters", where);
List sortingExp = new ArrayList<>();
if (criteria.getSorting() != null)
for (Sorting sorting : criteria.getSortings()) {
QuerySimpleField field = query.getSimpleFieldsMap().get(sorting.getField());
if (Boolean.FALSE.equals(field.getIsSorted()))
continue;
sortingExp.add(field.getSortingExpression());
inMap(map, field.getId(), field.getSortingMapping(), getSortingDirectionExpression(sorting.getDirection(), selection));
}
map.put("sorting", sortingExp);
if (criteria.getAdditionalFields() != null) {
criteria.getAdditionalFields().entrySet().stream().filter(es -> nonNull(es.getValue()))
.forEach(es -> map.put(es.getKey(), es.getValue()));
}
}
private String getSortingDirectionExpression(SortingDirection direction, N2oQuery.Selection selection) {
if (direction == SortingDirection.ASC)
return Objects.requireNonNullElse(selection.getAscExpression(), ascExpression);
else
return Objects.requireNonNullElse(selection.getDescExpression(), descExpression);
}
private void prepareSelectKeys(Map map, List fields, CompiledQuery query) {
for (AbstractField field : fields) {
if (field instanceof QueryReferenceField) {
QueryReferenceField referenceField = (QueryReferenceField) field;
if (referenceField.getSelectKey() != null) {
List displayedInnerFields = query.getDisplayedInnerFields(referenceField);
map.put(referenceField.getSelectKey(),
displayedInnerFields.stream().map(AbstractField::getSelectExpression).collect(Collectors.toList()));
prepareSelectKeys(map, displayedInnerFields, query);
}
}
}
}
public void prepareMapForPage(Map map, N2oPreparedCriteria criteria, boolean pageStartsWith0) {
map.put("limit", criteria.getSize());
map.put("offset", criteria.getFirst());
if (criteria.getCount() != null)
map.put("count", criteria.getCount());
map.put("page", pageStartsWith0 ? criteria.getPage() - 1 : criteria.getPage());
}
private Object prepareFilterValue(Object value, N2oQuery.Filter filter, DataSet data) {
Object result = value;
result = contextProcessor.resolve(result);
result = domainProcessor.deserialize(result, filter == null ? null : filter.getDomain());
result = normalizeValue(result, filter == null ? null : filter.getNormalize(), data, parser, applicationContext);
return result;
}
private DataSet prepareSingleResult(Object res, CompiledQuery query,
N2oQuery.Selection selection) {
Object result;
try {
result = outMap(res, selection.getResultMapping(), Object.class);
} catch (N2oException e) {
throw new N2oRecordNotFoundException(e);
}
result = normalizeValue(result, selection.getResultNormalize(), null, parser, applicationContext);
return mapFields(result, query.getDisplayFields(), null);
}
private CollectionPage preparePageResult(Object res, CompiledQuery query, N2oQuery.Selection selection,
N2oPreparedCriteria criteria) {
Object additionalInfo = null;
if (selection.getAdditionalMapping() != null)
additionalInfo = outMap(res, selection.getAdditionalMapping(), Object.class);
Collection> result = outMap(res, selection.getResultMapping(), Collection.class);
try {
result = (Collection>) normalizeValue(
result, selection.getResultNormalize(), null, parser, applicationContext);
} catch (ClassCastException e) {
throw new N2oException("В результате нормализации через `result-normalize` получилась не коллекция");
}
List content = result.stream()
.map(obj -> mapFields(obj, query.getDisplayFields(), null))
.collect(Collectors.toList());
CollectionPage collectionPage;
if (Boolean.FALSE.equals(criteria.getWithCount())) {
Boolean hasNext = getNext(content, criteria, selection, query);
collectionPage = new CollectionPage<>(hasNext, content, criteria);
} else {
int size = getSize(content, criteria, () -> {
if (criteria.getSize() == 1) {
return 1;
} else if (selection.getCountMapping() == null) {
return executeCount(query, criteria);
} else {
return calculateCount(res, selection.getCountMapping());
}
});
collectionPage = new CollectionPage<>(size, content, criteria);
}
collectionPage.setAdditionalInfo(additionalInfo);
return collectionPage;
}
private N2oQuery.Selection chooseSelection(N2oQuery.Selection[] selections, Set filterFields, String queryId) {
if (selections == null)
return null;
if (filterFields == null) {
N2oQuery.Selection result = findBaseSelection(selections);
if (result == null) {
throw new N2oException(String.format("В %s.query.xml не найден запрос без фильтров", queryId));
}
return result;
}
return findSelectionByFilters(selections, filterFields);
}
private N2oQuery.Selection findBaseSelection(N2oQuery.Selection[] lists) {
for (N2oQuery.Selection selection : lists)
if (ArrayUtils.isEmpty(selection.getFilters()))
return selection;
return null;
}
private N2oQuery.Selection findSelectionByFilters(N2oQuery.Selection[] selections, Set filterFields) {
for (N2oQuery.Selection selection : selections) {
if (ArrayUtils.isEmpty(selection.getFilters()))
continue;
Set filters = Arrays.stream(selection.getFilters()).collect(Collectors.toSet());
if (filters.equals(filterFields))
return selection;
}
return findBaseSelection(selections);
}
/**
* Приведение значений фильтраций к домену и контексту
*/
private void resolveRestriction(CompiledQuery query, N2oPreparedCriteria criteria) {
Set restrictionsForRemove = null;
DataSet data = new DataSet();
for (Restriction restriction : criteria.getRestrictions()) {
data.put(restriction.getFieldId(), restriction.getValue());
}
for (Restriction restriction : criteria.getRestrictions()) {
N2oQuery.Filter filter = query.getFiltersMap().get(restriction.getFieldId()).get(restriction.getType());
Object value = prepareFilterValue(restriction.getValue(), filter, data);
if (value != null) {
restriction.setValue(value);
} else if (FilterType.Arity.nullary == restriction.getType().arity) {
restriction.setValue(Boolean.TRUE);
} else {
//удаляем фильтрацию, если в результате резолва контекста значение по умолчанию стало null
if (restrictionsForRemove == null)
restrictionsForRemove = new HashSet<>();
restrictionsForRemove.add(restriction.getFieldId());
}
}
if (restrictionsForRemove != null) {
restrictionsForRemove.forEach(criteria::removeFilterForField);
}
}
private void addIdIfNotPresent(CompiledQuery query, CollectionPage collectionPage) {
if (!query.getSimpleFieldsMap().containsKey(QuerySimpleField.PK))
return;
if (Boolean.TRUE.equals(query.getFieldsMap().get(QuerySimpleField.PK).getIsSelected()))
return;
int i = 1;
for (DataSet dataSet : collectionPage.getCollection()) {
dataSet.put(QuerySimpleField.PK, i++);
}
}
private DataSet mapFields(Object entry, List fields, DataSet parentData) {
DataSet resultDataSet = new DataSet();
fields.forEach(field -> mapField(field, resultDataSet, entry));
fields.forEach(field -> normalizeField(field, resultDataSet, parentData));
fields.forEach(field -> processInnerFields(field, resultDataSet, resultDataSet));
fields.stream()
.filter(
field -> field instanceof QuerySimpleField && ((QuerySimpleField) field).getN2oSwitch() != null)
.forEach(
field -> applySwitch(resultDataSet, (QuerySimpleField) field)
);
return resultDataSet;
}
private void applySwitch(DataSet resultDataSet, QuerySimpleField field) {
String valueFieldId = field.getN2oSwitch().getValueFieldId();
String expression = ScriptProcessor.buildSwitchExpression(field.getN2oSwitch())
.replace("`", "")
.replace(valueFieldId, String.format("#this.get('%s')", valueFieldId));
Object resultSwitch = normalizeValue(resultDataSet, expression, null, parser, applicationContext);
resultDataSet.replace(valueFieldId, resultSwitch);
}
private void mapField(AbstractField field, DataSet target, Object entry) {
if (field instanceof QueryReferenceField)
outMap(target, entry, field.getId(), field.getMapping(), null, contextProcessor);
else
outMap(target, entry, field.getId(), field.getMapping(), ((QuerySimpleField) field).getDefaultValue(), contextProcessor);
}
private void processInnerFields(AbstractField field, DataSet target, DataSet parentData) {
if (field instanceof QueryReferenceField) {
if (field instanceof QueryListField && target.getList(field.getId()) != null) {
DataList list = new DataList(target.getList(field.getId()));
for (int i = 0; i < list.size(); i++)
list.set(i, mapFields(target.getList(field.getId()).get(i), Arrays.asList(((QueryListField) field).getFields()), parentData));
target.put(field.getId(), list);
} else if (target.get(field.getId()) != null)
target.put(field.getId(), mapFields(target.get(field.getId()), Arrays.asList(((QueryReferenceField) field).getFields()), parentData));
}
}
private void normalizeField(AbstractField field, DataSet resultDataSet, DataSet parentDataSet) {
if (field.getNormalize() != null) {
Object obj = resultDataSet.get(field.getId());
obj = contextProcessor.resolve(obj);
try {
resultDataSet.put(field.getId(), normalizeValue(obj, field.getNormalize(), resultDataSet, parentDataSet, parser, applicationContext));
} catch (N2oSpelException e) {
e.setFieldId(field.getId());
throw e;
}
}
}
private Integer getSize(Collection content, N2oPreparedCriteria criteria,
IntSupplier totalSupplier) {
int size;
if (criteria.getFirst() == 0) {
if (criteria.getSize() > content.size()) {
size = content.size();
} else {
size = totalSupplier.getAsInt();
}
} else if (!content.isEmpty() && criteria.getSize() > content.size()) {
size = criteria.getFirst() + content.size();
} else {
size = totalSupplier.getAsInt();
}
return size;
}
private Boolean getNext(Collection content, N2oPreparedCriteria criteria,
N2oQuery.Selection selection, CompiledQuery query) {
if (criteria.getSize() <= content.size()) {
N2oPreparedCriteria nextCriteria = new N2oPreparedCriteria(criteria);
nextCriteria.setPage(nextCriteria.getPage() + 1);
Object res = executeQuery(selection, query, nextCriteria);
Collection> result = outMap(res, selection.getResultMapping(), Collection.class);
try {
result = (Collection>) normalizeValue(
result, selection.getResultNormalize(), null, parser, applicationContext);
} catch (ClassCastException e) {
throw new N2oException("В результате нормализации через `result-normalize` получилась не коллекция");
}
return !result.isEmpty();
}
return false;
}
@Override
public void setEnvironment(MetadataEnvironment environment) {
this.contextProcessor = environment.getContextProcessor();
this.domainProcessor = environment.getDomainProcessor();
}
}