1
1
package ubic .gemma .persistence .service ;
2
2
3
- import lombok .AccessLevel ;
4
- import lombok .AllArgsConstructor ;
5
- import lombok .Value ;
6
- import lombok .With ;
3
+ import lombok .*;
7
4
import org .apache .commons .lang3 .ArrayUtils ;
8
5
import org .hibernate .SessionFactory ;
9
6
import org .hibernate .metadata .ClassMetadata ;
18
15
import ubic .gemma .persistence .util .Sort ;
19
16
20
17
import javax .annotation .Nullable ;
18
+ import javax .annotation .OverridingMethodsMustInvokeSuper ;
21
19
import java .util .*;
22
20
import java .util .concurrent .ConcurrentHashMap ;
21
+ import java .util .regex .Pattern ;
22
+ import java .util .stream .Collectors ;
23
23
24
24
/**
25
25
* Base implementation for {@link FilteringVoEnabledDao}.
@@ -33,7 +33,7 @@ public abstract class AbstractFilteringVoEnabledDao<O extends Identifiable, VO e
33
33
/**
34
34
* Maximum depth to explore when enumerating filterable properties via {@link #getFilterableProperties()}.
35
35
*/
36
- protected static final int FILTERABLE_PROPERTIES_MAX_DEPTH = 3 ;
36
+ private static final int FILTERABLE_PROPERTIES_MAX_DEPTH = 3 ;
37
37
38
38
/**
39
39
* Cached partial filterable properties meta, computed as we go.
@@ -53,12 +53,18 @@ private static class Key {
53
53
*/
54
54
private final Set <String > filterableProperties ;
55
55
56
+ /**
57
+ * Aliases for filterable properties.
58
+ */
59
+ private final Set <FilterablePropertyAlias > filterablePropertyAliases ;
60
+
56
61
protected AbstractFilteringVoEnabledDao ( @ Nullable String objectAlias , Class <? extends O > elementClass , SessionFactory sessionFactory ) {
57
62
super ( elementClass , sessionFactory );
58
63
this .objectAlias = objectAlias ;
59
- Set <String > result = new HashSet <>();
60
- addFilterableProperties ( "" , elementClass , result , FILTERABLE_PROPERTIES_MAX_DEPTH );
61
- this .filterableProperties = Collections .unmodifiableSet ( result );
64
+ this .filterablePropertyAliases = new HashSet <>();
65
+ registerFilterablePropertyAliases ( this .filterablePropertyAliases );
66
+ this .filterableProperties = new HashSet <>();
67
+ registerFilterableProperties ( this .filterableProperties );
62
68
}
63
69
64
70
/**
@@ -122,10 +128,32 @@ public final List<VO> loadAllValueObjects() {
122
128
}
123
129
124
130
@ Override
125
- public Set <String > getFilterableProperties () {
131
+ public final Set <String > getFilterableProperties () {
126
132
return filterableProperties ;
127
133
}
128
134
135
+ /**
136
+ * Register filterable properties.
137
+ * @param properties a collection to which filterable properties are to be added
138
+ */
139
+ @ OverridingMethodsMustInvokeSuper
140
+ protected void registerFilterableProperties ( Set <String > properties ) {
141
+ addFilterableProperties ( "" , elementClass , properties , FILTERABLE_PROPERTIES_MAX_DEPTH );
142
+ // FIXME: the aliases are not available because they are registered afterward in the constructor
143
+ Set <FilterablePropertyAlias > aliases = new HashSet <>();
144
+ registerFilterablePropertyAliases ( aliases );
145
+ for ( FilterablePropertyAlias alias : aliases ) {
146
+ addFilterableProperties ( alias .prefix , alias .propertyType , properties , FILTERABLE_PROPERTIES_MAX_DEPTH - 1 );
147
+ }
148
+ }
149
+
150
+ /**
151
+ * Register aliases for filterable properties.
152
+ * @param aliases a collection to which aliases are to be added
153
+ */
154
+ protected void registerFilterablePropertyAliases ( Set <FilterablePropertyAlias > aliases ) {
155
+ }
156
+
129
157
@ Override
130
158
public Class <?> getFilterablePropertyType ( String propertyName ) throws IllegalArgumentException {
131
159
return getFilterablePropertyMeta ( propertyName ).propertyType ;
@@ -139,7 +167,7 @@ public String getFilterablePropertyDescription( String propertyName ) throws Ill
139
167
140
168
@ Nullable
141
169
@ Override
142
- public List <Object > getFilterablePropertyAvailableValues ( String propertyName ) {
170
+ public List <Object > getFilterablePropertyAvailableValues ( String propertyName ) throws IllegalArgumentException {
143
171
return getFilterablePropertyMeta ( propertyName ).availableValues ;
144
172
}
145
173
@@ -164,7 +192,10 @@ public final Sort getSort( String property, @Nullable Sort.Direction direction )
164
192
/**
165
193
* Helper that inspects a class and add all the filterable properties with the given prefix.
166
194
*/
167
- protected void addFilterableProperties ( String prefix , Class <?> entityClass , Set <String > destination , int maxDepth ) {
195
+ private void addFilterableProperties ( String prefix , Class <?> entityClass , Set <String > destination , int maxDepth ) {
196
+ if ( !prefix .isEmpty () && !prefix .endsWith ( "." ) ) {
197
+ throw new IllegalArgumentException ( "A non-empty prefix must end with a '.' character." );
198
+ }
168
199
if ( maxDepth <= 0 ) {
169
200
throw new IllegalArgumentException ( String .format ( "Maximum depth for adding filterable properties of %s to %s must be strictly positive." ,
170
201
entityClass .getName (), prefix ) );
@@ -213,6 +244,22 @@ protected static class FilterablePropertyMeta {
213
244
List <Object > availableValues ;
214
245
}
215
246
247
+ @ Value
248
+ @ EqualsAndHashCode (of = "prefix" )
249
+ protected static class FilterablePropertyAlias {
250
+ String prefix ;
251
+ @ Nullable
252
+ String objectAlias ;
253
+ Class <?> propertyType ;
254
+ /**
255
+ * If this alias is actual aliasing another alias.
256
+ * <p>
257
+ * Example: {@code taxon. -> primaryTaxon.}
258
+ */
259
+ @ Nullable
260
+ String aliasFor ;
261
+ }
262
+
216
263
/**
217
264
* Obtain various meta-information used to infer what to use in a {@link Filter} or {@link Sort}.
218
265
* <p>
@@ -224,6 +271,17 @@ protected static class FilterablePropertyMeta {
224
271
* @see #getSort(String, Sort.Direction)
225
272
*/
226
273
protected FilterablePropertyMeta getFilterablePropertyMeta ( String propertyName ) throws IllegalArgumentException {
274
+ // replace longer prefix first
275
+ List <FilterablePropertyAlias > aliases = filterablePropertyAliases .stream ()
276
+ .sorted ( Comparator .comparing ( f -> f .prefix .length (), Comparator .reverseOrder () ) )
277
+ .collect ( Collectors .toList () );
278
+ for ( FilterablePropertyAlias alias : aliases ) {
279
+ if ( propertyName .startsWith ( alias .prefix ) && !propertyName .equals ( alias .prefix + "size" ) ) {
280
+ String fieldName = propertyName .replaceFirst ( "^" + Pattern .quote ( alias .prefix ), "" );
281
+ return getFilterablePropertyMeta ( alias .objectAlias , fieldName , alias .propertyType )
282
+ .withDescription ( alias .aliasFor != null ? String .format ( "alias for %s.%s" , alias .aliasFor , fieldName ) : null );
283
+ }
284
+ }
227
285
return getFilterablePropertyMeta ( objectAlias , propertyName , elementClass );
228
286
}
229
287
0 commit comments