/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* Copyright (c) 2012, 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.ejb.test.cascade.multicircle;
import javax.persistence.EntityManager;
import javax.persistence.RollbackException;
import junit.framework.Assert;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.hibernate.TransientPropertyValueException;
import org.hibernate.ejb.test.BaseEntityManagerFunctionalTestCase;
import org.hibernate.testing.FailureExpected;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
/**
* This test uses a complicated model that requires Hibernate to delay
* inserts until non-nullable transient entity dependencies are resolved.
*
* All IDs are generated from a sequence.
*
* JPA cascade types are used (javax.persistence.CascadeType)..
*
* This test uses the following model:
*
* <code>
* ------------------------------ N G
* |
* | 1
* | |
* | |
* | N
* |
* | E N--------------0,1 * F
* |
* | 1 N
* | | |
* | | |
* 1 N |
* * |
* B * N---1 D * 1------------------
* *
* N N
* | |
* | |
* 1 |
* |
* C * 1-----
*</code>
*
* In the diagram, all associations are bidirectional;
* assocations marked with '*' cascade persist, save, merge operations to the
* associated entities (e.g., B cascades persist to D, but D does not cascade
* persist to B);
*
* b, c, d, e, f, and g are all transient unsaved that are associated with each other.
*
* When saving b, the entities are added to the ActionQueue in the following order:
* c, d (depends on e), f (depends on d, g), e, b, g.
*
* Entities are inserted in the following order:
* c, e, d, b, g, f.
*/
public class MultiCircleJpaCascadeTest extends BaseEntityManagerFunctionalTestCase {
private B b;
private C c;
private D d;
private E e;
private F f;
private G g;
private boolean skipCleanup;
@Before
public void setup() {
b = new B();
c = new C();
d = new D();
e = new E();
f = new F();
g = new G();
b.getGCollection().add( g );
b.setC( c );
b.setD( d );
c.getBCollection().add( b );
c.getDCollection().add( d );
d.getBCollection().add( b );
d.setC( c );
d.setE( e );
d.getFCollection().add( f );
e.getDCollection().add( d );
e.setF( f );
f.getECollection().add( e );
f.setD( d );
f.setG( g );
g.setB( b );
g.getFCollection().add( f );
skipCleanup = false;
}
@After
public void cleanup() {
if ( ! skipCleanup ) {
b.setC( null );
b.setD( null );
b.getGCollection().remove( g );
c.getBCollection().remove( b );
c.getDCollection().remove( d );
d.getBCollection().remove( b );
d.setC( null );
d.setE( null );
d.getFCollection().remove( f );
e.getDCollection().remove( d );
e.setF( null );
f.setD( null );
f.getECollection().remove( e );
f.setG( null );
g.setB( null );
g.getFCollection().remove( f );
EntityManager em = getOrCreateEntityManager();
em.getTransaction().begin();
b = em.merge( b );
c = em.merge( c );
d = em.merge( d );
e = em.merge( e );
f = em.merge( f );
g = em.merge( g );
em.remove( f );
em.remove( g );
em.remove( b );
em.remove( d );
em.remove( e );
em.remove( c );
em.getTransaction().commit();
em.close();
}
}
@Test
public void testPersist() {
EntityManager em = getOrCreateEntityManager();
em.getTransaction().begin();
em.persist( b );
em.getTransaction().commit();
em.close();
check();
}
@Test
public void testPersistNoCascadeToTransient() {
skipCleanup = true;
EntityManager em = getOrCreateEntityManager();
em.getTransaction().begin();
try {
em.persist( c );
fail( "should have failed." );
}
catch( IllegalStateException ex ) {
assertTrue( TransientPropertyValueException.class.isInstance( ex.getCause() ) );
TransientPropertyValueException pve = (TransientPropertyValueException) ex.getCause();
assertEquals( G.class.getName(), pve.getTransientEntityName() );
assertEquals( F.class.getName(), pve.getPropertyOwnerEntityName() );
assertEquals( "g", pve.getPropertyName() );
}
em.getTransaction().rollback();
em.close();
}
@Test
@FailureExpected( jiraKey = "HHH-6999" )
// fails on d.e; should pass
public void testPersistThenUpdate() {
EntityManager em = getOrCreateEntityManager();
em.getTransaction().begin();
em.persist( b );
// remove old e from associations
e.getDCollection().remove( d );
d.setE( null );
f.getECollection().remove( e );
e.setF( null );
// add new e to associations
e = new E();
e.getDCollection().add( d );
f.getECollection().add( e );
d.setE( e );
e.setF( f );
em.getTransaction().commit();
em.close();
check();
}
@Test
public void testPersistThenUpdateNoCascadeToTransient() {
// expected to fail, so nothing will be persisted.
skipCleanup = true;
// remove elements from collections and persist
c.getBCollection().clear();
c.getDCollection().clear();
EntityManager em = getOrCreateEntityManager();
em.getTransaction().begin();
em.persist( c );
// now add the elements back
c.getBCollection().add( b );
c.getDCollection().add( d );
try {
em.getTransaction().commit();
fail( "should have thrown IllegalStateException");
}
catch ( RollbackException ex ) {
assertTrue( ex.getCause() instanceof IllegalStateException );
IllegalStateException ise = ( IllegalStateException ) ex.getCause();
// should fail on entity g (due to no cascade to f.g);
// instead it fails on entity e ( due to no cascade to d.e)
// because e is not in the process of being saved yet.
// when HHH-6999 is fixed, this test should be changed to
// check for g and f.g
assertTrue( ise.getCause() instanceof TransientPropertyValueException );
TransientPropertyValueException tpve = ( TransientPropertyValueException ) ise.getCause();
assertEquals( E.class.getName(), tpve.getTransientEntityName() );
assertEquals( D.class.getName(), tpve.getPropertyOwnerEntityName() );
assertEquals( "e", tpve.getPropertyName() );
}
em.close();
}
@Test
public void testMerge() {
EntityManager em = getOrCreateEntityManager();
em.getTransaction().begin();
b = em.merge( b );
c = b.getC();
d = b.getD();
e = d.getE();
f = e.getF();
g = f.getG();
em.getTransaction().commit();
em.close();
check();
}
private void check() {
EntityManager em = getOrCreateEntityManager();
em.getTransaction().begin();
B bRead = em.find( B.class, b.getId() );
Assert.assertEquals( b, bRead );
G gRead = bRead.getGCollection().iterator().next();
Assert.assertEquals( g, gRead );
C cRead = bRead.getC();
Assert.assertEquals( c, cRead );
D dRead = bRead.getD();
Assert.assertEquals( d, dRead );
Assert.assertSame( bRead, cRead.getBCollection().iterator().next() );
Assert.assertSame( dRead, cRead.getDCollection().iterator().next() );
Assert.assertSame( bRead, dRead.getBCollection().iterator().next() );
Assert.assertEquals( cRead, dRead.getC() );
E eRead = dRead.getE();
Assert.assertEquals( e, eRead );
F fRead = dRead.getFCollection().iterator().next();
Assert.assertEquals( f, fRead );
Assert.assertSame( dRead, eRead.getDCollection().iterator().next() );
Assert.assertSame( fRead, eRead.getF() );
Assert.assertSame( eRead, fRead.getECollection().iterator().next() );
Assert.assertSame( dRead, fRead.getD() );
Assert.assertSame( gRead, fRead.getG());
Assert.assertSame( bRead, gRead.getB() );
Assert.assertSame( fRead, gRead.getFCollection().iterator().next() );
em.getTransaction().commit();
em.close();
}
@Override
protected Class[] getAnnotatedClasses() {
return new Class[]{
B.class,
C.class,
D.class,
E.class,
F.class,
G.class
};
}
}