org.redkalex.source.search.SearchInfo Maven / Gradle / Ivy
Show all versions of redkale-plugins Show documentation
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package org.redkalex.source.search;
import java.io.Serializable;
import java.lang.reflect.*;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.ReentrantLock;
import java.util.function.IntFunction;
import java.util.logging.*;
import org.redkale.annotation.ConstructorParameters;
import org.redkale.annotation.LogExcludeLevel;
import org.redkale.annotation.LogLevel;
import org.redkale.convert.json.*;
import org.redkale.persistence.*;
import org.redkale.persistence.SearchColumn;
import org.redkale.persistence.VirtualEntity;
import org.redkale.source.*;
import org.redkale.util.*;
/**
* Search Entity操作类
*
* 详情见: https://redkale.org
*
* @since 2.4.0
* @author zhangjx
* @param Search Entity类的泛型
*/
@SuppressWarnings("unchecked")
public final class SearchInfo {
// 全局静态资源
private static final ConcurrentHashMap entityInfos = new ConcurrentHashMap<>();
private static final ReentrantLock infosLock = new ReentrantLock();
// 日志
private static final Logger logger = Logger.getLogger(SearchInfo.class.getSimpleName());
// Entity类名
private final Class type;
// 类对应的数据表名, 如果是VirtualEntity 类, 则该字段为null
private final String table;
// JsonConvert
private final JsonConvert jsonConvert;
// Entity构建器
private final Creator creator;
// Entity数值构建器
private final IntFunction arrayer;
// Entity构建器参数
private final String[] constructorParameters;
// Entity构建器参数Attribute
private final Attribute[] constructorAttributes;
// Entity构建器参数Attribute
private final Attribute[] unconstructorAttributes;
// 主键
private final Attribute primary;
// 用于存储绑定在EntityInfo上的对象
private final ConcurrentHashMap subobjectMap = new ConcurrentHashMap<>();
// key是field的name, 不是sql字段。
// 存放所有与数据库对应的字段, 包括主键
private final HashMap> attributeMap = new HashMap<>();
// 存放所有与数据库对应的字段, 包括主键
private final Attribute[] attributes;
// key是field的name, value是Column的别名,即数据库表的字段名
// 只有field.name 与 Column.name不同才存放在aliasmap里.
private final Map aliasmap;
// 所有可更新字段,即排除了主键字段和标记为@Column(updatable=false)的字段
private final Map> updateAttributeMap = new HashMap<>();
// 分表 策略
private final DistributeTableStrategy tableStrategy;
// 数据库中所有字段
private final Attribute[] queryAttributes;
// 数据库中所有可新增字段
private final Attribute[] insertAttributes;
// 数据库中所有可更新字段
private final Attribute[] updateAttributes;
// 存放highlight虚拟主键字段
private final Attribute highlightAttributeId;
// 存放highlight虚拟索引字段
private final Attribute highlightAttributeIndex;
// 存放highlight虚拟字段
private final HashMap> highlightAttributeMap = new HashMap<>();
// 存放html定制的analyzer
private final HashMap customAnalyzerMap = new HashMap<>();
private final Map mappingTypes;
// 日志级别,从LogLevel获取
private final int logLevel;
private final boolean virtual;
// 日志控制
private final Map excludeLogLevels;
private final Type findResultType;
private final Type searchResultType;
public static SearchInfo load(Class clazz) {
return load(clazz, null);
}
public static SearchInfo load(Class clazz, final Properties conf) {
SearchInfo rs = entityInfos.get(clazz);
if (rs != null) {
return rs;
}
infosLock.lock();
try {
rs = entityInfos.get(clazz);
if (rs == null) {
rs = new SearchInfo(clazz, conf);
entityInfos.put(clazz, rs);
}
return rs;
} finally {
infosLock.unlock();
}
}
/**
* 给PrepareCompiler使用,用于预动态生成Attribute
*
* @since 2.5.0
* @param 泛型
* @param clazz Entity 实体类
* @param source 数据源
*/
public static void compile(Class clazz, SearchSource source) {
SearchInfo.load(clazz);
}
/**
* 构造函数
*
* @param type Entity类
* @param conf 配置信息
*/
private SearchInfo(Class type, Properties conf) {
this.type = type;
this.virtual = type.getAnnotation(VirtualEntity.class) != null;
this.findResultType = TypeToken.createParameterizedType(null, FindResult.class, type);
this.searchResultType = TypeToken.createParameterizedType(null, SearchResult.class, type);
// ---------------------------------------------
LogLevel ll = type.getAnnotation(LogLevel.class);
this.logLevel = ll == null ? Integer.MIN_VALUE : Level.parse(ll.value()).intValue();
Map> logmap = new HashMap<>();
for (LogExcludeLevel lel : type.getAnnotationsByType(LogExcludeLevel.class)) {
for (String onelevel : lel.levels()) {
int level = Level.parse(onelevel).intValue();
HashSet set = logmap.get(level);
if (set == null) {
set = new HashSet<>();
logmap.put(level, set);
}
for (String key : lel.keys()) {
set.add(key);
}
}
}
if (logmap.isEmpty()) {
this.excludeLogLevels = null;
} else {
this.excludeLogLevels = new HashMap<>();
logmap.forEach((l, set) -> excludeLogLevels.put(l, set.toArray(new String[set.size()])));
}
// ---------------------------------------------
Table t = type.getAnnotation(Table.class);
this.table = (t == null || t.name().isEmpty()) ? type.getSimpleName().toLowerCase() : t.name();
DistributeTable dt = type.getAnnotation(DistributeTable.class);
DistributeTableStrategy dts = null;
try {
dts = (dt == null) ? null : dt.strategy().getDeclaredConstructor().newInstance();
if (dts != null) {
RedkaleClassLoader.putReflectionDeclaredConstructors(
dt.strategy(), dt.strategy().getName());
}
} catch (Exception e) {
logger.log(Level.SEVERE, type + " init DistributeTableStrategy error", e);
}
this.tableStrategy = dts;
this.arrayer = Creator.funcArray(type);
this.creator = Creator.create(type);
ConstructorParameters cp = null;
try {
cp = this.creator.getClass().getMethod("create", Object[].class).getAnnotation(ConstructorParameters.class);
} catch (Exception e) {
logger.log(Level.SEVERE, type + " cannot find ConstructorParameters Creator", e);
}
this.constructorParameters = (cp == null || cp.value().length < 1) ? null : cp.value();
Attribute idAttr0 = null;
Map aliasmap0 = null;
Class cltmp = type;
Set fields = new HashSet<>();
List> queryattrs = new ArrayList<>();
List insertcols = new ArrayList<>();
List> insertattrs = new ArrayList<>();
List updatecols = new ArrayList<>();
List> updateattrs = new ArrayList<>();
Map mappings = new LinkedHashMap<>();
JsonFactory factory = JsonFactory.root();
Attribute highlightAttrId = null;
Attribute highlightAttrIndex = null;
do {
for (Field field : cltmp.getDeclaredFields()) {
if (Modifier.isStatic(field.getModifiers())) {
continue;
}
if (Modifier.isFinal(field.getModifiers())) {
continue;
}
if (field.getAnnotation(Transient.class) != null) {
continue;
}
if (fields.contains(field.getName())) {
continue;
}
final String fieldname = field.getName();
final Column col = field.getAnnotation(Column.class);
int strlen = col == null || col.length() < 1 ? 255 : col.length();
final String sqlfield = col == null || col.name().isEmpty() ? fieldname : col.name();
if (!fieldname.equals(sqlfield)) {
if (aliasmap0 == null) {
aliasmap0 = new HashMap<>();
}
aliasmap0.put(fieldname, sqlfield);
}
Attribute attr;
try {
attr = Attribute.create(type, cltmp, field);
} catch (RuntimeException e) {
continue;
}
boolean text = false;
SearchColumn sc = field.getAnnotation(SearchColumn.class);
if (sc != null) {
if (!sc.highlight().isEmpty()) {
if (!sc.ignore()) {
throw new SourceException(
"@SearchColumn.ignore must be true when highlight is not empty on field(" + field
+ ")");
}
if (field.getType() != String.class) {
throw new SourceException("@SearchColumn.ignore must be on String field(" + field + ")");
}
if (SearchColumn.HighLights.HIGHLIGHT_NAME_ID.equals(sc.highlight())) {
highlightAttrId = attr;
} else if (SearchColumn.HighLights.HIGHLIGHT_NAME_INDEX.equals(sc.highlight())) {
highlightAttrIndex = attr;
} else {
highlightAttributeMap.put(sc.highlight(), attr);
}
}
text = sc.text();
if (sc.ignore()) {
if (factory == JsonFactory.root()) {
factory = JsonFactory.create();
}
factory.register(type, true, fieldname);
continue;
}
}
if (field.getAnnotation(org.redkale.persistence.Id.class) != null && idAttr0 == null) {
idAttr0 = attr;
insertcols.add(sqlfield);
insertattrs.add(attr);
} else {
if (col == null || col.insertable()) {
insertcols.add(sqlfield);
insertattrs.add(attr);
}
if (col == null || col.updatable()) {
updatecols.add(sqlfield);
updateattrs.add(attr);
updateAttributeMap.put(fieldname, attr);
}
}
if (attr.type() == boolean.class || attr.type() == Boolean.class) {
mappings.put(attr.field(), Utility.ofMap("type", "boolean", "index", false));
} else if (attr.type() == float.class || attr.type() == Float.class) {
mappings.put(attr.field(), Utility.ofMap("type", "double", "index", false));
} else if (attr.type() == double.class || attr.type() == Double.class) {
mappings.put(attr.field(), Utility.ofMap("type", "double", "index", false));
} else if (attr.type().isPrimitive() || Number.class.isAssignableFrom(attr.type())) {
mappings.put(
attr.field(),
Utility.ofMap("type", sc != null && sc.date() ? "date" : "long", "index", false));
} else if (CharSequence.class.isAssignableFrom(attr.type())) {
Map m = new LinkedHashMap<>();
if (sc != null && !sc.options().isEmpty()) {
if ("false".equalsIgnoreCase(sc.options())) {
m.put("index", false);
} else {
m.put("index_options", sc.options());
}
}
if (sc != null && (sc.html() || !sc.analyzer().isEmpty())) {
String analyzer = (sc.html() ? "html_" : "") + sc.analyzer();
if ("html_".equals(analyzer)) {
analyzer = "html_standard";
}
if (analyzer.startsWith("html_")) {
customAnalyzerMap.put(
analyzer,
Utility.ofMap(
"type",
"custom",
"tokenizer",
analyzer.replace("html_", ""),
"char_filter",
new String[] {"html_strip"}));
}
m.put("analyzer", analyzer);
}
if (sc != null && (sc.html() || !sc.searchAnalyzer().isEmpty())) {
String searchAnalyzer = (sc.html() ? "html_" : "") + sc.searchAnalyzer();
if ("html_".equals(searchAnalyzer)) {
searchAnalyzer = "html_standard";
}
if (searchAnalyzer.startsWith("html_")) {
customAnalyzerMap.put(
searchAnalyzer,
Utility.ofMap(
"type",
"custom",
"tokenizer",
searchAnalyzer.replace("html_", ""),
"char_filter",
new String[] {"html_strip"}));
}
m.put("search_analyzer", searchAnalyzer);
}
if (text) {
m.put("type", sc != null && sc.date() ? "date" : (sc != null && sc.ip() ? "ip" : "text"));
} else {
m.put("type", "keyword");
m.put("ignore_above", strlen);
}
mappings.put(attr.field(), m);
} else {
if (sc != null && "false".equalsIgnoreCase(sc.options())) {
mappings.put(attr.field(), Utility.ofMap("type", "object", "index", false));
} else {
mappings.put(attr.field(), Utility.ofMap("type", "object"));
}
}
queryattrs.add(attr);
fields.add(fieldname);
attributeMap.put(fieldname, attr);
}
} while ((cltmp = cltmp.getSuperclass()) != Object.class);
if (idAttr0 == null) {
throw new SourceException(type.getName() + " have no primary column by @org.redkale.persistence.Id");
}
this.jsonConvert = factory.getConvert();
this.primary = idAttr0;
this.aliasmap = aliasmap0;
this.highlightAttributeId = highlightAttrId;
this.highlightAttributeIndex = highlightAttrIndex;
this.attributes = attributeMap.values().toArray(new Attribute[attributeMap.size()]);
this.queryAttributes = queryattrs.toArray(new Attribute[queryattrs.size()]);
this.insertAttributes = insertattrs.toArray(new Attribute[insertattrs.size()]);
this.updateAttributes = updateattrs.toArray(new Attribute[updateattrs.size()]);
this.mappingTypes = mappings;
if (this.constructorParameters == null) {
this.constructorAttributes = null;
this.unconstructorAttributes = null;
} else {
this.constructorAttributes = new Attribute[this.constructorParameters.length];
List> unconstructorAttrs = new ArrayList<>();
for (Attribute attr : queryAttributes) {
int pos = -1;
for (int i = 0; i < this.constructorParameters.length; i++) {
if (attr.field().equals(this.constructorParameters[i])) {
pos = i;
break;
}
}
if (pos >= 0) {
this.constructorAttributes[pos] = attr;
} else {
unconstructorAttrs.add(attr);
}
}
this.unconstructorAttributes = unconstructorAttrs.toArray(new Attribute[unconstructorAttrs.size()]);
}
}
public Class getType() {
return type;
}
public DistributeTableStrategy getTableStrategy() {
return tableStrategy;
}
public String getOriginTable() {
return table;
}
/**
* 根据主键值获取Entity的表名
*
* @param primary Entity主键值
* @return String
*/
public String getTable(Serializable primary) {
if (tableStrategy == null) {
return table;
}
String t = tableStrategy.getTable(table, primary);
if (t == null || t.isEmpty()) {
throw new SourceException(table + " tableStrategy.getTable is empty, primary=" + primary);
}
return t;
}
/**
* 根据过滤条件获取Entity的表名,多表用逗号隔开
*
* @param node 过滤条件
* @return String
*/
public String getTable(FilterNode node) {
if (tableStrategy == null) {
return table;
}
String[] ts = tableStrategy.getTables(table, node);
if (Utility.isEmpty(ts)) {
throw new SourceException(table + " tableStrategy.getTable is empty, filter=" + node);
}
if (ts.length == 1) {
return ts[0];
}
return Utility.joining(ts, ',');
}
/**
* 根据Entity对象获取Entity的表名
*
* @param bean Entity对象
* @return String
*/
public String getTable(T bean) {
if (tableStrategy == null) {
return table;
}
String t = tableStrategy.getTable(table, bean);
if (t == null || t.isEmpty()) {
throw new SourceException(table + " tableStrategy.getTable is empty, entity=" + bean);
}
return t;
}
/**
* 获取Entity数组构建器
*
* @return Creator
*/
public IntFunction getArrayer() {
return arrayer;
}
/**
* 获取主键字段的Attribute
*
* @return Attribute
*/
public Attribute getPrimary() {
return this.primary;
}
/**
* 根据Entity字段名获取字段的Attribute
*
* @param fieldname Class字段名
* @return Attribute
*/
public Attribute getAttribute(String fieldname) {
if (fieldname == null) {
return null;
}
return this.attributeMap.get(fieldname);
}
/**
* 根据Entity字段名获取可更新字段的Attribute
*
* @param fieldname Class字段名
* @return Attribute
*/
public Attribute getUpdateAttribute(String fieldname) {
return this.updateAttributeMap.get(fieldname);
}
public Type getFindResultType() {
return findResultType;
}
public Type getSearchResultType() {
return searchResultType;
}
public Attribute[] getAttributes() {
return attributes;
}
public Attribute[] getUpdateAttributes() {
return updateAttributes;
}
public Map getMappingTypes() {
return mappingTypes;
}
public Map getCustomAnalyzerMap() {
return customAnalyzerMap;
}
public Map createIndexMap() {
Map map = Utility.ofMap("mappings", Utility.ofMap("properties", getMappingTypes()));
if (!customAnalyzerMap.isEmpty()) {
map.put("settings", Utility.ofMap("analysis", Utility.ofMap("analyzer", customAnalyzerMap)));
}
return map;
}
public JsonConvert getConvert() {
return jsonConvert;
}
/**
* 获取主键字段的表字段名
*
* @return String
*/
public String getPrimarySQLColumn() {
return getSQLColumn(this.primary.field());
}
/**
* 根据field字段名获取数据库对应的字段名
*
* @param fieldname 字段名
* @return String
*/
public String getSQLColumn(String fieldname) {
return this.aliasmap == null ? fieldname : aliasmap.getOrDefault(fieldname, fieldname);
}
public Attribute getHighlightAttribute(String fieldname) {
return this.highlightAttributeMap.get(fieldname);
}
public Attribute getHighlightAttributeId() {
return highlightAttributeId;
}
public Attribute getHighlightAttributeIndex() {
return highlightAttributeIndex;
}
public boolean isVirtual() {
return virtual;
}
}