Skip to content

Commit d612215

Browse files
committed
Improve definition of filterable properties and aliases
Move property alias logic in AbstractFilteringVoEnabledDao to hide more implementation details. Rename factorValueCharacteristics and bioMaterialCharacteristics to use a common prefix with already existing paths.
1 parent e8d84a1 commit d612215

File tree

8 files changed

+135
-146
lines changed

8 files changed

+135
-146
lines changed

gemma-core/src/main/java/ubic/gemma/persistence/service/AbstractCriteriaFilteringVoEnabledDao.java

+24-28
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,9 @@
3535
*/
3636
public abstract class AbstractCriteriaFilteringVoEnabledDao<O extends Identifiable, VO extends IdentifiableValueObject<O>> extends AbstractFilteringVoEnabledDao<O, VO> {
3737

38+
@Autowired
39+
private PlatformTransactionManager platformTransactionManager;
40+
3841
protected AbstractCriteriaFilteringVoEnabledDao( Class<? extends O> elementClass, SessionFactory sessionFactory ) {
3942
// This is a good default objet alias for Hibernate Criteria since null is used to refer to the root entity.
4043
super( null, elementClass, sessionFactory );
@@ -232,52 +235,45 @@ public long countPreFilter( @Nullable Filters filters ) {
232235
return ret;
233236
}
234237

238+
@Override
239+
protected FilterablePropertyMeta getFilterablePropertyMeta( String propertyName ) throws IllegalArgumentException {
240+
FilterablePropertyMeta meta = super.getFilterablePropertyMeta( propertyName );
241+
List<FilterablePropertyCriteriaAlias> aliases = getFilterablePropertyCriteriaAliases();
242+
// substitute longest path first
243+
aliases.sort( Comparator.comparing( a -> a.propertyName.length(), Comparator.reverseOrder() ) );
244+
for ( FilterablePropertyCriteriaAlias alias : aliases ) {
245+
if ( propertyName.startsWith( alias.propertyName + "." ) ) {
246+
propertyName = propertyName.replaceFirst( "^" + Pattern.quote( alias.propertyName + "." ), "" );
247+
return meta
248+
.withObjectAlias( alias.alias )
249+
.withPropertyName( propertyName );
250+
}
251+
}
252+
return meta;
253+
}
254+
235255
@Value
236-
protected static class FilterablePropertyAlias {
256+
private static class FilterablePropertyCriteriaAlias {
237257
String propertyName;
238258
String alias;
239259
}
240260

241-
@Autowired
242-
private PlatformTransactionManager platformTransactionManager;
243-
244-
/**
245-
* Unfortunately, because of how criteria API works, you have to explicitly list all aliases.
246-
* TODO: infer this from the criteria.
247-
*/
248-
protected List<FilterablePropertyAlias> getFilterablePropertyAliases() {
261+
private List<FilterablePropertyCriteriaAlias> getFilterablePropertyCriteriaAliases() {
249262
// FIXME: unfortunately, this requires a session...
250263
Criteria criteria = new TransactionTemplate( platformTransactionManager ).execute( ( ts ) -> getFilteringCriteria( Filters.empty() ) );
251264
if ( criteria instanceof CriteriaImpl ) {
252265
//noinspection unchecked
253266
Iterator<CriteriaImpl.Subcriteria> it = ( ( CriteriaImpl ) criteria ).iterateSubcriteria();
254-
List<FilterablePropertyAlias> result = new ArrayList<>();
267+
List<FilterablePropertyCriteriaAlias> result = new ArrayList<>();
255268
while ( it.hasNext() ) {
256269
CriteriaImpl.Subcriteria sc = it.next();
257-
result.add( new FilterablePropertyAlias( sc.getPath(), sc.getAlias() ) );
270+
result.add( new FilterablePropertyCriteriaAlias( sc.getPath(), sc.getAlias() ) );
258271
}
259272
return result;
260273
}
261274
return Collections.emptyList();
262275
}
263276

264-
@Override
265-
protected FilterablePropertyMeta getFilterablePropertyMeta( String propertyName ) throws IllegalArgumentException {
266-
FilterablePropertyMeta meta = super.getFilterablePropertyMeta( propertyName );
267-
List<FilterablePropertyAlias> aliases = getFilterablePropertyAliases();
268-
// substitute longest path first
269-
aliases.sort( Comparator.comparing( a -> a.propertyName.length(), Comparator.reverseOrder() ) );
270-
for ( FilterablePropertyAlias alias : aliases ) {
271-
if ( propertyName.startsWith( alias.propertyName + "." ) ) {
272-
propertyName = propertyName.replaceFirst( "^" + Pattern.quote( alias.propertyName + "." ), "" );
273-
return meta
274-
.withObjectAlias( alias.alias )
275-
.withPropertyName( propertyName );
276-
}
277-
}
278-
return meta;
279-
}
280-
281277
private static void addOrder( Criteria query, Sort sort ) {
282278
String propertyName = formPropertyName( sort.getObjectAlias(), sort.getPropertyName() );
283279
// handle .size ordering

gemma-core/src/main/java/ubic/gemma/persistence/service/AbstractFilteringVoEnabledDao.java

+69-11
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,6 @@
11
package ubic.gemma.persistence.service;
22

3-
import lombok.AccessLevel;
4-
import lombok.AllArgsConstructor;
5-
import lombok.Value;
6-
import lombok.With;
3+
import lombok.*;
74
import org.apache.commons.lang3.ArrayUtils;
85
import org.hibernate.SessionFactory;
96
import org.hibernate.metadata.ClassMetadata;
@@ -18,8 +15,11 @@
1815
import ubic.gemma.persistence.util.Sort;
1916

2017
import javax.annotation.Nullable;
18+
import javax.annotation.OverridingMethodsMustInvokeSuper;
2119
import java.util.*;
2220
import java.util.concurrent.ConcurrentHashMap;
21+
import java.util.regex.Pattern;
22+
import java.util.stream.Collectors;
2323

2424
/**
2525
* Base implementation for {@link FilteringVoEnabledDao}.
@@ -33,7 +33,7 @@ public abstract class AbstractFilteringVoEnabledDao<O extends Identifiable, VO e
3333
/**
3434
* Maximum depth to explore when enumerating filterable properties via {@link #getFilterableProperties()}.
3535
*/
36-
protected static final int FILTERABLE_PROPERTIES_MAX_DEPTH = 3;
36+
private static final int FILTERABLE_PROPERTIES_MAX_DEPTH = 3;
3737

3838
/**
3939
* Cached partial filterable properties meta, computed as we go.
@@ -53,12 +53,18 @@ private static class Key {
5353
*/
5454
private final Set<String> filterableProperties;
5555

56+
/**
57+
* Aliases for filterable properties.
58+
*/
59+
private final Set<FilterablePropertyAlias> filterablePropertyAliases;
60+
5661
protected AbstractFilteringVoEnabledDao( @Nullable String objectAlias, Class<? extends O> elementClass, SessionFactory sessionFactory ) {
5762
super( elementClass, sessionFactory );
5863
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 );
6268
}
6369

6470
/**
@@ -122,10 +128,32 @@ public final List<VO> loadAllValueObjects() {
122128
}
123129

124130
@Override
125-
public Set<String> getFilterableProperties() {
131+
public final Set<String> getFilterableProperties() {
126132
return filterableProperties;
127133
}
128134

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+
129157
@Override
130158
public Class<?> getFilterablePropertyType( String propertyName ) throws IllegalArgumentException {
131159
return getFilterablePropertyMeta( propertyName ).propertyType;
@@ -139,7 +167,7 @@ public String getFilterablePropertyDescription( String propertyName ) throws Ill
139167

140168
@Nullable
141169
@Override
142-
public List<Object> getFilterablePropertyAvailableValues( String propertyName ) {
170+
public List<Object> getFilterablePropertyAvailableValues( String propertyName ) throws IllegalArgumentException {
143171
return getFilterablePropertyMeta( propertyName ).availableValues;
144172
}
145173

@@ -164,7 +192,10 @@ public final Sort getSort( String property, @Nullable Sort.Direction direction )
164192
/**
165193
* Helper that inspects a class and add all the filterable properties with the given prefix.
166194
*/
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+
}
168199
if ( maxDepth <= 0 ) {
169200
throw new IllegalArgumentException( String.format( "Maximum depth for adding filterable properties of %s to %s must be strictly positive.",
170201
entityClass.getName(), prefix ) );
@@ -213,6 +244,22 @@ protected static class FilterablePropertyMeta {
213244
List<Object> availableValues;
214245
}
215246

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+
216263
/**
217264
* Obtain various meta-information used to infer what to use in a {@link Filter} or {@link Sort}.
218265
* <p>
@@ -224,6 +271,17 @@ protected static class FilterablePropertyMeta {
224271
* @see #getSort(String, Sort.Direction)
225272
*/
226273
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+
}
227285
return getFilterablePropertyMeta( objectAlias, propertyName, elementClass );
228286
}
229287

gemma-core/src/main/java/ubic/gemma/persistence/service/AbstractQueryFilteringVoEnabledDao.java

+8-56
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
package ubic.gemma.persistence.service;
22

3-
import lombok.Value;
43
import org.apache.commons.lang3.NotImplementedException;
54
import org.apache.commons.lang3.time.StopWatch;
65
import org.hibernate.Query;
@@ -13,9 +12,10 @@
1312
import ubic.gemma.persistence.util.Sort;
1413

1514
import javax.annotation.Nullable;
16-
import java.util.*;
15+
import java.util.EnumSet;
16+
import java.util.List;
17+
import java.util.Objects;
1718
import java.util.concurrent.TimeUnit;
18-
import java.util.regex.Pattern;
1919
import java.util.stream.Collectors;
2020

2121
/**
@@ -46,54 +46,6 @@ protected AbstractQueryFilteringVoEnabledDao( String objectAlias, Class<O> eleme
4646
super( objectAlias, elementClass, sessionFactory );
4747
}
4848

49-
@Value
50-
protected static class FilterablePropertyQueryAlias {
51-
String prefix;
52-
@Nullable
53-
String objectAlias;
54-
Class<?> propertyType;
55-
}
56-
57-
/**
58-
* Since HQL-based filtering cannot simply detect aliases in the query, you have to declare them explicitly.
59-
*/
60-
protected FilterablePropertyQueryAlias[] getFilterablePropertyQueryAliases() {
61-
return new FilterablePropertyQueryAlias[0];
62-
}
63-
64-
@Override
65-
public Set<String> getFilterableProperties() {
66-
Set<String> results = super.getFilterableProperties();
67-
if ( getFilterablePropertyQueryAliases().length > 0 ) {
68-
results = new HashSet<>( results );
69-
for ( FilterablePropertyQueryAlias alias : getFilterablePropertyQueryAliases() ) {
70-
addFilterableProperties( alias.prefix, alias.propertyType, results, FILTERABLE_PROPERTIES_MAX_DEPTH - 1 );
71-
}
72-
results = Collections.unmodifiableSet( results );
73-
}
74-
return results;
75-
}
76-
77-
/**
78-
* Checks for special properties that are allowed to be referenced on certain objects. E.g. characteristics on EEs.
79-
* {@inheritDoc}
80-
*/
81-
@Override
82-
protected FilterablePropertyMeta getFilterablePropertyMeta( String propertyName ) {
83-
// replace longer prefix first
84-
List<FilterablePropertyQueryAlias> aliases = Arrays.stream( getFilterablePropertyQueryAliases() )
85-
.sorted( Comparator.comparing( f -> f.prefix.length(), Comparator.reverseOrder() ) )
86-
.collect( Collectors.toList() );
87-
for ( FilterablePropertyQueryAlias alias : aliases ) {
88-
if ( propertyName.startsWith( alias.prefix ) && !propertyName.equals( alias.prefix + "size" ) ) {
89-
String fieldName = propertyName.replaceFirst( "^" + Pattern.quote( alias.prefix ), "" );
90-
return getFilterablePropertyMeta( alias.objectAlias, fieldName, alias.propertyType );
91-
}
92-
}
93-
return super.getFilterablePropertyMeta( propertyName );
94-
}
95-
96-
9749
/**
9850
* Produce a query for retrieving value objects after applying a set of filters and a given ordering.
9951
* <p>
@@ -120,21 +72,21 @@ protected Query getFilteringCountQuery( @Nullable Filters filters ) {
12072
}
12173

12274
/**
123-
* Process a result from {@link #getFilteringQuery(Filters, Sort, EnumSet)} into a {@link O}.
75+
* Process a properties from {@link #getFilteringQuery(Filters, Sort, EnumSet)} into a {@link O}.
12476
* <p>
125-
* The default is to simply cast the result to {@link O}, assuming that it is the only return value of the query.
77+
* The default is to simply cast the properties to {@link O}, assuming that it is the only return value of the query.
12678
*/
12779
protected O processFilteringQueryResultToEntity( Object result ) {
12880
//noinspection unchecked
12981
return ( O ) result;
13082
}
13183

13284
/**
133-
* Process a result from {@link #getFilteringQuery(Filters, Sort, EnumSet)} into a {@link VO} value object.
85+
* Process a properties from {@link #getFilteringQuery(Filters, Sort, EnumSet)} into a {@link VO} value object.
13486
* <p>
135-
* The result is obtained from {@link Query#list()}.
87+
* The properties is obtained from {@link Query#list()}.
13688
* <p>
137-
* By default, it will process the result with {@link #processFilteringQueryResultToEntity(Object)} and then apply
89+
* By default, it will process the properties with {@link #processFilteringQueryResultToEntity(Object)} and then apply
13890
* {@link #doLoadValueObject(Identifiable)} to obtain a value object.
13991
*
14092
* @return a value object, or null, and it will be ignored when constructing the {@link Slice} in {@link #loadValueObjectsPreFilter(Filters, Sort, int, int)}

gemma-core/src/main/java/ubic/gemma/persistence/service/analysis/expression/diff/ExpressionAnalysisResultSetDaoImpl.java

+5-6
Original file line numberDiff line numberDiff line change
@@ -234,13 +234,12 @@ protected Criteria getFilteringCriteria( @Nullable Filters filters ) {
234234
}
235235

236236
@Override
237-
public Set<String> getFilterableProperties() {
238-
Set<String> results = new HashSet<>( super.getFilterableProperties() );
237+
protected void registerFilterableProperties( Set<String> properties ) {
238+
super.registerFilterableProperties( properties );
239239
// these cause a org.hibernate.MappingException: Unknown collection role exception (see https://github.com/PavlidisLab/Gemma/issues/518)
240-
results.remove( "analysis.experimentAnalyzed.characteristics.size" );
241-
results.remove( "analysis.experimentAnalyzed.otherRelevantPublications.size" );
242-
results.remove( "experimentalFactors.size" );
243-
return results;
240+
properties.remove( "analysis.experimentAnalyzed.characteristics.size" );
241+
properties.remove( "analysis.experimentAnalyzed.otherRelevantPublications.size" );
242+
properties.remove( "experimentalFactors.size" );
244243
}
245244

246245
@Override

gemma-core/src/main/java/ubic/gemma/persistence/service/common/auditAndSecurity/curation/AbstractCuratableDao.java

+7-9
Original file line numberDiff line numberDiff line change
@@ -7,16 +7,14 @@
77
import ubic.gemma.model.common.auditAndSecurity.curation.AbstractCuratableValueObject;
88
import ubic.gemma.model.common.auditAndSecurity.curation.Curatable;
99
import ubic.gemma.model.common.auditAndSecurity.curation.CurationDetails;
10-
import ubic.gemma.model.common.description.DatabaseEntry;
1110
import ubic.gemma.persistence.service.AbstractQueryFilteringVoEnabledDao;
1211
import ubic.gemma.persistence.service.common.auditAndSecurity.CurationDetailsDao;
13-
import ubic.gemma.persistence.util.Filters;
1412
import ubic.gemma.persistence.util.Filter;
15-
import ubic.gemma.persistence.util.Slice;
13+
import ubic.gemma.persistence.util.Filters;
1614

1715
import javax.annotation.Nullable;
16+
import javax.annotation.OverridingMethodsMustInvokeSuper;
1817
import java.util.*;
19-
import java.util.stream.Collectors;
2018

2119
/**
2220
* Created by tesarst on 07/03/17.
@@ -91,12 +89,12 @@ protected void addNonTroubledFilter( Filters filters, @Nullable String objectAli
9189
}
9290

9391
@Override
94-
public Set<String> getFilterableProperties() {
95-
Set<String> result = new HashSet<>( super.getFilterableProperties() );
96-
result.addAll( Arrays.asList( "lastUpdated", "troubled", "needsAttention" ) );
97-
return result;
92+
@OverridingMethodsMustInvokeSuper
93+
protected void registerFilterableProperties( Set<String> properties ) {
94+
super.registerFilterableProperties( properties );
95+
properties.addAll( Arrays.asList( "lastUpdated", "troubled", "needsAttention" ) );
9896
}
99-
97+
10098
/**
10199
* {@inheritDoc}
102100
* <p>

0 commit comments

Comments
 (0)