Skip to content

Commit

Permalink
HHH-5764 - Support for multi-level derived ids
Browse files Browse the repository at this point in the history
(cherry picked from commit a32372d)
  • Loading branch information
mattdrees authored and sebersole committed Nov 9, 2015
1 parent b4b6fa9 commit 943acc7
Show file tree
Hide file tree
Showing 16 changed files with 484 additions and 61 deletions.
Expand Up @@ -1611,7 +1611,8 @@ public void processSecondPasses(MetadataBuildingContext buildingContext) {

processSecondPasses( pkDrivenByDefaultMapsIdSecondPassList );
processSecondPasses( setSimpleValueTypeSecondPassList );
processSecondPasses( copyIdentifierComponentSecondPasList );

processCopyIdentifierSecondPassesInOrder();

processFkSecondPassesInOrder();

Expand All @@ -1637,6 +1638,14 @@ public void processSecondPasses(MetadataBuildingContext buildingContext) {
}
}

private void processCopyIdentifierSecondPassesInOrder() {
if ( copyIdentifierComponentSecondPasList == null ) {
return;
}
sortCopyIdentifierComponentSecondPasses();
processSecondPasses( copyIdentifierComponentSecondPasList );
}

private void processSecondPasses(ArrayList<? extends SecondPass> secondPasses) {
if ( secondPasses == null ) {
return;
Expand All @@ -1649,6 +1658,40 @@ private void processSecondPasses(ArrayList<? extends SecondPass> secondPasses) {
secondPasses.clear();
}

private void sortCopyIdentifierComponentSecondPasses() {

ArrayList<CopyIdentifierComponentSecondPass> sorted =
new ArrayList<CopyIdentifierComponentSecondPass>( copyIdentifierComponentSecondPasList.size() );
Set<CopyIdentifierComponentSecondPass> toSort = new HashSet<CopyIdentifierComponentSecondPass>();
toSort.addAll( copyIdentifierComponentSecondPasList );
topologicalSort( sorted, toSort );
copyIdentifierComponentSecondPasList = sorted;
}

/* naive O(n^3) topological sort */
private void topologicalSort( List<CopyIdentifierComponentSecondPass> sorted, Set<CopyIdentifierComponentSecondPass> toSort ) {
while (!toSort.isEmpty()) {
CopyIdentifierComponentSecondPass independent = null;

searchForIndependent:
for ( CopyIdentifierComponentSecondPass secondPass : toSort ) {
for ( CopyIdentifierComponentSecondPass other : toSort ) {
if (secondPass.dependentUpon( other )) {
continue searchForIndependent;
}
}
independent = secondPass;
break;
}
if (independent == null) {
throw new MappingException( "cyclic dependency in derived identities" );
}
toSort.remove( independent );
sorted.add( independent );
}
}


private void processFkSecondPassesInOrder() {
if ( fkSecondPassList == null || fkSecondPassList.isEmpty() ) {
return;
Expand Down
Expand Up @@ -10,6 +10,7 @@
import java.util.Iterator;
import java.util.Locale;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;

import org.hibernate.AnnotationException;
import org.hibernate.AssertionFailure;
Expand Down Expand Up @@ -75,81 +76,138 @@ public void doSecondPass(Map persistentClasses) throws MappingException {
columnByReferencedName.put( referencedColumnName.toLowerCase(Locale.ROOT), joinColumn );
}
//try default column orientation
int index = 0;
AtomicInteger index = new AtomicInteger( 0 );
if ( columnByReferencedName.isEmpty() ) {
isExplicitReference = false;
for (Ejb3JoinColumn joinColumn : joinColumns) {
columnByReferencedName.put( "" + index, joinColumn );
index++;
columnByReferencedName.put( "" + index.get(), joinColumn );
index.getAndIncrement();
}
index = 0;
index.set( 0 );
}

while ( properties.hasNext() ) {
Property referencedProperty = properties.next();
if ( referencedProperty.isComposite() ) {
throw new AssertionFailure( "Unexpected nested component on the referenced entity when mapping a @MapsId: "
+ referencedEntityName);
Property property = createComponentProperty( referencedPersistentClass, isExplicitReference, columnByReferencedName, index, referencedProperty );
component.addProperty( property );
}
else {
Property property = new Property();
property.setName( referencedProperty.getName() );
//FIXME set optional?
//property.setOptional( property.isOptional() );
property.setPersistentClass( component.getOwner() );
property.setPropertyAccessorName( referencedProperty.getPropertyAccessorName() );
SimpleValue value = new SimpleValue( buildingContext.getMetadataCollector(), component.getTable() );
property.setValue( value );
final SimpleValue referencedValue = (SimpleValue) referencedProperty.getValue();
value.setTypeName( referencedValue.getTypeName() );
value.setTypeParameters( referencedValue.getTypeParameters() );
final Iterator<Selectable> columns = referencedValue.getColumnIterator();

if ( joinColumns[0].isNameDeferred() ) {
joinColumns[0].copyReferencedStructureAndCreateDefaultJoinColumns(
referencedPersistentClass,
columns,
value);
Property property = createSimpleProperty( referencedPersistentClass, isExplicitReference, columnByReferencedName, index, referencedProperty );
component.addProperty( property );
}
}
}

private Property createComponentProperty(
PersistentClass referencedPersistentClass,
boolean isExplicitReference,
Map<String, Ejb3JoinColumn> columnByReferencedName,
AtomicInteger index,
Property referencedProperty ) {
Property property = new Property();
property.setName( referencedProperty.getName() );
//FIXME set optional?
//property.setOptional( property.isOptional() );
property.setPersistentClass( component.getOwner() );
property.setPropertyAccessorName( referencedProperty.getPropertyAccessorName() );
Component value = new Component( buildingContext.getMetadataCollector(), component.getOwner() );

property.setValue( value );
final Component referencedValue = (Component) referencedProperty.getValue();
value.setTypeName( referencedValue.getTypeName() );
value.setTypeParameters( referencedValue.getTypeParameters() );
value.setComponentClassName( referencedValue.getComponentClassName() );


Iterator<Property> propertyIterator = referencedValue.getPropertyIterator();
while(propertyIterator.hasNext())
{
Property referencedComponentProperty = propertyIterator.next();

if ( referencedComponentProperty.isComposite() ) {
Property componentProperty = createComponentProperty( referencedValue.getOwner(), isExplicitReference, columnByReferencedName, index, referencedComponentProperty );
value.addProperty( componentProperty );
}
else {
Property componentProperty = createSimpleProperty( referencedValue.getOwner(), isExplicitReference, columnByReferencedName, index, referencedComponentProperty );
value.addProperty( componentProperty );
}
}

return property;
}


private Property createSimpleProperty(
PersistentClass referencedPersistentClass,
boolean isExplicitReference,
Map<String, Ejb3JoinColumn> columnByReferencedName,
AtomicInteger index,
Property referencedProperty ) {
Property property = new Property();
property.setName( referencedProperty.getName() );
//FIXME set optional?
//property.setOptional( property.isOptional() );
property.setPersistentClass( component.getOwner() );
property.setPropertyAccessorName( referencedProperty.getPropertyAccessorName() );
SimpleValue value = new SimpleValue( buildingContext.getMetadataCollector(), component.getTable() );
property.setValue( value );
final SimpleValue referencedValue = (SimpleValue) referencedProperty.getValue();
value.setTypeName( referencedValue.getTypeName() );
value.setTypeParameters( referencedValue.getTypeParameters() );
final Iterator<Selectable> columns = referencedValue.getColumnIterator();

if ( joinColumns[0].isNameDeferred() ) {
joinColumns[0].copyReferencedStructureAndCreateDefaultJoinColumns(
referencedPersistentClass,
columns,
value);
}
else {
//FIXME take care of Formula
while ( columns.hasNext() ) {
final Selectable selectable = columns.next();
if ( ! Column.class.isInstance( selectable ) ) {
log.debug( "Encountered formula definition; skipping" );
continue;
}
final Column column = (Column) selectable;
final Ejb3JoinColumn joinColumn;
String logicalColumnName = null;
if ( isExplicitReference ) {
final String columnName = column.getName();
logicalColumnName = buildingContext.getMetadataCollector().getLogicalColumnName(
referencedPersistentClass.getTable(),
columnName
);
//JPA 2 requires referencedColumnNames to be case insensitive
joinColumn = columnByReferencedName.get( logicalColumnName.toLowerCase(Locale.ROOT ) );
}
else {
//FIXME take care of Formula
while ( columns.hasNext() ) {
final Selectable selectable = columns.next();
if ( ! Column.class.isInstance( selectable ) ) {
log.debug( "Encountered formula definition; skipping" );
continue;
}
final Column column = (Column) selectable;
final Ejb3JoinColumn joinColumn;
String logicalColumnName = null;
if ( isExplicitReference ) {
final String columnName = column.getName();
logicalColumnName = buildingContext.getMetadataCollector().getLogicalColumnName(
referencedPersistentClass.getTable(),
columnName
);
//JPA 2 requires referencedColumnNames to be case insensitive
joinColumn = columnByReferencedName.get( logicalColumnName.toLowerCase(Locale.ROOT ) );
}
else {
joinColumn = columnByReferencedName.get( "" + index );
index++;
}
if ( joinColumn == null && ! joinColumns[0].isNameDeferred() ) {
throw new AnnotationException(
isExplicitReference ?
"Unable to find column reference in the @MapsId mapping: " + logicalColumnName :
"Implicit column reference in the @MapsId mapping fails, try to use explicit referenceColumnNames: " + referencedEntityName
);
}
final String columnName = joinColumn == null || joinColumn.isNameDeferred() ? "tata_" + column.getName() : joinColumn
.getName();
value.addColumn( new Column( columnName ) );
column.setValue( value );
}
joinColumn = columnByReferencedName.get( "" + index.get() );
index.getAndIncrement();
}
component.addProperty( property );
if ( joinColumn == null && ! joinColumns[0].isNameDeferred() ) {
throw new AnnotationException(
isExplicitReference ?
"Unable to find column reference in the @MapsId mapping: " + logicalColumnName :
"Implicit column reference in the @MapsId mapping fails, try to use explicit referenceColumnNames: " + referencedEntityName
);
}
final String columnName = joinColumn == null || joinColumn.isNameDeferred() ? "tata_" + column.getName() : joinColumn
.getName();
value.addColumn( new Column( columnName ) );
if ( joinColumn != null ) {
joinColumn.linkWithValue( value );
}
column.setValue( value );
}
}
return property;
}

public boolean dependentUpon( CopyIdentifierComponentSecondPass other ) {
return this.referencedEntityName.equals( other.component.getOwner().getEntityName() );
}
}
@@ -0,0 +1,17 @@
package org.hibernate.test.annotations.derivedidentities.e3.b2;

import javax.persistence.EmbeddedId;
import javax.persistence.Entity;
import javax.persistence.ManyToOne;
import javax.persistence.MapsId;

@Entity
public class Dependent {

@EmbeddedId
DependentId id;

@MapsId("empPK")
@ManyToOne
Employee emp;
}
@@ -0,0 +1,10 @@
package org.hibernate.test.annotations.derivedidentities.e3.b2;

import javax.persistence.Embeddable;
import java.io.Serializable;

@Embeddable
public class DependentId implements Serializable {
String name;
EmployeeId empPK;
}
@@ -0,0 +1,87 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* Copyright (c) 2011, Red Hat Inc. 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 Inc.
*
* 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.test.annotations.derivedidentities.e3.b2;

import org.hibernate.Session;
import org.hibernate.test.util.SchemaUtil;
import org.hibernate.testing.junit4.BaseNonConfigCoreFunctionalTestCase;
import org.junit.Test;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;

/**
* @author Emmanuel Bernard
* @author Matt Drees
*/
public class DerivedIdentityEmbeddedIdParentEmbeddedIdGrandparentEmbeddedIdDepTest extends BaseNonConfigCoreFunctionalTestCase {
@Test
public void testManyToOne() throws Exception {
assertTrue( SchemaUtil.isColumnPresent( "Dependent", "emp_firstName", metadata() ) );
assertTrue( SchemaUtil.isColumnPresent( "Dependent", "emp_lastName", metadata() ) );
assertTrue( SchemaUtil.isColumnPresent( "Dependent", "name", metadata() ) );
assertTrue( !SchemaUtil.isColumnPresent( "Dependent", "firstName", metadata() ) );
assertTrue( !SchemaUtil.isColumnPresent( "Dependent", "lastName", metadata() ) );

assertTrue( SchemaUtil.isColumnPresent( "Policy", "dep_emp_firstName", metadata() ) );
assertTrue( SchemaUtil.isColumnPresent( "Policy", "dep_emp_lastName", metadata() ) );
assertTrue( SchemaUtil.isColumnPresent( "Policy", "type", metadata() ) );
assertTrue( !SchemaUtil.isColumnPresent( "Policy", "firstName", metadata() ) );
assertTrue( !SchemaUtil.isColumnPresent( "Policy", "lastName", metadata() ) );
assertTrue( !SchemaUtil.isColumnPresent( "Policy", "name", metadata() ) );


final Employee e = new Employee();
e.empId = new EmployeeId();
e.empId.firstName = "Emmanuel";
e.empId.lastName = "Bernard";
final Session s = openSession();
s.getTransaction().begin();
s.persist( e );
final Dependent d = new Dependent();
d.emp = e;
d.id = new DependentId();
d.id.name = "Doggy";
s.persist( d );
Policy p = new Policy();
p.dep = d;
p.id = new PolicyId();
p.id.type = "Vet Insurance";
s.persist( p );

s.flush();
s.clear();
p = (Policy) s.get( Policy.class, p.id );
assertNotNull( p.dep );
assertEquals( e.empId.firstName, p.dep.emp.empId.firstName );
s.getTransaction().rollback();
s.close();
}

@Override
protected Class<?>[] getAnnotatedClasses() {
return new Class<?>[]{Policy.class, Dependent.class, Employee.class};
}
}

0 comments on commit 943acc7

Please sign in to comment.