HHH-11895 Support traversal of components in audit query API

This commit is contained in:
Felix Feisst 2017-07-28 03:17:52 -04:00 committed by Chris Cranford
parent 25421733d6
commit d9f3e82291
42 changed files with 1123 additions and 82 deletions

View File

@ -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) {

View File

@ -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
);
}
}
}

View File

@ -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 <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
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
}
}

View File

@ -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<String> entityNames) {
entityNames.add( entityName );
final EntityConfiguration entCfg = entitiesConfigurations.get( entityName );

View File

@ -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<String, RelationDescription> relations;
private final Map<String, ComponentDescription> 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;
}

View File

@ -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<String, String> aliasToEntityNameMap,
Map<String, String> 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<String, String> aliasToEntityNameMap,
Map<String, String> 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<String, String> aliasToEntityNameMap,
Map<String, String> 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<String, String> aliasToEntityNameMap,
Map<String, String> 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,

View File

@ -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<String, String> aliasToEntityNameMap, AuditFunction function) {
public void addProjection(
Configuration configuration,
Map<String, String> aliasToEntityNameMap,
Map<String, String> 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<String, String> aliasToEntityNameMap,
Map<String, String> aliasToComponentPropertyNameMap,
MutableInteger paramCounter,
Map<String, Object> 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();

View File

@ -53,6 +53,7 @@ public class AggregatedAuditExpression implements AuditCriterion, ExtendableCrit
EnversService enversService,
AuditReaderImplementor versionsReader,
Map<String, String> aliasToEntityNameMap,
Map<String, String> 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 );
}
/**

View File

@ -36,6 +36,7 @@ public class AuditConjunction implements AuditCriterion, ExtendableCriterion {
EnversService enversService,
AuditReaderImplementor versionsReader,
Map<String, String> aliasToEntityNameMap,
Map<String, String> 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
);
}
}
}

View File

@ -21,6 +21,7 @@ public interface AuditCriterion {
EnversService enversService,
AuditReaderImplementor versionsReader,
Map<String, String> aliasToEntityNameMap,
Map<String, String> aliasToComponentPropertyNameMap,
String baseAlias,
QueryBuilder qb,
Parameters parameters);

View File

@ -36,6 +36,7 @@ public class AuditDisjunction implements AuditCriterion, ExtendableCriterion {
EnversService enversService,
AuditReaderImplementor versionsReader,
Map<String, String> aliasToEntityNameMap,
Map<String, String> 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
);
}
}
}

View File

@ -240,9 +240,15 @@ public class AuditFunction implements AuditProjection {
EnversService enversService,
AuditReaderImplementor auditReader,
Map<String, String> aliasToEntityNameMap,
Map<String, String> aliasToComponentPropertyNameMap,
String baseAlias,
QueryBuilder queryBuilder) {
queryBuilder.addProjection( enversService.getConfig(), aliasToEntityNameMap, this );
queryBuilder.addProjection(
enversService.getConfig(),
aliasToEntityNameMap,
aliasToComponentPropertyNameMap,
this
);
}
@Override

View File

@ -405,20 +405,33 @@ public class AuditProperty<T> implements AuditProjection {
// Projection on this property
@Override
public void addProjectionToQuery(EnversService enversService, AuditReaderImplementor auditReader,
Map<String, String> aliasToEntityNameMap, String baseAlias, QueryBuilder queryBuilder) {
public void addProjectionToQuery(
EnversService enversService,
AuditReaderImplementor auditReader,
Map<String, String> aliasToEntityNameMap,
Map<String, String> 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<T> 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;
}

View File

@ -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<String, String> aliasToEntityNameMap,
Map<String, String> 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);

View File

@ -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 );
}
}

View File

@ -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<String, String> aliasToEntityNameMap,
Map<String, String> 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.

View File

@ -40,12 +40,14 @@ public class FunctionFunctionAuditExpression implements AuditCriterion {
EnversService enversService,
AuditReaderImplementor auditReader,
Map<String, String> aliasToEntityNameMap,
Map<String, String> aliasToComponentPropertyNameMap,
String baseAlias,
QueryBuilder queryBuilder,
Parameters parameters) {
parameters.addWhereWithFunction(
enversService.getConfig(),
aliasToEntityNameMap,
aliasToComponentPropertyNameMap,
leftFunction,
op,
rightFunction

View File

@ -45,6 +45,7 @@ public class FunctionPropertyAuditExpression implements AuditCriterion {
EnversService enversService,
AuditReaderImplementor auditReader,
Map<String, String> aliasToEntityNameMap,
Map<String, String> 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
);

View File

@ -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();

View File

@ -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 ) );
}
}

View File

@ -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, ")" );
}
}

View File

@ -32,12 +32,30 @@ public class LogicalAuditExpression implements AuditCriterion {
EnversService enversService,
AuditReaderImplementor versionsReader,
Map<String, String> aliasToEntityNameMap,
Map<String, String> 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" )
);
}
}

View File

@ -28,9 +28,18 @@ public class NotAuditExpression implements AuditCriterion {
EnversService enversService,
AuditReaderImplementor versionsReader,
Map<String, String> aliasToEntityNameMap,
Map<String, String> 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()
);
}
}

View File

@ -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 );

View File

@ -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 );

View File

@ -44,6 +44,7 @@ public class PropertyAuditExpression implements AuditCriterion {
EnversService enversService,
AuditReaderImplementor versionsReader,
Map<String, String> aliasToEntityNameMap,
Map<String, String> 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 );
}
}

View File

@ -43,6 +43,7 @@ public class PropertyFunctionAuditExpression implements AuditCriterion {
EnversService enversService,
AuditReaderImplementor auditReader,
Map<String, String> aliasToEntityNameMap,
Map<String, String> 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,

View File

@ -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." );

View File

@ -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<QueryParameterData> 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, ")" );
}
}
}

View File

@ -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 );

View File

@ -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 ) {

View File

@ -37,10 +37,18 @@ public class SimpleFunctionAuditExpression implements AuditCriterion {
EnversService enversService,
AuditReaderImplementor versionsReader,
Map<String, String> aliasToEntityNameMap,
Map<String, String> aliasToComponentPropertyNameMap,
String baseAlias,
QueryBuilder qb,
Parameters parameters) {
parameters.addWhereWithFunction( enversService.getConfig(), aliasToEntityNameMap, function, op, value );
parameters.addWhereWithFunction(
enversService.getConfig(),
aliasToEntityNameMap,
aliasToComponentPropertyNameMap,
function,
op,
value
);
}
}

View File

@ -51,6 +51,7 @@ public abstract class AbstractAuditQuery implements AuditQueryImplementor {
protected String versionsEntityName;
protected QueryBuilder qb;
protected final Map<String, String> aliasToEntityNameMap = new HashMap<>();
protected final Map<String, String> 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<String, AuditProjection> 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<String, AuditProjection> 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 );
}

View File

@ -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<Q extends AuditQueryImplementor>
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<String, String> aliasToEntityNameMap;
private final Map<String, String> aliasToComponentPropertyNameMap;
private final List<AuditCriterion> criterions = new ArrayList<>();
private final Parameters parameters;
private final List<AuditAssociationQueryImpl<?>> associationQueries = new ArrayList<>();
@ -68,6 +72,7 @@ public class AuditAssociationQueryImpl<Q extends AuditQueryImplementor>
final String propertyName,
final JoinType joinType,
final Map<String, String> aliasToEntityNameMap,
final Map<String, String> aliasToComponentPropertyNameMap,
final String ownerAlias,
final String userSuppliedAlias) {
this.enversService = enversService;
@ -77,12 +82,26 @@ public class AuditAssociationQueryImpl<Q extends AuditQueryImplementor>
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<Q extends AuditQueryImplementor>
)
);
}
if ( relationDescription != null ) {
this.entityName = relationDescription.getToEntityName();
this.relationDescription = relationDescription;
this.ownerAlias = ownerAlias;
this.alias = userSuppliedAlias == null ? queryBuilder.generateAlias() : userSuppliedAlias;
}
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<Q extends AuditQueryImplementor>
associationName,
joinType,
aliasToEntityNameMap,
aliasToComponentPropertyNameMap,
this.alias,
alias
);
@ -162,7 +187,14 @@ public class AuditAssociationQueryImpl<Q extends AuditQueryImplementor>
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<Q extends AuditQueryImplementor>
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<Q extends AuditQueryImplementor>
}
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<Q extends AuditQueryImplementor>
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

View File

@ -114,6 +114,7 @@ public class EntitiesAtRevisionQuery extends AbstractAuditQuery {
enversService,
versionsReader,
aliasToEntityNameMap,
aliasToComponentPropertyNameMap,
QueryConstants.REFERENCED_ENTITY_ALIAS,
qb,
qb.getRootParameters()

View File

@ -66,6 +66,7 @@ public class EntitiesModifiedAtRevisionQuery extends AbstractAuditQuery {
enversService,
versionsReader,
aliasToEntityNameMap,
aliasToComponentPropertyNameMap,
QueryConstants.REFERENCED_ENTITY_ALIAS,
qb,
qb.getRootParameters()

View File

@ -112,6 +112,7 @@ public class RevisionsOfEntityQuery extends AbstractAuditQuery {
enversService,
versionsReader,
aliasToEntityNameMap,
aliasToComponentPropertyNameMap,
QueryConstants.REFERENCED_ENTITY_ALIAS,
qb,
qb.getRootParameters()

View File

@ -32,6 +32,7 @@ public interface AuditProjection {
EnversService enversService,
AuditReaderImplementor auditReader,
Map<String, String> aliasToEntityNameMap,
Map<String, String> aliasToComponentPropertyNameMap,
String baseAlias,
QueryBuilder queryBuilder);

View File

@ -34,8 +34,13 @@ public class EntityAuditProjection implements AuditProjection {
}
@Override
public void addProjectionToQuery(EnversService enversService, AuditReaderImplementor auditReader,
Map<String, String> aliasToEntityNameMap, String baseAlias, QueryBuilder queryBuilder) {
public void addProjectionToQuery(
EnversService enversService,
AuditReaderImplementor auditReader,
Map<String, String> aliasToEntityNameMap,
Map<String, String> aliasToComponentPropertyNameMap,
String baseAlias,
QueryBuilder queryBuilder) {
String projectionEntityAlias = getAlias( baseAlias );
queryBuilder.addProjection(
null,

View File

@ -43,6 +43,7 @@ public class PropertyAuditProjection implements AuditProjection {
EnversService enversService,
AuditReaderImplementor auditReader,
Map<String, String> aliasToEntityNameMap,
Map<String, String> 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;
}
}

View File

@ -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 <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
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<Symbol> 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<Symbol> getMultiSymbols() {
return multiSymbols;
}
public void setMultiSymbols(Set<Symbol> 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<Integer> 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<Integer> 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<Integer> 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 );
}
}

View File

@ -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 <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
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 );
}
}