/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* Copyright (c) 2008-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.cascade.circle;
import java.util.Iterator;
import org.hibernate.JDBCException;
import org.hibernate.PropertyValueException;
import org.hibernate.Session;
import org.hibernate.TransientObjectException;
import org.hibernate.cfg.Configuration;
import org.hibernate.cfg.Environment;
import org.hibernate.engine.spi.SessionImplementor;
import org.junit.Test;
import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
/**
* The test case uses the following model:
*
* <- ->
* -- (N : 0,1) -- Tour
* | <- ->
* | -- (1 : N) -- (pickup) ----
* -> | | |
* Route -- (1 : N) -- Node Transport
* | <- -> |
* -- (1 : N) -- (delivery) --
*
* Arrows indicate the direction of cascade-merge.
*
* It reproduced the following issues:
* http://opensource.atlassian.com/projects/hibernate/browse/HHH-3046
* http://opensource.atlassian.com/projects/hibernate/browse/HHH-3810
* <p/>
* This tests that merge is cascaded properly from each entity.
*
* @author Pavol Zibrita, Gail Badner
*/
public class MultiPathCircleCascadeTest extends BaseCoreFunctionalTestCase {
@Override
public void configure(Configuration cfg) {
cfg.setProperty( Environment.GENERATE_STATISTICS, "true" );
cfg.setProperty( Environment.STATEMENT_BATCH_SIZE, "0" );
}
@Override
public String[] getMappings() {
return new String[] {
"cascade/circle/MultiPathCircleCascade.hbm.xml"
};
}
protected void cleanupTest() {
Session s = openSession();
s.beginTransaction();
s.createQuery( "delete from Transport" );
s.createQuery( "delete from Tour" );
s.createQuery( "delete from Node" );
s.createQuery( "delete from Route" );
}
@Test
public void testMergeEntityWithNonNullableTransientEntity() {
Route route = getUpdatedDetachedEntity();
Node node = (Node) route.getNodes().iterator().next();
route.getNodes().remove( node );
Route routeNew = new Route();
routeNew.setName( "new route" );
routeNew.getNodes().add( node );
node.setRoute( routeNew );
Session s = openSession();
s.beginTransaction();
try {
s.merge( node );
s.getTransaction().commit();
fail( "should have thrown an exception" );
}
catch (Exception ex) {
checkExceptionFromNullValueForNonNullable(
ex,
((SessionImplementor) s).getFactory().getSettings().isCheckNullability(),
false
);
}
finally {
s.getTransaction().rollback();
s.close();
}
}
@Test
public void testMergeEntityWithNonNullableEntityNull() {
Route route = getUpdatedDetachedEntity();
Node node = (Node) route.getNodes().iterator().next();
route.getNodes().remove( node );
node.setRoute( null );
Session s = openSession();
s.beginTransaction();
try {
s.merge( node );
s.getTransaction().commit();
fail( "should have thrown an exception" );
}
catch (Exception ex) {
checkExceptionFromNullValueForNonNullable(
ex,
((SessionImplementor) s).getFactory().getSettings().isCheckNullability(),
true
);
}
finally {
s.getTransaction().rollback();
s.close();
}
}
@Test
public void testMergeEntityWithNonNullablePropSetToNull() {
Route route = getUpdatedDetachedEntity();
Node node = (Node) route.getNodes().iterator().next();
node.setName( null );
Session s = openSession();
s.beginTransaction();
try {
s.merge( route );
s.getTransaction().commit();
fail( "should have thrown an exception" );
}
catch (Exception ex) {
checkExceptionFromNullValueForNonNullable(
ex,
((SessionImplementor) s).getFactory().getSettings().isCheckNullability(),
true
);
}
finally {
s.getTransaction().rollback();
s.close();
}
}
@Test
public void testMergeRoute() {
Route route = getUpdatedDetachedEntity();
clearCounts();
Session s = openSession();
s.beginTransaction();
s.merge( route );
s.getTransaction().commit();
s.close();
assertInsertCount( 4 );
assertUpdateCount( 1 );
s = openSession();
s.beginTransaction();
route = (Route) s.get( Route.class, route.getRouteID() );
checkResults( route, true );
s.getTransaction().commit();
s.close();
}
@Test
public void testMergePickupNode() {
Route route = getUpdatedDetachedEntity();
clearCounts();
Session s = openSession();
s.beginTransaction();
Iterator it = route.getNodes().iterator();
Node node = (Node) it.next();
Node pickupNode;
if ( node.getName().equals( "pickupNodeB" ) ) {
pickupNode = node;
}
else {
node = (Node) it.next();
assertEquals( "pickupNodeB", node.getName() );
pickupNode = node;
}
pickupNode = (Node) s.merge( pickupNode );
s.getTransaction().commit();
s.close();
assertInsertCount( 4 );
assertUpdateCount( 0 );
s = openSession();
s.beginTransaction();
route = (Route) s.get( Route.class, route.getRouteID() );
checkResults( route, false );
s.getTransaction().commit();
s.close();
}
@Test
public void testMergeDeliveryNode() {
Route route = getUpdatedDetachedEntity();
clearCounts();
Session s = openSession();
s.beginTransaction();
Iterator it = route.getNodes().iterator();
Node node = (Node) it.next();
Node deliveryNode;
if ( node.getName().equals( "deliveryNodeB" ) ) {
deliveryNode = node;
}
else {
node = (Node) it.next();
assertEquals( "deliveryNodeB", node.getName() );
deliveryNode = node;
}
deliveryNode = (Node) s.merge( deliveryNode );
s.getTransaction().commit();
s.close();
assertInsertCount( 4 );
assertUpdateCount( 0 );
s = openSession();
s.beginTransaction();
route = (Route) s.get( Route.class, route.getRouteID() );
checkResults( route, false );
s.getTransaction().commit();
s.close();
}
@Test
public void testMergeTour() {
Route route = getUpdatedDetachedEntity();
clearCounts();
Session s = openSession();
s.beginTransaction();
Tour tour = (Tour) s.merge( ((Node) route.getNodes().toArray()[0]).getTour() );
s.getTransaction().commit();
s.close();
assertInsertCount( 4 );
assertUpdateCount( 0 );
s = openSession();
s.beginTransaction();
route = (Route) s.get( Route.class, route.getRouteID() );
checkResults( route, false );
s.getTransaction().commit();
s.close();
}
@Test
public void testMergeTransport() {
Route route = getUpdatedDetachedEntity();
clearCounts();
Session s = openSession();
s.beginTransaction();
Node node = ((Node) route.getNodes().toArray()[0]);
Transport transport;
if ( node.getPickupTransports().size() == 1 ) {
transport = (Transport) node.getPickupTransports().toArray()[0];
}
else {
transport = (Transport) node.getDeliveryTransports().toArray()[0];
}
transport = (Transport) s.merge( transport );
s.getTransaction().commit();
s.close();
assertInsertCount( 4 );
assertUpdateCount( 0 );
s = openSession();
s.beginTransaction();
route = (Route) s.get( Route.class, route.getRouteID() );
checkResults( route, false );
s.getTransaction().commit();
s.close();
}
private Route getUpdatedDetachedEntity() {
Session s = openSession();
s.beginTransaction();
Route route = new Route();
route.setName( "routeA" );
s.save( route );
s.getTransaction().commit();
s.close();
route.setName( "new routeA" );
route.setTransientField( new String( "sfnaouisrbn" ) );
Tour tour = new Tour();
tour.setName( "tourB" );
Transport transport = new Transport();
transport.setName( "transportB" );
Node pickupNode = new Node();
pickupNode.setName( "pickupNodeB" );
Node deliveryNode = new Node();
deliveryNode.setName( "deliveryNodeB" );
pickupNode.setRoute( route );
pickupNode.setTour( tour );
pickupNode.getPickupTransports().add( transport );
pickupNode.setTransientField( "pickup node aaaaaaaaaaa" );
deliveryNode.setRoute( route );
deliveryNode.setTour( tour );
deliveryNode.getDeliveryTransports().add( transport );
deliveryNode.setTransientField( "delivery node aaaaaaaaa" );
tour.getNodes().add( pickupNode );
tour.getNodes().add( deliveryNode );
route.getNodes().add( pickupNode );
route.getNodes().add( deliveryNode );
transport.setPickupNode( pickupNode );
transport.setDeliveryNode( deliveryNode );
transport.setTransientField( "aaaaaaaaaaaaaa" );
return route;
}
private void checkResults(Route route, boolean isRouteUpdated) {
// since merge is not cascaded to route, this method needs to
// know whether route is expected to be updated
if ( isRouteUpdated ) {
assertEquals( "new routeA", route.getName() );
}
assertEquals( 2, route.getNodes().size() );
Node deliveryNode = null;
Node pickupNode = null;
for ( Iterator it = route.getNodes().iterator(); it.hasNext(); ) {
Node node = (Node) it.next();
if ( "deliveryNodeB".equals( node.getName() ) ) {
deliveryNode = node;
}
else if ( "pickupNodeB".equals( node.getName() ) ) {
pickupNode = node;
}
else {
fail( "unknown node" );
}
}
assertNotNull( deliveryNode );
assertSame( route, deliveryNode.getRoute() );
assertEquals( 1, deliveryNode.getDeliveryTransports().size() );
assertEquals( 0, deliveryNode.getPickupTransports().size() );
assertNotNull( deliveryNode.getTour() );
assertEquals( "node original value", deliveryNode.getTransientField() );
assertNotNull( pickupNode );
assertSame( route, pickupNode.getRoute() );
assertEquals( 0, pickupNode.getDeliveryTransports().size() );
assertEquals( 1, pickupNode.getPickupTransports().size() );
assertNotNull( pickupNode.getTour() );
assertEquals( "node original value", pickupNode.getTransientField() );
assertTrue( !deliveryNode.getNodeID().equals( pickupNode.getNodeID() ) );
assertSame( deliveryNode.getTour(), pickupNode.getTour() );
assertSame(
deliveryNode.getDeliveryTransports().iterator().next(),
pickupNode.getPickupTransports().iterator().next()
);
Tour tour = deliveryNode.getTour();
Transport transport = (Transport) deliveryNode.getDeliveryTransports().iterator().next();
assertEquals( "tourB", tour.getName() );
assertEquals( 2, tour.getNodes().size() );
assertTrue( tour.getNodes().contains( deliveryNode ) );
assertTrue( tour.getNodes().contains( pickupNode ) );
assertEquals( "transportB", transport.getName() );
assertSame( deliveryNode, transport.getDeliveryNode() );
assertSame( pickupNode, transport.getPickupNode() );
assertEquals( "transport original value", transport.getTransientField() );
}
@Test
public void testMergeData3Nodes() {
Session s = openSession();
s.beginTransaction();
Route route = new Route();
route.setName( "routeA" );
s.save( route );
s.getTransaction().commit();
s.close();
clearCounts();
s = openSession();
s.beginTransaction();
route = (Route) s.get( Route.class, new Long( 1 ) );
//System.out.println(route);
route.setName( "new routA" );
route.setTransientField( new String( "sfnaouisrbn" ) );
Tour tour = new Tour();
tour.setName( "tourB" );
Transport transport1 = new Transport();
transport1.setName( "TRANSPORT1" );
Transport transport2 = new Transport();
transport2.setName( "TRANSPORT2" );
Node node1 = new Node();
node1.setName( "NODE1" );
Node node2 = new Node();
node2.setName( "NODE2" );
Node node3 = new Node();
node3.setName( "NODE3" );
node1.setRoute( route );
node1.setTour( tour );
node1.getPickupTransports().add( transport1 );
node1.setTransientField( "node 1" );
node2.setRoute( route );
node2.setTour( tour );
node2.getDeliveryTransports().add( transport1 );
node2.getPickupTransports().add( transport2 );
node2.setTransientField( "node 2" );
node3.setRoute( route );
node3.setTour( tour );
node3.getDeliveryTransports().add( transport2 );
node3.setTransientField( "node 3" );
tour.getNodes().add( node1 );
tour.getNodes().add( node2 );
tour.getNodes().add( node3 );
route.getNodes().add( node1 );
route.getNodes().add( node2 );
route.getNodes().add( node3 );
transport1.setPickupNode( node1 );
transport1.setDeliveryNode( node2 );
transport1.setTransientField( "aaaaaaaaaaaaaa" );
transport2.setPickupNode( node2 );
transport2.setDeliveryNode( node3 );
transport2.setTransientField( "bbbbbbbbbbbbb" );
Route mergedRoute = (Route) s.merge( route );
s.getTransaction().commit();
s.close();
assertInsertCount( 6 );
assertUpdateCount( 1 );
}
protected void checkExceptionFromNullValueForNonNullable(
Exception ex, boolean checkNullability, boolean isNullValue
) {
if ( checkNullability ) {
if ( isNullValue ) {
assertTrue( ex instanceof PropertyValueException );
}
else {
assertTrue( ex instanceof TransientObjectException );
}
}
else {
assertTrue( ex instanceof JDBCException );
}
}
protected void clearCounts() {
sessionFactory().getStatistics().clear();
}
protected void assertInsertCount(int expected) {
int inserts = (int) sessionFactory().getStatistics().getEntityInsertCount();
assertEquals( "unexpected insert count", expected, inserts );
}
protected void assertUpdateCount(int expected) {
int updates = (int) sessionFactory().getStatistics().getEntityUpdateCount();
assertEquals( "unexpected update counts", expected, updates );
}
protected void assertDeleteCount(int expected) {
int deletes = (int) sessionFactory().getStatistics().getEntityDeleteCount();
assertEquals( "unexpected delete counts", expected, deletes );
}
}