
com.redis.om.spring.RedisModulesConfiguration Maven / Gradle / Ivy
package com.redis.om.spring;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.time.Instant;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.data.redis.RedisProperties;
import org.springframework.boot.autoconfigure.gson.GsonBuilderCustomizer;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.context.annotation.Primary;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.context.event.EventListener;
import org.springframework.core.type.filter.AnnotationTypeFilter;
import org.springframework.data.geo.Point;
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
import org.springframework.data.redis.core.RedisHash;
import org.springframework.data.redis.core.RedisOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.TimeToLive;
import org.springframework.data.redis.core.convert.KeyspaceConfiguration.KeyspaceSettings;
import org.springframework.data.redis.core.mapping.RedisMappingContext;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import org.springframework.data.util.ClassTypeInformation;
import org.springframework.util.ClassUtils;
import com.github.f4b6a3.ulid.Ulid;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.annotations.JsonAdapter;
import com.redis.om.spring.annotations.Bloom;
import com.redis.om.spring.annotations.Document;
import com.redis.om.spring.annotations.DocumentScore;
import com.redis.om.spring.annotations.EnableRedisDocumentRepositories;
import com.redis.om.spring.annotations.EnableRedisEnhancedRepositories;
import com.redis.om.spring.annotations.GeoIndexed;
import com.redis.om.spring.annotations.Indexed;
import com.redis.om.spring.annotations.NumericIndexed;
import com.redis.om.spring.annotations.Searchable;
import com.redis.om.spring.annotations.TagIndexed;
import com.redis.om.spring.annotations.TextIndexed;
import com.redis.om.spring.client.RedisModulesClient;
import com.redis.om.spring.ops.RedisModulesOperations;
import com.redis.om.spring.ops.json.JSONOperations;
import com.redis.om.spring.ops.pds.BloomOperations;
import com.redis.om.spring.ops.search.SearchOperations;
import com.redis.om.spring.repository.query.QueryUtils;
import com.redis.om.spring.search.stream.EntityStream;
import com.redis.om.spring.search.stream.EntityStreamImpl;
import com.redis.om.spring.serialization.gson.DateTypeAdapter;
import com.redis.om.spring.serialization.gson.InstantTypeAdapter;
import com.redis.om.spring.serialization.gson.LocalDateTimeTypeAdapter;
import com.redis.om.spring.serialization.gson.LocalDateTypeAdapter;
import com.redis.om.spring.serialization.gson.PointTypeAdapter;
import com.redis.om.spring.serialization.gson.UlidTypeAdapter;
import io.redisearch.FieldName;
import io.redisearch.Schema;
import io.redisearch.Schema.Field;
import io.redisearch.Schema.FieldType;
import io.redisearch.Schema.TagField;
import io.redisearch.Schema.TextField;
import io.redisearch.client.Client;
import io.redisearch.client.Client.IndexOptions;
import io.redisearch.client.IndexDefinition;
@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties(RedisProperties.class)
@EnableAspectJAutoProxy
@ComponentScan("com.redis.om.spring.bloom")
@ComponentScan("com.redis.om.spring.autocomplete")
@ComponentScan("com.redis.om.spring.metamodel")
public class RedisModulesConfiguration extends CachingConfigurerSupport {
private static final Log logger = LogFactory.getLog(RedisModulesConfiguration.class);
@Bean
public GsonBuilder gsonBuilder(List customizers) {
GsonBuilder builder = new GsonBuilder();
// Enable the spring.gson.* configuration in the configuration file
customizers.forEach((c) -> c.customize(builder));
builder.registerTypeAdapter(Point.class, PointTypeAdapter.getInstance());
builder.registerTypeAdapter(Date.class, DateTypeAdapter.getInstance());
builder.registerTypeAdapter(LocalDate.class, LocalDateTypeAdapter.getInstance());
builder.registerTypeAdapter(LocalDateTime.class, LocalDateTimeTypeAdapter.getInstance());
builder.registerTypeAdapter(Ulid.class, UlidTypeAdapter.getInstance());
builder.registerTypeAdapter(Instant.class, InstantTypeAdapter.getInstance());
return builder;
}
@Bean(name = "redisModulesClient")
RedisModulesClient redisModulesClient(JedisConnectionFactory jedisConnectionFactory, GsonBuilder builder) {
return new RedisModulesClient(jedisConnectionFactory, builder);
}
@Bean(name = "redisModulesOperations")
@Primary
@ConditionalOnMissingBean
RedisModulesOperations> redisModulesOperations(RedisModulesClient rmc, RedisTemplate, ?> template) {
return new RedisModulesOperations<>(rmc, template);
}
@Bean(name = "redisJSONOperations")
JSONOperations> redisJSONOperations(RedisModulesOperations> redisModulesOperations) {
return redisModulesOperations.opsForJSON();
}
@Bean(name = "redisBloomOperations")
BloomOperations> redisBloomOperations(RedisModulesOperations> redisModulesOperations) {
return redisModulesOperations.opsForBloom();
}
@Bean(name = "redisTemplate")
@Primary
public RedisTemplate, ?> redisTemplate(JedisConnectionFactory connectionFactory) {
RedisTemplate, ?> template = new RedisTemplate<>();
template.setKeySerializer(new StringRedisSerializer());
template.setDefaultSerializer(new StringRedisSerializer());
template.setConnectionFactory(connectionFactory);
return template;
}
@Bean(name = "keyspaceToIndexMap")
public KeyspaceToIndexMap keyspaceToIndexMap() {
return new KeyspaceToIndexMap();
}
@Bean(name = "redisJSONKeyValueAdapter")
RedisJSONKeyValueAdapter getRedisJSONKeyValueAdapter(RedisOperations, ?> redisOps,
JSONOperations> redisJSONOperations, RedisMappingContext mappingContext) {
return new RedisJSONKeyValueAdapter(redisOps, redisJSONOperations, mappingContext);
}
@Bean(name = "redisJSONKeyValueTemplate")
public CustomRedisKeyValueTemplate getRedisJSONKeyValueTemplate(RedisOperations, ?> redisOps,
JSONOperations> redisJSONOperations, RedisMappingContext mappingContext) {
return new CustomRedisKeyValueTemplate(getRedisJSONKeyValueAdapter(redisOps, redisJSONOperations, mappingContext),
mappingContext);
}
@Bean(name = "redisCustomKeyValueTemplate")
public CustomRedisKeyValueTemplate getKeyValueTemplate(RedisOperations, ?> redisOps,
RedisModulesOperations> redisModulesOperations, RedisMappingContext mappingContext,
KeyspaceToIndexMap keyspaceToIndexMap) {
return new CustomRedisKeyValueTemplate(
new RedisEnhancedKeyValueAdapter(redisOps, redisModulesOperations, mappingContext, keyspaceToIndexMap),
mappingContext);
}
@Bean(name = "streamingQueryBuilder")
EntityStream streamingQueryBuilder(RedisModulesOperations> redisModulesOperations, Gson gson) {
return new EntityStreamImpl(redisModulesOperations, gson);
}
@EventListener(ContextRefreshedEvent.class)
public void ensureIndexesAreCreated(ContextRefreshedEvent cre) {
logger.info("Creating Indexes......");
ApplicationContext ac = cre.getApplicationContext();
createIndicesFor(Document.class, ac);
createIndicesFor(RedisHash.class, ac);
}
@EventListener(ContextRefreshedEvent.class)
public void processBloom(ContextRefreshedEvent cre) {
ApplicationContext ac = cre.getApplicationContext();
@SuppressWarnings("unchecked")
RedisModulesOperations rmo = (RedisModulesOperations) ac.getBean("redisModulesOperations");
Set beanDefs = getBeanDefinitionsFor(ac, Document.class, RedisHash.class);
for (BeanDefinition beanDef : beanDefs) {
try {
Class> cl = Class.forName(beanDef.getBeanClassName());
for (java.lang.reflect.Field field : cl.getDeclaredFields()) {
// Text
if (field.isAnnotationPresent(Bloom.class)) {
Bloom bloom = field.getAnnotation(Bloom.class);
BloomOperations ops = rmo.opsForBloom();
String filterName = !ObjectUtils.isEmpty(bloom.name()) ? bloom.name()
: String.format("bf:%s:%s", cl.getSimpleName(), field.getName());
ops.createFilter(filterName, bloom.capacity(), bloom.errorRate());
}
}
} catch (Exception e) {
logger.debug("Error during processing of @Bloom annotation: ", e);
}
}
}
private void createIndicesFor(Class> cls, ApplicationContext ac) {
@SuppressWarnings("unchecked")
RedisModulesOperations rmo = (RedisModulesOperations) ac.getBean("redisModulesOperations");
RedisMappingContext mappingContext = (RedisMappingContext) ac.getBean("keyValueMappingContext");
KeyspaceToIndexMap keyspaceToIndexMap = (KeyspaceToIndexMap) ac.getBean("keyspaceToIndexMap");
Set beanDefs = new HashSet<>();
beanDefs.addAll(getBeanDefinitionsFor(ac, cls));
logger.info(String.format("Found %s @%s annotated Beans...", beanDefs.size(), cls.getSimpleName()));
for (BeanDefinition beanDef : beanDefs) {
String indexName = "";
String scoreField = null;
try {
Class> cl = Class.forName(beanDef.getBeanClassName());
indexName = cl.getName() + "Idx";
logger.info(String.format("Found @%s annotated class: %s", cls.getSimpleName(), cl.getName()));
List fields = new ArrayList<>();
for (java.lang.reflect.Field field : cl.getDeclaredFields()) {
fields.addAll(findIndexFields(field, null, cls == Document.class));
// @DocumentScore
if (field.isAnnotationPresent(DocumentScore.class)) {
String fieldPrefix = cls == Document.class ? "$." : "";
scoreField = fieldPrefix + field.getName();
}
}
String entityPrefix = cl.getName() + ":";
if (!fields.isEmpty()) {
Schema schema = new Schema();
SearchOperations opsForSearch = rmo.opsForSearch(indexName);
for (Field field : fields) {
schema.addField(field);
}
IndexDefinition index = new IndexDefinition(
cls == Document.class ? IndexDefinition.Type.JSON : IndexDefinition.Type.HASH);
if (cl.isAnnotationPresent(Document.class)) {
Document document = cl.getAnnotation(Document.class);
index.setAsync(document.async());
if (ObjectUtils.isNotEmpty(document.value())) {
entityPrefix = document.value();
}
if (ObjectUtils.isNotEmpty(document.filter())) {
index.setFilter(document.filter());
}
if (ObjectUtils.isNotEmpty(document.language())) {
index.setLanguage(document.language());
}
if (ObjectUtils.isNotEmpty(document.languageField())) {
index.setLanguageField(document.languageField());
}
index.setScore(document.score());
if (scoreField != null) {
index.setScoreFiled(scoreField);
}
} else if (cl.isAnnotationPresent(RedisHash.class)) {
RedisHash hash = cl.getAnnotation(RedisHash.class);
if (ObjectUtils.isNotEmpty(hash.value())) {
entityPrefix = hash.value();
}
}
index.setPrefixes(entityPrefix);
IndexOptions ops = Client.IndexOptions.defaultOptions().setDefinition(index);
opsForSearch.createIndex(schema, ops);
keyspaceToIndexMap.addKeySpaceMapping(entityPrefix, cl, true);
} else {
keyspaceToIndexMap.addKeySpaceMapping(entityPrefix, cl, false);
}
// TTL
if (cl.isAnnotationPresent(Document.class)) {
KeyspaceSettings setting = new KeyspaceSettings(cl, cl.getName() + ":");
// Default TTL
Document document = cl.getAnnotation(Document.class);
if (document.timeToLive() > 0) {
setting.setTimeToLive(document.timeToLive());
}
for (java.lang.reflect.Field field : cl.getDeclaredFields()) {
// @TimeToLive
if (field.isAnnotationPresent(TimeToLive.class)) {
setting.setTimeToLivePropertyName(field.getName());
}
}
mappingContext.getMappingConfiguration().getKeyspaceConfiguration().addKeyspaceSettings(setting);
}
} catch (Exception e) {
logger.warn(String.format("Skipping index creation for %s because %s", indexName, e.getMessage()));
}
}
}
private List findIndexFields(java.lang.reflect.Field field, String prefix, boolean isDocument) {
List fields = new ArrayList<>();
if (field.isAnnotationPresent(Indexed.class)) {
logger.info(String.format("FOUND @Indexed annotation on field of type: %s", field.getType()));
Indexed indexed = field.getAnnotation(Indexed.class);
Class> fieldType = ClassUtils.resolvePrimitiveIfNecessary(field.getType());
//
// Any Character class or Boolean -> Tag Search Field
//
if (CharSequence.class.isAssignableFrom(fieldType) || (fieldType == Boolean.class)) {
fields.add(indexAsTagFieldFor(field, isDocument, prefix, indexed.sortable(), indexed.separator(),
indexed.arrayIndex()));
}
//
// Any Numeric class -> Numeric Search Field
//
else if (Number.class.isAssignableFrom(fieldType) || (fieldType == LocalDateTime.class)
|| (field.getType() == LocalDate.class) || (field.getType() == Date.class)) {
fields.add(indexAsNumericFieldFor(field, isDocument, prefix, indexed.sortable(), indexed.noindex()));
}
//
// Set / List
//
else if (Set.class.isAssignableFrom(fieldType) || List.class.isAssignableFrom(fieldType)) {
Optional> maybeCollectionType = com.redis.om.spring.util.ObjectUtils.getCollectionElementType(field);
// It is only possible to index an array of strings or booleans in a TAG
// identifier.
// https://redis.io/docs/stack/json/indexing_json/#json-arrays-can-only-be-indexed-in-tag-identifiers
if (maybeCollectionType.isPresent()) {
Class> collectionType = maybeCollectionType.get();
if (CharSequence.class.isAssignableFrom(collectionType) || (collectionType == Boolean.class)) {
fields.add(indexAsTagFieldFor(field, isDocument, prefix, indexed.sortable(), indexed.separator(),
indexed.arrayIndex()));
// Index nested fields
} else if (isDocument) {
// Index nested JSON Array field. But the current implementation of RediSearch
// supports array only for TAG fields, not TEXT fields.
// https://github.com/RediSearch/RediSearch/issues/2293
logger.debug(String.format("FOUND nested field on field of type: %s", field.getType()));
fields.addAll(indexAsNestedFieldFor(field, prefix));
}
} else {
logger.debug(String.format("Could not determine the type of elements in the collection %s in entity %s",
field.getName(), field.getDeclaringClass().getSimpleName()));
}
}
//
// Point
//
else if (fieldType == Point.class) {
fields.add(indexAsGeoFieldFor(field, isDocument, prefix, indexed.sortable(), indexed.noindex()));
}
//
// Recursively explore the fields for Index annotated fields
//
else {
for (java.lang.reflect.Field subfield : field.getType().getDeclaredFields()) {
fields.addAll(findIndexFields(subfield, field.getName(), isDocument));
}
}
}
// Searchable - behaves like Text indexed
else if (field.isAnnotationPresent(Searchable.class)) {
logger.info(String.format("FOUND @Searchable annotation on field of type: %s", field.getType()));
Searchable searchable = field.getAnnotation(Searchable.class);
fields.add(indexAsTextFieldFor(field, isDocument, prefix, searchable));
}
// Text
else if (field.isAnnotationPresent(TextIndexed.class)) {
TextIndexed ti = field.getAnnotation(TextIndexed.class);
fields.add(indexAsTextFieldFor(field, isDocument, prefix, ti));
}
// Tag
else if (field.isAnnotationPresent(TagIndexed.class)) {
TagIndexed ti = field.getAnnotation(TagIndexed.class);
fields.add(indexAsTagFieldFor(field, isDocument, prefix, ti));
}
// Geo
else if (field.isAnnotationPresent(GeoIndexed.class)) {
GeoIndexed gi = field.getAnnotation(GeoIndexed.class);
fields.add(indexAsGeoFieldFor(field, isDocument, prefix, gi));
}
// Numeric
else if (field.isAnnotationPresent(NumericIndexed.class)) {
NumericIndexed ni = field.getAnnotation(NumericIndexed.class);
fields.add(indexAsNumericFieldFor(field, isDocument, prefix, ni));
}
return fields;
}
@SuppressWarnings({ "unchecked", "rawtypes" })
private Set getBeanDefinitionsFor(ApplicationContext ac, Class... classes) {
Map annotatedBeans = ac.getBeansWithAnnotation(SpringBootApplication.class);
Class> app = annotatedBeans.values().toArray()[0].getClass();
Set beanDefs = new HashSet<>();
ClassPathScanningCandidateComponentProvider provider = new ClassPathScanningCandidateComponentProvider(false);
for (Class cls : classes) {
provider.addIncludeFilter(new AnnotationTypeFilter(cls));
}
if (app.isAnnotationPresent(EnableRedisDocumentRepositories.class)) {
EnableRedisDocumentRepositories edr = app.getAnnotation(EnableRedisDocumentRepositories.class);
if (edr.basePackages().length > 0) {
for (String pkg : edr.basePackages()) {
beanDefs.addAll(provider.findCandidateComponents(pkg));
}
} else if (edr.basePackageClasses().length > 0) {
for (Class> pkg : edr.basePackageClasses()) {
beanDefs.addAll(provider.findCandidateComponents(pkg.getPackageName()));
}
} else {
beanDefs.addAll(provider.findCandidateComponents(app.getPackageName()));
}
}
if (app.isAnnotationPresent(EnableRedisEnhancedRepositories.class)) {
EnableRedisEnhancedRepositories er = app.getAnnotation(EnableRedisEnhancedRepositories.class);
if (er.basePackages().length > 0) {
for (String pkg : er.basePackages()) {
beanDefs.addAll(provider.findCandidateComponents(pkg));
}
} else if (er.basePackageClasses().length > 0) {
for (Class> pkg : er.basePackageClasses()) {
beanDefs.addAll(provider.findCandidateComponents(pkg.getPackageName()));
}
} else {
beanDefs.addAll(provider.findCandidateComponents(app.getPackageName()));
}
}
return beanDefs;
}
private Field indexAsTagFieldFor(java.lang.reflect.Field field, boolean isDocument, String prefix, TagIndexed ti) {
ClassTypeInformation> typeInfo = ClassTypeInformation.from(field.getType());
String chain = (prefix == null || prefix.isBlank()) ? "" : prefix + ".";
String fieldPrefix = isDocument ? "$." + chain : chain;
String fieldPostfix = (isDocument && typeInfo.isCollectionLike() && !field.isAnnotationPresent(JsonAdapter.class))
? "[*]"
: "";
FieldName fieldName = FieldName.of(fieldPrefix + field.getName() + fieldPostfix);
if (!ObjectUtils.isEmpty(ti.alias())) {
fieldName = fieldName.as(ti.alias());
} else {
fieldName = fieldName.as(QueryUtils.searchIndexFieldAliasFor(field, prefix));
}
return new TagField(fieldName, ti.separator(), false);
}
private Field indexAsTagFieldFor(java.lang.reflect.Field field, boolean isDocument, String prefix, boolean sortable,
String separator, int arrayIndex) {
ClassTypeInformation> typeInfo = ClassTypeInformation.from(field.getType());
String chain = (prefix == null || prefix.isBlank()) ? "" : prefix + ".";
String fieldPrefix = isDocument ? "$." + chain : chain;
String index = (arrayIndex != Integer.MIN_VALUE) ? ".[" + arrayIndex + "]" : "[*]";
String fieldPostfix = (isDocument && typeInfo.isCollectionLike() && !field.isAnnotationPresent(JsonAdapter.class))
? index
: "";
FieldName fieldName = FieldName.of(fieldPrefix + field.getName() + fieldPostfix);
fieldName = fieldName.as(QueryUtils.searchIndexFieldAliasFor(field, prefix));
return new TagField(fieldName, separator.isBlank() ? null : separator, sortable);
}
private Field indexAsTextFieldFor(java.lang.reflect.Field field, boolean isDocument, String prefix, TextIndexed ti) {
String chain = (prefix == null || prefix.isBlank()) ? "" : prefix + ".";
String fieldPrefix = isDocument ? "$." + chain : chain;
FieldName fieldName = FieldName.of(fieldPrefix + field.getName());
if (!ObjectUtils.isEmpty(ti.alias())) {
fieldName = fieldName.as(ti.alias());
} else {
fieldName = fieldName.as(QueryUtils.searchIndexFieldAliasFor(field, prefix));
}
String phonetic = ObjectUtils.isEmpty(ti.phonetic()) ? null : ti.phonetic();
return new TextField(fieldName, ti.weight(), ti.sortable(), ti.nostem(), ti.noindex(), phonetic);
}
private Field indexAsTextFieldFor(java.lang.reflect.Field field, boolean isDocument, String prefix, Searchable ti) {
String chain = (prefix == null || prefix.isBlank()) ? "" : prefix + ".";
String fieldPrefix = isDocument ? "$." + chain : chain;
FieldName fieldName = FieldName.of(fieldPrefix + field.getName());
if (!ObjectUtils.isEmpty(ti.alias())) {
fieldName = fieldName.as(ti.alias());
} else {
fieldName = fieldName.as(QueryUtils.searchIndexFieldAliasFor(field, prefix));
}
String phonetic = ObjectUtils.isEmpty(ti.phonetic()) ? null : ti.phonetic();
return new TextField(fieldName, ti.weight(), ti.sortable(), ti.nostem(), ti.noindex(), phonetic);
}
private Field indexAsGeoFieldFor(java.lang.reflect.Field field, boolean isDocument, String prefix, GeoIndexed gi) {
String chain = (prefix == null || prefix.isBlank()) ? "" : prefix + ".";
String fieldPrefix = isDocument ? "$." + chain : chain;
FieldName fieldName = FieldName.of(fieldPrefix + field.getName());
if (!ObjectUtils.isEmpty(gi.alias())) {
fieldName = fieldName.as(gi.alias());
} else {
fieldName = fieldName.as(QueryUtils.searchIndexFieldAliasFor(field, prefix));
}
return new Field(fieldName, FieldType.Geo);
}
private Field indexAsNumericFieldFor(java.lang.reflect.Field field, boolean isDocument, String prefix,
NumericIndexed ni) {
String chain = (prefix == null || prefix.isBlank()) ? "" : prefix + ".";
String fieldPrefix = isDocument ? "$." + chain : chain;
FieldName fieldName = FieldName.of(fieldPrefix + field.getName());
if (!ObjectUtils.isEmpty(ni.alias())) {
fieldName = fieldName.as(ni.alias());
} else {
fieldName = fieldName.as(QueryUtils.searchIndexFieldAliasFor(field, prefix));
}
return new Field(fieldName, FieldType.Numeric);
}
private Field indexAsNumericFieldFor(java.lang.reflect.Field field, boolean isDocument, String prefix,
boolean sortable, boolean noIndex) {
String chain = (prefix == null || prefix.isBlank()) ? "" : prefix + ".";
String fieldPrefix = isDocument ? "$." + chain : chain;
FieldName fieldName = FieldName.of(fieldPrefix + field.getName());
fieldName = fieldName.as(QueryUtils.searchIndexFieldAliasFor(field, prefix));
return new Field(fieldName, FieldType.Numeric, sortable, noIndex);
}
private Field indexAsGeoFieldFor(java.lang.reflect.Field field, boolean isDocument, String prefix, boolean sortable,
boolean noIndex) {
String chain = (prefix == null || prefix.isBlank()) ? "" : prefix + ".";
String fieldPrefix = isDocument ? "$." + chain : chain;
FieldName fieldName = FieldName.of(fieldPrefix + field.getName());
fieldName = fieldName.as(QueryUtils.searchIndexFieldAliasFor(field, prefix));
return new Field(fieldName, FieldType.Geo);
}
private List indexAsNestedFieldFor(java.lang.reflect.Field field, String prefix) {
String chain = (prefix == null || prefix.isBlank()) ? "" : prefix + ".";
String fieldPrefix = "$." + chain;
return getNestedField(fieldPrefix, field, prefix, null);
}
private List getNestedField(String fieldPrefix, java.lang.reflect.Field field, String prefix,
List fieldList) {
if (fieldList == null) {
fieldList = new ArrayList<>();
}
Type genericType = field.getGenericType();
if (genericType instanceof ParameterizedType) {
ParameterizedType pt = (ParameterizedType) genericType;
Class> actualTypeArgument = (Class>) pt.getActualTypeArguments()[0];
java.lang.reflect.Field[] subDeclaredFields = actualTypeArgument.getDeclaredFields();
String tempPrefix = "";
if (prefix == null) {
prefix = field.getName();
} else {
prefix += "." + field.getName();
}
for (java.lang.reflect.Field subField : subDeclaredFields) {
Optional> maybeCollectionType = com.redis.om.spring.util.ObjectUtils
.getCollectionElementType(subField);
if (subField.isAnnotationPresent(TagIndexed.class)) {
TagIndexed ti = subField.getAnnotation(TagIndexed.class);
tempPrefix = field.getName() + "[0:].";
FieldName fieldName = FieldName.of(fieldPrefix + tempPrefix + subField.getName());
fieldName = fieldName.as(QueryUtils.searchIndexFieldAliasFor(subField, prefix));
logger.info(String.format("Creating nested relationships: %s -> %s", field.getName(), subField.getName()));
fieldList.add(new TagField(fieldName, ti.separator(), false));
continue;
} else if (subField.isAnnotationPresent(Indexed.class)) {
boolean subFieldIsTagField = ((subField.isAnnotationPresent(Indexed.class)
&& ((CharSequence.class.isAssignableFrom(subField.getType()) || (subField.getType() == Boolean.class)
|| (maybeCollectionType.isPresent() && (CharSequence.class.isAssignableFrom(maybeCollectionType.get())
|| (maybeCollectionType.get() == Boolean.class)))))));
if (subFieldIsTagField) {
Indexed indexed = subField.getAnnotation(Indexed.class);
tempPrefix = field.getName() + "[0:].";
FieldName fieldName = FieldName.of(fieldPrefix + tempPrefix + subField.getName());
fieldName = fieldName.as(QueryUtils.searchIndexFieldAliasFor(subField, prefix));
logger.info(String.format("Creating nested relationships: %s -> %s", field.getName(), subField.getName()));
fieldList.add(new TagField(fieldName, indexed.separator(), false));
continue;
}
}
getNestedField(fieldPrefix + tempPrefix, subField, prefix, fieldList);
}
}
return fieldList;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy