/*!
Copyright (c) REBUILD
and/or its owners. All rights reserved.
rebuild is dual-licensed under commercial and open source licenses (GPLv3).
See LICENSE and COMMERCIAL in the project root for license information.
*/
package com.rebuild.core.service.query;
import cn.devezhao.commons.CalendarUtils;
import cn.devezhao.commons.ObjectUtils;
import cn.devezhao.momentjava.Moment;
import cn.devezhao.persist4j.Entity;
import cn.devezhao.persist4j.Field;
import cn.devezhao.persist4j.dialect.FieldType;
import cn.devezhao.persist4j.dialect.Type;
import cn.devezhao.persist4j.engine.ID;
import cn.devezhao.persist4j.metadata.MissingMetaExcetion;
import cn.devezhao.persist4j.query.compiler.QueryCompiler;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.rebuild.core.Application;
import com.rebuild.core.UserContextHolder;
import com.rebuild.core.metadata.EntityHelper;
import com.rebuild.core.metadata.MetadataHelper;
import com.rebuild.core.metadata.easymeta.DisplayType;
import com.rebuild.core.metadata.easymeta.EasyMetaFactory;
import com.rebuild.core.metadata.impl.EasyFieldConfigProps;
import com.rebuild.core.privileges.UserHelper;
import com.rebuild.core.privileges.bizz.Department;
import com.rebuild.core.support.License;
import com.rebuild.core.support.SetUser;
import com.rebuild.core.support.i18n.Language;
import com.rebuild.utils.CommonsUtils;
import com.rebuild.utils.JSONUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.math.NumberUtils;
import org.springframework.util.Assert;
import java.math.BigDecimal;
import java.text.MessageFormat;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
import java.time.temporal.TemporalAccessor;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import static cn.devezhao.commons.CalendarUtils.addDay;
import static cn.devezhao.commons.CalendarUtils.addMonth;
/**
* 高级查询解析器
*
* {
* [entity]: 'xxx',
* [type]: 'xxx',
* [equation]: 'xxx',
* items: [
* { field:'xxx', op: 'xxx', value: 'xxx' },
* ...
* ],
* [values]: [
* 'xxx', ...
* ]
* }
*
*
* @author devezhao
* @since 09/29/2018
*/
@Slf4j
public class AdvFilterParser extends SetUser {
// 虚拟字段:当前审批人
@Deprecated
public static final String VF_ACU = "$APPROVALCURRENTUSER$";
// 快速查询
private static final String MODE_QUICK = "QUICK";
// 名称字段 &
private static final String NAME_FIELD_PREFIX = "" + QueryCompiler.NAME_FIELD_PREFIX;
final private JSONObject filterExpr;
final private Entity rootEntity;
// v3.1 条件值使用记录作为变量
final private ID varRecord;
transient private Set
includeFields = null;
/**
* @param filterExpr
*/
public AdvFilterParser(JSONObject filterExpr) {
this(filterExpr, MetadataHelper.getEntity(filterExpr.getString("entity")));
}
/**
* @param filterExpr
* @param rootEntity
*/
public AdvFilterParser(JSONObject filterExpr, Entity rootEntity) {
Assert.notNull(filterExpr, "[filterExpr] cannot be null");
this.filterExpr = filterExpr;
this.rootEntity = rootEntity;
this.varRecord = null;
String entityName = filterExpr.getString("entity");
if (entityName != null && !entityName.equalsIgnoreCase(this.rootEntity.getName())) {
Assert.isTrue(entityName.equalsIgnoreCase(this.rootEntity.getName()),
"Filter(2) uses different entities : " + entityName + ", " + this.rootEntity.getName());
}
}
/**
* @param filterExpr
* @param varRecord 条件中包含字段变量,将从该记录中提取实际值替换
*/
public AdvFilterParser(JSONObject filterExpr, ID varRecord) {
this.filterExpr = filterExpr;
this.rootEntity = MetadataHelper.getEntity(varRecord.getEntityCode());
this.varRecord = License.isRbvAttached() ? varRecord : null;
String entityName = filterExpr.getString("entity");
if (entityName != null) {
Assert.isTrue(entityName.equalsIgnoreCase(this.rootEntity.getName()),
"Filter(3) uses different entities : " + entityName + ", " + this.rootEntity.getName() + ", " + varRecord);
}
}
/**
* @return
*/
public String toSqlWhere() {
if (filterExpr == null || filterExpr.isEmpty()) return null;
this.includeFields = new HashSet<>();
// 自动确定查询项
if (MODE_QUICK.equalsIgnoreCase(filterExpr.getString("type"))) {
String quickFields = filterExpr.getString("quickFields");
JSONArray quickItems = buildQuickFilterItems(quickFields, 1);
// TODO v3.6-b4,3.7 值1|值2 UNTEST
// 转义可输入 \|
JSONObject values = filterExpr.getJSONObject("values");
String[] valuesPlus = values.values().iterator().next().toString().split("(? 1) {
values.clear();
values.put("1", valuesPlus[0].trim());
for (int i = 2; i <= valuesPlus.length; i++) {
JSONArray quickItemsPlus = buildQuickFilterItems(quickFields, i);
values.put(String.valueOf(i), valuesPlus[i - 1].trim());
quickItems.addAll(quickItemsPlus);
}
filterExpr.put("values", values);
}
filterExpr.put("items", quickItems);
}
JSONArray items = filterExpr.getJSONArray("items");
items = items == null ? JSONUtils.EMPTY_ARRAY : items;
JSONObject values = filterExpr.getJSONObject("values");
values = values == null ? JSONUtils.EMPTY_OBJECT : values;
String equation = StringUtils.defaultIfBlank(filterExpr.getString("equation"), "OR");
Map indexItemSqls = new LinkedHashMap<>();
int incrIndex = 1;
for (Object o : items) {
JSONObject item = (JSONObject) o;
Integer index = item.getInteger("index");
if (index == null) {
index = incrIndex++;
}
String itemSql = parseItem(item, values, rootEntity);
if (itemSql != null) {
indexItemSqls.put(index, itemSql.trim());
this.includeFields.add(item.getString("field"));
}
if (CommonsUtils.DEVLOG) System.out.println("[dev] Parse item : " + item + " >> " + itemSql);
}
if (indexItemSqls.isEmpty()) return null;
String equationHold = equation;
if ((equation = validEquation(equation)) == null) {
throw new FilterParseException(Language.L("无效的高级表达式 : %s", equationHold));
}
if ("OR".equalsIgnoreCase(equation)) {
return "( " + StringUtils.join(indexItemSqls.values(), " or ") + " )";
} else if ("AND".equalsIgnoreCase(equation)) {
return "( " + StringUtils.join(indexItemSqls.values(), " and ") + " )";
} else {
// 高级表达式 eg: (1 AND 2) or (3 AND 4)
String[] tokens = equation.toLowerCase().split(" ");
List itemSqls = new ArrayList<>();
for (String token : tokens) {
if (StringUtils.isBlank(token)) {
continue;
}
boolean hasRP = false; // the `)`
if (token.length() > 1) {
if (token.startsWith("(")) {
itemSqls.add("(");
token = token.substring(1