Skip to content

Commit b6af7fd

Browse files
authored
Merge pull request #186 from xenit-eu/ACC-2213-fts
[ACC-2213] FTS
2 parents e5750cf + a31c0a1 commit b6af7fd

File tree

18 files changed

+437
-116
lines changed

18 files changed

+437
-116
lines changed

contentgrid-appserver-app/src/main/java/com/contentgrid/appserver/example/ContentgridAppConfiguration.java

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
import com.contentgrid.appserver.application.model.relations.flags.HiddenEndpointFlag;
1616
import com.contentgrid.appserver.application.model.searchfilters.AttributeSearchFilter;
1717
import com.contentgrid.appserver.application.model.searchfilters.AttributeSearchFilter.Operation;
18+
import com.contentgrid.appserver.application.model.searchfilters.FullTextSearchAttributeSearchFilter;
1819
import com.contentgrid.appserver.application.model.searchfilters.flags.HiddenSearchFilterFlag;
1920
import com.contentgrid.appserver.application.model.sortable.SortableField;
2021
import com.contentgrid.appserver.application.model.values.ApplicationName;
@@ -33,9 +34,13 @@
3334
import org.springframework.context.annotation.Bean;
3435
import org.springframework.context.annotation.Configuration;
3536

37+
import java.util.Locale;
38+
3639
@Configuration(proxyBeanMethods = false)
3740
public class ContentgridAppConfiguration {
3841

42+
private static final String COMMENT = "comment";
43+
3944
@Bean
4045
ApplicationResolver applicationResolver() {
4146
var person = Entity.builder()
@@ -64,6 +69,12 @@ ApplicationResolver applicationResolver() {
6469
.type(Type.TEXT)
6570
.build()
6671
)
72+
.attribute(SimpleAttribute.builder()
73+
.name(AttributeName.of(COMMENT))
74+
.description(COMMENT)
75+
.column(ColumnName.of(COMMENT))
76+
.type(Type.TEXT)
77+
.build())
6778
.attribute(SimpleAttribute.builder()
6879
.name(AttributeName.of("birth_date"))
6980
.description("Birth date")
@@ -81,6 +92,16 @@ ApplicationResolver applicationResolver() {
8192
.name(FilterName.of("last_name"))
8293
.attributePath(PropertyPath.of(AttributeName.of("last_name")))
8394
.build())
95+
.searchFilter(AttributeSearchFilter.builder()
96+
.operation(Operation.PREFIX)
97+
.name(FilterName.of("%s~prefix".formatted(COMMENT)))
98+
.attributePath(PropertyPath.of(AttributeName.of(COMMENT)))
99+
.build())
100+
.searchFilter(FullTextSearchAttributeSearchFilter.builder()
101+
.name(FilterName.of("%s~fts".formatted(COMMENT)))
102+
.attributePath(PropertyPath.of(AttributeName.of(COMMENT)))
103+
.locale(Locale.ENGLISH)
104+
.build())
84105
.searchFilter(AttributeSearchFilter.builder()
85106
.operation(Operation.EXACT)
86107
.name(FilterName.of("__internal_person__friends"))

contentgrid-appserver-application-model/src/main/java/com/contentgrid/appserver/application/model/searchfilters/AttributeSearchFilter.java

Lines changed: 9 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -4,55 +4,27 @@
44
import com.contentgrid.appserver.application.model.attributes.SimpleAttribute.Type;
55
import com.contentgrid.appserver.application.model.exceptions.InvalidSearchFilterException;
66
import com.contentgrid.appserver.application.model.i18n.ConfigurableTranslatable;
7-
import com.contentgrid.appserver.application.model.i18n.Translatable;
87
import com.contentgrid.appserver.application.model.i18n.TranslatableImpl;
98
import com.contentgrid.appserver.application.model.i18n.TranslationBuilderSupport;
109
import com.contentgrid.appserver.application.model.searchfilters.flags.SearchFilterFlag;
1110
import com.contentgrid.appserver.application.model.values.FilterName;
1211
import com.contentgrid.appserver.application.model.values.PropertyPath;
13-
import java.util.Locale;
14-
import java.util.Set;
15-
import lombok.AccessLevel;
1612
import lombok.Builder;
1713
import lombok.Getter;
1814
import lombok.NonNull;
1915
import lombok.Singular;
20-
import lombok.experimental.Delegate;
16+
17+
import java.util.Set;
2118

2219
/**
2320
* AttributeSearchFilter is a class representing search filters that operate on entity attributes.
2421
*/
2522
@Getter
26-
public class AttributeSearchFilter implements SearchFilter {
23+
public class AttributeSearchFilter extends BaseAttributeSearchFilter {
2724

2825
@NonNull
2926
private final Operation operation;
3027

31-
/**
32-
* The name of the search filter.
33-
*/
34-
@NonNull
35-
private final FilterName name;
36-
37-
/**
38-
* The path to the attribute this search filter operates on.
39-
* For simple attributes, this will be a single-element list.
40-
* For composite attributes, this will be a multi-element list representing the path.
41-
*/
42-
@NonNull
43-
private final PropertyPath attributePath;
44-
45-
@NonNull
46-
@Delegate
47-
@Getter(value = AccessLevel.NONE)
48-
private final Translatable<SearchFilterTranslations> translations;
49-
50-
/**
51-
* Flags on the search filter
52-
*/
53-
@NonNull
54-
private final Set<SearchFilterFlag> flags;
55-
5628
/**
5729
* Constructs an AttributeSearchFilter with the specified parameters.
5830
*
@@ -68,18 +40,14 @@ public class AttributeSearchFilter implements SearchFilter {
6840
@NonNull ConfigurableTranslatable<SearchFilterTranslations, ConfigurableSearchFilterTranslations> translations,
6941
@NonNull PropertyPath attributePath,
7042
@NonNull @Singular Set<SearchFilterFlag> flags) {
43+
super(name, translations, attributePath, flags);
44+
7145
this.operation = operation;
72-
this.name = name;
73-
this.translations = translations.withTranslationsBy(Locale.ROOT, t -> {
74-
if(t.getName() == null) {
75-
t = t.withName(name.getValue());
76-
}
77-
return t;
78-
});
79-
this.attributePath = attributePath;
80-
this.flags = Set.copyOf(flags);
46+
}
8147

82-
flags.forEach(flag -> flag.checkSupported(this));
48+
public static AttributeSearchFilterBuilder builder() {
49+
return new AttributeSearchFilterBuilder()
50+
.translations(new TranslatableImpl<>(ConfigurableSearchFilterTranslations::new));
8351
}
8452

8553
/**
@@ -112,11 +80,6 @@ public boolean supports(SimpleAttribute attribute) {
11280
}
11381
}
11482

115-
public static AttributeSearchFilterBuilder builder() {
116-
return new AttributeSearchFilterBuilder()
117-
.translations(new TranslatableImpl<>(ConfigurableSearchFilterTranslations::new));
118-
}
119-
12083
public static class AttributeSearchFilterBuilder extends TranslationBuilderSupport<SearchFilterTranslations, ConfigurableSearchFilterTranslations, AttributeSearchFilterBuilder> {
12184
{
12285
getTranslations = () -> translations;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
package com.contentgrid.appserver.application.model.searchfilters;
2+
3+
import com.contentgrid.appserver.application.model.i18n.ConfigurableTranslatable;
4+
import com.contentgrid.appserver.application.model.i18n.Translatable;
5+
import com.contentgrid.appserver.application.model.searchfilters.flags.SearchFilterFlag;
6+
import com.contentgrid.appserver.application.model.values.FilterName;
7+
import com.contentgrid.appserver.application.model.values.PropertyPath;
8+
import lombok.AccessLevel;
9+
import lombok.Getter;
10+
import lombok.NonNull;
11+
import lombok.Singular;
12+
import lombok.experimental.Delegate;
13+
14+
import java.util.Locale;
15+
import java.util.Set;
16+
17+
/**
18+
* Base class for the attribute-based search filters (e.g. {@link FullTextSearchAttributeSearchFilter} and {@link AttributeSearchFilter}).
19+
* This base class was originally introduced to share common logic between different attribute search filters,
20+
* while making sure that each filter type can have its own builder and specific properties.
21+
*/
22+
@Getter
23+
public abstract class BaseAttributeSearchFilter implements SearchFilter {
24+
25+
/**
26+
* The name of the search filter.
27+
*/
28+
@NonNull
29+
private final FilterName name;
30+
31+
/**
32+
* The path to the attribute this search filter operates on.
33+
* For simple attributes, this will be a single-element list.
34+
* For composite attributes, this will be a multi-element list representing the path.
35+
*/
36+
@NonNull
37+
private final PropertyPath attributePath;
38+
39+
@NonNull
40+
@Delegate
41+
@Getter(value = AccessLevel.NONE)
42+
private final Translatable<SearchFilterTranslations> translations;
43+
44+
/**
45+
* Flags on the search filter
46+
*/
47+
@NonNull
48+
private final Set<SearchFilterFlag> flags;
49+
50+
protected BaseAttributeSearchFilter(
51+
@NonNull FilterName name,
52+
@NonNull ConfigurableTranslatable<SearchFilterTranslations, ConfigurableSearchFilterTranslations> translations,
53+
@NonNull PropertyPath attributePath,
54+
@NonNull @Singular Set<SearchFilterFlag> flags) {
55+
this.name = name;
56+
this.translations = translations.withTranslationsBy(Locale.ROOT, t -> {
57+
if(t.getName() == null) {
58+
t = t.withName(name.getValue());
59+
}
60+
return t;
61+
});
62+
this.attributePath = attributePath;
63+
this.flags = Set.copyOf(flags);
64+
65+
flags.forEach(flag -> flag.checkSupported(this));
66+
}
67+
68+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
package com.contentgrid.appserver.application.model.searchfilters;
2+
3+
import com.contentgrid.appserver.application.model.attributes.SimpleAttribute;
4+
import com.contentgrid.appserver.application.model.i18n.ConfigurableTranslatable;
5+
import com.contentgrid.appserver.application.model.i18n.TranslatableImpl;
6+
import com.contentgrid.appserver.application.model.i18n.TranslationBuilderSupport;
7+
import com.contentgrid.appserver.application.model.searchfilters.flags.SearchFilterFlag;
8+
import com.contentgrid.appserver.application.model.values.FilterName;
9+
import com.contentgrid.appserver.application.model.values.PropertyPath;
10+
import lombok.Builder;
11+
import lombok.Getter;
12+
import lombok.NonNull;
13+
import lombok.Singular;
14+
15+
import java.util.Locale;
16+
import java.util.Set;
17+
18+
19+
/**
20+
* FullTextSearchAttributeSearchFilter is a search filter that performs full-text search operations on a specified attribute.
21+
* <br>
22+
* The main difference between this and a regular {@link AttributeSearchFilter} is that this filter specifies a {@link Locale}.
23+
*/
24+
@Getter
25+
public class FullTextSearchAttributeSearchFilter extends BaseAttributeSearchFilter implements LocaleAwareSearchFilter {
26+
27+
28+
Locale locale;
29+
30+
@Builder
31+
FullTextSearchAttributeSearchFilter(@NonNull FilterName name,
32+
@NonNull ConfigurableTranslatable<SearchFilterTranslations, ConfigurableSearchFilterTranslations> translations,
33+
@NonNull PropertyPath attributePath,
34+
@NonNull @Singular Set<SearchFilterFlag> flags,
35+
@NonNull Locale locale) {
36+
super(name, translations, attributePath, flags);
37+
38+
this.locale = locale;
39+
}
40+
41+
public static FullTextSearchAttributeSearchFilterBuilder builder() {
42+
return new FullTextSearchAttributeSearchFilterBuilder()
43+
.translations(new TranslatableImpl<>(ConfigurableSearchFilterTranslations::new));
44+
}
45+
46+
public static class FullTextSearchAttributeSearchFilterBuilder extends TranslationBuilderSupport<SearchFilterTranslations, ConfigurableSearchFilterTranslations, FullTextSearchAttributeSearchFilter.FullTextSearchAttributeSearchFilterBuilder> {
47+
{
48+
getTranslations = () -> translations;
49+
}
50+
51+
public FullTextSearchAttributeSearchFilterBuilder attribute(@NonNull SimpleAttribute attribute) {
52+
this.attributePath = PropertyPath.of(attribute.getName());
53+
return this;
54+
}
55+
}
56+
57+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
2+
package com.contentgrid.appserver.application.model.searchfilters;
3+
4+
import java.util.Locale;
5+
6+
public interface LocaleAwareSearchFilter {
7+
8+
Locale getLocale();
9+
10+
}

contentgrid-appserver-domain/src/main/java/com/contentgrid/appserver/domain/ThunkExpressionGenerator.java

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
import com.contentgrid.appserver.application.model.relations.ManyToManyRelation;
77
import com.contentgrid.appserver.application.model.relations.OneToManyRelation;
88
import com.contentgrid.appserver.application.model.searchfilters.AttributeSearchFilter;
9+
import com.contentgrid.appserver.application.model.searchfilters.BaseAttributeSearchFilter;
10+
import com.contentgrid.appserver.application.model.searchfilters.FullTextSearchAttributeSearchFilter;
911
import com.contentgrid.appserver.application.model.searchfilters.SearchFilter;
1012
import com.contentgrid.appserver.application.model.values.AttributePath;
1113
import com.contentgrid.appserver.application.model.values.FilterName;
@@ -46,7 +48,7 @@ static ThunkExpression<Boolean> from(Application application, Entity entity, Map
4648
SearchFilter searchFilter = maybeSearchFilter.get();
4749

4850
// currently only handle attribute search filters
49-
if (searchFilter instanceof AttributeSearchFilter attributeSearchFilter) {
51+
if (searchFilter instanceof BaseAttributeSearchFilter attributeSearchFilter) {
5052
var attribute = application.resolvePropertyPath(entity, attributeSearchFilter.getAttributePath());
5153
List<PathElement> pathElements;
5254

@@ -113,9 +115,18 @@ private static Scalar<?> parseValueToScalar(SimpleAttribute.Type type, String va
113115
};
114116
}
115117

116-
private static ThunkExpression<Boolean> createExpression(AttributeSearchFilter filter, List<PathElement> pathElements, Scalar<?> value) {
118+
private static ThunkExpression<Boolean> createExpression(BaseAttributeSearchFilter filter,
119+
List<PathElement> pathElements,
120+
Scalar<?> value) throws IllegalArgumentException {
117121
SymbolicReference attr = SymbolicReference.of(Variable.named("entity"), pathElements);
118122

123+
if (filter instanceof FullTextSearchAttributeSearchFilter ftsSearchFilter) return createExpression(ftsSearchFilter, attr, value);
124+
if (filter instanceof AttributeSearchFilter attrSearchFilter) return createExpression(attrSearchFilter, attr, value);
125+
126+
throw new IllegalArgumentException("Received unknown filter type (%s).".formatted(filter.getClass().getName()));
127+
}
128+
129+
private static ThunkExpression<Boolean> createExpression(AttributeSearchFilter filter, SymbolicReference attr, Scalar<?> value) {
119130
return switch (filter.getOperation()) {
120131
case EXACT -> Comparison.areEqual(attr, value);
121132
case PREFIX -> StringComparison.contentGridPrefixSearchMatch(attr, value.assertResultType(String.class));
@@ -126,6 +137,12 @@ private static ThunkExpression<Boolean> createExpression(AttributeSearchFilter f
126137
};
127138
}
128139

140+
private static ThunkExpression<Boolean> createExpression(FullTextSearchAttributeSearchFilter filter,
141+
SymbolicReference attr,
142+
Scalar<?> value) {
143+
return StringComparison.contentGridFullTextSearchMatch(attr, value.assertResultType(String.class), filter.getLocale());
144+
}
145+
129146
private static List<PathElement> convertPath(Application application, Entity entity, PropertyPath path) {
130147
List<PathElement> pathElements = new ArrayList<>();
131148
Entity currentEntity = entity;

0 commit comments

Comments
 (0)