12
12
import ubic .gemma .model .common .Identifiable ;
13
13
import ubic .gemma .persistence .util .Filter ;
14
14
import ubic .gemma .persistence .util .Sort ;
15
+ import ubic .gemma .persistence .util .Subquery ;
15
16
16
17
import javax .annotation .Nullable ;
17
18
import javax .annotation .OverridingMethodsMustInvokeSuper ;
@@ -53,6 +54,11 @@ private static class Key {
53
54
*/
54
55
private final Set <String > filterableProperties = new HashSet <>();
55
56
57
+ /**
58
+ * Subset of {@link #filterableProperties} that should use a subquery for filtering.
59
+ */
60
+ private final Set <String > filterablePropertiesViaSubquery = new HashSet <>();
61
+
56
62
/**
57
63
* Aliases for filterable properties.
58
64
*/
@@ -89,15 +95,22 @@ protected class FilterablePropertiesConfigurer {
89
95
90
96
private final Map <String , Class <?>> entityByPrefix = new HashMap <>();
91
97
98
+ public void registerProperty ( String propertyName ) {
99
+ registerProperty ( propertyName , false );
100
+ }
101
+
92
102
/**
93
103
* Register a given property.
94
104
* @throws IllegalArgumentException if the property is already registered
95
105
*/
96
- public void registerProperty ( String propertyName ) {
106
+ public void registerProperty ( String propertyName , boolean useSubquery ) {
97
107
if ( getFilterablePropertyMeta ( propertyName ) == null ) {
98
108
throw new IllegalArgumentException ( "Property %s does not have any associated meta information." );
99
109
}
100
110
if ( filterableProperties .add ( propertyName ) ) {
111
+ if ( useSubquery ) {
112
+ filterablePropertiesViaSubquery .add ( propertyName );
113
+ }
101
114
log .debug ( String .format ( "Registered property %s." , propertyName ) );
102
115
} else {
103
116
throw new IllegalArgumentException ( String .format ( "Filterable property %s is already registered." ,
@@ -150,6 +163,10 @@ public void unregisterProperties( Predicate<? super String> predicate ) throws I
150
163
}
151
164
}
152
165
166
+ public void registerEntity ( String prefix , Class <?> entityClass , int maxDepth ) throws IllegalArgumentException {
167
+ registerEntity ( prefix , entityClass , maxDepth , false );
168
+ }
169
+
153
170
/**
154
171
* Register an entity available at a given prefix.
155
172
* <p>
@@ -159,10 +176,11 @@ public void unregisterProperties( Predicate<? super String> predicate ) throws I
159
176
* @param maxDepth maximum depth for visiting properties. For example, zero would expose no property but the
160
177
* entity itself, 1 would expose the properties of the alias, 2 would expose the properties
161
178
* of any entity directly related to the given entity, etc.
179
+ * @param useSubquery whether to use a subquery when filtering by this entity (and its descendant)
162
180
* @throws IllegalArgumentException if no entity of the given type is registered under the given prefix or if
163
181
* the prefix is invalid
164
182
*/
165
- public void registerEntity ( String prefix , Class <?> entityClass , int maxDepth ) throws IllegalArgumentException {
183
+ public void registerEntity ( String prefix , Class <?> entityClass , int maxDepth , boolean useSubquery ) throws IllegalArgumentException {
166
184
if ( !prefix .isEmpty () && !prefix .endsWith ( "." ) ) {
167
185
throw new IllegalArgumentException ( "A non-empty prefix must end with a '.' character." );
168
186
}
@@ -190,7 +208,7 @@ public void registerEntity( String prefix, Class<?> entityClass, int maxDepth )
190
208
Type propertyType = propertyTypes [i ];
191
209
if ( propertyType .isEntityType () ) {
192
210
if ( maxDepth > 1 ) {
193
- registerEntity ( prefix + propertyName + "." , propertyType .getReturnedClass (), maxDepth - 1 );
211
+ registerEntity ( prefix + propertyName + "." , propertyType .getReturnedClass (), maxDepth - 1 , useSubquery );
194
212
} else {
195
213
log .debug ( String .format ( "Max depth reached, will not recurse into %s" , propertyName ) );
196
214
}
@@ -201,7 +219,7 @@ public void registerEntity( String prefix, Class<?> entityClass, int maxDepth )
201
219
log .debug ( String .format ( "Property %s%s of type %s was excluded in %s: BLOBs and CLOBs are not exposed by default." ,
202
220
prefix , propertyName , propertyType .getName (), entityClass .getName () ) );
203
221
} else if ( Filter .getConversionService ().canConvert ( String .class , propertyType .getReturnedClass () ) ) {
204
- registerProperty ( prefix + propertyName );
222
+ registerProperty ( prefix + propertyName , useSubquery );
205
223
} else {
206
224
log .warn ( String .format ( "Property %s%s of type %s in %s is not supported and will be skipped." ,
207
225
prefix , propertyName , propertyType .getReturnedClass ().getName (), entityClass .getName () ) );
@@ -239,16 +257,23 @@ public void unregisterEntity( String prefix, Class<?> entityClass ) {
239
257
}
240
258
}
241
259
260
+ /**
261
+ * @see #registerAlias(String, String, Class, String, int, boolean)
262
+ */
263
+ public void registerAlias ( String prefix , @ Nullable String objectAlias , Class <?> propertyType , @ Nullable String aliasFor , int maxDepth ) {
264
+ registerAlias ( prefix , objectAlias , propertyType , aliasFor , maxDepth , false );
265
+ }
266
+
242
267
/**
243
268
* Register an alias for a property.
244
269
* <p>
245
270
* This also registers a property under the given prefix as per {@link #registerEntity(String, Class, int)}.
246
271
* @param objectAlias internal alias used to refer to the entity as per {@link Filter#getObjectAlias()}.
247
272
* @see #registerEntity(String, Class, int)
248
273
*/
249
- public void registerAlias ( String prefix , @ Nullable String objectAlias , Class <?> propertyType , @ Nullable String aliasFor , int maxDepth ) {
274
+ public void registerAlias ( String prefix , @ Nullable String objectAlias , Class <?> propertyType , @ Nullable String aliasFor , int maxDepth , boolean useSubquery ) {
250
275
filterablePropertyAliases .add ( new FilterablePropertyAlias ( prefix , objectAlias , propertyType , aliasFor ) );
251
- registerEntity ( prefix , propertyType , maxDepth );
276
+ registerEntity ( prefix , propertyType , maxDepth , useSubquery );
252
277
log .debug ( String .format ( "Registered alias for %s (%s) %s." , objectAlias , propertyType .getName (), summarizePrefix ( prefix ) ) );
253
278
}
254
279
@@ -285,13 +310,42 @@ public List<Object> getFilterablePropertyAllowedValues( String propertyName ) th
285
310
@ Override
286
311
public final Filter getFilter ( String property , Filter .Operator operator , String value ) {
287
312
FilterablePropertyMeta propertyMeta = getFilterablePropertyMeta ( property );
288
- return Filter .parse ( propertyMeta .objectAlias , propertyMeta .propertyName , propertyMeta .propertyType , operator , value , property );
313
+ return nestIfSubquery ( Filter .parse ( propertyMeta .objectAlias , propertyMeta .propertyName , propertyMeta .propertyType , operator , value , property ),
314
+ ( property ) );
289
315
}
290
316
291
317
@ Override
292
318
public final Filter getFilter ( String property , Filter .Operator operator , Collection <String > values ) {
293
319
FilterablePropertyMeta propertyMeta = getFilterablePropertyMeta ( property );
294
- return Filter .parse ( propertyMeta .objectAlias , propertyMeta .propertyName , propertyMeta .propertyType , operator , values , property );
320
+ return nestIfSubquery ( Filter .parse ( propertyMeta .objectAlias , propertyMeta .propertyName , propertyMeta .propertyType , operator , values , property ),
321
+ property );
322
+ }
323
+
324
+ private Filter nestIfSubquery ( Filter f , String propertyName ) {
325
+ if ( filterablePropertiesViaSubquery .contains ( propertyName ) ) {
326
+ String entityName = getSessionFactory ().getClassMetadata ( elementClass ).getEntityName ();
327
+ List <Subquery .Alias > aliases ;
328
+ if ( f .getObjectAlias () != null ) {
329
+ aliases = null ;
330
+ for ( FilterablePropertyAlias fpa : filterablePropertyAliases ) {
331
+ if ( f .getObjectAlias ().equals ( fpa .getObjectAlias () ) ) {
332
+ // FIXME: the prefix is not always a valid path
333
+ aliases = Collections .singletonList ( new Subquery .Alias ( fpa .prefix .substring ( 0 , fpa .prefix .length () - 1 ), fpa .getObjectAlias () ) );
334
+ break ;
335
+ }
336
+ }
337
+ if ( aliases == null ) {
338
+ throw new IllegalArgumentException ();
339
+ }
340
+ } else {
341
+ // the property refers to the root entity, no need for aliases
342
+ aliases = Collections .emptyList ();
343
+ }
344
+ return Filter .by ( objectAlias , getIdentifierPropertyName (), Long .class , Filter .Operator .inSubquery ,
345
+ new Subquery ( entityName , getIdentifierPropertyName (), aliases , f ) );
346
+ } else {
347
+ return f ;
348
+ }
295
349
}
296
350
297
351
@ Override
0 commit comments