From 04fae574ae0f9f171dbae0b077246aafd3ff3dc2 Mon Sep 17 00:00:00 2001 From: Adam Warski Date: Thu, 10 Dec 2009 20:28:27 +0000 Subject: [PATCH] HHH-4090: - adding support for many-to-many relations from an audited, to a non-audited entity git-svn-id: https://svn.jboss.org/repos/hibernate/core/trunk@18201 1b8cb986-b30d-0410-93ca-fae66ebed9b2 --- .../metadata/AuditMetadataGenerator.java | 33 +++- .../metadata/CollectionMetadataGenerator.java | 40 ++--- .../metadata/QueryGeneratorBuilder.java | 20 ++- .../ToOneRelationMetadataGenerator.java | 21 +-- .../mapper/relation/MiddleIdData.java | 41 +++-- .../BasicCollectionInitializor.java | 12 +- .../relation/query/QueryGeneratorTools.java | 2 +- .../query/RelationQueryGenerator.java | 2 + .../query/ThreeEntityQueryGenerator.java | 4 +- .../TwoEntityOneAuditedQueryGenerator.java | 108 +++++++++++ .../query/TwoEntityQueryGenerator.java | 2 +- .../M2MTargetNotAuditedEntity.java | 116 ++++++++++++ .../M2MRelationNotAuditedTarget.java | 167 ++++++++++++++++++ 13 files changed, 493 insertions(+), 75 deletions(-) create mode 100644 envers/src/main/java/org/hibernate/envers/entities/mapper/relation/query/TwoEntityOneAuditedQueryGenerator.java create mode 100644 envers/src/test/java/org/hibernate/envers/test/entities/manytomany/unidirectional/M2MTargetNotAuditedEntity.java create mode 100644 envers/src/test/java/org/hibernate/envers/test/integration/manytomany/unidirectional/M2MRelationNotAuditedTarget.java diff --git a/envers/src/main/java/org/hibernate/envers/configuration/metadata/AuditMetadataGenerator.java b/envers/src/main/java/org/hibernate/envers/configuration/metadata/AuditMetadataGenerator.java index f44257328f..3bf59cec3b 100644 --- a/envers/src/main/java/org/hibernate/envers/configuration/metadata/AuditMetadataGenerator.java +++ b/envers/src/main/java/org/hibernate/envers/configuration/metadata/AuditMetadataGenerator.java @@ -40,6 +40,7 @@ import org.hibernate.envers.entities.mapper.MultiPropertyMapper; import org.hibernate.envers.entities.mapper.SubclassPropertyMapper; import org.hibernate.envers.tools.StringTools; import org.hibernate.envers.tools.Triple; +import org.hibernate.envers.RelationTargetAuditMode; import org.hibernate.MappingException; import org.hibernate.cfg.Configuration; @@ -348,7 +349,7 @@ public final class AuditMetadataGenerator { String parentEntityName = null; EntityConfiguration entityCfg = new EntityConfiguration(entityName, idMapper, propertyMapper, parentEntityName); - notAuditedEntitiesConfigurations.put(pc.getEntityName(), entityCfg); + notAuditedEntitiesConfigurations.put(entityName, entityCfg); return; } @@ -476,6 +477,36 @@ public final class AuditMetadataGenerator { throw new MappingException(message); } + /** + * Reads the id mapping data of a referenced entity. + * @param entityName Name of the entity which is the source of the relation. + * @param referencedEntityName Name of the entity which is the target of the relation. + * @param propertyAuditingData Auditing data of the property that is the source of the relation. + * @param allowNotAuditedTarget Are not-audited target entities allowed. + * @throws MappingException If a relation from an audited to a non-audited entity is detected, which is not + * mapped using {@link RelationTargetAuditMode#NOT_AUDITED}. + * @return The id mapping data of the related entity. + */ + IdMappingData getReferencedIdMappingData(String entityName, String referencedEntityName, + PropertyAuditingData propertyAuditingData, + boolean allowNotAuditedTarget) { + EntityConfiguration configuration = getEntitiesConfigurations().get(referencedEntityName); + if (configuration == null) { + RelationTargetAuditMode relationTargetAuditMode = propertyAuditingData.getRelationTargetAuditMode(); + configuration = getNotAuditedEntitiesConfigurations().get(referencedEntityName); + + if (configuration == null || !allowNotAuditedTarget || !RelationTargetAuditMode.NOT_AUDITED.equals(relationTargetAuditMode)) { + throw new MappingException("An audited relation from " + entityName + "." + + propertyAuditingData.getName() + " to a not audited entity " + referencedEntityName + "!" + + (allowNotAuditedTarget ? + " Such mapping is possible, but has to be explicitly defined using @Audited(targetAuditMode = NOT_AUDITED)." : + "")); + } + } + + return configuration.getIdMappingData(); + } + /** * Get the notAuditedEntitiesConfigurations property. * diff --git a/envers/src/main/java/org/hibernate/envers/configuration/metadata/CollectionMetadataGenerator.java b/envers/src/main/java/org/hibernate/envers/configuration/metadata/CollectionMetadataGenerator.java index 1af2292e08..755ab6ba6e 100644 --- a/envers/src/main/java/org/hibernate/envers/configuration/metadata/CollectionMetadataGenerator.java +++ b/envers/src/main/java/org/hibernate/envers/configuration/metadata/CollectionMetadataGenerator.java @@ -155,28 +155,26 @@ public final class CollectionMetadataGenerator { } } + private MiddleIdData createMiddleIdData(IdMappingData idMappingData, String prefix, String entityName) { + return new MiddleIdData(mainGenerator.getVerEntCfg(), idMappingData, prefix, entityName, + mainGenerator.getEntitiesConfigurations().containsKey(entityName)); + } + @SuppressWarnings({"unchecked"}) private void addOneToManyAttached() { String mappedBy = getMappedBy(propertyValue); - EntityConfiguration referencedEntityConfiguration = mainGenerator.getEntitiesConfigurations() - .get(referencedEntityName); - - if (referencedEntityConfiguration == null) { - throwRelationNotAudited(referencedEntityName); - // Impossible to get here. - throw new AssertionError(); - } - IdMappingData referencedIdMapping = referencedEntityConfiguration.getIdMappingData(); + IdMappingData referencedIdMapping = mainGenerator.getReferencedIdMappingData(referencingEntityName, + referencedEntityName, propertyAuditingData, false); IdMappingData referencingIdMapping = referencingEntityConfiguration.getIdMappingData(); // Generating the id mappers data for the referencing side of the relation. - MiddleIdData referencingIdData = new MiddleIdData(mainGenerator.getVerEntCfg(), referencingIdMapping, + MiddleIdData referencingIdData = createMiddleIdData(referencingIdMapping, mappedBy + "_", referencingEntityName); // And for the referenced side. The prefixed mapper won't be used (as this collection isn't persisted // in a join table, so the prefix value is arbitrary). - MiddleIdData referencedIdData = new MiddleIdData(mainGenerator.getVerEntCfg(), referencedIdMapping, + MiddleIdData referencedIdData = createMiddleIdData(referencedIdMapping, null, referencedEntityName); // Generating the element mapping. @@ -293,7 +291,7 @@ public final class CollectionMetadataGenerator { } // Storing the id data of the referencing entity: original mapper, prefixed mapper and entity name. - MiddleIdData referencingIdData = new MiddleIdData(mainGenerator.getVerEntCfg(), referencingIdMapping, + MiddleIdData referencingIdData = createMiddleIdData(referencingIdMapping, referencingPrefixRelated, referencingEntityName); // Creating a query generator builder, to which additional id data will be added, in case this collection @@ -390,14 +388,9 @@ public final class CollectionMetadataGenerator { String prefixRelated = prefix + "_"; String referencedEntityName = getReferencedEntityName(value); - EntityConfiguration referencedEntityConfiguration = mainGenerator.getEntitiesConfigurations() - .get(referencedEntityName); - if (referencedEntityConfiguration == null) { - throwRelationNotAudited(referencedEntityName); - // Impossible to get here. - throw new AssertionError(); - } - IdMappingData referencedIdMapping = referencedEntityConfiguration.getIdMappingData(); + + IdMappingData referencedIdMapping = mainGenerator.getReferencedIdMappingData(referencingEntityName, + referencedEntityName, propertyAuditingData, true); // Adding related-entity (in this case: the referenced entities id) id mapping to the xml only if the // relation isn't inverse (so when xmlMapping is not null). @@ -410,7 +403,7 @@ public final class CollectionMetadataGenerator { } // Storing the id data of the referenced entity: original mapper, prefixed mapper and entity name. - MiddleIdData referencedIdData = new MiddleIdData(mainGenerator.getVerEntCfg(), referencedIdMapping, + MiddleIdData referencedIdData = createMiddleIdData(referencedIdMapping, prefixRelated, referencedEntityName); // And adding it to the generator builder. queryGeneratorBuilder.addRelation(referencedIdData); @@ -541,9 +534,4 @@ public final class CollectionMetadataGenerator { throw new MappingException("Unable to read the mapped by attribute for " + propertyName + " in " + referencingEntityName + "!"); } - - private void throwRelationNotAudited(String referencedEntityName) { - throw new MappingException("An audited relation from " + referencingEntityName + - " to a non-audited entity: " + referencedEntityName); - } } diff --git a/envers/src/main/java/org/hibernate/envers/configuration/metadata/QueryGeneratorBuilder.java b/envers/src/main/java/org/hibernate/envers/configuration/metadata/QueryGeneratorBuilder.java index 5856a80d5b..a6a1a3f068 100644 --- a/envers/src/main/java/org/hibernate/envers/configuration/metadata/QueryGeneratorBuilder.java +++ b/envers/src/main/java/org/hibernate/envers/configuration/metadata/QueryGeneratorBuilder.java @@ -30,10 +30,8 @@ import org.hibernate.envers.configuration.GlobalConfiguration; import org.hibernate.envers.configuration.AuditEntitiesConfiguration; import org.hibernate.envers.entities.mapper.relation.MiddleComponentData; import org.hibernate.envers.entities.mapper.relation.MiddleIdData; -import org.hibernate.envers.entities.mapper.relation.query.OneEntityQueryGenerator; -import org.hibernate.envers.entities.mapper.relation.query.RelationQueryGenerator; -import org.hibernate.envers.entities.mapper.relation.query.ThreeEntityQueryGenerator; -import org.hibernate.envers.entities.mapper.relation.query.TwoEntityQueryGenerator; +import org.hibernate.envers.entities.mapper.relation.query.*; +import org.hibernate.MappingException; /** * Builds query generators, for reading collection middle tables, along with any related entities. @@ -66,9 +64,19 @@ public final class QueryGeneratorBuilder { return new OneEntityQueryGenerator(verEntCfg, auditMiddleEntityName, referencingIdData, componentDatas); } else if (idDatas.size() == 1) { - return new TwoEntityQueryGenerator(globalCfg, verEntCfg, auditMiddleEntityName, referencingIdData, - idDatas.get(0), componentDatas); + if (idDatas.get(0).isAudited()) { + return new TwoEntityQueryGenerator(globalCfg, verEntCfg, auditMiddleEntityName, referencingIdData, + idDatas.get(0), componentDatas); + } else { + return new TwoEntityOneAuditedQueryGenerator(verEntCfg, auditMiddleEntityName, referencingIdData, + idDatas.get(0), componentDatas); + } } else if (idDatas.size() == 2) { + // All entities must be audited. + if (!idDatas.get(0).isAudited() || !idDatas.get(1).isAudited()) { + throw new MappingException("Ternary relations using @Audited(targetAuditMode = NOT_AUDITED) are not supported."); + } + return new ThreeEntityQueryGenerator(globalCfg, verEntCfg, auditMiddleEntityName, referencingIdData, idDatas.get(0), idDatas.get(1), componentDatas); } else { diff --git a/envers/src/main/java/org/hibernate/envers/configuration/metadata/ToOneRelationMetadataGenerator.java b/envers/src/main/java/org/hibernate/envers/configuration/metadata/ToOneRelationMetadataGenerator.java index e323477390..3144d060fb 100644 --- a/envers/src/main/java/org/hibernate/envers/configuration/metadata/ToOneRelationMetadataGenerator.java +++ b/envers/src/main/java/org/hibernate/envers/configuration/metadata/ToOneRelationMetadataGenerator.java @@ -24,7 +24,6 @@ package org.hibernate.envers.configuration.metadata; import org.dom4j.Element; -import org.hibernate.envers.RelationTargetAuditMode; import org.hibernate.envers.entities.EntityConfiguration; import org.hibernate.envers.entities.IdMappingData; import org.hibernate.envers.entities.PropertyData; @@ -55,24 +54,8 @@ public final class ToOneRelationMetadataGenerator { CompositeMapperBuilder mapper, String entityName, boolean insertable) { String referencedEntityName = ((ToOne) value).getReferencedEntityName(); - EntityConfiguration configuration = mainGenerator.getEntitiesConfigurations().get(referencedEntityName); - if (configuration == null) { - configuration = mainGenerator.getNotAuditedEntitiesConfigurations().get(referencedEntityName); - if (configuration != null) { - RelationTargetAuditMode relationTargetAuditMode = propertyAuditingData.getRelationTargetAuditMode(); - if (!RelationTargetAuditMode.NOT_AUDITED.equals(relationTargetAuditMode)) { - throw new MappingException("An audited relation from " + entityName + "." - + propertyAuditingData.getName() + " to a not audited entity " + referencedEntityName + "!" - + ". Such mapping is possible, but has to be strictly defined using RelationTargetAuditMode.NOT_AUDITED in @Audited."); - } - } - } - if (configuration == null) { - throw new MappingException("An audited relation from " + entityName + "." - + propertyAuditingData.getName() + " to a not audited entity " + referencedEntityName + "!"); - } - - IdMappingData idMapping = configuration.getIdMappingData(); + IdMappingData idMapping = mainGenerator.getReferencedIdMappingData(entityName, referencedEntityName, + propertyAuditingData, true); String lastPropertyPrefix = propertyAuditingData.getName() + "_"; diff --git a/envers/src/main/java/org/hibernate/envers/entities/mapper/relation/MiddleIdData.java b/envers/src/main/java/org/hibernate/envers/entities/mapper/relation/MiddleIdData.java index 0ead22ec23..b6f777343d 100644 --- a/envers/src/main/java/org/hibernate/envers/entities/mapper/relation/MiddleIdData.java +++ b/envers/src/main/java/org/hibernate/envers/entities/mapper/relation/MiddleIdData.java @@ -33,44 +33,51 @@ import org.hibernate.envers.entities.mapper.id.IdMapper; * @author Adam Warski (adam at warski dot org) */ public final class MiddleIdData { - /** - * Original id mapper of the related entity. - */ private final IdMapper originalMapper; - /** - * Prefixed id mapper (with the names for the id fields that are used in the middle table) of the related entity. - */ private final IdMapper prefixedMapper; - /** - * Name of the related entity. - */ private final String entityName; - /** - * Versions name of the related entity. - */ - private final String versionsEntityName; + private final String auditEntityName; public MiddleIdData(AuditEntitiesConfiguration verEntCfg, IdMappingData mappingData, String prefix, - String entityName) { + String entityName, boolean audited) { this.originalMapper = mappingData.getIdMapper(); this.prefixedMapper = mappingData.getIdMapper().prefixMappedProperties(prefix); this.entityName = entityName; - this.versionsEntityName = verEntCfg.getAuditEntityName(entityName); + this.auditEntityName = audited ? verEntCfg.getAuditEntityName(entityName) : null; } + /** + * @return Original id mapper of the related entity. + */ public IdMapper getOriginalMapper() { return originalMapper; } + /** + * @return prefixed id mapper (with the names for the id fields that are used in the middle table) of the related entity. + */ public IdMapper getPrefixedMapper() { return prefixedMapper; } + /** + * @return Name of the related entity (regular, not audited). + */ public String getEntityName() { return entityName; } - public String getVersionsEntityName() { - return versionsEntityName; + /** + * @return Audit name of the related entity. + */ + public String getAuditEntityName() { + return auditEntityName; + } + + /** + * @return Is the entity, to which this middle id data correspond, audited. + */ + public boolean isAudited() { + return auditEntityName != null; } } diff --git a/envers/src/main/java/org/hibernate/envers/entities/mapper/relation/lazy/initializor/BasicCollectionInitializor.java b/envers/src/main/java/org/hibernate/envers/entities/mapper/relation/lazy/initializor/BasicCollectionInitializor.java index 83b305bf72..28f16e21cf 100644 --- a/envers/src/main/java/org/hibernate/envers/entities/mapper/relation/lazy/initializor/BasicCollectionInitializor.java +++ b/envers/src/main/java/org/hibernate/envers/entities/mapper/relation/lazy/initializor/BasicCollectionInitializor.java @@ -66,8 +66,16 @@ public class BasicCollectionInitializor extends AbstractCo @SuppressWarnings({"unchecked"}) protected void addToCollection(T collection, Object collectionRow) { Object elementData = ((List) collectionRow).get(elementComponentData.getComponentIndex()); - Object element = elementComponentData.getComponentMapper().mapToObjectFromFullMap(entityInstantiator, - (Map) elementData, null, revision); + + // If the target entity is not audited, the elements may be the entities already, so we have to check + // if they are maps or not. + Object element; + if (elementData instanceof Map) { + element = elementComponentData.getComponentMapper().mapToObjectFromFullMap(entityInstantiator, + (Map) elementData, null, revision); + } else { + element = elementData; + } collection.add(element); } } diff --git a/envers/src/main/java/org/hibernate/envers/entities/mapper/relation/query/QueryGeneratorTools.java b/envers/src/main/java/org/hibernate/envers/entities/mapper/relation/query/QueryGeneratorTools.java index fe56a158f1..2c6659196a 100644 --- a/envers/src/main/java/org/hibernate/envers/entities/mapper/relation/query/QueryGeneratorTools.java +++ b/envers/src/main/java/org/hibernate/envers/entities/mapper/relation/query/QueryGeneratorTools.java @@ -37,7 +37,7 @@ public class QueryGeneratorTools { MiddleIdData idData, String revisionPropertyPath, String originalIdPropertyName, String alias1, String alias2) { // SELECT max(e.revision) FROM versionsReferencedEntity e2 - QueryBuilder maxERevQb = qb.newSubQueryBuilder(idData.getVersionsEntityName(), alias2); + QueryBuilder maxERevQb = qb.newSubQueryBuilder(idData.getAuditEntityName(), alias2); maxERevQb.addProjection("max", revisionPropertyPath, false); // WHERE Parameters maxERevQbParameters = maxERevQb.getRootParameters(); diff --git a/envers/src/main/java/org/hibernate/envers/entities/mapper/relation/query/RelationQueryGenerator.java b/envers/src/main/java/org/hibernate/envers/entities/mapper/relation/query/RelationQueryGenerator.java index b27b2bcd55..3120e5288b 100644 --- a/envers/src/main/java/org/hibernate/envers/entities/mapper/relation/query/RelationQueryGenerator.java +++ b/envers/src/main/java/org/hibernate/envers/entities/mapper/relation/query/RelationQueryGenerator.java @@ -28,6 +28,8 @@ import org.hibernate.envers.reader.AuditReaderImplementor; import org.hibernate.Query; /** + * TODO: cleanup implementations and extract common code + * * Implementations of this interface provide a method to generate queries on a relation table (a table used * for mapping relations). The query can select, apart from selecting the content of the relation table, also data of * other "related" entities. diff --git a/envers/src/main/java/org/hibernate/envers/entities/mapper/relation/query/ThreeEntityQueryGenerator.java b/envers/src/main/java/org/hibernate/envers/entities/mapper/relation/query/ThreeEntityQueryGenerator.java index 00c0665c99..d1e5205ec1 100644 --- a/envers/src/main/java/org/hibernate/envers/entities/mapper/relation/query/ThreeEntityQueryGenerator.java +++ b/envers/src/main/java/org/hibernate/envers/entities/mapper/relation/query/ThreeEntityQueryGenerator.java @@ -85,8 +85,8 @@ public final class ThreeEntityQueryGenerator implements RelationQueryGenerator { // SELECT new list(ee) FROM middleEntity ee QueryBuilder qb = new QueryBuilder(versionsMiddleEntityName, "ee"); - qb.addFrom(referencedIdData.getVersionsEntityName(), "e"); - qb.addFrom(indexIdData.getVersionsEntityName(), "f"); + qb.addFrom(referencedIdData.getAuditEntityName(), "e"); + qb.addFrom(indexIdData.getAuditEntityName(), "f"); qb.addProjection("new list", "ee, e, f", false, false); // WHERE Parameters rootParameters = qb.getRootParameters(); diff --git a/envers/src/main/java/org/hibernate/envers/entities/mapper/relation/query/TwoEntityOneAuditedQueryGenerator.java b/envers/src/main/java/org/hibernate/envers/entities/mapper/relation/query/TwoEntityOneAuditedQueryGenerator.java new file mode 100644 index 0000000000..a831b3e919 --- /dev/null +++ b/envers/src/main/java/org/hibernate/envers/entities/mapper/relation/query/TwoEntityOneAuditedQueryGenerator.java @@ -0,0 +1,108 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2008, Red Hat Middleware LLC or third-party contributors as + * indicated by the @author tags or express copyright attribution + * statements applied by the authors. All third-party contributions are + * distributed under license by Red Hat Middleware LLC. + * + * This copyrighted material is made available to anyone wishing to use, modify, + * copy, or redistribute it subject to the terms and conditions of the GNU + * Lesser General Public License, as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this distribution; if not, write to: + * Free Software Foundation, Inc. + * 51 Franklin Street, Fifth Floor + * Boston, MA 02110-1301 USA + */ +package org.hibernate.envers.entities.mapper.relation.query; + +import java.util.Collections; + +import org.hibernate.envers.RevisionType; +import org.hibernate.envers.configuration.AuditEntitiesConfiguration; +import org.hibernate.envers.entities.mapper.id.QueryParameterData; +import org.hibernate.envers.entities.mapper.relation.MiddleComponentData; +import org.hibernate.envers.entities.mapper.relation.MiddleIdData; +import org.hibernate.envers.reader.AuditReaderImplementor; +import org.hibernate.envers.tools.query.Parameters; +import org.hibernate.envers.tools.query.QueryBuilder; + +import org.hibernate.Query; + +/** + * Selects data from a relation middle-table and a related non-audited entity. + * @author Adam Warski (adam at warski dot org) + */ +public final class TwoEntityOneAuditedQueryGenerator implements RelationQueryGenerator { + private final String queryString; + private final MiddleIdData referencingIdData; + + public TwoEntityOneAuditedQueryGenerator( + AuditEntitiesConfiguration verEntCfg, + String versionsMiddleEntityName, + MiddleIdData referencingIdData, + MiddleIdData referencedIdData, + MiddleComponentData... componentDatas) { + this.referencingIdData = referencingIdData; + + /* + * The query that we need to create: + * SELECT new list(ee, e) FROM referencedEntity e, middleEntity ee + * WHERE + * (entities referenced by the middle table; id_ref_ed = id of the referenced entity) + * ee.id_ref_ed = e.id_ref_ed AND + * (only entities referenced by the association; id_ref_ing = id of the referencing entity) + * ee.id_ref_ing = :id_ref_ing AND + * (the association at revision :revision) + * ee.revision = (SELECT max(ee2.revision) FROM middleEntity ee2 + * WHERE ee2.revision <= :revision AND ee2.originalId.* = ee.originalId.*) AND + * (only non-deleted entities and associations) + * ee.revision_type != DEL + */ + String revisionPropertyPath = verEntCfg.getRevisionNumberPath(); + String originalIdPropertyName = verEntCfg.getOriginalIdPropName(); + + String eeOriginalIdPropertyPath = "ee." + originalIdPropertyName; + + // SELECT new list(ee) FROM middleEntity ee + QueryBuilder qb = new QueryBuilder(versionsMiddleEntityName, "ee"); + qb.addFrom(referencedIdData.getEntityName(), "e"); + qb.addProjection("new list", "ee, e", false, false); + // WHERE + Parameters rootParameters = qb.getRootParameters(); + // ee.id_ref_ed = e.id_ref_ed + referencedIdData.getPrefixedMapper().addIdsEqualToQuery(rootParameters, eeOriginalIdPropertyPath, + referencedIdData.getOriginalMapper(), "e"); + // ee.originalId.id_ref_ing = :id_ref_ing + referencingIdData.getPrefixedMapper().addNamedIdEqualsToQuery(rootParameters, originalIdPropertyName, true); + + // ee.revision = (SELECT max(...) ...) + QueryGeneratorTools.addAssociationAtRevision(qb, rootParameters, referencingIdData, versionsMiddleEntityName, + eeOriginalIdPropertyPath, revisionPropertyPath, originalIdPropertyName, componentDatas); + + // ee.revision_type != DEL + rootParameters.addWhereWithNamedParam(verEntCfg.getRevisionTypePropName(), "!=", "delrevisiontype"); + + StringBuilder sb = new StringBuilder(); + qb.build(sb, Collections.emptyMap()); + queryString = sb.toString(); + } + + public Query getQuery(AuditReaderImplementor versionsReader, Object primaryKey, Number revision) { + Query query = versionsReader.getSession().createQuery(queryString); + query.setParameter("revision", revision); + query.setParameter("delrevisiontype", RevisionType.DEL); + for (QueryParameterData paramData: referencingIdData.getPrefixedMapper().mapToQueryParametersFromId(primaryKey)) { + paramData.setParameterValue(query); + } + + return query; + } +} \ No newline at end of file diff --git a/envers/src/main/java/org/hibernate/envers/entities/mapper/relation/query/TwoEntityQueryGenerator.java b/envers/src/main/java/org/hibernate/envers/entities/mapper/relation/query/TwoEntityQueryGenerator.java index 385382da24..1c0eadbc73 100644 --- a/envers/src/main/java/org/hibernate/envers/entities/mapper/relation/query/TwoEntityQueryGenerator.java +++ b/envers/src/main/java/org/hibernate/envers/entities/mapper/relation/query/TwoEntityQueryGenerator.java @@ -78,7 +78,7 @@ public final class TwoEntityQueryGenerator implements RelationQueryGenerator { // SELECT new list(ee) FROM middleEntity ee QueryBuilder qb = new QueryBuilder(versionsMiddleEntityName, "ee"); - qb.addFrom(referencedIdData.getVersionsEntityName(), "e"); + qb.addFrom(referencedIdData.getAuditEntityName(), "e"); qb.addProjection("new list", "ee, e", false, false); // WHERE Parameters rootParameters = qb.getRootParameters(); diff --git a/envers/src/test/java/org/hibernate/envers/test/entities/manytomany/unidirectional/M2MTargetNotAuditedEntity.java b/envers/src/test/java/org/hibernate/envers/test/entities/manytomany/unidirectional/M2MTargetNotAuditedEntity.java new file mode 100644 index 0000000000..d7c2e9a883 --- /dev/null +++ b/envers/src/test/java/org/hibernate/envers/test/entities/manytomany/unidirectional/M2MTargetNotAuditedEntity.java @@ -0,0 +1,116 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2008, Red Hat Middleware LLC or third-party contributors as + * indicated by the @author tags or express copyright attribution + * statements applied by the authors. All third-party contributions are + * distributed under license by Red Hat Middleware LLC. + * + * This copyrighted material is made available to anyone wishing to use, modify, + * copy, or redistribute it subject to the terms and conditions of the GNU + * Lesser General Public License, as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this distribution; if not, write to: + * Free Software Foundation, Inc. + * 51 Franklin Street, Fifth Floor + * Boston, MA 02110-1301 USA + */ +package org.hibernate.envers.test.entities.manytomany.unidirectional; + +import javax.persistence.*; + +import org.hibernate.envers.Audited; +import org.hibernate.envers.RelationTargetAuditMode; +import org.hibernate.envers.test.entities.UnversionedStrTestEntity; + +import java.util.List; + +/** + * Audited entity with a many-to-many-reference to not audited entity. + * @author Toamsz Bech + * @author Adam Warski + */ +@Entity +public class M2MTargetNotAuditedEntity { + @Id + private Integer id; + + @Audited + private String data; + + @Audited(targetAuditMode = RelationTargetAuditMode.NOT_AUDITED) + @ManyToMany(fetch = FetchType.LAZY) + private List references; + + public M2MTargetNotAuditedEntity() { } + + public M2MTargetNotAuditedEntity(Integer id, String data, List references) { + this.id = id; + this.data = data; + this.references = references; + } + + public M2MTargetNotAuditedEntity(String data, List references) { + this.data = data; + this.references = references; + } + + public M2MTargetNotAuditedEntity(Integer id, String data) { + this.id = id; + this.data = data; + } + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public String getData() { + return data; + } + + public void setData(String data) { + this.data = data; + } + + public List getReferences() { + return references; + } + + public void setReferences(List references) { + this.references = references; + } + + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof M2MTargetNotAuditedEntity)) return false; + + M2MTargetNotAuditedEntity that = (M2MTargetNotAuditedEntity) o; + + if (data != null ? !data.equals(that.getData()) : that.getData() != null) return false; + //noinspection RedundantIfStatement + if (id != null ? !id.equals(that.getId()) : that.getId() != null) return false; + + return true; + } + + public int hashCode() { + int result; + result = (id != null ? id.hashCode() : 0); + result = 31 * result + (data != null ? data.hashCode() : 0); + return result; + } + + public String toString() { + return "M2MTargetNotAuditedEntity(id = " + id + ", data = " + data + ")"; + } +} diff --git a/envers/src/test/java/org/hibernate/envers/test/integration/manytomany/unidirectional/M2MRelationNotAuditedTarget.java b/envers/src/test/java/org/hibernate/envers/test/integration/manytomany/unidirectional/M2MRelationNotAuditedTarget.java new file mode 100644 index 0000000000..ce022f4daa --- /dev/null +++ b/envers/src/test/java/org/hibernate/envers/test/integration/manytomany/unidirectional/M2MRelationNotAuditedTarget.java @@ -0,0 +1,167 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2008, Red Hat Middleware LLC or third-party contributors as + * indicated by the @author tags or express copyright attribution + * statements applied by the authors. All third-party contributions are + * distributed under license by Red Hat Middleware LLC. + * + * This copyrighted material is made available to anyone wishing to use, modify, + * copy, or redistribute it subject to the terms and conditions of the GNU + * Lesser General Public License, as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this distribution; if not, write to: + * Free Software Foundation, Inc. + * 51 Franklin Street, Fifth Floor + * Boston, MA 02110-1301 USA + */ +package org.hibernate.envers.test.integration.manytomany.unidirectional; + +import java.util.Arrays; +import java.util.List; +import java.util.ArrayList; + +import javax.persistence.EntityManager; + +import org.hibernate.ejb.Ejb3Configuration; +import org.hibernate.envers.test.AbstractEntityTest; +import static org.hibernate.envers.test.tools.TestTools.*; +import org.hibernate.envers.test.entities.UnversionedStrTestEntity; +import org.hibernate.envers.test.entities.manytomany.unidirectional.M2MTargetNotAuditedEntity; + +import org.testng.annotations.BeforeClass; +import org.testng.annotations.Test; + +/** + * A test for auditing a many-to-many relation where the target entity is not audited. + * @author Adam Warski + */ +public class M2MRelationNotAuditedTarget extends AbstractEntityTest { + private Integer tnae1_id; + private Integer tnae2_id; + + private Integer uste1_id; + private Integer uste2_id; + + public void configure(Ejb3Configuration cfg) { + cfg.addAnnotatedClass(M2MTargetNotAuditedEntity.class); + cfg.addAnnotatedClass(UnversionedStrTestEntity.class); + } + + @BeforeClass(dependsOnMethods = "init") + public void initData() { + EntityManager em = getEntityManager(); + + UnversionedStrTestEntity uste1 = new UnversionedStrTestEntity("str1"); + UnversionedStrTestEntity uste2 = new UnversionedStrTestEntity("str2"); + + // No revision + em.getTransaction().begin(); + + em.persist(uste1); + em.persist(uste2); + + em.getTransaction().commit(); + + // Revision 1 + em.getTransaction().begin(); + + uste1 = em.find(UnversionedStrTestEntity.class, uste1.getId()); + uste2 = em.find(UnversionedStrTestEntity.class, uste2.getId()); + + M2MTargetNotAuditedEntity tnae1 = new M2MTargetNotAuditedEntity(1, "tnae1", new ArrayList()); + M2MTargetNotAuditedEntity tnae2 = new M2MTargetNotAuditedEntity(2, "tnae2", new ArrayList()); + tnae2.getReferences().add(uste1); + tnae2.getReferences().add(uste2); + em.persist(tnae1); + em.persist(tnae2); + + em.getTransaction().commit(); + + // Revision 2 + em.getTransaction().begin(); + + tnae1 = em.find(M2MTargetNotAuditedEntity.class, tnae1.getId()); + tnae2 = em.find(M2MTargetNotAuditedEntity.class, tnae2.getId()); + + tnae1.getReferences().add(uste1); + tnae2.getReferences().remove(uste1); + + em.getTransaction().commit(); + + // Revision 3 + em.getTransaction().begin(); + + tnae1 = em.find(M2MTargetNotAuditedEntity.class, tnae1.getId()); + tnae2 = em.find(M2MTargetNotAuditedEntity.class, tnae2.getId()); + + //field not changed!!! + tnae1.getReferences().add(uste1); + tnae2.getReferences().remove(uste2); + + em.getTransaction().commit(); + + // Revision 4 + em.getTransaction().begin(); + + tnae1 = em.find(M2MTargetNotAuditedEntity.class, tnae1.getId()); + tnae2 = em.find(M2MTargetNotAuditedEntity.class, tnae2.getId()); + + tnae1.getReferences().add(uste2); + tnae2.getReferences().add(uste1); + + em.getTransaction().commit(); + + // + tnae1_id = tnae1.getId(); + tnae2_id = tnae2.getId(); + uste1_id = uste1.getId(); + uste2_id = uste2.getId(); + } + + @Test + public void testRevisionsCounts() { + List revisions = getAuditReader().getRevisions(M2MTargetNotAuditedEntity.class, tnae1_id); + assert Arrays.asList(1, 2, 4).equals(revisions); + revisions = getAuditReader().getRevisions(M2MTargetNotAuditedEntity.class, tnae2_id); + assert Arrays.asList(1, 2, 3, 4).equals(revisions); + } + + @Test + public void testHistoryOfTnae1_id() { + UnversionedStrTestEntity uste1 = getEntityManager().find(UnversionedStrTestEntity.class, uste1_id); + UnversionedStrTestEntity uste2 = getEntityManager().find(UnversionedStrTestEntity.class, uste2_id); + + M2MTargetNotAuditedEntity rev1 = getAuditReader().find(M2MTargetNotAuditedEntity.class, tnae1_id, 1); + M2MTargetNotAuditedEntity rev2 = getAuditReader().find(M2MTargetNotAuditedEntity.class, tnae1_id, 2); + M2MTargetNotAuditedEntity rev3 = getAuditReader().find(M2MTargetNotAuditedEntity.class, tnae1_id, 3); + M2MTargetNotAuditedEntity rev4 = getAuditReader().find(M2MTargetNotAuditedEntity.class, tnae1_id, 4); + + checkList(rev1.getReferences()); + checkList(rev2.getReferences(), uste1); + checkList(rev3.getReferences(), uste1); + checkList(rev4.getReferences(), uste1, uste2); + } + + @Test + public void testHistoryOfTnae2_id() { + UnversionedStrTestEntity uste1 = getEntityManager().find(UnversionedStrTestEntity.class, uste1_id); + UnversionedStrTestEntity uste2 = getEntityManager().find(UnversionedStrTestEntity.class, uste2_id); + + M2MTargetNotAuditedEntity rev1 = getAuditReader().find(M2MTargetNotAuditedEntity.class, tnae2_id, 1); + M2MTargetNotAuditedEntity rev2 = getAuditReader().find(M2MTargetNotAuditedEntity.class, tnae2_id, 2); + M2MTargetNotAuditedEntity rev3 = getAuditReader().find(M2MTargetNotAuditedEntity.class, tnae2_id, 3); + M2MTargetNotAuditedEntity rev4 = getAuditReader().find(M2MTargetNotAuditedEntity.class, tnae2_id, 4); + + checkList(rev1.getReferences(), uste1, uste2); + checkList(rev2.getReferences(), uste2); + checkList(rev3.getReferences()); + checkList(rev4.getReferences(), uste1); + } +}