From d9f3e822911f8fd90b1c72d1a83bf448e5b0054b Mon Sep 17 00:00:00 2001 From: Felix Feisst Date: Fri, 28 Jul 2017 03:17:52 -0400 Subject: [PATCH] HHH-11895 Support traversal of components in audit query API --- .../metadata/ComponentMetadataGenerator.java | 6 + ...iddleTableCollectionMetadataGenerator.java | 9 +- .../entities/ComponentDescription.java | 66 ++++ .../entities/EntitiesConfigurations.java | 21 ++ .../entities/EntityConfiguration.java | 15 + .../internal/tools/query/Parameters.java | 13 + .../internal/tools/query/QueryBuilder.java | 19 +- .../criteria/AggregatedAuditExpression.java | 37 +- .../query/criteria/AuditConjunction.java | 11 +- .../envers/query/criteria/AuditCriterion.java | 1 + .../query/criteria/AuditDisjunction.java | 11 +- .../envers/query/criteria/AuditFunction.java | 8 +- .../envers/query/criteria/AuditProperty.java | 30 +- .../internal/AbstractAtomicExpression.java | 14 +- .../internal/BetweenAuditExpression.java | 8 +- .../criteria/internal/CriteriaTools.java | 39 ++ .../FunctionFunctionAuditExpression.java | 2 + .../FunctionPropertyAuditExpression.java | 16 +- .../internal/IdentifierEqAuditExpression.java | 1 + .../internal/IlikeAuditExpression.java | 13 +- .../criteria/internal/InAuditExpression.java | 6 +- .../internal/LogicalAuditExpression.java | 22 +- .../criteria/internal/NotAuditExpression.java | 11 +- .../internal/NotNullAuditExpression.java | 6 +- .../internal/NullAuditExpression.java | 4 +- .../internal/PropertyAuditExpression.java | 21 +- .../PropertyFunctionAuditExpression.java | 13 +- .../RelatedAuditEqualityExpression.java | 8 +- .../internal/RelatedAuditInExpression.java | 10 +- .../internal/RevisionTypeAuditExpression.java | 1 + .../internal/SimpleAuditExpression.java | 10 +- .../SimpleFunctionAuditExpression.java | 10 +- .../internal/impl/AbstractAuditQuery.java | 42 ++- .../impl/AuditAssociationQueryImpl.java | 172 +++++++-- .../impl/EntitiesAtRevisionQuery.java | 1 + .../impl/EntitiesModifiedAtRevisionQuery.java | 1 + .../internal/impl/RevisionsOfEntityQuery.java | 1 + .../query/projection/AuditProjection.java | 1 + .../internal/EntityAuditProjection.java | 9 +- .../internal/PropertyAuditProjection.java | 22 +- .../integration/query/ComponentQueryTest.java | 336 ++++++++++++++++++ .../query/NestedComponentQueryTest.java | 158 ++++++++ 42 files changed, 1123 insertions(+), 82 deletions(-) create mode 100644 hibernate-envers/src/main/java/org/hibernate/envers/internal/entities/ComponentDescription.java create mode 100644 hibernate-envers/src/test/java/org/hibernate/orm/test/envers/integration/query/ComponentQueryTest.java create mode 100644 hibernate-envers/src/test/java/org/hibernate/orm/test/envers/integration/query/NestedComponentQueryTest.java diff --git a/hibernate-envers/src/main/java/org/hibernate/envers/configuration/internal/metadata/ComponentMetadataGenerator.java b/hibernate-envers/src/main/java/org/hibernate/envers/configuration/internal/metadata/ComponentMetadataGenerator.java index d70085eee2..b80b6cc165 100644 --- a/hibernate-envers/src/main/java/org/hibernate/envers/configuration/internal/metadata/ComponentMetadataGenerator.java +++ b/hibernate-envers/src/main/java/org/hibernate/envers/configuration/internal/metadata/ComponentMetadataGenerator.java @@ -14,6 +14,7 @@ import org.hibernate.envers.boot.registry.classloading.ClassLoaderAccessHelper; import org.hibernate.envers.boot.spi.EnversMetadataBuildingContext; import org.hibernate.envers.configuration.internal.metadata.reader.ComponentAuditingData; import org.hibernate.envers.configuration.internal.metadata.reader.PropertyAuditingData; +import org.hibernate.envers.internal.entities.EntityConfiguration; import org.hibernate.envers.internal.entities.mapper.CompositeMapperBuilder; import org.hibernate.mapping.Component; import org.hibernate.mapping.Property; @@ -80,6 +81,11 @@ public final class ComponentMetadataGenerator extends AbstractMetadataGenerator ); } } + + if ( !firstPass ) { + final EntityConfiguration owningEntityConfiguration = getAuditedEntityConfigurations().get( entityName ); + owningEntityConfiguration.addToOneComponent( propertyAuditingData.getName(), componentAuditingData ); + } } private String getClassNameForComponent(Component component) { diff --git a/hibernate-envers/src/main/java/org/hibernate/envers/configuration/internal/metadata/MiddleTableCollectionMetadataGenerator.java b/hibernate-envers/src/main/java/org/hibernate/envers/configuration/internal/metadata/MiddleTableCollectionMetadataGenerator.java index 99c890e2cb..39168d9ed6 100644 --- a/hibernate-envers/src/main/java/org/hibernate/envers/configuration/internal/metadata/MiddleTableCollectionMetadataGenerator.java +++ b/hibernate-envers/src/main/java/org/hibernate/envers/configuration/internal/metadata/MiddleTableCollectionMetadataGenerator.java @@ -237,7 +237,7 @@ public class MiddleTableCollectionMetadataGenerator extends AbstractCollectionMe MiddleIdData referencingIdData, String referencedPrefix, String auditMiddleEntityName) { - // Only if this is a relation (when there is a referenced entity). + // Only if this is a relation (when there is a referenced entity or a component). if ( context.getReferencedEntityName() != null ) { final IdMappingData referencedIdMapping = getReferencedIdMappingData( context.getReferencingEntityName(), @@ -270,5 +270,12 @@ public class MiddleTableCollectionMetadataGenerator extends AbstractCollectionMe ); } } + else { + context.getReferencingEntityConfiguration().addToManyComponent( + context.getPropertyName(), + auditMiddleEntityName, + referencingIdData + ); + } } } diff --git a/hibernate-envers/src/main/java/org/hibernate/envers/internal/entities/ComponentDescription.java b/hibernate-envers/src/main/java/org/hibernate/envers/internal/entities/ComponentDescription.java new file mode 100644 index 0000000000..95f003c15f --- /dev/null +++ b/hibernate-envers/src/main/java/org/hibernate/envers/internal/entities/ComponentDescription.java @@ -0,0 +1,66 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.envers.internal.entities; + +import org.hibernate.envers.configuration.internal.metadata.reader.ComponentAuditingData; +import org.hibernate.envers.internal.entities.mapper.relation.MiddleIdData; + +/** + * @author Felix Feisst (feisst dot felix at gmail dot com) + */ +public class ComponentDescription { + + private final ComponentType type; + private final String propertyName; + private final String auditMiddleEntityName; + private final MiddleIdData middleIdData; + private final ComponentAuditingData auditingData; + + private ComponentDescription(ComponentType type, + String propertyName, + String auditMiddleEntityName, + MiddleIdData middleIdData, + ComponentAuditingData componentAuditingData) { + this.type = type; + this.propertyName = propertyName; + this.auditMiddleEntityName = auditMiddleEntityName; + this.middleIdData = middleIdData; + this.auditingData = componentAuditingData; + } + + public static ComponentDescription many(String propertyName, String auditMiddleEntityName, MiddleIdData middleIdData) { + return new ComponentDescription( ComponentType.MANY, propertyName, auditMiddleEntityName, middleIdData, null ); + } + + public static ComponentDescription one(String propertyName, ComponentAuditingData componentAuditingData) { + return new ComponentDescription( ComponentType.ONE, propertyName, null, null, componentAuditingData ); + } + + public ComponentType getType() { + return type; + } + + public String getPropertyName() { + return propertyName; + } + + public String getAuditMiddleEntityName() { + return auditMiddleEntityName; + } + + public MiddleIdData getMiddleIdData() { + return middleIdData; + } + + public ComponentAuditingData getAuditingData() { + return auditingData; + } + + public enum ComponentType { + ONE, MANY + } +} diff --git a/hibernate-envers/src/main/java/org/hibernate/envers/internal/entities/EntitiesConfigurations.java b/hibernate-envers/src/main/java/org/hibernate/envers/internal/entities/EntitiesConfigurations.java index 98305a3924..8407954fa1 100644 --- a/hibernate-envers/src/main/java/org/hibernate/envers/internal/entities/EntitiesConfigurations.java +++ b/hibernate-envers/src/main/java/org/hibernate/envers/internal/entities/EntitiesConfigurations.java @@ -124,6 +124,27 @@ public class EntitiesConfigurations { return descriptions; } + public ComponentDescription getComponentDescription(final String entityName, final String propertyName) { + final EntityConfiguration entCfg; + if ( isVersioned( entityName ) ) { + entCfg = get( entityName ); + } + else { + entCfg = getNotVersionEntityConfiguration( entityName ); + } + final ComponentDescription relDesc = entCfg.getComponentDescription( propertyName ); + if ( relDesc != null ) { + return relDesc; + } + else if ( entCfg.getParentEntityName() != null ) { + // The field may be declared in a superclass ... + return getComponentDescription( entCfg.getParentEntityName(), propertyName ); + } + else { + return null; + } + } + private void addWithParentEntityNames(String entityName, Set entityNames) { entityNames.add( entityName ); final EntityConfiguration entCfg = entitiesConfigurations.get( entityName ); diff --git a/hibernate-envers/src/main/java/org/hibernate/envers/internal/entities/EntityConfiguration.java b/hibernate-envers/src/main/java/org/hibernate/envers/internal/entities/EntityConfiguration.java index 8a0bb4483e..74952e1f3c 100644 --- a/hibernate-envers/src/main/java/org/hibernate/envers/internal/entities/EntityConfiguration.java +++ b/hibernate-envers/src/main/java/org/hibernate/envers/internal/entities/EntityConfiguration.java @@ -9,6 +9,7 @@ package org.hibernate.envers.internal.entities; import java.util.HashMap; import java.util.Map; +import org.hibernate.envers.configuration.internal.metadata.reader.ComponentAuditingData; import org.hibernate.envers.internal.entities.mapper.ExtendedPropertyMapper; import org.hibernate.envers.internal.entities.mapper.PropertyMapper; import org.hibernate.envers.internal.entities.mapper.id.IdMapper; @@ -31,6 +32,7 @@ public class EntityConfiguration { private final ExtendedPropertyMapper propertyMapper; // Maps from property name private final Map relations; + private final Map components; private final String parentEntityName; public EntityConfiguration( @@ -46,6 +48,7 @@ public class EntityConfiguration { this.parentEntityName = parentEntityName; this.relations = new HashMap<>(); + this.components = new HashMap<>(); } public void addToOneRelation( @@ -170,6 +173,14 @@ public class EntityConfiguration { ); } + public void addToManyComponent(String propertyName, String auditMiddleEntityName, MiddleIdData middleIdData) { + components.put( propertyName, ComponentDescription.many( propertyName, auditMiddleEntityName, middleIdData ) ); + } + + public void addToOneComponent(String propertyName, ComponentAuditingData auditingData) { + components.put( propertyName, ComponentDescription.one( propertyName, auditingData ) ); + } + public boolean isRelation(String propertyName) { return relations.get( propertyName ) != null; } @@ -178,6 +189,10 @@ public class EntityConfiguration { return relations.get( propertyName ); } + public ComponentDescription getComponentDescription(String propertyName) { + return components.get( propertyName ); + } + public IdMappingData getIdMappingData() { return idMappingData; } diff --git a/hibernate-envers/src/main/java/org/hibernate/envers/internal/tools/query/Parameters.java b/hibernate-envers/src/main/java/org/hibernate/envers/internal/tools/query/Parameters.java index b1ba87f6ff..3212a26bfa 100644 --- a/hibernate-envers/src/main/java/org/hibernate/envers/internal/tools/query/Parameters.java +++ b/hibernate-envers/src/main/java/org/hibernate/envers/internal/tools/query/Parameters.java @@ -318,6 +318,7 @@ public class Parameters { * * @param configuration the configuration. * @param aliasToEntityNameMap alias to entity name map, never {@literal null} + * @param aliasToComponentPropertyNameMap alias to component property name map, never {@literal null} * @param function the function. * @param op the operator. * @param value the scalar value. @@ -325,6 +326,7 @@ public class Parameters { public void addWhereWithFunction( Configuration configuration, Map aliasToEntityNameMap, + Map aliasToComponentPropertyNameMap, AuditFunction function, String op, Object value) { @@ -333,6 +335,7 @@ public class Parameters { QueryBuilder.appendFunctionArgument( configuration, aliasToEntityNameMap, + aliasToComponentPropertyNameMap, queryParamCounter, localQueryParamValues, alias, @@ -354,6 +357,7 @@ public class Parameters { * * @param configuration the configuration. * @param aliasToEntityNameMap alias to entity name map, never {@literal null} + * @param aliasToComponentPropertyNameMap alias to component property name map, never {@literal null} * @param function the function. * @param op the operator. * @param aliasRight the optional alias of the right property, may be {@literal null} @@ -362,6 +366,7 @@ public class Parameters { public void addWhereWithFunction( Configuration configuration, Map aliasToEntityNameMap, + Map aliasToComponentPropertyNameMap, AuditFunction function, String op, String aliasRight, @@ -371,6 +376,7 @@ public class Parameters { QueryBuilder.appendFunctionArgument( configuration, aliasToEntityNameMap, + aliasToComponentPropertyNameMap, queryParamCounter, localQueryParamValues, alias, @@ -393,6 +399,7 @@ public class Parameters { * * @param configuration the configuration. * @param aliasToEntityNameMap alias to entity name map, never {@literal null} + * @param aliasToComponentPropertyNameMap alias to component property name map, never {@literal null} * @param aliasLeft the optional alias of the left property, may be {@literal null} * @param left the property. * @param op the operator. @@ -401,6 +408,7 @@ public class Parameters { public void addWhereWithFunction( Configuration configuration, Map aliasToEntityNameMap, + Map aliasToComponentPropertyNameMap, String aliasLeft, String left, String op, @@ -417,6 +425,7 @@ public class Parameters { QueryBuilder.appendFunctionArgument( configuration, aliasToEntityNameMap, + aliasToComponentPropertyNameMap, queryParamCounter, localQueryParamValues, alias, @@ -432,6 +441,7 @@ public class Parameters { * * @param configuration the configuration. * @param aliasToEntityNameMap alias to entity name map, never {@literal null} + * @param aliasToComponentPropertyNameMap alias to component property name map, never {@literal null} * @param left the left-side function. * @param op the operator. * @param right the right-side function. @@ -439,6 +449,7 @@ public class Parameters { public void addWhereWithFunction( Configuration configuration, Map aliasToEntityNameMap, + Map aliasToComponentPropertyNameMap, AuditFunction left, String op, AuditFunction right) { @@ -447,6 +458,7 @@ public class Parameters { QueryBuilder.appendFunctionArgument( configuration, aliasToEntityNameMap, + aliasToComponentPropertyNameMap, queryParamCounter, localQueryParamValues, alias, @@ -459,6 +471,7 @@ public class Parameters { QueryBuilder.appendFunctionArgument( configuration, aliasToEntityNameMap, + aliasToComponentPropertyNameMap, queryParamCounter, localQueryParamValues, alias, diff --git a/hibernate-envers/src/main/java/org/hibernate/envers/internal/tools/query/QueryBuilder.java b/hibernate-envers/src/main/java/org/hibernate/envers/internal/tools/query/QueryBuilder.java index f80def0857..e4794c9120 100644 --- a/hibernate-envers/src/main/java/org/hibernate/envers/internal/tools/query/QueryBuilder.java +++ b/hibernate-envers/src/main/java/org/hibernate/envers/internal/tools/query/QueryBuilder.java @@ -26,10 +26,10 @@ import org.hibernate.envers.internal.entities.RevisionTypeType; import org.hibernate.envers.internal.entities.mapper.id.QueryParameterData; import org.hibernate.envers.internal.tools.MutableInteger; import org.hibernate.envers.internal.tools.StringTools; -import org.hibernate.envers.internal.tools.Triple; import org.hibernate.envers.query.criteria.AuditFunction; import org.hibernate.envers.query.criteria.AuditId; import org.hibernate.envers.query.criteria.AuditProperty; +import org.hibernate.envers.query.criteria.internal.CriteriaTools; import org.hibernate.envers.query.order.NullPrecedence; import org.hibernate.envers.tools.Pair; import org.hibernate.query.Query; @@ -220,11 +220,16 @@ public class QueryBuilder { } } - public void addProjection(Configuration configuration, Map aliasToEntityNameMap, AuditFunction function) { + public void addProjection( + Configuration configuration, + Map aliasToEntityNameMap, + Map aliasToComponentPropertyNameMap, + AuditFunction function) { final StringBuilder expression = new StringBuilder(); appendFunctionArgument( configuration, aliasToEntityNameMap, + aliasToComponentPropertyNameMap, paramCounter, projectionQueryParamValues, alias, @@ -237,6 +242,7 @@ public class QueryBuilder { protected static void appendFunctionArgument( Configuration configuration, Map aliasToEntityNameMap, + Map aliasToComponentPropertyNameMap, MutableInteger paramCounter, Map queryParamValues, String alias, @@ -253,6 +259,7 @@ public class QueryBuilder { appendFunctionArgument( configuration, aliasToEntityNameMap, + aliasToComponentPropertyNameMap, paramCounter, queryParamValues, alias, @@ -292,8 +299,14 @@ public class QueryBuilder { if ( propertyAlias != null ) { expression.append( propertyAlias ).append( '.' ); } + String propertyPrefix = CriteriaTools.determineComponentPropertyPrefix( + configuration.getEnversService(), + aliasToEntityNameMap, + aliasToComponentPropertyNameMap, + propertyAlias + ); String propertyName = property.getPropertyNameGetter().get( configuration ); - expression.append( propertyName ); + expression.append( propertyPrefix.concat( propertyName ) ); } else { String queryParam = "_p" + paramCounter.getAndIncrease(); diff --git a/hibernate-envers/src/main/java/org/hibernate/envers/query/criteria/AggregatedAuditExpression.java b/hibernate-envers/src/main/java/org/hibernate/envers/query/criteria/AggregatedAuditExpression.java index 848f169af7..a98ccc2a1b 100644 --- a/hibernate-envers/src/main/java/org/hibernate/envers/query/criteria/AggregatedAuditExpression.java +++ b/hibernate-envers/src/main/java/org/hibernate/envers/query/criteria/AggregatedAuditExpression.java @@ -53,6 +53,7 @@ public class AggregatedAuditExpression implements AuditCriterion, ExtendableCrit EnversService enversService, AuditReaderImplementor versionsReader, Map aliasToEntityNameMap, + Map aliasToComponentPropertyNameMap, String baseAlias, QueryBuilder qb, Parameters parameters) { @@ -64,8 +65,15 @@ public class AggregatedAuditExpression implements AuditCriterion, ExtendableCrit entityName, propertyNameGetter ); + String componentPrefix = CriteriaTools.determineComponentPropertyPrefix( + enversService, + aliasToEntityNameMap, + aliasToComponentPropertyNameMap, + effectiveAlias + ); + String prefixedPropertyName = componentPrefix.concat( propertyName ); - CriteriaTools.checkPropertyNotARelation( enversService, entityName, propertyName ); + CriteriaTools.checkPropertyNotARelation( enversService, entityName, prefixedPropertyName ); // Make sure our conditions are ANDed together even if the parent Parameters have a different connective Parameters subParams = parameters.addSubParameters( Parameters.AND ); @@ -78,17 +86,34 @@ public class AggregatedAuditExpression implements AuditCriterion, ExtendableCrit // Adding all specified conditions both to the main query, as well as to the // aggregated one. for ( AuditCriterion versionsCriteria : criterions ) { - versionsCriteria.addToQuery( enversService, versionsReader, aliasToEntityNameMap, effectiveAlias, qb, subParams ); - versionsCriteria.addToQuery( enversService, versionsReader, aliasToEntityNameMap, subQueryAlias, subQb, subQb.getRootParameters() ); + versionsCriteria.addToQuery( + enversService, + versionsReader, + aliasToEntityNameMap, + aliasToComponentPropertyNameMap, + effectiveAlias, + qb, + subParams + ); + + versionsCriteria.addToQuery( + enversService, + versionsReader, + aliasToEntityNameMap, + aliasToComponentPropertyNameMap, + subQueryAlias, + subQb, + subQb.getRootParameters() + ); } // Setting the desired projection of the aggregated query switch ( mode ) { case MIN: - subQb.addProjection( "min", subQb.getAlias(), propertyName, false ); + subQb.addProjection( "min", subQb.getAlias(), prefixedPropertyName, false ); break; case MAX: - subQb.addProjection( "max", subQb.getAlias(), propertyName, false ); + subQb.addProjection( "max", subQb.getAlias(), prefixedPropertyName, false ); } // Correlating subquery with the outer query by entity id. See JIRA HHH-7827. @@ -102,7 +127,7 @@ public class AggregatedAuditExpression implements AuditCriterion, ExtendableCrit } // Adding the constrain on the result of the aggregated criteria - subParams.addWhere( effectiveAlias, propertyName, "=", subQb ); + subParams.addWhere( effectiveAlias, prefixedPropertyName, "=", subQb ); } /** diff --git a/hibernate-envers/src/main/java/org/hibernate/envers/query/criteria/AuditConjunction.java b/hibernate-envers/src/main/java/org/hibernate/envers/query/criteria/AuditConjunction.java index 35b0ab0a8e..1900b3cf2f 100644 --- a/hibernate-envers/src/main/java/org/hibernate/envers/query/criteria/AuditConjunction.java +++ b/hibernate-envers/src/main/java/org/hibernate/envers/query/criteria/AuditConjunction.java @@ -36,6 +36,7 @@ public class AuditConjunction implements AuditCriterion, ExtendableCriterion { EnversService enversService, AuditReaderImplementor versionsReader, Map aliasToEntityNameMap, + Map aliasToComponentPropertyNameMap, String alias, QueryBuilder qb, Parameters parameters) { @@ -46,7 +47,15 @@ public class AuditConjunction implements AuditCriterion, ExtendableCriterion { } else { for ( AuditCriterion criterion : criterions ) { - criterion.addToQuery( enversService, versionsReader, aliasToEntityNameMap, alias, qb, andParameters ); + criterion.addToQuery( + enversService, + versionsReader, + aliasToEntityNameMap, + aliasToComponentPropertyNameMap, + alias, + qb, + andParameters + ); } } } diff --git a/hibernate-envers/src/main/java/org/hibernate/envers/query/criteria/AuditCriterion.java b/hibernate-envers/src/main/java/org/hibernate/envers/query/criteria/AuditCriterion.java index d74c77b335..598988d8a5 100644 --- a/hibernate-envers/src/main/java/org/hibernate/envers/query/criteria/AuditCriterion.java +++ b/hibernate-envers/src/main/java/org/hibernate/envers/query/criteria/AuditCriterion.java @@ -21,6 +21,7 @@ public interface AuditCriterion { EnversService enversService, AuditReaderImplementor versionsReader, Map aliasToEntityNameMap, + Map aliasToComponentPropertyNameMap, String baseAlias, QueryBuilder qb, Parameters parameters); diff --git a/hibernate-envers/src/main/java/org/hibernate/envers/query/criteria/AuditDisjunction.java b/hibernate-envers/src/main/java/org/hibernate/envers/query/criteria/AuditDisjunction.java index 6e02fc1487..1d06f5010c 100644 --- a/hibernate-envers/src/main/java/org/hibernate/envers/query/criteria/AuditDisjunction.java +++ b/hibernate-envers/src/main/java/org/hibernate/envers/query/criteria/AuditDisjunction.java @@ -36,6 +36,7 @@ public class AuditDisjunction implements AuditCriterion, ExtendableCriterion { EnversService enversService, AuditReaderImplementor versionsReader, Map aliasToEntityNameMap, + Map aliasToComponentPropertyNameMap, String alias, QueryBuilder qb, Parameters parameters) { @@ -46,7 +47,15 @@ public class AuditDisjunction implements AuditCriterion, ExtendableCriterion { } else { for ( AuditCriterion criterion : criterions ) { - criterion.addToQuery( enversService, versionsReader, aliasToEntityNameMap, alias, qb, orParameters ); + criterion.addToQuery( + enversService, + versionsReader, + aliasToEntityNameMap, + aliasToComponentPropertyNameMap, + alias, + qb, + orParameters + ); } } } diff --git a/hibernate-envers/src/main/java/org/hibernate/envers/query/criteria/AuditFunction.java b/hibernate-envers/src/main/java/org/hibernate/envers/query/criteria/AuditFunction.java index a5ec6de698..f897717d86 100644 --- a/hibernate-envers/src/main/java/org/hibernate/envers/query/criteria/AuditFunction.java +++ b/hibernate-envers/src/main/java/org/hibernate/envers/query/criteria/AuditFunction.java @@ -240,9 +240,15 @@ public class AuditFunction implements AuditProjection { EnversService enversService, AuditReaderImplementor auditReader, Map aliasToEntityNameMap, + Map aliasToComponentPropertyNameMap, String baseAlias, QueryBuilder queryBuilder) { - queryBuilder.addProjection( enversService.getConfig(), aliasToEntityNameMap, this ); + queryBuilder.addProjection( + enversService.getConfig(), + aliasToEntityNameMap, + aliasToComponentPropertyNameMap, + this + ); } @Override diff --git a/hibernate-envers/src/main/java/org/hibernate/envers/query/criteria/AuditProperty.java b/hibernate-envers/src/main/java/org/hibernate/envers/query/criteria/AuditProperty.java index dd7f8e9c74..df07c95cad 100644 --- a/hibernate-envers/src/main/java/org/hibernate/envers/query/criteria/AuditProperty.java +++ b/hibernate-envers/src/main/java/org/hibernate/envers/query/criteria/AuditProperty.java @@ -405,20 +405,33 @@ public class AuditProperty implements AuditProjection { // Projection on this property @Override - public void addProjectionToQuery(EnversService enversService, AuditReaderImplementor auditReader, - Map aliasToEntityNameMap, String baseAlias, QueryBuilder queryBuilder) { + public void addProjectionToQuery( + EnversService enversService, + AuditReaderImplementor auditReader, + Map aliasToEntityNameMap, + Map aliasToComponentPropertyNameMap, + String baseAlias, + QueryBuilder queryBuilder) { String projectionEntityAlias = getAlias( baseAlias ); String projectionEntityName = aliasToEntityNameMap.get( projectionEntityAlias ); String propertyName = CriteriaTools.determinePropertyName( enversService, auditReader, projectionEntityName, - propertyNameGetter ); + propertyNameGetter + ); + String propertyNamePrefix = CriteriaTools.determineComponentPropertyPrefix( + enversService, + aliasToEntityNameMap, + aliasToComponentPropertyNameMap, + projectionEntityAlias + ); queryBuilder.addProjection( null, projectionEntityAlias, - propertyName, - false ); + propertyNamePrefix.concat( propertyName ), + false + ); } // Order @@ -438,7 +451,12 @@ public class AuditProperty implements AuditProjection { } @Override - public Object convertQueryResult(EnversService enversService, EntityInstantiator entityInstantiator, String entityName, Number revision, Object value) { + public Object convertQueryResult( + EnversService enversService, + EntityInstantiator entityInstantiator, + String entityName, + Number revision, + Object value) { return value; } diff --git a/hibernate-envers/src/main/java/org/hibernate/envers/query/criteria/internal/AbstractAtomicExpression.java b/hibernate-envers/src/main/java/org/hibernate/envers/query/criteria/internal/AbstractAtomicExpression.java index e5eceeffa7..44c855c90d 100644 --- a/hibernate-envers/src/main/java/org/hibernate/envers/query/criteria/internal/AbstractAtomicExpression.java +++ b/hibernate-envers/src/main/java/org/hibernate/envers/query/criteria/internal/AbstractAtomicExpression.java @@ -21,9 +21,9 @@ import org.hibernate.envers.query.criteria.AuditCriterion; * the corresponding entity name. The effect alias is either the alias that has been * specified at creation time of this expression or if that alias is null, the base * alias is used. This calculation is done in the - * {@link AuditCriterion#addToQuery(EnversService, AuditReaderImplementor, Map, String, QueryBuilder, Parameters)} + * {@link AuditCriterion#addToQuery(EnversService, AuditReaderImplementor, Map, Map, String, QueryBuilder, Parameters)} * implementation and then delegated for the concrete work to the template method - * {@link #addToQuery(EnversService, AuditReaderImplementor, String, String, QueryBuilder, Parameters)}. + * {@link #addToQuery(EnversService, AuditReaderImplementor, String, String, String, QueryBuilder, Parameters)} * * @author Felix Feisst (feisst dot felix at gmail dot com) */ @@ -40,12 +40,19 @@ abstract class AbstractAtomicExpression implements AuditCriterion { EnversService enversService, AuditReaderImplementor versionsReader, Map aliasToEntityNameMap, + Map aliasToComponentPropertyNameMap, String baseAlias, QueryBuilder qb, Parameters parameters) { final String effectiveAlias = alias == null ? baseAlias : alias; final String entityName = aliasToEntityNameMap.get( effectiveAlias ); - addToQuery(enversService, versionsReader, entityName, effectiveAlias, qb, parameters); + final String componentPrefix = CriteriaTools.determineComponentPropertyPrefix( + enversService, + aliasToEntityNameMap, + aliasToComponentPropertyNameMap, + effectiveAlias + ); + addToQuery(enversService, versionsReader, entityName, effectiveAlias, componentPrefix, qb, parameters); } protected abstract void addToQuery( @@ -53,6 +60,7 @@ abstract class AbstractAtomicExpression implements AuditCriterion { AuditReaderImplementor versionsReader, String entityName, String alias, + String componentPrefix, QueryBuilder qb, Parameters parameters); diff --git a/hibernate-envers/src/main/java/org/hibernate/envers/query/criteria/internal/BetweenAuditExpression.java b/hibernate-envers/src/main/java/org/hibernate/envers/query/criteria/internal/BetweenAuditExpression.java index 455826ef5b..bf95efaa61 100644 --- a/hibernate-envers/src/main/java/org/hibernate/envers/query/criteria/internal/BetweenAuditExpression.java +++ b/hibernate-envers/src/main/java/org/hibernate/envers/query/criteria/internal/BetweenAuditExpression.java @@ -33,6 +33,7 @@ public class BetweenAuditExpression extends AbstractAtomicExpression { AuditReaderImplementor versionsReader, String entityName, String alias, + String componentPrefix, QueryBuilder qb, Parameters parameters) { String propertyName = CriteriaTools.determinePropertyName( @@ -41,10 +42,11 @@ public class BetweenAuditExpression extends AbstractAtomicExpression { entityName, propertyNameGetter ); - CriteriaTools.checkPropertyNotARelation( enversService, entityName, propertyName ); + String prefixedPropertyName = componentPrefix.concat( propertyName ); + CriteriaTools.checkPropertyNotARelation( enversService, entityName, prefixedPropertyName ); Parameters subParams = parameters.addSubParameters( Parameters.AND ); - subParams.addWhereWithParam( alias, propertyName, ">=", lo ); - subParams.addWhereWithParam( alias, propertyName, "<=", hi ); + subParams.addWhereWithParam( alias, prefixedPropertyName, ">=", lo ); + subParams.addWhereWithParam( alias, prefixedPropertyName, "<=", hi ); } } diff --git a/hibernate-envers/src/main/java/org/hibernate/envers/query/criteria/internal/CriteriaTools.java b/hibernate-envers/src/main/java/org/hibernate/envers/query/criteria/internal/CriteriaTools.java index a50b058d58..27dbfcf8e8 100644 --- a/hibernate-envers/src/main/java/org/hibernate/envers/query/criteria/internal/CriteriaTools.java +++ b/hibernate-envers/src/main/java/org/hibernate/envers/query/criteria/internal/CriteriaTools.java @@ -10,10 +10,12 @@ import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Locale; +import java.util.Map; import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.envers.boot.internal.EnversService; import org.hibernate.envers.exception.AuditException; +import org.hibernate.envers.internal.entities.ComponentDescription; import org.hibernate.envers.internal.entities.RelationDescription; import org.hibernate.envers.internal.entities.RelationType; import org.hibernate.envers.internal.reader.AuditReaderImplementor; @@ -66,6 +68,13 @@ public abstract class CriteriaTools { ); } + public static ComponentDescription getComponent( + EnversService enversService, + String entityName, + String propertyName) { + return enversService.getEntitiesConfigurations().getComponentDescription( entityName, propertyName ); + } + public static String determinePropertyName( EnversService enversService, AuditReaderImplementor versionsReader, @@ -119,6 +128,36 @@ public abstract class CriteriaTools { return propertyName; } + /** + * @param enversService The EnversService + * @param aliasToEntityNameMap the map from aliases to entity names + * @param aliasToComponentPropertyNameMap the map from aliases to component property name, if an alias is for a + * component + * @param alias the alias + * @return The prefix that has to be used when referring to a property of a component. If no prefix is required or + * the alias is not a component, the empty string is returned (but never null) + */ + public static String determineComponentPropertyPrefix( + EnversService enversService, + Map aliasToEntityNameMap, + Map aliasToComponentPropertyNameMap, + String alias) { + String componentPrefix = ""; + final String entityName = aliasToEntityNameMap.get( alias ); + final String owningComponentPropertyName = aliasToComponentPropertyNameMap.get( alias ); + if ( owningComponentPropertyName != null ) { + final ComponentDescription componentDescription = CriteriaTools.getComponent( + enversService, + entityName, + owningComponentPropertyName + ); + if ( componentDescription.getType() == ComponentDescription.ComponentType.ONE ) { + componentPrefix = componentDescription.getPropertyName().concat( "_" ); + } + } + return componentPrefix; + } + /** * @param sessionFactory Session factory. * @param entityName Entity name. diff --git a/hibernate-envers/src/main/java/org/hibernate/envers/query/criteria/internal/FunctionFunctionAuditExpression.java b/hibernate-envers/src/main/java/org/hibernate/envers/query/criteria/internal/FunctionFunctionAuditExpression.java index 2661c1eb62..bea7065b0b 100644 --- a/hibernate-envers/src/main/java/org/hibernate/envers/query/criteria/internal/FunctionFunctionAuditExpression.java +++ b/hibernate-envers/src/main/java/org/hibernate/envers/query/criteria/internal/FunctionFunctionAuditExpression.java @@ -40,12 +40,14 @@ public class FunctionFunctionAuditExpression implements AuditCriterion { EnversService enversService, AuditReaderImplementor auditReader, Map aliasToEntityNameMap, + Map aliasToComponentPropertyNameMap, String baseAlias, QueryBuilder queryBuilder, Parameters parameters) { parameters.addWhereWithFunction( enversService.getConfig(), aliasToEntityNameMap, + aliasToComponentPropertyNameMap, leftFunction, op, rightFunction diff --git a/hibernate-envers/src/main/java/org/hibernate/envers/query/criteria/internal/FunctionPropertyAuditExpression.java b/hibernate-envers/src/main/java/org/hibernate/envers/query/criteria/internal/FunctionPropertyAuditExpression.java index f8906d0271..333ad20aaf 100644 --- a/hibernate-envers/src/main/java/org/hibernate/envers/query/criteria/internal/FunctionPropertyAuditExpression.java +++ b/hibernate-envers/src/main/java/org/hibernate/envers/query/criteria/internal/FunctionPropertyAuditExpression.java @@ -45,6 +45,7 @@ public class FunctionPropertyAuditExpression implements AuditCriterion { EnversService enversService, AuditReaderImplementor auditReader, Map aliasToEntityNameMap, + Map aliasToComponentPropertyNameMap, String baseAlias, QueryBuilder queryBuilder, Parameters parameters) { @@ -54,13 +55,22 @@ public class FunctionPropertyAuditExpression implements AuditCriterion { enversService, auditReader, entityName, - propertyNameGetter ); - CriteriaTools.checkPropertyNotARelation( enversService, entityName, propertyName ); + propertyNameGetter + ); + String propertyNamePrefix = CriteriaTools.determineComponentPropertyPrefix( + enversService, + aliasToEntityNameMap, + aliasToComponentPropertyNameMap, + effectiveAlias + ); + String prefixedPropertyName = propertyNamePrefix.concat( propertyName ); + CriteriaTools.checkPropertyNotARelation( enversService, entityName, prefixedPropertyName ); parameters.addWhereWithFunction( enversService.getConfig(), aliasToEntityNameMap, + aliasToComponentPropertyNameMap, effectiveAlias, - propertyName, + prefixedPropertyName, op, function ); diff --git a/hibernate-envers/src/main/java/org/hibernate/envers/query/criteria/internal/IdentifierEqAuditExpression.java b/hibernate-envers/src/main/java/org/hibernate/envers/query/criteria/internal/IdentifierEqAuditExpression.java index 9fdbb7e62b..08b56609fa 100644 --- a/hibernate-envers/src/main/java/org/hibernate/envers/query/criteria/internal/IdentifierEqAuditExpression.java +++ b/hibernate-envers/src/main/java/org/hibernate/envers/query/criteria/internal/IdentifierEqAuditExpression.java @@ -33,6 +33,7 @@ public class IdentifierEqAuditExpression extends AbstractAtomicExpression { AuditReaderImplementor versionsReader, String entityName, String alias, + String componentPrefix, QueryBuilder qb, Parameters parameters) { String prefix = enversService.getConfig().getOriginalIdPropertyName(); diff --git a/hibernate-envers/src/main/java/org/hibernate/envers/query/criteria/internal/IlikeAuditExpression.java b/hibernate-envers/src/main/java/org/hibernate/envers/query/criteria/internal/IlikeAuditExpression.java index f98d088c31..10c9eb9999 100644 --- a/hibernate-envers/src/main/java/org/hibernate/envers/query/criteria/internal/IlikeAuditExpression.java +++ b/hibernate-envers/src/main/java/org/hibernate/envers/query/criteria/internal/IlikeAuditExpression.java @@ -27,8 +27,12 @@ public class IlikeAuditExpression extends AbstractAtomicExpression { @Override protected void addToQuery( EnversService enversService, - AuditReaderImplementor versionsReader, String entityName, - String alias, QueryBuilder qb, Parameters parameters) { + AuditReaderImplementor versionsReader, + String entityName, + String alias, + String componentPrefix, + QueryBuilder qb, + Parameters parameters) { String propertyName = CriteriaTools.determinePropertyName( enversService, @@ -36,9 +40,10 @@ public class IlikeAuditExpression extends AbstractAtomicExpression { entityName, propertyNameGetter ); - CriteriaTools.checkPropertyNotARelation( enversService, entityName, propertyName ); - parameters.addWhereWithFunction( alias, propertyName, " lower ", " like ", value.toLowerCase( Locale.ROOT ) ); + String prefixedPropertyName = componentPrefix.concat( propertyName ); + CriteriaTools.checkPropertyNotARelation( enversService, entityName, prefixedPropertyName ); + parameters.addWhereWithFunction( alias, prefixedPropertyName, " lower ", " like ", value.toLowerCase( Locale.ROOT ) ); } } diff --git a/hibernate-envers/src/main/java/org/hibernate/envers/query/criteria/internal/InAuditExpression.java b/hibernate-envers/src/main/java/org/hibernate/envers/query/criteria/internal/InAuditExpression.java index 7ed5ea8cc1..bc8160a857 100644 --- a/hibernate-envers/src/main/java/org/hibernate/envers/query/criteria/internal/InAuditExpression.java +++ b/hibernate-envers/src/main/java/org/hibernate/envers/query/criteria/internal/InAuditExpression.java @@ -31,6 +31,7 @@ public class InAuditExpression extends AbstractAtomicExpression { AuditReaderImplementor versionsReader, String entityName, String alias, + String componentPrefix, QueryBuilder qb, Parameters parameters) { String propertyName = CriteriaTools.determinePropertyName( @@ -39,7 +40,8 @@ public class InAuditExpression extends AbstractAtomicExpression { entityName, propertyNameGetter ); - CriteriaTools.checkPropertyNotARelation( enversService, entityName, propertyName ); - parameters.addWhereWithParams( alias, propertyName, "in (", values, ")" ); + String prefixedPropertyName = componentPrefix.concat( propertyName ); + CriteriaTools.checkPropertyNotARelation( enversService, entityName, prefixedPropertyName ); + parameters.addWhereWithParams( alias, prefixedPropertyName, "in (", values, ")" ); } } diff --git a/hibernate-envers/src/main/java/org/hibernate/envers/query/criteria/internal/LogicalAuditExpression.java b/hibernate-envers/src/main/java/org/hibernate/envers/query/criteria/internal/LogicalAuditExpression.java index 92431b9a48..2fd9b9f77d 100644 --- a/hibernate-envers/src/main/java/org/hibernate/envers/query/criteria/internal/LogicalAuditExpression.java +++ b/hibernate-envers/src/main/java/org/hibernate/envers/query/criteria/internal/LogicalAuditExpression.java @@ -32,12 +32,30 @@ public class LogicalAuditExpression implements AuditCriterion { EnversService enversService, AuditReaderImplementor versionsReader, Map aliasToEntityNameMap, + Map aliasToComponentPropertyNameMap, String alias, QueryBuilder qb, Parameters parameters) { Parameters opParameters = parameters.addSubParameters( op ); - lhs.addToQuery( enversService, versionsReader, aliasToEntityNameMap, alias, qb, opParameters.addSubParameters( "and" ) ); - rhs.addToQuery( enversService, versionsReader, aliasToEntityNameMap, alias, qb, opParameters.addSubParameters( "and" ) ); + lhs.addToQuery( + enversService, + versionsReader, + aliasToEntityNameMap, + aliasToComponentPropertyNameMap, + alias, + qb, + opParameters.addSubParameters( "and" ) + ); + + rhs.addToQuery( + enversService, + versionsReader, + aliasToEntityNameMap, + aliasToComponentPropertyNameMap, + alias, + qb, + opParameters.addSubParameters( "and" ) + ); } } diff --git a/hibernate-envers/src/main/java/org/hibernate/envers/query/criteria/internal/NotAuditExpression.java b/hibernate-envers/src/main/java/org/hibernate/envers/query/criteria/internal/NotAuditExpression.java index a6c27368fc..648afae9fe 100644 --- a/hibernate-envers/src/main/java/org/hibernate/envers/query/criteria/internal/NotAuditExpression.java +++ b/hibernate-envers/src/main/java/org/hibernate/envers/query/criteria/internal/NotAuditExpression.java @@ -28,9 +28,18 @@ public class NotAuditExpression implements AuditCriterion { EnversService enversService, AuditReaderImplementor versionsReader, Map aliasToEntityNameMap, + Map aliasToComponentPropertyNameMap, String alias, QueryBuilder qb, Parameters parameters) { - criterion.addToQuery( enversService, versionsReader, aliasToEntityNameMap, alias, qb, parameters.addNegatedParameters() ); + criterion.addToQuery( + enversService, + versionsReader, + aliasToEntityNameMap, + aliasToComponentPropertyNameMap, + alias, + qb, + parameters.addNegatedParameters() + ); } } diff --git a/hibernate-envers/src/main/java/org/hibernate/envers/query/criteria/internal/NotNullAuditExpression.java b/hibernate-envers/src/main/java/org/hibernate/envers/query/criteria/internal/NotNullAuditExpression.java index 4f018c8e2f..695a96eb3c 100644 --- a/hibernate-envers/src/main/java/org/hibernate/envers/query/criteria/internal/NotNullAuditExpression.java +++ b/hibernate-envers/src/main/java/org/hibernate/envers/query/criteria/internal/NotNullAuditExpression.java @@ -34,6 +34,7 @@ public class NotNullAuditExpression extends AbstractAtomicExpression { AuditReaderImplementor versionsReader, String entityName, String alias, + String componentPrefix, QueryBuilder qb, Parameters parameters) { String propertyName = CriteriaTools.determinePropertyName( @@ -42,10 +43,11 @@ public class NotNullAuditExpression extends AbstractAtomicExpression { entityName, propertyNameGetter ); - RelationDescription relatedEntity = CriteriaTools.getRelatedEntity( enversService, entityName, propertyName ); + String prefixedPropertyName = componentPrefix.concat( propertyName ); + RelationDescription relatedEntity = CriteriaTools.getRelatedEntity( enversService, entityName, prefixedPropertyName ); if ( relatedEntity == null ) { - parameters.addNotNullRestriction( alias, propertyName ); + parameters.addNotNullRestriction( alias, prefixedPropertyName ); } else if ( relatedEntity.getRelationType() == RelationType.TO_ONE ) { relatedEntity.getIdMapper().addIdEqualsToQuery( parameters, null, alias, null, false ); diff --git a/hibernate-envers/src/main/java/org/hibernate/envers/query/criteria/internal/NullAuditExpression.java b/hibernate-envers/src/main/java/org/hibernate/envers/query/criteria/internal/NullAuditExpression.java index 65f183480e..90c6a08212 100644 --- a/hibernate-envers/src/main/java/org/hibernate/envers/query/criteria/internal/NullAuditExpression.java +++ b/hibernate-envers/src/main/java/org/hibernate/envers/query/criteria/internal/NullAuditExpression.java @@ -34,6 +34,7 @@ public class NullAuditExpression extends AbstractAtomicExpression { AuditReaderImplementor versionsReader, String entityName, String alias, + String componentPrefix, QueryBuilder qb, Parameters parameters) { String propertyName = CriteriaTools.determinePropertyName( @@ -43,9 +44,10 @@ public class NullAuditExpression extends AbstractAtomicExpression { propertyNameGetter ); RelationDescription relatedEntity = CriteriaTools.getRelatedEntity( enversService, entityName, propertyName ); + String prefixedPropertyName = componentPrefix.concat( propertyName ); if ( relatedEntity == null ) { - parameters.addNullRestriction( alias, propertyName ); + parameters.addNullRestriction( alias, prefixedPropertyName ); } else if ( relatedEntity.getRelationType() == RelationType.TO_ONE ) { relatedEntity.getIdMapper().addIdEqualsToQuery( parameters, null, alias, null, true ); diff --git a/hibernate-envers/src/main/java/org/hibernate/envers/query/criteria/internal/PropertyAuditExpression.java b/hibernate-envers/src/main/java/org/hibernate/envers/query/criteria/internal/PropertyAuditExpression.java index cf80e9311c..e90fd780c8 100644 --- a/hibernate-envers/src/main/java/org/hibernate/envers/query/criteria/internal/PropertyAuditExpression.java +++ b/hibernate-envers/src/main/java/org/hibernate/envers/query/criteria/internal/PropertyAuditExpression.java @@ -44,6 +44,7 @@ public class PropertyAuditExpression implements AuditCriterion { EnversService enversService, AuditReaderImplementor versionsReader, Map aliasToEntityNameMap, + Map aliasToComponentPropertyNameMap, String baseAlias, QueryBuilder qb, Parameters parameters) { @@ -57,15 +58,29 @@ public class PropertyAuditExpression implements AuditCriterion { entityName, propertyNameGetter ); - CriteriaTools.checkPropertyNotARelation( enversService, entityName, propertyName ); + String propertyNamePrefix = CriteriaTools.determineComponentPropertyPrefix( + enversService, + aliasToEntityNameMap, + aliasToComponentPropertyNameMap, + effectiveAlias + ); + String otherPropertyNamePrefix = CriteriaTools.determineComponentPropertyPrefix( + enversService, + aliasToEntityNameMap, + aliasToComponentPropertyNameMap, + effectiveOtherAlias + ); + String prefixedPropertyName = propertyNamePrefix.concat( propertyName ); + String prefixedOtherPropertyName = otherPropertyNamePrefix.concat( otherPropertyName ); + CriteriaTools.checkPropertyNotARelation( enversService, entityName, prefixedPropertyName ); /* * Check that the other property name is not a relation. However, we can only * do this for audited entities. If the other property belongs to a non-audited * entity, we have to skip this check. */ if ( enversService.getEntitiesConfigurations().isVersioned( otherEntityName ) ) { - CriteriaTools.checkPropertyNotARelation( enversService, otherEntityName, otherPropertyName ); + CriteriaTools.checkPropertyNotARelation( enversService, otherEntityName, prefixedOtherPropertyName ); } - parameters.addWhere( effectiveAlias, propertyName, op, effectiveOtherAlias, otherPropertyName ); + parameters.addWhere( effectiveAlias, prefixedPropertyName, op, effectiveOtherAlias, prefixedOtherPropertyName ); } } diff --git a/hibernate-envers/src/main/java/org/hibernate/envers/query/criteria/internal/PropertyFunctionAuditExpression.java b/hibernate-envers/src/main/java/org/hibernate/envers/query/criteria/internal/PropertyFunctionAuditExpression.java index 13b73611a0..e940bc9ab5 100644 --- a/hibernate-envers/src/main/java/org/hibernate/envers/query/criteria/internal/PropertyFunctionAuditExpression.java +++ b/hibernate-envers/src/main/java/org/hibernate/envers/query/criteria/internal/PropertyFunctionAuditExpression.java @@ -43,6 +43,7 @@ public class PropertyFunctionAuditExpression implements AuditCriterion { EnversService enversService, AuditReaderImplementor auditReader, Map aliasToEntityNameMap, + Map aliasToComponentPropertyNameMap, String baseAlias, QueryBuilder queryBuilder, Parameters parameters) { @@ -53,11 +54,21 @@ public class PropertyFunctionAuditExpression implements AuditCriterion { * the other property belongs to a non-audited entity, we have to skip this check. */ if ( enversService.getEntitiesConfigurations().isVersioned( otherEntityName ) ) { - CriteriaTools.checkPropertyNotARelation( enversService, otherEntityName, otherPropertyName ); + String otherPropertyNamePrefix = CriteriaTools.determineComponentPropertyPrefix( + enversService, + aliasToEntityNameMap, + aliasToComponentPropertyNameMap, effectiveOtherAlias + ); + CriteriaTools.checkPropertyNotARelation( + enversService, + otherEntityName, + otherPropertyNamePrefix.concat( otherPropertyName ) + ); } parameters.addWhereWithFunction( enversService.getConfig(), aliasToEntityNameMap, + aliasToComponentPropertyNameMap, function, op, effectiveOtherAlias, diff --git a/hibernate-envers/src/main/java/org/hibernate/envers/query/criteria/internal/RelatedAuditEqualityExpression.java b/hibernate-envers/src/main/java/org/hibernate/envers/query/criteria/internal/RelatedAuditEqualityExpression.java index 0c947c8c1b..01ca6ba310 100644 --- a/hibernate-envers/src/main/java/org/hibernate/envers/query/criteria/internal/RelatedAuditEqualityExpression.java +++ b/hibernate-envers/src/main/java/org/hibernate/envers/query/criteria/internal/RelatedAuditEqualityExpression.java @@ -39,6 +39,7 @@ public class RelatedAuditEqualityExpression extends AbstractAtomicExpression { AuditReaderImplementor versionsReader, String entityName, String alias, + String componentPrefix, QueryBuilder qb, Parameters parameters) { String propertyName = CriteriaTools.determinePropertyName( @@ -48,7 +49,12 @@ public class RelatedAuditEqualityExpression extends AbstractAtomicExpression { propertyNameGetter ); - RelationDescription relatedEntity = CriteriaTools.getRelatedEntity( enversService, entityName, propertyName ); + RelationDescription relatedEntity = CriteriaTools.getRelatedEntity( + enversService, + entityName, + componentPrefix.concat( propertyName ) + ); + if ( relatedEntity == null ) { throw new AuditException( "This criterion can only be used on a property that is a relation to another property." ); diff --git a/hibernate-envers/src/main/java/org/hibernate/envers/query/criteria/internal/RelatedAuditInExpression.java b/hibernate-envers/src/main/java/org/hibernate/envers/query/criteria/internal/RelatedAuditInExpression.java index 70c920c9a3..9160e23bdd 100644 --- a/hibernate-envers/src/main/java/org/hibernate/envers/query/criteria/internal/RelatedAuditInExpression.java +++ b/hibernate-envers/src/main/java/org/hibernate/envers/query/criteria/internal/RelatedAuditInExpression.java @@ -40,6 +40,7 @@ public class RelatedAuditInExpression extends AbstractAtomicExpression { AuditReaderImplementor versionsReader, String entityName, String alias, + String componentPrefix, QueryBuilder qb, Parameters parameters) { String propertyName = CriteriaTools.determinePropertyName( @@ -49,7 +50,12 @@ public class RelatedAuditInExpression extends AbstractAtomicExpression { propertyNameGetter ); - RelationDescription relatedEntity = CriteriaTools.getRelatedEntity( enversService, entityName, propertyName ); + RelationDescription relatedEntity = CriteriaTools.getRelatedEntity( + enversService, + entityName, + componentPrefix.concat( propertyName ) + ); + if ( relatedEntity == null ) { throw new AuditException( "The criterion can only be used on a property that is a relation to another property." ); @@ -69,7 +75,7 @@ public class RelatedAuditInExpression extends AbstractAtomicExpression { List qpdList = relatedEntity.getIdMapper().mapToQueryParametersFromId( propertyName ); if ( qpdList != null ) { QueryParameterData qpd = qpdList.iterator().next(); - parameters.addWhereWithParams( alias, qpd.getQueryParameterName(), "in (", ids, ")" ); + parameters.addWhereWithParams( alias, componentPrefix.concat( qpd.getQueryParameterName() ), "in (", ids, ")" ); } } } diff --git a/hibernate-envers/src/main/java/org/hibernate/envers/query/criteria/internal/RevisionTypeAuditExpression.java b/hibernate-envers/src/main/java/org/hibernate/envers/query/criteria/internal/RevisionTypeAuditExpression.java index 17423db78e..d9b177f82b 100644 --- a/hibernate-envers/src/main/java/org/hibernate/envers/query/criteria/internal/RevisionTypeAuditExpression.java +++ b/hibernate-envers/src/main/java/org/hibernate/envers/query/criteria/internal/RevisionTypeAuditExpression.java @@ -31,6 +31,7 @@ public class RevisionTypeAuditExpression extends AbstractAtomicExpression { AuditReaderImplementor versionsReader, String entityName, String alias, + String componentPrefix, QueryBuilder qb, Parameters parameters) { parameters.addWhereWithParam( alias, enversService.getConfig().getRevisionTypePropertyName(), op, value ); diff --git a/hibernate-envers/src/main/java/org/hibernate/envers/query/criteria/internal/SimpleAuditExpression.java b/hibernate-envers/src/main/java/org/hibernate/envers/query/criteria/internal/SimpleAuditExpression.java index 2a73ce00f2..406df8edd5 100644 --- a/hibernate-envers/src/main/java/org/hibernate/envers/query/criteria/internal/SimpleAuditExpression.java +++ b/hibernate-envers/src/main/java/org/hibernate/envers/query/criteria/internal/SimpleAuditExpression.java @@ -11,6 +11,7 @@ import java.util.Locale; import org.hibernate.engine.spi.SessionImplementor; import org.hibernate.envers.boot.internal.EnversService; import org.hibernate.envers.exception.AuditException; +import org.hibernate.envers.internal.entities.ComponentDescription; import org.hibernate.envers.internal.entities.RelationDescription; import org.hibernate.envers.internal.entities.RelationType; import org.hibernate.envers.internal.reader.AuditReaderImplementor; @@ -25,6 +26,7 @@ import org.hibernate.type.Type; * @author Adam Warski (adam at warski dot org) */ public class SimpleAuditExpression extends AbstractAtomicExpression { + private PropertyNameGetter propertyNameGetter; private Object value; private String op; @@ -42,6 +44,7 @@ public class SimpleAuditExpression extends AbstractAtomicExpression { AuditReaderImplementor versionsReader, String entityName, String alias, + String componentPrefix, QueryBuilder qb, Parameters parameters) { String propertyName = CriteriaTools.determinePropertyName( @@ -51,7 +54,8 @@ public class SimpleAuditExpression extends AbstractAtomicExpression { propertyNameGetter ); - RelationDescription relatedEntity = CriteriaTools.getRelatedEntity( enversService, entityName, propertyName ); + String prefixedPropertyName = componentPrefix.concat( propertyName ); + RelationDescription relatedEntity = CriteriaTools.getRelatedEntity( enversService, entityName, prefixedPropertyName ); if ( relatedEntity == null ) { // HHH-9178 - Add support to component type equality. @@ -75,14 +79,14 @@ public class SimpleAuditExpression extends AbstractAtomicExpression { final Object componentValue = componentType.getPropertyValue( value, i, session ); parameters.addWhereWithParam( alias, - propertyName + "_" + componentType.getPropertyNames()[ i ], + prefixedPropertyName + "_" + componentType.getPropertyNames()[ i ], op, componentValue ); } } else { - parameters.addWhereWithParam( alias, propertyName, op, value ); + parameters.addWhereWithParam( alias, prefixedPropertyName, op, value ); } } else if ( relatedEntity.getRelationType() == RelationType.TO_ONE ) { diff --git a/hibernate-envers/src/main/java/org/hibernate/envers/query/criteria/internal/SimpleFunctionAuditExpression.java b/hibernate-envers/src/main/java/org/hibernate/envers/query/criteria/internal/SimpleFunctionAuditExpression.java index 2fbc4be930..23f4d70509 100644 --- a/hibernate-envers/src/main/java/org/hibernate/envers/query/criteria/internal/SimpleFunctionAuditExpression.java +++ b/hibernate-envers/src/main/java/org/hibernate/envers/query/criteria/internal/SimpleFunctionAuditExpression.java @@ -37,10 +37,18 @@ public class SimpleFunctionAuditExpression implements AuditCriterion { EnversService enversService, AuditReaderImplementor versionsReader, Map aliasToEntityNameMap, + Map aliasToComponentPropertyNameMap, String baseAlias, QueryBuilder qb, Parameters parameters) { - parameters.addWhereWithFunction( enversService.getConfig(), aliasToEntityNameMap, function, op, value ); + parameters.addWhereWithFunction( + enversService.getConfig(), + aliasToEntityNameMap, + aliasToComponentPropertyNameMap, + function, + op, + value + ); } } diff --git a/hibernate-envers/src/main/java/org/hibernate/envers/query/internal/impl/AbstractAuditQuery.java b/hibernate-envers/src/main/java/org/hibernate/envers/query/internal/impl/AbstractAuditQuery.java index 212fa5e07d..c6ed56bb15 100644 --- a/hibernate-envers/src/main/java/org/hibernate/envers/query/internal/impl/AbstractAuditQuery.java +++ b/hibernate-envers/src/main/java/org/hibernate/envers/query/internal/impl/AbstractAuditQuery.java @@ -51,6 +51,7 @@ public abstract class AbstractAuditQuery implements AuditQueryImplementor { protected String versionsEntityName; protected QueryBuilder qb; protected final Map aliasToEntityNameMap = new HashMap<>(); + protected final Map aliasToComponentPropertyNameMap = new HashMap<>(); protected boolean hasOrder; @@ -138,7 +139,14 @@ public abstract class AbstractAuditQuery implements AuditQueryImplementor { String projectionEntityAlias = projection.getAlias( REFERENCED_ENTITY_ALIAS ); String projectionEntityName = aliasToEntityNameMap.get( projectionEntityAlias ); registerProjection( projectionEntityName, projection ); - projection.addProjectionToQuery( enversService, versionsReader, aliasToEntityNameMap, REFERENCED_ENTITY_ALIAS, qb ); + projection.addProjectionToQuery( + enversService, + versionsReader, + aliasToEntityNameMap, + aliasToComponentPropertyNameMap, + REFERENCED_ENTITY_ALIAS, + qb + ); return this; } @@ -162,7 +170,18 @@ public abstract class AbstractAuditQuery implements AuditQueryImplementor { orderEntityName, orderData.getPropertyName() ); - qb.addOrder( orderEntityAlias, propertyName, orderData.isAscending(), orderData.getNullPrecedence() ); + String componentPrefix = CriteriaTools.determineComponentPropertyPrefix( + enversService, + aliasToEntityNameMap, + aliasToComponentPropertyNameMap, + orderEntityAlias + ); + qb.addOrder( + orderEntityAlias, + componentPrefix.concat( propertyName ), + orderData.isAscending(), + orderData.getNullPrecedence() + ); return this; } @@ -187,6 +206,7 @@ public abstract class AbstractAuditQuery implements AuditQueryImplementor { associationName, joinType, aliasToEntityNameMap, + aliasToComponentPropertyNameMap, REFERENCED_ENTITY_ALIAS, alias ); @@ -312,7 +332,15 @@ public abstract class AbstractAuditQuery implements AuditQueryImplementor { if ( projections.size() == 1 ) { // qr is the value of the projection itself final Pair projection = projections.get( 0 ); - result.add( projection.getSecond().convertQueryResult( enversService, entityInstantiator, projection.getFirst(), revision, qr ) ); + result.add( + projection.getSecond().convertQueryResult( + enversService, + entityInstantiator, + projection.getFirst(), + revision, + qr + ) + ); } else { // qr is an array where each of its components holds the value of corresponding projection @@ -320,7 +348,13 @@ public abstract class AbstractAuditQuery implements AuditQueryImplementor { Object[] tresults = new Object[qresults.length]; for ( int i = 0; i < qresults.length; i++ ) { final Pair projection = projections.get( i ); - tresults[i] = projection.getSecond().convertQueryResult( enversService, entityInstantiator, projection.getFirst(), revision, qresults[i] ); + tresults[i] = projection.getSecond().convertQueryResult( + enversService, + entityInstantiator, + projection.getFirst(), + revision, + qresults[i] + ); } result.add( tresults ); } diff --git a/hibernate-envers/src/main/java/org/hibernate/envers/query/internal/impl/AuditAssociationQueryImpl.java b/hibernate-envers/src/main/java/org/hibernate/envers/query/internal/impl/AuditAssociationQueryImpl.java index 75d97b021d..0e7b06c611 100644 --- a/hibernate-envers/src/main/java/org/hibernate/envers/query/internal/impl/AuditAssociationQueryImpl.java +++ b/hibernate-envers/src/main/java/org/hibernate/envers/query/internal/impl/AuditAssociationQueryImpl.java @@ -23,6 +23,8 @@ import org.hibernate.envers.RevisionType; import org.hibernate.envers.boot.internal.EnversService; import org.hibernate.envers.configuration.Configuration; import org.hibernate.envers.exception.AuditException; +import org.hibernate.envers.internal.entities.ComponentDescription; +import org.hibernate.envers.internal.entities.ComponentDescription.ComponentType; import org.hibernate.envers.internal.entities.RelationDescription; import org.hibernate.envers.internal.entities.RelationType; import org.hibernate.envers.internal.entities.mapper.id.IdMapper; @@ -51,10 +53,12 @@ public class AuditAssociationQueryImpl private final JoinType joinType; private final String entityName; private final RelationDescription relationDescription; + private final ComponentDescription componentDescription; private final String ownerAlias; private final String ownerEntityName; private final String alias; private final Map aliasToEntityNameMap; + private final Map aliasToComponentPropertyNameMap; private final List criterions = new ArrayList<>(); private final Parameters parameters; private final List> associationQueries = new ArrayList<>(); @@ -68,6 +72,7 @@ public class AuditAssociationQueryImpl final String propertyName, final JoinType joinType, final Map aliasToEntityNameMap, + final Map aliasToComponentPropertyNameMap, final String ownerAlias, final String userSuppliedAlias) { this.enversService = enversService; @@ -77,12 +82,26 @@ public class AuditAssociationQueryImpl this.joinType = joinType; ownerEntityName = aliasToEntityNameMap.get( ownerAlias ); - final RelationDescription relationDescription = CriteriaTools.getRelatedEntity( + this.ownerAlias = ownerAlias; + this.alias = userSuppliedAlias == null ? queryBuilder.generateAlias() : userSuppliedAlias; + + String componentPrefix = CriteriaTools.determineComponentPropertyPrefix( + enversService, + aliasToEntityNameMap, + aliasToComponentPropertyNameMap, + ownerAlias + ); + String prefixedPropertyName = componentPrefix.concat( propertyName ); + + relationDescription = CriteriaTools.getRelatedEntity( enversService, ownerEntityName, - propertyName + prefixedPropertyName ); - if ( relationDescription == null ) { + + componentDescription = CriteriaTools.getComponent( enversService, ownerEntityName, prefixedPropertyName ); + + if ( relationDescription == null && componentDescription == null ) { throw new IllegalArgumentException( String.format( Locale.ENGLISH, @@ -92,12 +111,17 @@ public class AuditAssociationQueryImpl ) ); } - this.entityName = relationDescription.getToEntityName(); - this.relationDescription = relationDescription; - this.ownerAlias = ownerAlias; - this.alias = userSuppliedAlias == null ? queryBuilder.generateAlias() : userSuppliedAlias; + + if ( relationDescription != null ) { + this.entityName = relationDescription.getToEntityName(); + } + else { + aliasToComponentPropertyNameMap.put( alias, componentDescription.getPropertyName() ); + this.entityName = ownerEntityName; + } aliasToEntityNameMap.put( this.alias, entityName ); this.aliasToEntityNameMap = aliasToEntityNameMap; + this.aliasToComponentPropertyNameMap = aliasToComponentPropertyNameMap; parameters = queryBuilder.addParameters( this.alias ); } @@ -142,6 +166,7 @@ public class AuditAssociationQueryImpl associationName, joinType, aliasToEntityNameMap, + aliasToComponentPropertyNameMap, this.alias, alias ); @@ -162,7 +187,14 @@ public class AuditAssociationQueryImpl String projectionEntityAlias = projection.getAlias( alias ); String projectionEntityName = aliasToEntityNameMap.get( projectionEntityAlias ); registerProjection( projectionEntityName, projection ); - projection.addProjectionToQuery( enversService, auditReader, aliasToEntityNameMap, alias, queryBuilder ); + projection.addProjectionToQuery( + enversService, + auditReader, + aliasToEntityNameMap, + aliasToComponentPropertyNameMap, + alias, + queryBuilder + ); return this; } @@ -177,7 +209,18 @@ public class AuditAssociationQueryImpl orderEntityName, orderData.getPropertyName() ); - queryBuilder.addOrder( orderEntityAlias, propertyName, orderData.isAscending(), orderData.getNullPrecedence() ); + String componentPrefix = CriteriaTools.determineComponentPropertyPrefix( + enversService, + aliasToEntityNameMap, + aliasToComponentPropertyNameMap, + orderEntityAlias + ); + queryBuilder.addOrder( + orderEntityAlias, + componentPrefix.concat( propertyName ), + orderData.isAscending(), + orderData.getNullPrecedence() + ); return this; } @@ -240,7 +283,31 @@ public class AuditAssociationQueryImpl } protected void addCriterionsToQuery(AuditReaderImplementor versionsReader) { - final Configuration configuration = enversService.getConfig(); + if ( relationDescription != null ) { + createEntityJoin( enversService.getConfig() ); + } + else { + createComponentJoin( enversService.getConfig() ); + } + + for ( AuditCriterion criterion : criterions ) { + criterion.addToQuery( + enversService, + versionsReader, + aliasToEntityNameMap, + aliasToComponentPropertyNameMap, + alias, + queryBuilder, + parameters + ); + } + + for ( AuditAssociationQueryImpl query : associationQueries ) { + query.addCriterionsToQuery( versionsReader ); + } + } + + private void createEntityJoin(Configuration configuration) { boolean targetIsAudited = enversService.getEntitiesConfigurations().isVersioned( entityName ); String targetEntityName = entityName; if ( targetIsAudited ) { @@ -414,22 +481,85 @@ public class AuditAssociationQueryImpl true ); } + } - for ( AuditCriterion criterion : criterions ) { - criterion.addToQuery( - enversService, - versionsReader, - aliasToEntityNameMap, + private void createComponentJoin(Configuration configuration) { + String originalIdPropertyName = configuration.getOriginalIdPropertyName(); + String revisionPropertyPath = configuration.getRevisionNumberPath(); + if ( componentDescription.getType() == ComponentType.MANY ) { + // join middle_entity + Parameters joinConditionParameters = queryBuilder.addJoin( + joinType, + componentDescription.getAuditMiddleEntityName(), alias, - queryBuilder, - parameters + false ); - } - for ( final AuditAssociationQueryImpl sub : associationQueries ) { - sub.addCriterionsToQuery( versionsReader ); - } + String middleOriginalIdPropertyPath = alias + "." + originalIdPropertyName; + // join condition: owner.reference_id = middle.id_ref_ing + String ownerPrefix = ownerAlias + "." + originalIdPropertyName; + MiddleIdData middleIdData = componentDescription.getMiddleIdData(); + middleIdData.getPrefixedMapper().addIdsEqualToQuery( + joinConditionParameters, + middleOriginalIdPropertyPath, + middleIdData.getOriginalMapper(), + ownerPrefix + ); + // filter revisions of middle entity + Parameters middleParameters = queryBuilder.addParameters( alias ); + Parameters middleParametersToUse = middleParameters; + if ( joinType == JoinType.LEFT ) { + middleParametersToUse = middleParameters.addSubParameters( Parameters.OR ); + middleParametersToUse.addNullRestriction( revisionPropertyPath, true ); + middleParametersToUse = middleParametersToUse.addSubParameters( Parameters.AND ); + } + configuration.getAuditStrategy().addAssociationAtRevisionRestriction( + queryBuilder, + middleParametersToUse, + revisionPropertyPath, + configuration.getRevisionEndFieldName(), + true, + middleIdData, + componentDescription.getAuditMiddleEntityName(), + middleOriginalIdPropertyPath, + revisionPropertyPath, + originalIdPropertyName, + alias, + true + ); + + // filter deleted middle entities + String middleRevTypePropertyPath = middleOriginalIdPropertyPath + "." + configuration.getRevisionTypePropertyName(); + if ( joinType == JoinType.LEFT ) { + middleParametersToUse = middleParameters.addSubParameters( Parameters.OR ); + middleParametersToUse.addNullRestriction( middleRevTypePropertyPath, false ); + } + middleParametersToUse.addWhereWithParam( middleRevTypePropertyPath, false, "!=", RevisionType.DEL ); + } + else { + // ComponentType.ONE + /* + * The properties of a single component are directly mapped on the owner entity. Therefore no join would be + * required to access those properties (except the case an explicit on-clause has been specified). However, + * the user has supplied an alias and may be accessing properties of this component through that alias: If + * no join is generated, the 'virtual' alias has to be retranslated to the owning entity alias. To keep + * things simple a join on the owning entity itself is generated. The join is cheaper than other audit joins + * because we can join on the complete primary key (id + rev) and do not have to range filter on the target + * revision number. + */ + String targetEntityName = configuration.getAuditEntityName( entityName ); + Parameters joinConditionParameters = queryBuilder.addJoin( joinType, targetEntityName, alias, false ); + + // join condition: owner.reference_id = middle.id_reference_id + String ownerPrefix = ownerAlias + "." + originalIdPropertyName; + String middleOriginalIdPropertyPath = alias + "." + originalIdPropertyName; + IdMapper idMapper = enversService.getEntitiesConfigurations().get( entityName ).getIdMapper(); + idMapper.addIdsEqualToQuery( joinConditionParameters, ownerPrefix, middleOriginalIdPropertyPath ); + + // join condition: owner.rev=middle.rev + joinConditionParameters.addWhere( ownerAlias, revisionPropertyPath, "=", alias, revisionPropertyPath ); + } } @Override diff --git a/hibernate-envers/src/main/java/org/hibernate/envers/query/internal/impl/EntitiesAtRevisionQuery.java b/hibernate-envers/src/main/java/org/hibernate/envers/query/internal/impl/EntitiesAtRevisionQuery.java index 9d792051a4..919d54b7ff 100644 --- a/hibernate-envers/src/main/java/org/hibernate/envers/query/internal/impl/EntitiesAtRevisionQuery.java +++ b/hibernate-envers/src/main/java/org/hibernate/envers/query/internal/impl/EntitiesAtRevisionQuery.java @@ -114,6 +114,7 @@ public class EntitiesAtRevisionQuery extends AbstractAuditQuery { enversService, versionsReader, aliasToEntityNameMap, + aliasToComponentPropertyNameMap, QueryConstants.REFERENCED_ENTITY_ALIAS, qb, qb.getRootParameters() diff --git a/hibernate-envers/src/main/java/org/hibernate/envers/query/internal/impl/EntitiesModifiedAtRevisionQuery.java b/hibernate-envers/src/main/java/org/hibernate/envers/query/internal/impl/EntitiesModifiedAtRevisionQuery.java index 2bc3baac35..23e7a97188 100644 --- a/hibernate-envers/src/main/java/org/hibernate/envers/query/internal/impl/EntitiesModifiedAtRevisionQuery.java +++ b/hibernate-envers/src/main/java/org/hibernate/envers/query/internal/impl/EntitiesModifiedAtRevisionQuery.java @@ -66,6 +66,7 @@ public class EntitiesModifiedAtRevisionQuery extends AbstractAuditQuery { enversService, versionsReader, aliasToEntityNameMap, + aliasToComponentPropertyNameMap, QueryConstants.REFERENCED_ENTITY_ALIAS, qb, qb.getRootParameters() diff --git a/hibernate-envers/src/main/java/org/hibernate/envers/query/internal/impl/RevisionsOfEntityQuery.java b/hibernate-envers/src/main/java/org/hibernate/envers/query/internal/impl/RevisionsOfEntityQuery.java index 6d3b78a6f4..6524bee345 100644 --- a/hibernate-envers/src/main/java/org/hibernate/envers/query/internal/impl/RevisionsOfEntityQuery.java +++ b/hibernate-envers/src/main/java/org/hibernate/envers/query/internal/impl/RevisionsOfEntityQuery.java @@ -112,6 +112,7 @@ public class RevisionsOfEntityQuery extends AbstractAuditQuery { enversService, versionsReader, aliasToEntityNameMap, + aliasToComponentPropertyNameMap, QueryConstants.REFERENCED_ENTITY_ALIAS, qb, qb.getRootParameters() diff --git a/hibernate-envers/src/main/java/org/hibernate/envers/query/projection/AuditProjection.java b/hibernate-envers/src/main/java/org/hibernate/envers/query/projection/AuditProjection.java index 0a9b484c93..72ec5cdb37 100644 --- a/hibernate-envers/src/main/java/org/hibernate/envers/query/projection/AuditProjection.java +++ b/hibernate-envers/src/main/java/org/hibernate/envers/query/projection/AuditProjection.java @@ -32,6 +32,7 @@ public interface AuditProjection { EnversService enversService, AuditReaderImplementor auditReader, Map aliasToEntityNameMap, + Map aliasToComponentPropertyNameMap, String baseAlias, QueryBuilder queryBuilder); diff --git a/hibernate-envers/src/main/java/org/hibernate/envers/query/projection/internal/EntityAuditProjection.java b/hibernate-envers/src/main/java/org/hibernate/envers/query/projection/internal/EntityAuditProjection.java index db7421c66a..a53928c974 100644 --- a/hibernate-envers/src/main/java/org/hibernate/envers/query/projection/internal/EntityAuditProjection.java +++ b/hibernate-envers/src/main/java/org/hibernate/envers/query/projection/internal/EntityAuditProjection.java @@ -34,8 +34,13 @@ public class EntityAuditProjection implements AuditProjection { } @Override - public void addProjectionToQuery(EnversService enversService, AuditReaderImplementor auditReader, - Map aliasToEntityNameMap, String baseAlias, QueryBuilder queryBuilder) { + public void addProjectionToQuery( + EnversService enversService, + AuditReaderImplementor auditReader, + Map aliasToEntityNameMap, + Map aliasToComponentPropertyNameMap, + String baseAlias, + QueryBuilder queryBuilder) { String projectionEntityAlias = getAlias( baseAlias ); queryBuilder.addProjection( null, diff --git a/hibernate-envers/src/main/java/org/hibernate/envers/query/projection/internal/PropertyAuditProjection.java b/hibernate-envers/src/main/java/org/hibernate/envers/query/projection/internal/PropertyAuditProjection.java index 91f2744197..b46f55ea3c 100644 --- a/hibernate-envers/src/main/java/org/hibernate/envers/query/projection/internal/PropertyAuditProjection.java +++ b/hibernate-envers/src/main/java/org/hibernate/envers/query/projection/internal/PropertyAuditProjection.java @@ -43,6 +43,7 @@ public class PropertyAuditProjection implements AuditProjection { EnversService enversService, AuditReaderImplementor auditReader, Map aliasToEntityNameMap, + Map aliasToComponentPropertyNameMap, String baseAlias, QueryBuilder queryBuilder) { String projectionEntityAlias = getAlias( baseAlias ); @@ -51,16 +52,29 @@ public class PropertyAuditProjection implements AuditProjection { enversService, auditReader, projectionEntityName, - propertyNameGetter ); + propertyNameGetter + ); + String propertyNamePrefix = CriteriaTools.determineComponentPropertyPrefix( + enversService, + aliasToEntityNameMap, + aliasToComponentPropertyNameMap, + projectionEntityAlias + ); queryBuilder.addProjection( function, projectionEntityAlias, - propertyName, - distinct ); + propertyNamePrefix.concat( propertyName ), + distinct + ); } @Override - public Object convertQueryResult(EnversService enversService, EntityInstantiator entityInstantiator, String entityName, Number revision, Object value) { + public Object convertQueryResult( + EnversService enversService, + EntityInstantiator entityInstantiator, + String entityName, + Number revision, + Object value) { return value; } } diff --git a/hibernate-envers/src/test/java/org/hibernate/orm/test/envers/integration/query/ComponentQueryTest.java b/hibernate-envers/src/test/java/org/hibernate/orm/test/envers/integration/query/ComponentQueryTest.java new file mode 100644 index 0000000000..1c79955f9b --- /dev/null +++ b/hibernate-envers/src/test/java/org/hibernate/orm/test/envers/integration/query/ComponentQueryTest.java @@ -0,0 +1,336 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.orm.test.envers.integration.query; + +import static org.junit.Assert.assertEquals; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import jakarta.persistence.ElementCollection; +import jakarta.persistence.Embeddable; +import jakarta.persistence.Embedded; +import jakarta.persistence.Entity; +import jakarta.persistence.EntityManager; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.Id; +import jakarta.persistence.ManyToOne; +import jakarta.persistence.criteria.JoinType; + +import org.hibernate.envers.Audited; +import org.hibernate.envers.query.AuditEntity; +import org.hibernate.orm.test.envers.BaseEnversJPAFunctionalTestCase; +import org.hibernate.orm.test.envers.Priority; +import org.hibernate.testing.TestForIssue; +import org.junit.Test; + +/** + * @author Felix Feisst (feisst dot felix at gmail dot com) + */ +@SuppressWarnings("rawtypes") +@TestForIssue(jiraKey = "HHH-11895") +public class ComponentQueryTest extends BaseEnversJPAFunctionalTestCase { + + @Embeddable + public static class Symbol { + + @ManyToOne + private SymbolType type; + private String identifier; + + public Symbol() { + + } + + public Symbol(final SymbolType type, final String identifier) { + this.type = type; + this.identifier = identifier; + } + + public SymbolType getType() { + return type; + } + + public void setType(SymbolType type) { + this.type = type; + } + + public String getIdentifier() { + return identifier; + } + + public void setIdentifier(String identifier) { + this.identifier = identifier; + } + + } + + @Entity(name = "SymbolType") + @Audited + public static class SymbolType { + + @Id + @GeneratedValue + private Integer id; + + private String name; + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + } + + @Entity(name = "Asset") + @Audited + public static class Asset { + + @Id + @GeneratedValue + private Integer id; + + @Embedded + private Symbol singleSymbol; + + @ElementCollection + private Set multiSymbols = new HashSet<>(); + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public Symbol getSingleSymbol() { + return singleSymbol; + } + + public void setSingleSymbol(Symbol singleSymbol) { + this.singleSymbol = singleSymbol; + } + + public Set getMultiSymbols() { + return multiSymbols; + } + + public void setMultiSymbols(Set multiSymbols) { + this.multiSymbols = multiSymbols; + } + + } + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[]{ Asset.class, Symbol.class, SymbolType.class }; + } + + private SymbolType type1; + private SymbolType type2; + + private Asset asset1; + private Asset asset2; + private Asset asset3; + + @Test + @Priority(10) + public void initData() { + EntityManager em = getEntityManager(); + em.getTransaction().begin(); + + type1 = new SymbolType(); + type1.setName( "T1" ); + em.persist( type1 ); + + type2 = new SymbolType(); + type2.setName( "T2" ); + em.persist( type2 ); + + asset1 = new Asset(); + em.persist( asset1 ); + + asset2 = new Asset(); + asset2.setSingleSymbol( new Symbol( type1, "X1" ) ); + asset2.getMultiSymbols().add( new Symbol( type1, "X" ) ); + em.persist( asset2 ); + + asset3 = new Asset(); + asset3.setSingleSymbol( new Symbol( type2, "X2" ) ); + asset3.getMultiSymbols().add( new Symbol( type1, "Y" ) ); + asset3.getMultiSymbols().add( new Symbol( type2, "X" ) ); + em.persist( asset3 ); + + em.getTransaction().commit(); + } + + @Test + public void testSingleSymbolUsingIdentifier() { + List actual = getAuditReader().createQuery() + .forEntitiesAtRevision( Asset.class, 1 ) + .traverseRelation( "singleSymbol", JoinType.INNER, "s" ) + .add( AuditEntity.property( "s", "identifier" ).eq( "X1" ) ) + .up() + .addProjection( AuditEntity.id() ) + .getResultList(); + assertEquals( "Expected only asset2 to be returned", Collections.singletonList( asset2.getId() ), actual ); + } + + @Test + public void testMultiSymbolUsingIdentifier() { + List actual = getAuditReader().createQuery() + .forEntitiesAtRevision( Asset.class, 1 ) + .traverseRelation( "multiSymbols", JoinType.INNER, "s" ) + .add( AuditEntity.property( "s", "identifier" ).like( "X%" ) ) + .up() + .addProjection( AuditEntity.id() ) + .addOrder( AuditEntity.id().asc() ) + .getResultList(); + List expected = new ArrayList<>(); + Collections.addAll( expected, asset2.getId(), asset3.getId() ); + assertEquals( "Expected only the ids of the assets with symbol T1", expected, actual ); + } + + @Test + public void testSingleSymbolUsingType() { + List actual = getAuditReader().createQuery() + .forEntitiesAtRevision( Asset.class, 1 ) + .traverseRelation( "singleSymbol", JoinType.INNER, "s" ) + .add( AuditEntity.property( "s", "type" ).eq( type1 ) ) + .up() + .addProjection( AuditEntity.id() ) + .getResultList(); + assertEquals( "Expected only asset2 to be returned", Collections.singletonList( asset2.getId() ), actual ); + } + + @Test + public void testMultiSymbolUsingType() { + List actual = getAuditReader().createQuery() + .forEntitiesAtRevision( Asset.class, 1 ) + .traverseRelation( "multiSymbols", JoinType.INNER, "s" ) + .add( AuditEntity.property( "s", "type" ).eq( type2 ) ) + .up() + .addProjection( AuditEntity.id() ) + .getResultList(); + assertEquals( " Expected only asset3 to be returned", Collections.singletonList( asset3.getId() ), actual ); + } + + @Test + public void testJoinOnSingleComponentAssociation() { + List actual = getAuditReader().createQuery() + .forEntitiesAtRevision( Asset.class, 1 ) + .traverseRelation( "singleSymbol", JoinType.INNER, "s" ) + .traverseRelation( "type", JoinType.INNER, "t" ) + .add( AuditEntity.property( "t", "name" ).eq( "T1" ) ) + .up() + .up() + .addProjection( AuditEntity.id() ) + .getResultList(); + assertEquals( "Expected only asset2 to be returned", Collections.singletonList( asset2.getId() ), actual ); + } + + @Test + public void testJoinOnMultiComponentAssociation() { + List actual = getAuditReader().createQuery() + .forEntitiesAtRevision( Asset.class, 1 ) + .traverseRelation( "multiSymbols", JoinType.INNER, "s" ) + .traverseRelation( "type", JoinType.INNER, "t" ) + .add( AuditEntity.property( "t", "name" ).eq( "T2" ) ) + .up() + .up() + .addProjection( AuditEntity.id() ) + .getResultList(); + assertEquals( "Expected only asset3 to be returned", Collections.singletonList( asset3.getId() ), actual ); + } + + @Test + public void testOrderingOnSingleComponent() { + List actual = getAuditReader().createQuery() + .forEntitiesAtRevision( Asset.class, 1 ) + .addProjection( AuditEntity.id() ) + .traverseRelation( "singleSymbol", JoinType.LEFT, "s" ) + .addOrder( AuditEntity.property( "s", "identifier" ).asc() ) + .getResultList(); + List expected = new ArrayList<>(); + Collections.addAll( expected, asset1.getId(), asset2.getId(), asset3.getId() ); + assertEquals( "Expected all assets in correct order", expected, actual ); + } + + @Test + public void testOrderingOnMultiComponent() { + List actual = getAuditReader().createQuery() + .forEntitiesAtRevision( Asset.class, 1 ) + .addProjection( AuditEntity.id() ) + .traverseRelation( "multiSymbols", JoinType.LEFT, "s" ) + .traverseRelation( "type", JoinType.LEFT, "t" ) + .addOrder( AuditEntity.property( "t", "name" ).asc() ) + .addOrder( AuditEntity.property( "s", "identifier" ).asc() ) + .getResultList(); + List expected = new ArrayList<>(); + Collections.addAll( expected, asset1.getId(), asset2.getId(), asset3.getId(), asset3.getId() ); + assertEquals( "Expected all assets in correct order", expected, actual ); + } + + @Test + public void testProjectionOnSingleComponentProperty() { + List actual = getAuditReader().createQuery() + .forEntitiesAtRevision( Asset.class, 1 ) + .add( AuditEntity.id().eq( asset2.getId() ) ) + .traverseRelation( "singleSymbol", JoinType.INNER, "s" ) + .addProjection( AuditEntity.property( "s", "identifier" ) ) + .getResultList(); + assertEquals( "Expected the symbol identifier of asset2 to be returned", Collections.singletonList( "X1" ), actual ); + } + + @Test + public void testProjectionOnMultiComponentProperty() { + List actual = getAuditReader().createQuery() + .forEntitiesAtRevision( Asset.class, 1 ) + .add( AuditEntity.id().eq( asset2.getId() ) ) + .traverseRelation( "multiSymbols", JoinType.INNER, "s" ) + .addProjection( AuditEntity.property( "s", "identifier" ) ) + .getResultList(); + assertEquals( "Expected the symbol identifier of asset2 to be returned", Collections.singletonList( "X" ), actual ); + } + + @Test + public void testFunctionOnSingleComponentProperty() { + List actual = getAuditReader().createQuery() + .forEntitiesAtRevision( Asset.class, 1 ) + .add( AuditEntity.id().eq( asset2.getId() ) ) + .traverseRelation( "singleSymbol", JoinType.INNER, "s" ) + .addProjection( AuditEntity.function( "CONCAT", AuditEntity.property( "s", "identifier" ), "Z" ) ) + .getResultList(); + assertEquals( "Expecte the symbol identfier of asset2 concatenated with 'Z'", Collections.singletonList( "X1Z" ), actual ); + } + + @Test + public void testFunctionOnMultiComponentProperty() { + List actual = getAuditReader().createQuery() + .forEntitiesAtRevision( Asset.class, 1 ) + .add( AuditEntity.id().eq( asset2.getId() ) ) + .traverseRelation( "multiSymbols", JoinType.INNER, "s" ) + .addProjection( AuditEntity.function( "CONCAT", AuditEntity.property( "s", "identifier" ), "Z" ) ) + .getResultList(); + assertEquals( "Expecte the symbol identfier of asset2 concatenated with 'Z'", Collections.singletonList( "XZ" ), actual ); + } + +} diff --git a/hibernate-envers/src/test/java/org/hibernate/orm/test/envers/integration/query/NestedComponentQueryTest.java b/hibernate-envers/src/test/java/org/hibernate/orm/test/envers/integration/query/NestedComponentQueryTest.java new file mode 100644 index 0000000000..f7ee375969 --- /dev/null +++ b/hibernate-envers/src/test/java/org/hibernate/orm/test/envers/integration/query/NestedComponentQueryTest.java @@ -0,0 +1,158 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.orm.test.envers.integration.query; + +import static org.junit.Assert.assertEquals; + +import java.util.Collections; +import java.util.List; + +import jakarta.persistence.Embeddable; +import jakarta.persistence.Embedded; +import jakarta.persistence.Entity; +import jakarta.persistence.EntityManager; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.Id; +import jakarta.persistence.criteria.JoinType; + +import org.hibernate.envers.Audited; +import org.hibernate.envers.query.AuditEntity; +import org.hibernate.orm.test.envers.BaseEnversJPAFunctionalTestCase; +import org.hibernate.orm.test.envers.Priority; +import org.hibernate.testing.TestForIssue; +import org.junit.Test; + +/** + * @author Felix Feisst (feisst dot felix at gmail dot com) + */ +@TestForIssue(jiraKey = "HHH-11895") +public class NestedComponentQueryTest extends BaseEnversJPAFunctionalTestCase { + + @Embeddable + public static class Component1 { + + private String name1; + + @Embedded + private Component2 component2; + + public String getName1() { + return name1; + } + + public void setName1(String name1) { + this.name1 = name1; + } + + public Component2 getComponent2() { + return component2; + } + + public void setComponent2(Component2 component2) { + this.component2 = component2; + } + } + + @Embeddable + public static class Component2 { + + private String name2; + + public String getName2() { + return name2; + } + + public void setName2(String name2) { + this.name2 = name2; + } + } + + @Entity(name = "EntityOwner") + @Audited + public static class EntityOwner { + + @Id + @GeneratedValue + private Integer id; + + @Embedded + private Component1 component1; + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public Component1 getComponent1() { + return component1; + } + + public void setComponent1(Component1 component1) { + this.component1 = component1; + } + } + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[]{ EntityOwner.class, Component1.class, Component2.class }; + } + + private EntityOwner owner1; + private EntityOwner owner2; + + @Test + @Priority(10) + public void initData() { + EntityManager em = getEntityManager(); + em.getTransaction().begin(); + owner1 = new EntityOwner(); + Component1 component1 = new Component1(); + component1.setName1( "X" ); + owner1.setComponent1( component1 ); + Component2 component2 = new Component2(); + component2.setName2( "Y" ); + component1.setComponent2( component2 ); + em.persist( owner1 ); + owner2 = new EntityOwner(); + Component1 component12 = new Component1(); + component12.setName1( "Z" ); + owner2.setComponent1( component12 ); + Component2 component22 = new Component2(); + component22.setName2( "Z" ); + component12.setComponent2( component22 ); + em.persist( owner2 ); + em.getTransaction().commit(); + } + + @Test + public void testQueryNestedComponent() { + List actual = getAuditReader().createQuery() + .forEntitiesAtRevision( EntityOwner.class, 1 ) + .addProjection( AuditEntity.id() ) + .traverseRelation( "component1", JoinType.INNER, "c1" ) + .traverseRelation( "component2", JoinType.INNER, "c2" ) + .add( AuditEntity.property( "c2", "name2" ).eq( "Y" ) ) + .getResultList(); + assertEquals( "Expected owner1 to be returned", Collections.singletonList( owner1.getId() ), actual ); + } + + @Test + public void testQueryNestedComponentWithPropertyEquals() { + List actual = getAuditReader().createQuery() + .forEntitiesAtRevision( EntityOwner.class, 1 ) + .addProjection( AuditEntity.id() ) + .traverseRelation( "component1", JoinType.INNER, "c1" ) + .traverseRelation( "component2", JoinType.INNER, "c2" ) + .add( AuditEntity.property( "c1", "name1" ).eqProperty( "c2", "name2" ) ) + .getResultList(); + assertEquals( "Expected owner2 to be returned", Collections.singletonList( owner2.getId() ), actual ); + } + +}