PageRenderTime 47ms CodeModel.GetById 14ms RepoModel.GetById 0ms app.codeStats 0ms

/sandbox/simondud/Envers.NET.Spring/Envers.NET.SLN/Envers.NET/Envers/Configuration/Metadata/CollectionMetadataGenerator.cs

https://bitbucket.org/dabide/nhcontrib
C# | 574 lines | 355 code | 78 blank | 141 comment | 55 complexity | a1ef24e13b390e8ea70087fcd8d0d70d MD5 | raw file
Possible License(s): BSD-3-Clause, MPL-2.0-no-copyleft-exception, CC-BY-SA-3.0, GPL-2.0, Apache-2.0, LGPL-3.0, LGPL-2.1
  1. using System;
  2. using System.Collections;
  3. using System.Collections.Generic;
  4. using System.Collections.ObjectModel;
  5. using System.Linq;
  6. using System.Text;
  7. using Iesi.Collections.Generic;
  8. using log4net;
  9. using NHibernate.Envers.Entities.Mapper;
  10. using NHibernate.Envers.Configuration.Metadata.Reader;
  11. using NHibernate.Envers.Entities.Mapper.Relation.Component;
  12. using NHibernate.Envers.Entities.Mapper.Relation.Lazy.Proxy;
  13. using NHibernate.Envers.Entities.Mapper.Relation.Query;
  14. using NHibernate.Envers.Tools;
  15. using NHibernate.Type;
  16. using NHibernate.Mapping;
  17. using NHibernate.Envers.Entities;
  18. using NHibernate.Envers.Entities.Mapper.Id;
  19. using System.Xml;
  20. using NHibernate.Envers.Entities.Mapper.Relation;
  21. using C5;
  22. namespace NHibernate.Envers.Configuration.Metadata
  23. {
  24. /// <summary>
  25. ///Generates metadata for a collection-valued property.
  26. ///@author Simon Duduica, port of Envers omonyme class by Adam Warski (adam at warski dot org)
  27. ///TODO Simon - implement methods that throw NotImpl....
  28. /// </summary>
  29. /// TODO Simon.
  30. public sealed class CollectionMetadataGenerator {
  31. private static ILog log = LogManager.GetLogger(typeof(CollectionMetadataGenerator));
  32. private readonly AuditMetadataGenerator mainGenerator;
  33. private readonly String propertyName;
  34. private readonly NHibernate.Mapping.Collection propertyValue;
  35. private readonly ICompositeMapperBuilder currentMapper;
  36. private readonly String referencingEntityName;
  37. private readonly EntityXmlMappingData xmlMappingData;
  38. private readonly PropertyAuditingData propertyAuditingData;
  39. private readonly EntityConfiguration referencingEntityConfiguration;
  40. /**
  41. * Null if this collection isn't a relation to another entity.
  42. */
  43. private readonly String referencedEntityName;
  44. /**
  45. * @param mainGenerator Main generator, giving access to configuration and the basic mapper.
  46. * @param propertyValue Value of the collection, as mapped by Hibernate.
  47. * @param currentMapper Mapper, to which the appropriate {@link org.hibernate.envers.entities.mapper.PropertyMapper}
  48. * will be added.
  49. * @param referencingEntityName Name of the entity that owns this collection.
  50. * @param xmlMappingData In case this collection requires a middle table, additional mapping documents will
  51. * be created using this object.
  52. * @param propertyAuditingData Property auditing (meta-)data. Among other things, holds the name of the
  53. * property that references the collection in the referencing entity, the user data for middle (join)
  54. * table and the value of the <code>@MapKey</code> annotation, if there was one.
  55. */
  56. public CollectionMetadataGenerator(AuditMetadataGenerator mainGenerator,
  57. Mapping.Collection propertyValue, ICompositeMapperBuilder currentMapper,
  58. String referencingEntityName, EntityXmlMappingData xmlMappingData,
  59. PropertyAuditingData propertyAuditingData) {
  60. this.mainGenerator = mainGenerator;
  61. this.propertyValue = propertyValue;
  62. this.currentMapper = currentMapper;
  63. this.referencingEntityName = referencingEntityName;
  64. this.xmlMappingData = xmlMappingData;
  65. this.propertyAuditingData = propertyAuditingData;
  66. this.propertyName = propertyAuditingData.Name;
  67. referencingEntityConfiguration = mainGenerator.EntitiesConfigurations[referencingEntityName];
  68. if (referencingEntityConfiguration == null) {
  69. throw new MappingException("Unable to read auditing configuration for " + referencingEntityName + "!");
  70. }
  71. referencedEntityName = MappingTools.getReferencedEntityName(propertyValue.Element);
  72. }
  73. public void AddCollection() {
  74. IType type = propertyValue.Type;
  75. bool oneToManyAttachedType = type is BagType || type is SetType || type is MapType || type is ListType;
  76. bool inverseOneToMany = (propertyValue.Element is OneToMany) && (propertyValue.IsInverse);
  77. bool fakeOneToManyBidirectional = (propertyValue.Element is OneToMany) && (propertyAuditingData.AuditMappedBy != null);
  78. if (oneToManyAttachedType && (inverseOneToMany || fakeOneToManyBidirectional)) {
  79. // A one-to-many relation mapped using @ManyToOne and @OneToMany(mappedBy="...")
  80. AddOneToManyAttached(fakeOneToManyBidirectional);
  81. } else {
  82. // All other kinds of relations require a middle (join) table.
  83. AddWithMiddleTable();
  84. }
  85. }
  86. private MiddleIdData CreateMiddleIdData(IdMappingData idMappingData, String prefix, String entityName) {
  87. return new MiddleIdData(mainGenerator.VerEntCfg, idMappingData, prefix, entityName,
  88. mainGenerator.EntitiesConfigurations.ContainsKey(entityName));
  89. }
  90. private void AddOneToManyAttached(bool fakeOneToManyBidirectional) {
  91. //throw new NotImplementedException();
  92. log.Debug("Adding audit mapping for property " + referencingEntityName + "." + propertyName +
  93. ": one-to-many collection, using a join column on the referenced entity.");
  94. String mappedBy = GetMappedBy(propertyValue);
  95. IdMappingData referencedIdMapping = mainGenerator.GetReferencedIdMappingData(referencingEntityName,
  96. referencedEntityName, propertyAuditingData, false);
  97. IdMappingData referencingIdMapping = referencingEntityConfiguration.IdMappingData;
  98. // Generating the id mappers data for the referencing side of the relation.
  99. MiddleIdData referencingIdData = CreateMiddleIdData(referencingIdMapping,
  100. mappedBy + "_", referencingEntityName);
  101. // And for the referenced side. The prefixed mapper won't be used (as this collection isn't persisted
  102. // in a join table, so the prefix value is arbitrary).
  103. MiddleIdData referencedIdData = CreateMiddleIdData(referencedIdMapping,
  104. null, referencedEntityName);
  105. // Generating the element mapping.
  106. MiddleComponentData elementComponentData = new MiddleComponentData(
  107. new MiddleRelatedComponentMapper(referencedIdData), 0);
  108. // Generating the index mapping, if an index exists. It can only exists in case a javax.persistence.MapKey
  109. // annotation is present on the entity. So the middleEntityXml will be not be used. The queryGeneratorBuilder
  110. // will only be checked for nullnes.
  111. MiddleComponentData indexComponentData = AddIndex(null, null);
  112. // Generating the query generator - it should read directly from the related entity.
  113. IRelationQueryGenerator queryGenerator = new OneAuditEntityQueryGenerator(mainGenerator.GlobalCfg,
  114. mainGenerator.VerEntCfg, referencingIdData, referencedEntityName,
  115. referencedIdMapping.IdMapper);
  116. // Creating common mapper data.
  117. CommonCollectionMapperData commonCollectionMapperData = new CommonCollectionMapperData(
  118. mainGenerator.VerEntCfg, referencedEntityName,
  119. propertyAuditingData.getPropertyData(),
  120. referencingIdData, queryGenerator);
  121. IPropertyMapper fakeBidirectionalRelationMapper;
  122. IPropertyMapper fakeBidirectionalRelationIndexMapper;
  123. if (fakeOneToManyBidirectional)
  124. {
  125. // In case of a fake many-to-one bidirectional relation, we have to generate a mapper which maps
  126. // the mapped-by property name to the id of the related entity (which is the owner of the collection).
  127. String auditMappedBy = propertyAuditingData.AuditMappedBy;
  128. // Creating a prefixed relation mapper.
  129. IIdMapper relMapper = referencingIdMapping.IdMapper.PrefixMappedProperties(
  130. MappingTools.createToOneRelationPrefix(auditMappedBy));
  131. fakeBidirectionalRelationMapper = new ToOneIdMapper(
  132. relMapper,
  133. // The mapper will only be used to map from entity to map, so no need to provide other details
  134. // when constructing the PropertyData.
  135. new PropertyData(auditMappedBy, null, null, ModificationStore._NULL),
  136. referencedEntityName, false);
  137. // Checking if there's an index defined. If so, adding a mapper for it.
  138. if (propertyAuditingData.PositionMappedBy != null)
  139. {
  140. String positionMappedBy = propertyAuditingData.PositionMappedBy;
  141. fakeBidirectionalRelationIndexMapper = new SinglePropertyMapper(new PropertyData(positionMappedBy, null, null, ModificationStore._NULL));
  142. // Also, overwriting the index component data to properly read the index.
  143. indexComponentData = new MiddleComponentData(new MiddleStraightComponentMapper(positionMappedBy), 0);
  144. }
  145. else
  146. {
  147. fakeBidirectionalRelationIndexMapper = null;
  148. }
  149. }
  150. else
  151. {
  152. fakeBidirectionalRelationMapper = null;
  153. fakeBidirectionalRelationIndexMapper = null;
  154. }
  155. // Checking the type of the collection and adding an appropriate mapper.
  156. AddMapper(commonCollectionMapperData, elementComponentData, indexComponentData);
  157. // Storing information about this relation.
  158. referencingEntityConfiguration.AddToManyNotOwningRelation(propertyName, mappedBy,
  159. referencedEntityName, referencingIdData.PrefixedMapper, fakeBidirectionalRelationMapper,
  160. fakeBidirectionalRelationIndexMapper);
  161. }
  162. /**
  163. * Adds mapping of the id of a related entity to the given xml mapping, prefixing the id with the given prefix.
  164. * @param xmlMapping Mapping, to which to add the xml.
  165. * @param prefix Prefix for the names of properties which will be prepended to properties that form the id.
  166. * @param columnNameIterator Iterator over the column names that will be used for properties that form the id.
  167. * @param relatedIdMapping Id mapping data of the related entity.
  168. */
  169. private void AddRelatedToXmlMapping(XmlElement xmlMapping, String prefix,
  170. MetadataTools.ColumnNameEnumerator columnNameIterator,
  171. IdMappingData relatedIdMapping) {
  172. XmlElement properties = (XmlElement) relatedIdMapping.XmlRelationMapping.Clone();
  173. MetadataTools.PrefixNamesInPropertyElement(properties, prefix, columnNameIterator, true, true);
  174. foreach (XmlElement idProperty in (System.Collections.Generic.IList<XmlElement>) properties.ChildNodes) {
  175. xmlMapping.AppendChild((XmlElement) idProperty.Clone());
  176. }
  177. }
  178. private String GetMiddleTableName(Mapping.Collection value, String entityName) {
  179. // We check how Hibernate maps the collection.
  180. if (value.Element is OneToMany && !value.IsInverse) {
  181. // This must be a @JoinColumn+@OneToMany mapping. Generating the table name, as Hibernate doesn't use a
  182. // middle table for mapping this relation.
  183. String refEntName = MappingTools.getReferencedEntityName(value.Element);
  184. return entityName.Substring(entityName.LastIndexOf(".") + 1) + "_" +
  185. refEntName.Substring(refEntName.LastIndexOf(".") + 1);
  186. } else {
  187. // Hibernate uses a middle table for mapping this relation, so we get it's name directly.
  188. return value.CollectionTable.Name;
  189. }
  190. }
  191. private void AddWithMiddleTable() {
  192. log.Debug("Adding audit mapping for property " + referencingEntityName + "." + propertyName +
  193. ": collection with a join table.");
  194. // Generating the name of the middle table
  195. String auditMiddleTableName;
  196. String auditMiddleEntityName;
  197. if (!String.IsNullOrEmpty(propertyAuditingData.JoinTable.Name))
  198. {
  199. auditMiddleTableName = propertyAuditingData.JoinTable.Name;
  200. auditMiddleEntityName = propertyAuditingData.JoinTable.Name;
  201. }
  202. else
  203. {
  204. String middleTableName = GetMiddleTableName(propertyValue, referencingEntityName);
  205. auditMiddleTableName = mainGenerator.VerEntCfg.GetAuditTableName(null, middleTableName);
  206. auditMiddleEntityName = mainGenerator.VerEntCfg.GetAuditEntityName(middleTableName);
  207. }
  208. log.Debug("Using join table name: " + auditMiddleTableName);
  209. // Generating the XML mapping for the middle entity, only if the relation isn't inverse.
  210. // If the relation is inverse, will be later checked by comparing middleEntityXml with null.
  211. XmlElement middleEntityXml;
  212. if (!propertyValue.IsInverse)
  213. {
  214. // Generating a unique middle entity name
  215. auditMiddleEntityName = mainGenerator.AuditEntityNameRegister.createUnique(auditMiddleEntityName);
  216. // Registering the generated name
  217. mainGenerator.AuditEntityNameRegister.register(auditMiddleEntityName);
  218. middleEntityXml = CreateMiddleEntityXml(auditMiddleTableName, auditMiddleEntityName, propertyValue.Where);
  219. }
  220. else
  221. {
  222. middleEntityXml = null;
  223. }
  224. // ******
  225. // Generating the mapping for the referencing entity (it must be an entity).
  226. // ******
  227. // Getting the id-mapping data of the referencing entity (the entity that "owns" this collection).
  228. IdMappingData referencingIdMapping = referencingEntityConfiguration.IdMappingData;
  229. // Only valid for an inverse relation; null otherwise.
  230. String mappedBy;
  231. // The referencing prefix is always for a related entity. So it has always the "_" at the end added.
  232. String referencingPrefixRelated;
  233. String referencedPrefix;
  234. if (propertyValue.IsInverse)
  235. {
  236. // If the relation is inverse, then referencedEntityName is not null.
  237. mappedBy = GetMappedBy(propertyValue.CollectionTable, mainGenerator.Cfg.GetClassMapping(referencedEntityName));
  238. referencingPrefixRelated = mappedBy + "_";
  239. referencedPrefix = StringTools.GetLastComponent(referencedEntityName);
  240. }
  241. else
  242. {
  243. mappedBy = null;
  244. referencingPrefixRelated = StringTools.GetLastComponent(referencingEntityName) + "_";
  245. referencedPrefix = referencedEntityName == null ? "element" : propertyName;
  246. }
  247. // Storing the id data of the referencing entity: original mapper, prefixed mapper and entity name.
  248. MiddleIdData referencingIdData = CreateMiddleIdData(referencingIdMapping,
  249. referencingPrefixRelated, referencingEntityName);
  250. // Creating a query generator builder, to which additional id data will be added, in case this collection
  251. // references some entities (either from the element or index). At the end, this will be used to build
  252. // a query generator to read the raw data collection from the middle table.
  253. QueryGeneratorBuilder queryGeneratorBuilder = new QueryGeneratorBuilder(mainGenerator.GlobalCfg,
  254. mainGenerator.VerEntCfg, referencingIdData, auditMiddleEntityName);
  255. // Adding the XML mapping for the referencing entity, if the relation isn't inverse.
  256. if (middleEntityXml != null)
  257. {
  258. // Adding related-entity (in this case: the referencing's entity id) id mapping to the xml.
  259. AddRelatedToXmlMapping(middleEntityXml, referencingPrefixRelated,
  260. MetadataTools.GetColumnNameEnumerator(propertyValue.Key.ColumnIterator.GetEnumerator()),
  261. referencingIdMapping);
  262. }
  263. // ******
  264. // Generating the element mapping.
  265. // ******
  266. MiddleComponentData elementComponentData = AddValueToMiddleTable(propertyValue.Element, middleEntityXml,
  267. queryGeneratorBuilder, referencedPrefix, propertyAuditingData.JoinTable.InverseJoinColumns);
  268. // ******
  269. // Generating the index mapping, if an index exists.
  270. // ******
  271. MiddleComponentData indexComponentData = AddIndex(middleEntityXml, queryGeneratorBuilder);
  272. // ******
  273. // Generating the property mapper.
  274. // ******
  275. // Building the query generator.
  276. IRelationQueryGenerator queryGenerator = queryGeneratorBuilder.Build(new Collection<MiddleComponentData>{elementComponentData, indexComponentData});
  277. // Creating common data
  278. CommonCollectionMapperData commonCollectionMapperData = new CommonCollectionMapperData(
  279. mainGenerator.VerEntCfg, auditMiddleEntityName,
  280. propertyAuditingData.getPropertyData(),
  281. referencingIdData, queryGenerator);
  282. // Checking the type of the collection and adding an appropriate mapper.
  283. AddMapper(commonCollectionMapperData, elementComponentData, indexComponentData);
  284. // ******
  285. // Storing information about this relation.
  286. // ******
  287. StoreMiddleEntityRelationInformation(mappedBy);
  288. }
  289. private MiddleComponentData AddIndex(XmlElement middleEntityXml, QueryGeneratorBuilder queryGeneratorBuilder) {
  290. if (propertyValue is IndexedCollection)
  291. {
  292. IndexedCollection indexedValue = (IndexedCollection)propertyValue;
  293. String mapKey = propertyAuditingData.MapKey;
  294. if (mapKey == null)
  295. {
  296. // This entity doesn't specify a javax.persistence.MapKey. Mapping it to the middle entity.
  297. return AddValueToMiddleTable(indexedValue.Index, middleEntityXml,
  298. queryGeneratorBuilder, "mapkey", null);
  299. }
  300. else
  301. {
  302. IdMappingData referencedIdMapping =
  303. mainGenerator.EntitiesConfigurations[referencedEntityName].IdMappingData;
  304. int currentIndex = queryGeneratorBuilder == null ? 0 : queryGeneratorBuilder.CurrentIndex;
  305. if ("".Equals(mapKey))
  306. {
  307. // The key of the map is the id of the entity.
  308. return new MiddleComponentData(new MiddleMapKeyIdComponentMapper(mainGenerator.VerEntCfg,
  309. referencedIdMapping.IdMapper), currentIndex);
  310. }
  311. else
  312. {
  313. // The key of the map is a property of the entity.
  314. return new MiddleComponentData(new MiddleMapKeyPropertyComponentMapper(mapKey,
  315. propertyAuditingData.AccessType), currentIndex);
  316. }
  317. }
  318. }
  319. else
  320. {
  321. // No index - creating a dummy mapper.
  322. return new MiddleComponentData(new MiddleDummyComponentMapper(), 0);
  323. }
  324. }
  325. /**
  326. *
  327. * @param value Value, which should be mapped to the middle-table, either as a relation to another entity,
  328. * or as a simple value.
  329. * @param xmlMapping If not <code>null</code>, xml mapping for this value is added to this element.
  330. * @param queryGeneratorBuilder In case <code>value</code> is a relation to another entity, information about it
  331. * should be added to the given.
  332. * @param prefix Prefix for proeprty names of related entities identifiers.
  333. * @param joinColumns Names of columns to use in the xml mapping, if this array isn't null and has any elements.
  334. * @return Data for mapping this component.
  335. */
  336. //@SuppressWarnings({"unchecked"})
  337. private MiddleComponentData AddValueToMiddleTable(IValue value, XmlElement xmlMapping,
  338. QueryGeneratorBuilder queryGeneratorBuilder,
  339. String prefix, JoinColumnAttribute[] joinColumns) {
  340. IType type = value.Type;
  341. if (type is ManyToOneType) {
  342. String prefixRelated = prefix + "_";
  343. String referencedEntityName = MappingTools.getReferencedEntityName(value);
  344. IdMappingData referencedIdMapping = mainGenerator.GetReferencedIdMappingData(referencingEntityName,
  345. referencedEntityName, propertyAuditingData, true);
  346. // Adding related-entity (in this case: the referenced entities id) id mapping to the xml only if the
  347. // relation isn't inverse (so when <code>xmlMapping</code> is not null).
  348. if (xmlMapping != null) {
  349. AddRelatedToXmlMapping(xmlMapping, prefixRelated,
  350. joinColumns != null && joinColumns.Length > 0
  351. ? MetadataTools.GetColumnNameEnumerator(joinColumns)
  352. : MetadataTools.GetColumnNameEnumerator(value.ColumnIterator.GetEnumerator()),
  353. referencedIdMapping);
  354. }
  355. // Storing the id data of the referenced entity: original mapper, prefixed mapper and entity name.
  356. MiddleIdData referencedIdData = CreateMiddleIdData(referencedIdMapping,
  357. prefixRelated, referencedEntityName);
  358. // And adding it to the generator builder.
  359. queryGeneratorBuilder.AddRelation(referencedIdData);
  360. return new MiddleComponentData(new MiddleRelatedComponentMapper(referencedIdData),
  361. queryGeneratorBuilder.CurrentIndex);
  362. } else {
  363. // Last but one parameter: collection components are always insertable
  364. bool mapped = mainGenerator.BasicMetadataGenerator.AddBasic(xmlMapping,
  365. new PropertyAuditingData(prefix, "field", ModificationStore.FULL, RelationTargetAuditMode.AUDITED, null, null, false),
  366. value, null, true, true);
  367. if (mapped) {
  368. // Simple values are always stored in the first item of the array returned by the query generator.
  369. return new MiddleComponentData(new MiddleSimpleComponentMapper(mainGenerator.VerEntCfg, prefix), 0);
  370. } else {
  371. mainGenerator.ThrowUnsupportedTypeException(type, referencingEntityName, propertyName);
  372. // Impossible to get here.
  373. throw new AssertionFailure();
  374. }
  375. }
  376. }
  377. private void AddMapper(CommonCollectionMapperData commonCollectionMapperData, MiddleComponentData elementComponentData,
  378. MiddleComponentData indexComponentData) {
  379. IType type = propertyValue.Type;
  380. if (type is SortedSetType) {
  381. currentMapper.AddComposite(propertyAuditingData.getPropertyData(),
  382. new BasicCollectionMapper<IDictionary>(commonCollectionMapperData,
  383. typeof(TreeSet<>), typeof(SortedSetProxy<>), elementComponentData));
  384. }
  385. else if (type is SetType) {
  386. currentMapper.AddComposite(propertyAuditingData.getPropertyData(),
  387. new BasicCollectionMapper<Set>(commonCollectionMapperData,
  388. typeof(HashedSet<>), typeof(SetProxy<>), elementComponentData));
  389. }
  390. else
  391. throw new NotImplementedException();
  392. //else if (type is SortedMapType) {
  393. // // Indexed collection, so <code>indexComponentData</code> is not null.
  394. // currentMapper.addComposite(propertyAuditingData.getPropertyData(),
  395. // new MapCollectionMapper<Map>(commonCollectionMapperData,
  396. // TreeMap.class, SortedMapProxy.class, elementComponentData, indexComponentData));
  397. //} else if (type is MapType) {
  398. // // Indexed collection, so <code>indexComponentData</code> is not null.
  399. // currentMapper.addComposite(propertyAuditingData.getPropertyData(),
  400. // new MapCollectionMapper<Map>(commonCollectionMapperData,
  401. // HashMap.class, MapProxy.class, elementComponentData, indexComponentData));
  402. //} else if (type is BagType) {
  403. // currentMapper.addComposite(propertyAuditingData.getPropertyData(),
  404. // new BasicCollectionMapper<List>(commonCollectionMapperData,
  405. // ArrayList.class, ListProxy.class, elementComponentData));
  406. //} else if (type is ListType) {
  407. // // Indexed collection, so <code>indexComponentData</code> is not null.
  408. // currentMapper.addComposite(propertyAuditingData.getPropertyData(),
  409. // new ListCollectionMapper(commonCollectionMapperData,
  410. // elementComponentData, indexComponentData));
  411. //} else {
  412. // mainGenerator.ThrowUnsupportedTypeException(type, referencingEntityName, propertyName);
  413. //}
  414. }
  415. private void StoreMiddleEntityRelationInformation(String mappedBy) {
  416. // Only if this is a relation (when there is a referenced entity).
  417. if (referencedEntityName != null)
  418. {
  419. if (propertyValue.IsInverse)
  420. {
  421. referencingEntityConfiguration.AddToManyMiddleNotOwningRelation(propertyName, mappedBy, referencedEntityName);
  422. }
  423. else
  424. {
  425. referencingEntityConfiguration.addToManyMiddleRelation(propertyName, referencedEntityName);
  426. }
  427. }
  428. }
  429. private XmlElement CreateMiddleEntityXml(String auditMiddleTableName, String auditMiddleEntityName, String where) {
  430. String schema = mainGenerator.GetSchema(propertyAuditingData.JoinTable.Schema, propertyValue.CollectionTable);
  431. String catalog = mainGenerator.GetCatalog(propertyAuditingData.JoinTable.Catalog, propertyValue.CollectionTable);
  432. XmlElement middleEntityXml = MetadataTools.CreateEntity(xmlMappingData.newAdditionalMapping(),
  433. new AuditTableData(auditMiddleEntityName, auditMiddleTableName, schema, catalog), null);
  434. XmlElement middleEntityXmlId = middleEntityXml.OwnerDocument.CreateElement("composite-id");
  435. middleEntityXml.AppendChild(middleEntityXmlId);
  436. // If there is a where clause on the relation, adding it to the middle entity.
  437. if (where != null)
  438. {
  439. middleEntityXml.SetAttribute("where", where);
  440. }
  441. middleEntityXmlId.SetAttribute("name", mainGenerator.VerEntCfg.OriginalIdPropName);
  442. // Adding the revision number as a foreign key to the revision info entity to the composite id of the
  443. // middle table.
  444. mainGenerator.AddRevisionInfoRelation(middleEntityXmlId);
  445. // Adding the revision type property to the entity xml.
  446. mainGenerator.AddRevisionType(middleEntityXml);
  447. // All other properties should also be part of the primary key of the middle entity.
  448. return middleEntityXmlId;
  449. }
  450. private String GetMappedBy(Mapping.Collection collectionValue) {
  451. PersistentClass referencedClass = ((OneToMany)collectionValue.Element).AssociatedClass;
  452. // If there's an @AuditMappedBy specified, returning it directly.
  453. String auditMappedBy = propertyAuditingData.AuditMappedBy;
  454. if (auditMappedBy != null)
  455. {
  456. return auditMappedBy;
  457. }
  458. IEnumerator<Property> assocClassProps = referencedClass.PropertyIterator.GetEnumerator();
  459. while (assocClassProps.MoveNext())
  460. {
  461. Property property = assocClassProps.Current;
  462. if (Toolz.IteratorsContentEqual(property.Value.ColumnIterator.GetEnumerator(),
  463. collectionValue.Key.ColumnIterator.GetEnumerator()))
  464. {
  465. return property.Name;
  466. }
  467. }
  468. throw new MappingException("Unable to read the mapped by attribute for " + propertyName + " in "
  469. + referencingEntityName + "!");
  470. }
  471. private String GetMappedBy(Table collectionTable, PersistentClass referencedClass) {
  472. // If there's an @AuditMappedBy specified, returning it directly.
  473. String auditMappedBy = propertyAuditingData.AuditMappedBy;
  474. if (auditMappedBy != null)
  475. {
  476. return auditMappedBy;
  477. }
  478. IEnumerator<Property> properties = referencedClass.PropertyIterator.GetEnumerator();
  479. while (properties.MoveNext())
  480. {
  481. Property property = properties.Current;
  482. if (property.Value is ICollection)
  483. {
  484. // The equality is intentional. We want to find a collection property with the same collection table.
  485. //noinspection ObjectEquality
  486. if (((NHibernate.Mapping.Collection)property.Value).CollectionTable == collectionTable)
  487. {
  488. return property.Name;
  489. }
  490. }
  491. }
  492. throw new MappingException("Unable to read the mapped by attribute for " + propertyName + " in "
  493. + referencingEntityName + "!");
  494. }
  495. }
  496. }