Skip to content

Commit

Permalink
Improve definition of filterable properties and aliases
Browse files Browse the repository at this point in the history
Rename factorValueCharacteristics and bioMaterialCharacteristics to use
a common prefix with already existing paths.
  • Loading branch information
arteymix committed Jan 26, 2023
1 parent e8d84a1 commit 79530fc
Show file tree
Hide file tree
Showing 8 changed files with 135 additions and 146 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,9 @@
*/
public abstract class AbstractCriteriaFilteringVoEnabledDao<O extends Identifiable, VO extends IdentifiableValueObject<O>> extends AbstractFilteringVoEnabledDao<O, VO> {

@Autowired
private PlatformTransactionManager platformTransactionManager;

protected AbstractCriteriaFilteringVoEnabledDao( Class<? extends O> elementClass, SessionFactory sessionFactory ) {
// This is a good default objet alias for Hibernate Criteria since null is used to refer to the root entity.
super( null, elementClass, sessionFactory );
Expand Down Expand Up @@ -232,52 +235,45 @@ public long countPreFilter( @Nullable Filters filters ) {
return ret;
}

@Override
protected FilterablePropertyMeta getFilterablePropertyMeta( String propertyName ) throws IllegalArgumentException {
FilterablePropertyMeta meta = super.getFilterablePropertyMeta( propertyName );
List<FilterablePropertyCriteriaAlias> aliases = getFilterablePropertyCriteriaAliases();
// substitute longest path first
aliases.sort( Comparator.comparing( a -> a.propertyName.length(), Comparator.reverseOrder() ) );
for ( FilterablePropertyCriteriaAlias alias : aliases ) {
if ( propertyName.startsWith( alias.propertyName + "." ) ) {
propertyName = propertyName.replaceFirst( "^" + Pattern.quote( alias.propertyName + "." ), "" );
return meta
.withObjectAlias( alias.alias )
.withPropertyName( propertyName );
}
}
return meta;
}

@Value
protected static class FilterablePropertyAlias {
private static class FilterablePropertyCriteriaAlias {
String propertyName;
String alias;
}

@Autowired
private PlatformTransactionManager platformTransactionManager;

/**
* Unfortunately, because of how criteria API works, you have to explicitly list all aliases.
* TODO: infer this from the criteria.
*/
protected List<FilterablePropertyAlias> getFilterablePropertyAliases() {
private List<FilterablePropertyCriteriaAlias> getFilterablePropertyCriteriaAliases() {
// FIXME: unfortunately, this requires a session...
Criteria criteria = new TransactionTemplate( platformTransactionManager ).execute( ( ts ) -> getFilteringCriteria( Filters.empty() ) );
if ( criteria instanceof CriteriaImpl ) {
//noinspection unchecked
Iterator<CriteriaImpl.Subcriteria> it = ( ( CriteriaImpl ) criteria ).iterateSubcriteria();
List<FilterablePropertyAlias> result = new ArrayList<>();
List<FilterablePropertyCriteriaAlias> result = new ArrayList<>();
while ( it.hasNext() ) {
CriteriaImpl.Subcriteria sc = it.next();
result.add( new FilterablePropertyAlias( sc.getPath(), sc.getAlias() ) );
result.add( new FilterablePropertyCriteriaAlias( sc.getPath(), sc.getAlias() ) );
}
return result;
}
return Collections.emptyList();
}

@Override
protected FilterablePropertyMeta getFilterablePropertyMeta( String propertyName ) throws IllegalArgumentException {
FilterablePropertyMeta meta = super.getFilterablePropertyMeta( propertyName );
List<FilterablePropertyAlias> aliases = getFilterablePropertyAliases();
// substitute longest path first
aliases.sort( Comparator.comparing( a -> a.propertyName.length(), Comparator.reverseOrder() ) );
for ( FilterablePropertyAlias alias : aliases ) {
if ( propertyName.startsWith( alias.propertyName + "." ) ) {
propertyName = propertyName.replaceFirst( "^" + Pattern.quote( alias.propertyName + "." ), "" );
return meta
.withObjectAlias( alias.alias )
.withPropertyName( propertyName );
}
}
return meta;
}

private static void addOrder( Criteria query, Sort sort ) {
String propertyName = formPropertyName( sort.getObjectAlias(), sort.getPropertyName() );
// handle .size ordering
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
package ubic.gemma.persistence.service;

import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Value;
import lombok.With;
import lombok.*;
import org.apache.commons.lang3.ArrayUtils;
import org.hibernate.SessionFactory;
import org.hibernate.metadata.ClassMetadata;
Expand All @@ -18,8 +15,11 @@
import ubic.gemma.persistence.util.Sort;

import javax.annotation.Nullable;
import javax.annotation.OverridingMethodsMustInvokeSuper;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

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

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

/**
* Aliases for filterable properties.
*/
private final Set<FilterablePropertyAlias> filterablePropertyAliases;

protected AbstractFilteringVoEnabledDao( @Nullable String objectAlias, Class<? extends O> elementClass, SessionFactory sessionFactory ) {
super( elementClass, sessionFactory );
this.objectAlias = objectAlias;
Set<String> result = new HashSet<>();
addFilterableProperties( "", elementClass, result, FILTERABLE_PROPERTIES_MAX_DEPTH );
this.filterableProperties = Collections.unmodifiableSet( result );
this.filterablePropertyAliases = new HashSet<>();
registerFilterablePropertyAliases( this.filterablePropertyAliases );
this.filterableProperties = new HashSet<>();
registerFilterableProperties( this.filterableProperties );
}

/**
Expand Down Expand Up @@ -122,10 +128,32 @@ public final List<VO> loadAllValueObjects() {
}

@Override
public Set<String> getFilterableProperties() {
public final Set<String> getFilterableProperties() {
return filterableProperties;
}

/**
* Register filterable properties.
* @param properties a collection to which filterable properties are to be added
*/
@OverridingMethodsMustInvokeSuper
protected void registerFilterableProperties( Set<String> properties ) {
addFilterableProperties( "", elementClass, properties, FILTERABLE_PROPERTIES_MAX_DEPTH );
// FIXME: the aliases are not available because they are registered afterward in the constructor
Set<FilterablePropertyAlias> aliases = new HashSet<>();
registerFilterablePropertyAliases( aliases );
for ( FilterablePropertyAlias alias : aliases ) {
addFilterableProperties( alias.prefix, alias.propertyType, properties, FILTERABLE_PROPERTIES_MAX_DEPTH - 1 );
}
}

/**
* Register aliases for filterable properties.
* @param aliases a collection to which aliases are to be added
*/
protected void registerFilterablePropertyAliases( Set<FilterablePropertyAlias> aliases ) {
}

@Override
public Class<?> getFilterablePropertyType( String propertyName ) throws IllegalArgumentException {
return getFilterablePropertyMeta( propertyName ).propertyType;
Expand All @@ -139,7 +167,7 @@ public String getFilterablePropertyDescription( String propertyName ) throws Ill

@Nullable
@Override
public List<Object> getFilterablePropertyAvailableValues( String propertyName ) {
public List<Object> getFilterablePropertyAvailableValues( String propertyName ) throws IllegalArgumentException {
return getFilterablePropertyMeta( propertyName ).availableValues;
}

Expand All @@ -164,7 +192,10 @@ public final Sort getSort( String property, @Nullable Sort.Direction direction )
/**
* Helper that inspects a class and add all the filterable properties with the given prefix.
*/
protected void addFilterableProperties( String prefix, Class<?> entityClass, Set<String> destination, int maxDepth ) {
private void addFilterableProperties( String prefix, Class<?> entityClass, Set<String> destination, int maxDepth ) {
if ( !prefix.isEmpty() && !prefix.endsWith( "." ) ) {
throw new IllegalArgumentException( "A non-empty prefix must end with a '.' character." );
}
if ( maxDepth <= 0 ) {
throw new IllegalArgumentException( String.format( "Maximum depth for adding filterable properties of %s to %s must be strictly positive.",
entityClass.getName(), prefix ) );
Expand Down Expand Up @@ -213,6 +244,22 @@ protected static class FilterablePropertyMeta {
List<Object> availableValues;
}

@Value
@EqualsAndHashCode(of = "prefix")
protected static class FilterablePropertyAlias {
String prefix;
@Nullable
String objectAlias;
Class<?> propertyType;
/**
* If this alias is actual aliasing another alias.
* <p>
* Example: {@code taxon. -> primaryTaxon.}
*/
@Nullable
String aliasFor;
}

/**
* Obtain various meta-information used to infer what to use in a {@link Filter} or {@link Sort}.
* <p>
Expand All @@ -224,6 +271,17 @@ protected static class FilterablePropertyMeta {
* @see #getSort(String, Sort.Direction)
*/
protected FilterablePropertyMeta getFilterablePropertyMeta( String propertyName ) throws IllegalArgumentException {
// replace longer prefix first
List<FilterablePropertyAlias> aliases = filterablePropertyAliases.stream()
.sorted( Comparator.comparing( f -> f.prefix.length(), Comparator.reverseOrder() ) )
.collect( Collectors.toList() );
for ( FilterablePropertyAlias alias : aliases ) {
if ( propertyName.startsWith( alias.prefix ) && !propertyName.equals( alias.prefix + "size" ) ) {
String fieldName = propertyName.replaceFirst( "^" + Pattern.quote( alias.prefix ), "" );
return getFilterablePropertyMeta( alias.objectAlias, fieldName, alias.propertyType )
.withDescription( alias.aliasFor != null ? String.format( "alias for %s.%s", alias.aliasFor, fieldName ) : null );
}
}
return getFilterablePropertyMeta( objectAlias, propertyName, elementClass );
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package ubic.gemma.persistence.service;

import lombok.Value;
import org.apache.commons.lang3.NotImplementedException;
import org.apache.commons.lang3.time.StopWatch;
import org.hibernate.Query;
Expand All @@ -13,9 +12,10 @@
import ubic.gemma.persistence.util.Sort;

import javax.annotation.Nullable;
import java.util.*;
import java.util.EnumSet;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

/**
Expand Down Expand Up @@ -46,54 +46,6 @@ protected AbstractQueryFilteringVoEnabledDao( String objectAlias, Class<O> eleme
super( objectAlias, elementClass, sessionFactory );
}

@Value
protected static class FilterablePropertyQueryAlias {
String prefix;
@Nullable
String objectAlias;
Class<?> propertyType;
}

/**
* Since HQL-based filtering cannot simply detect aliases in the query, you have to declare them explicitly.
*/
protected FilterablePropertyQueryAlias[] getFilterablePropertyQueryAliases() {
return new FilterablePropertyQueryAlias[0];
}

@Override
public Set<String> getFilterableProperties() {
Set<String> results = super.getFilterableProperties();
if ( getFilterablePropertyQueryAliases().length > 0 ) {
results = new HashSet<>( results );
for ( FilterablePropertyQueryAlias alias : getFilterablePropertyQueryAliases() ) {
addFilterableProperties( alias.prefix, alias.propertyType, results, FILTERABLE_PROPERTIES_MAX_DEPTH - 1 );
}
results = Collections.unmodifiableSet( results );
}
return results;
}

/**
* Checks for special properties that are allowed to be referenced on certain objects. E.g. characteristics on EEs.
* {@inheritDoc}
*/
@Override
protected FilterablePropertyMeta getFilterablePropertyMeta( String propertyName ) {
// replace longer prefix first
List<FilterablePropertyQueryAlias> aliases = Arrays.stream( getFilterablePropertyQueryAliases() )
.sorted( Comparator.comparing( f -> f.prefix.length(), Comparator.reverseOrder() ) )
.collect( Collectors.toList() );
for ( FilterablePropertyQueryAlias alias : aliases ) {
if ( propertyName.startsWith( alias.prefix ) && !propertyName.equals( alias.prefix + "size" ) ) {
String fieldName = propertyName.replaceFirst( "^" + Pattern.quote( alias.prefix ), "" );
return getFilterablePropertyMeta( alias.objectAlias, fieldName, alias.propertyType );
}
}
return super.getFilterablePropertyMeta( propertyName );
}


/**
* Produce a query for retrieving value objects after applying a set of filters and a given ordering.
* <p>
Expand All @@ -120,21 +72,21 @@ protected Query getFilteringCountQuery( @Nullable Filters filters ) {
}

/**
* Process a result from {@link #getFilteringQuery(Filters, Sort, EnumSet)} into a {@link O}.
* Process a properties from {@link #getFilteringQuery(Filters, Sort, EnumSet)} into a {@link O}.
* <p>
* The default is to simply cast the result to {@link O}, assuming that it is the only return value of the query.
* The default is to simply cast the properties to {@link O}, assuming that it is the only return value of the query.
*/
protected O processFilteringQueryResultToEntity( Object result ) {
//noinspection unchecked
return ( O ) result;
}

/**
* Process a result from {@link #getFilteringQuery(Filters, Sort, EnumSet)} into a {@link VO} value object.
* Process a properties from {@link #getFilteringQuery(Filters, Sort, EnumSet)} into a {@link VO} value object.
* <p>
* The result is obtained from {@link Query#list()}.
* The properties is obtained from {@link Query#list()}.
* <p>
* By default, it will process the result with {@link #processFilteringQueryResultToEntity(Object)} and then apply
* By default, it will process the properties with {@link #processFilteringQueryResultToEntity(Object)} and then apply
* {@link #doLoadValueObject(Identifiable)} to obtain a value object.
*
* @return a value object, or null, and it will be ignored when constructing the {@link Slice} in {@link #loadValueObjectsPreFilter(Filters, Sort, int, int)}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -234,13 +234,12 @@ protected Criteria getFilteringCriteria( @Nullable Filters filters ) {
}

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

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,14 @@
import ubic.gemma.model.common.auditAndSecurity.curation.AbstractCuratableValueObject;
import ubic.gemma.model.common.auditAndSecurity.curation.Curatable;
import ubic.gemma.model.common.auditAndSecurity.curation.CurationDetails;
import ubic.gemma.model.common.description.DatabaseEntry;
import ubic.gemma.persistence.service.AbstractQueryFilteringVoEnabledDao;
import ubic.gemma.persistence.service.common.auditAndSecurity.CurationDetailsDao;
import ubic.gemma.persistence.util.Filters;
import ubic.gemma.persistence.util.Filter;
import ubic.gemma.persistence.util.Slice;
import ubic.gemma.persistence.util.Filters;

import javax.annotation.Nullable;
import javax.annotation.OverridingMethodsMustInvokeSuper;
import java.util.*;
import java.util.stream.Collectors;

/**
* Created by tesarst on 07/03/17.
Expand Down Expand Up @@ -91,12 +89,12 @@ protected void addNonTroubledFilter( Filters filters, @Nullable String objectAli
}

@Override
public Set<String> getFilterableProperties() {
Set<String> result = new HashSet<>( super.getFilterableProperties() );
result.addAll( Arrays.asList( "lastUpdated", "troubled", "needsAttention" ) );
return result;
@OverridingMethodsMustInvokeSuper
protected void registerFilterableProperties( Set<String> properties ) {
super.registerFilterableProperties( properties );
properties.addAll( Arrays.asList( "lastUpdated", "troubled", "needsAttention" ) );
}

/**
* {@inheritDoc}
* <p>
Expand Down
Loading

0 comments on commit 79530fc

Please sign in to comment.