diff --git a/hibernate-core/src/main/java/org/hibernate/TransientPropertyValueException.java b/hibernate-core/src/main/java/org/hibernate/TransientPropertyValueException.java new file mode 100644 index 0000000000..da3deb59e8 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/TransientPropertyValueException.java @@ -0,0 +1,99 @@ +/* + * 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; + +import org.hibernate.internal.util.StringHelper; + +/** + * Thrown when a property cannot be persisted because it is an association + * with a transient unsaved entity instance. + * + * @author Gail Badner + */ +public class TransientPropertyValueException extends TransientObjectException { + private final String transientEntityName; + private final String propertyOwnerEntityName; + private final String propertyName; + + /** + * Constructs an {@link TransientPropertyValueException} instance. + * + * @param message - the exception message; + * @param transientEntityName - the entity name for the transient entity + * @param propertyOwnerEntityName - the entity name for entity that owns + * the association property. + * @param propertyName - the property name + */ + public TransientPropertyValueException( + String message, + String transientEntityName, + String propertyOwnerEntityName, + String propertyName) { + super(message); + this.transientEntityName = transientEntityName; + this.propertyOwnerEntityName = propertyOwnerEntityName; + this.propertyName = propertyName; + } + + /** + * Returns the entity name for the transient entity. + * @return the entity name for the transient entity. + */ + public String getTransientEntityName() { + return transientEntityName; + } + + /** + * Returns the entity name for entity that owns the association + * property. + * @return the entity name for entity that owns the association + * property + */ + public String getPropertyOwnerEntityName() { + return propertyOwnerEntityName; + } + + /** + * Returns the property name. + * @return the property name. + */ + public String getPropertyName() { + return propertyName; + } + + + /** + * Return the exception message. + * @return the exception message. + */ + @Override + public String getMessage() { + return new StringBuilder( super.getMessage() ) + .append( ": " ) + .append( StringHelper.qualify( propertyOwnerEntityName, propertyName ) ) + .append( " -> " ) + .append( transientEntityName ) + .toString(); + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/action/internal/UnresolvedEntityInsertActions.java b/hibernate-core/src/main/java/org/hibernate/action/internal/UnresolvedEntityInsertActions.java index 65fcd41860..09b694ea1d 100644 --- a/hibernate-core/src/main/java/org/hibernate/action/internal/UnresolvedEntityInsertActions.java +++ b/hibernate-core/src/main/java/org/hibernate/action/internal/UnresolvedEntityInsertActions.java @@ -36,6 +36,7 @@ import java.util.TreeSet; import org.jboss.logging.Logger; import org.hibernate.PropertyValueException; +import org.hibernate.TransientPropertyValueException; import org.hibernate.engine.internal.NonNullableTransientDependencies; import org.hibernate.engine.spi.EntityEntry; import org.hibernate.engine.spi.SessionImplementor; @@ -131,8 +132,9 @@ public class UnresolvedEntityInsertActions { nonNullableTransientDependencies.getNonNullableTransientEntities().iterator().next(); String firstPropertyPath = nonNullableTransientDependencies.getNonNullableTransientPropertyPaths( firstTransientDependency ).iterator().next(); - throw new PropertyValueException( + throw new TransientPropertyValueException( "Not-null property references a transient value - transient instance must be saved before current operation", + firstDependentAction.getSession().guessEntityName( firstTransientDependency ), firstDependentAction.getEntityName(), firstPropertyPath ); diff --git a/hibernate-core/src/main/java/org/hibernate/engine/spi/CascadingAction.java b/hibernate-core/src/main/java/org/hibernate/engine/spi/CascadingAction.java index b092cab78c..92a9b11747 100755 --- a/hibernate-core/src/main/java/org/hibernate/engine/spi/CascadingAction.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/spi/CascadingAction.java @@ -34,6 +34,7 @@ import org.hibernate.LockMode; import org.hibernate.LockOptions; import org.hibernate.ReplicationMode; import org.hibernate.TransientObjectException; +import org.hibernate.TransientPropertyValueException; import org.hibernate.collection.spi.PersistentCollection; import org.hibernate.engine.internal.ForeignKeys; import org.hibernate.event.spi.EventSource; @@ -377,10 +378,11 @@ public abstract class CascadingAction { && ForeignKeys.isTransient( childEntityName, child, null, session ) ) { String parentEntiytName = persister.getEntityName(); String propertyName = persister.getPropertyNames()[propertyIndex]; - throw new TransientObjectException( - "object references an unsaved transient instance - " + - "save the transient instance before flushing: " + - parentEntiytName + "." + propertyName + " -> " + childEntityName + throw new TransientPropertyValueException( + "object references an unsaved transient instance - save the transient instance before flushing", + childEntityName, + parentEntiytName, + propertyName ); } @@ -393,7 +395,7 @@ public abstract class CascadingAction { private boolean isInManagedState(Object child, EventSource session) { EntityEntry entry = session.getPersistenceContext().getEntry( child ); - return entry != null && + return entry != null && ( entry.getStatus() == Status.MANAGED || entry.getStatus() == Status.READ_ONLY || diff --git a/hibernate-core/src/matrix/java/org/hibernate/test/cascade/circle/MultiPathCircleCascadeTest.java b/hibernate-core/src/matrix/java/org/hibernate/test/cascade/circle/MultiPathCircleCascadeTest.java index f328d24422..5f373d8f13 100644 --- a/hibernate-core/src/matrix/java/org/hibernate/test/cascade/circle/MultiPathCircleCascadeTest.java +++ b/hibernate-core/src/matrix/java/org/hibernate/test/cascade/circle/MultiPathCircleCascadeTest.java @@ -31,6 +31,7 @@ import org.hibernate.JDBCException; import org.hibernate.PropertyValueException; import org.hibernate.Session; import org.hibernate.TransientObjectException; +import org.hibernate.TransientPropertyValueException; import org.hibernate.cfg.Configuration; import org.hibernate.cfg.Environment; import org.hibernate.engine.spi.SessionImplementor; @@ -682,7 +683,7 @@ public class MultiPathCircleCascadeTest extends BaseCoreFunctionalTestCase { } } else { - assertTrue( ex instanceof PropertyValueException ); + assertTrue( ex instanceof TransientPropertyValueException ); } } diff --git a/hibernate-entitymanager/src/matrix/java/org/hibernate/ejb/test/cascade/multicircle/AbstractEntity.java b/hibernate-entitymanager/src/matrix/java/org/hibernate/ejb/test/cascade/multicircle/AbstractEntity.java new file mode 100644 index 0000000000..0fb9d8883b --- /dev/null +++ b/hibernate-entitymanager/src/matrix/java/org/hibernate/ejb/test/cascade/multicircle/AbstractEntity.java @@ -0,0 +1,94 @@ +/* + * 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 java.io.Serializable; +import java.util.Date; +import java.util.UUID; +import javax.persistence.Basic; +import javax.persistence.Column; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.MappedSuperclass; + +@MappedSuperclass +public abstract class AbstractEntity implements Serializable { + + @Id + @GeneratedValue + private Long id; + @Basic + @Column(unique = true, updatable = false, length = 36, columnDefinition = "char(36)") + private String uuid; + @Column(updatable = false) + private Date created; + + public AbstractEntity() { + super(); + uuid = UUID.randomUUID().toString(); + created = new Date(); + } + + public Long getId() { + return id; + } + + public String getUuid() { + return uuid; + } + + public Date getCreated() { + return created; + } + + @Override + public int hashCode() { + return uuid == null ? 0 : uuid.hashCode(); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (!(obj instanceof AbstractEntity )) + return false; + final AbstractEntity other = (AbstractEntity) obj; + if (uuid == null) { + if (other.uuid != null) + return false; + } else if (!uuid.equals(other.uuid)) + return false; + return true; + } + + public String toString() { + if (id != null) { + return "id: '" + id + "' uuid: '" + uuid + "'"; + } else { + return "id: 'transient entity' " + " uuid: '" + uuid + "'"; + } + } +} diff --git a/hibernate-entitymanager/src/matrix/java/org/hibernate/ejb/test/cascade/multicircle/B.java b/hibernate-entitymanager/src/matrix/java/org/hibernate/ejb/test/cascade/multicircle/B.java new file mode 100644 index 0000000000..ff0152fe99 --- /dev/null +++ b/hibernate-entitymanager/src/matrix/java/org/hibernate/ejb/test/cascade/multicircle/B.java @@ -0,0 +1,85 @@ +/* + * 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; + +/** + * No Documentation + */ +@javax.persistence.Entity +public class B extends AbstractEntity { + private static final long serialVersionUID = 325417243L; + + /** + * No documentation + */ + @javax.persistence.OneToMany(cascade = { + javax.persistence.CascadeType.MERGE, javax.persistence.CascadeType.PERSIST, javax.persistence.CascadeType.REFRESH} + , mappedBy = "b") + private java.util.Set gCollection = new java.util.HashSet(); + + + /** + * No documentation + */ + @javax.persistence.ManyToOne(cascade = { + javax.persistence.CascadeType.MERGE, javax.persistence.CascadeType.PERSIST, javax.persistence.CascadeType.REFRESH} + , optional = false) + private C c; + + + + /** + * No documentation + */ + @javax.persistence.ManyToOne(cascade = { + javax.persistence.CascadeType.MERGE, javax.persistence.CascadeType.PERSIST, javax.persistence.CascadeType.REFRESH} + , optional = false) + private D d; + + public java.util.Set getGCollection() { + return gCollection; + } + + public void setGCollection( + java.util.Set parameter) { + this.gCollection = parameter; + } + + public C getC() { + return c; + } + + public void setC(C parameter) { + this.c = parameter; + } + + public D getD() { + return d; + } + + public void setD(D parameter) { + this.d = parameter; + } + +} diff --git a/hibernate-entitymanager/src/matrix/java/org/hibernate/ejb/test/cascade/multicircle/C.java b/hibernate-entitymanager/src/matrix/java/org/hibernate/ejb/test/cascade/multicircle/C.java new file mode 100644 index 0000000000..c8509168f7 --- /dev/null +++ b/hibernate-entitymanager/src/matrix/java/org/hibernate/ejb/test/cascade/multicircle/C.java @@ -0,0 +1,59 @@ +/* + * 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 java.util.Set; + +/** + * No Documentation + */ +@javax.persistence.Entity +public class C extends AbstractEntity { + private static final long serialVersionUID = 1226955752L; + + @javax.persistence.OneToMany(mappedBy = "c") + private Set bCollection = new java.util.HashSet(); + + @javax.persistence.OneToMany(cascade = { + javax.persistence.CascadeType.MERGE, javax.persistence.CascadeType.PERSIST, javax.persistence.CascadeType.REFRESH} + , mappedBy = "c") + private Set dCollection = new java.util.HashSet(); + + public Set getBCollection() { + return bCollection; + } + + public void setBCollection(Set bCollection) { + this.bCollection = bCollection; + } + + public Set getDCollection() { + return dCollection; + } + + public void setDCollection(Set dCollection) { + this.dCollection = dCollection; + } + +} diff --git a/hibernate-entitymanager/src/matrix/java/org/hibernate/ejb/test/cascade/multicircle/D.java b/hibernate-entitymanager/src/matrix/java/org/hibernate/ejb/test/cascade/multicircle/D.java new file mode 100644 index 0000000000..6f18d2bc2b --- /dev/null +++ b/hibernate-entitymanager/src/matrix/java/org/hibernate/ejb/test/cascade/multicircle/D.java @@ -0,0 +1,78 @@ +/* + * 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; + +/** + * No Documentation + */ +@javax.persistence.Entity +public class D extends AbstractEntity { + private static final long serialVersionUID = 2417176961L; + + @javax.persistence.OneToMany(mappedBy = "d") + private java.util.Set bCollection = new java.util.HashSet(); + + @javax.persistence.ManyToOne(optional = false) + private C c; + + @javax.persistence.ManyToOne(optional = false) + private E e; + + @javax.persistence.OneToMany(cascade = { + javax.persistence.CascadeType.MERGE, javax.persistence.CascadeType.PERSIST, javax.persistence.CascadeType.REFRESH}, + mappedBy = "d" + ) + private java.util.Set fCollection = new java.util.HashSet(); + + public java.util.Set getBCollection() { + return bCollection; + } + public void setBCollection( + java.util.Set parameter) { + this.bCollection = parameter; + } + + public C getC() { + return c; + } + public void setC(C c) { + this.c = c; + } + + public E getE() { + return e; + } + public void setE(E e) { + this.e = e; + } + + public java.util.Set getFCollection() { + return fCollection; + } + public void setFCollection( + java.util.Set parameter) { + this.fCollection = parameter; + } + +} diff --git a/hibernate-entitymanager/src/matrix/java/org/hibernate/ejb/test/cascade/multicircle/E.java b/hibernate-entitymanager/src/matrix/java/org/hibernate/ejb/test/cascade/multicircle/E.java new file mode 100644 index 0000000000..1882e6323d --- /dev/null +++ b/hibernate-entitymanager/src/matrix/java/org/hibernate/ejb/test/cascade/multicircle/E.java @@ -0,0 +1,52 @@ +/* + * 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; + +/** + * No Documentation + */ +@javax.persistence.Entity +public class E extends AbstractEntity { + private static final long serialVersionUID = 1226955558L; + + @javax.persistence.OneToMany(mappedBy = "e") + private java.util.Set dCollection = new java.util.HashSet(); + + @javax.persistence.ManyToOne(optional = true) + private F f; + + public java.util.Set getDCollection() { + return dCollection; + } + public void setDCollection(java.util.Set dCollection) { + this.dCollection = dCollection; + } + + public F getF() { + return f; + } + public void setF(F parameter) { + this.f = parameter; + } +} diff --git a/hibernate-entitymanager/src/matrix/java/org/hibernate/ejb/test/cascade/multicircle/F.java b/hibernate-entitymanager/src/matrix/java/org/hibernate/ejb/test/cascade/multicircle/F.java new file mode 100644 index 0000000000..c90892cf40 --- /dev/null +++ b/hibernate-entitymanager/src/matrix/java/org/hibernate/ejb/test/cascade/multicircle/F.java @@ -0,0 +1,69 @@ +/* + * 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; + +/** + * No Documentation + */ +@javax.persistence.Entity +public class F extends AbstractEntity { + private static final long serialVersionUID = 1471534025L; + + /** + * No documentation + */ + @javax.persistence.OneToMany(cascade = { + javax.persistence.CascadeType.MERGE, javax.persistence.CascadeType.PERSIST, javax.persistence.CascadeType.REFRESH} + , mappedBy = "f") + private java.util.Set eCollection = new java.util.HashSet(); + + @javax.persistence.ManyToOne(optional = false) + private D d; + + @javax.persistence.ManyToOne(optional = false) + private G g; + + public java.util.Set getECollection() { + return eCollection; + } + public void setECollection( + java.util.Set parameter) { + this.eCollection = parameter; + } + + public D getD() { + return d; + } + public void setD(D parameter) { + this.d = parameter; + } + + public G getG() { + return g; + } + public void setG(G parameter) { + this.g = parameter; + } + +} diff --git a/hibernate-entitymanager/src/matrix/java/org/hibernate/ejb/test/cascade/multicircle/G.java b/hibernate-entitymanager/src/matrix/java/org/hibernate/ejb/test/cascade/multicircle/G.java new file mode 100644 index 0000000000..e6ce4c9d78 --- /dev/null +++ b/hibernate-entitymanager/src/matrix/java/org/hibernate/ejb/test/cascade/multicircle/G.java @@ -0,0 +1,53 @@ +/* + * 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; + +/** + * No Documentation + */ +@javax.persistence.Entity +public class G extends AbstractEntity { + private static final long serialVersionUID = 325417437L; + + @javax.persistence.ManyToOne(optional = false) + private B b; + + @javax.persistence.OneToMany(mappedBy = "g") + private java.util.Set fCollection = new java.util.HashSet(); + + public B getB() { + return b; + } + public void setB(B parameter){ + this.b = parameter; + } + + public java.util.Set getFCollection() { + return fCollection; + } + public void setFCollection( + java.util.Set parameter) { + this.fCollection = parameter; + } +} diff --git a/hibernate-entitymanager/src/matrix/java/org/hibernate/ejb/test/cascade/multicircle/MultiCircleJpaCascadeTest.java b/hibernate-entitymanager/src/matrix/java/org/hibernate/ejb/test/cascade/multicircle/MultiCircleJpaCascadeTest.java new file mode 100644 index 0000000000..babb8ed891 --- /dev/null +++ b/hibernate-entitymanager/src/matrix/java/org/hibernate/ejb/test/cascade/multicircle/MultiCircleJpaCascadeTest.java @@ -0,0 +1,333 @@ +/* + * 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 static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import org.hibernate.TransientPropertyValueException; +import org.hibernate.ejb.test.BaseEntityManagerFunctionalTestCase; +import org.hibernate.testing.FailureExpected; + +/** + * 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: + * + * + * ------------------------------ N G + * | + * | 1 + * | | + * | | + * | N + * | + * | E N--------------0,1 * F + * | + * | 1 N + * | | | + * | | | + * 1 N | + * * | + * B * N---1 D * 1------------------ + * * + * N N + * | | + * | | + * 1 | + * | + * C * 1----- + * + * + * 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 + }; + } + +}