Hibernate SVN: r16571 - search/trunk/src/test/java/org/hibernate/search/test/util.
by hibernate-commits@lists.jboss.org
Author: hardy.ferentschik
Date: 2009-05-15 11:17:35 -0400 (Fri, 15 May 2009)
New Revision: 16571
Modified:
search/trunk/src/test/java/org/hibernate/search/test/util/FullTextSessionBuilder.java
Log:
Removed hard dependency to HSQLDB
Modified: search/trunk/src/test/java/org/hibernate/search/test/util/FullTextSessionBuilder.java
===================================================================
--- search/trunk/src/test/java/org/hibernate/search/test/util/FullTextSessionBuilder.java 2009-05-15 09:41:22 UTC (rev 16570)
+++ search/trunk/src/test/java/org/hibernate/search/test/util/FullTextSessionBuilder.java 2009-05-15 15:17:35 UTC (rev 16571)
@@ -16,38 +16,24 @@
* which need to use several differently configured SessionFactories.
*
* @author Sanne Grinovero
+ * @author Hardy Ferentschik
*/
public class FullTextSessionBuilder {
- private AnnotationConfiguration cfg = new AnnotationConfiguration();
+ private AnnotationConfiguration cfg;
private SessionFactory sessionFactory;
private Session session;
public FullTextSessionBuilder() {
+ cfg = new AnnotationConfiguration();
cfg.setProperty( Environment.HBM2DDL_AUTO, "create-drop" );
- //DB type:
- cfg.setProperty( Environment.URL, "jdbc:hsqldb:mem:." );
- cfg.setProperty( Environment.DRIVER,
- org.hsqldb.jdbcDriver.class.getCanonicalName() );
- cfg.setProperty( Environment.DIALECT,
- org.hibernate.dialect.HSQLDialect.class.getCanonicalName() );
- //connection:
- cfg.setProperty( Environment.USER, "sa" );
- cfg.setProperty( Environment.PASS, "" );
- cfg.setProperty( Environment.ISOLATION, "2" );
- cfg.setProperty( Environment.POOL_SIZE, "1" );
- cfg.setProperty( Environment.ORDER_UPDATES, "true" );
+
//cache:
cfg.setProperty( Environment.USE_SECOND_LEVEL_CACHE, "true" );
cfg.setProperty( Environment.CACHE_PROVIDER,
org.hibernate.cache.HashtableCacheProvider.class.getCanonicalName() );
cfg.setProperty( Environment.USE_QUERY_CACHE, "true" );
- //debugging/logging:
- cfg.setProperty( Environment.SHOW_SQL, "false" );
- cfg.setProperty( Environment.USE_SQL_COMMENTS, "true" );
- cfg.setProperty( Environment.FORMAT_SQL, "true" );
- cfg.setProperty( Environment.USE_STRUCTURED_CACHE, "true" );
- cfg.setProperty( Environment.GENERATE_STATISTICS, "true" );
+
//search specific:
cfg.setProperty( org.hibernate.search.Environment.ANALYZER_CLASS,
StopAnalyzer.class.getName() );
@@ -57,9 +43,9 @@
/**
* Override before building any parameter, or add new ones.
- * @param key
- * @param value
- * @return the same builder (this)
+ * @param key Property name.
+ * @param value Property value.
+ * @return the same builder (this).
*/
public FullTextSessionBuilder setProperty(String key, String value) {
cfg.setProperty( key, value );
@@ -67,8 +53,8 @@
}
/**
- * Adds classes to the SessionFactory being built
- * @param annotatedClass
+ * Adds classes to the SessionFactory being built.
+ * @param annotatedClass The annotated class to add to the configuration.
* @return the same builder (this)
*/
public FullTextSessionBuilder addAnnotatedClass(Class annotatedClass) {
@@ -78,7 +64,7 @@
/**
* Creates a new FullTextSession based upon the configuration built so far.
- * @return
+ * @return new FullTextSession based upon the configuration built so far.
*/
public FullTextSession build() {
if ( session != null || sessionFactory != null ) {
@@ -101,5 +87,4 @@
sessionFactory.close();
sessionFactory = null;
}
-
}
15 years, 6 months
Hibernate SVN: r16570 - search/branches/Branch_3_1.
by hibernate-commits@lists.jboss.org
Author: jcosta(a)redhat.com
Date: 2009-05-15 05:41:22 -0400 (Fri, 15 May 2009)
New Revision: 16570
Modified:
search/branches/Branch_3_1/pom.xml
Log:
HSEARCH-82 - Changed the database credentials, to use the same as Core's Branch_3_3
Modified: search/branches/Branch_3_1/pom.xml
===================================================================
--- search/branches/Branch_3_1/pom.xml 2009-05-14 20:27:19 UTC (rev 16569)
+++ search/branches/Branch_3_1/pom.xml 2009-05-15 09:41:22 UTC (rev 16570)
@@ -344,9 +344,9 @@
<properties>
<db.dialect>org.hibernate.dialect.MySQL5InnoDBDialect</db.dialect>
<jdbc.driver>com.mysql.jdbc.Driver</jdbc.driver>
- <jdbc.url>jdbc:mysql://dev02.qa.atl.jboss.com/hibbrtru</jdbc.url>
- <jdbc.user>hibbrtru</jdbc.user>
- <jdbc.pass>hibbrtru</jdbc.pass>
+ <jdbc.url>jdbc:mysql://dev02.qa.atl.jboss.com/hibbr330</jdbc.url>
+ <jdbc.user>hibbr330</jdbc.user>
+ <jdbc.pass>hibbr330</jdbc.pass>
<jdbc.isolation/>
</properties>
</profile>
@@ -365,9 +365,9 @@
<properties>
<db.dialect>org.hibernate.dialect.PostgreSQLDialect</db.dialect>
<jdbc.driver>org.postgresql.Driver</jdbc.driver>
- <jdbc.url>jdbc:postgresql://dev01.qa.atl.jboss.com:5432:hibbrtru</jdbc.url>
- <jdbc.user>hibbrtru</jdbc.user>
- <jdbc.pass>hibbrtru</jdbc.pass>
+ <jdbc.url>jdbc:postgresql://dev01.qa.atl.jboss.com:5432:hibbr330</jdbc.url>
+ <jdbc.user>hibbr330</jdbc.user>
+ <jdbc.pass>hibbr330</jdbc.pass>
<jdbc.isolation/>
</properties>
</profile>
@@ -397,8 +397,8 @@
<db.dialect>org.hibernate.dialect.DB2Dialect</db.dialect>
<jdbc.driver>com.ibm.db2.jcc.DB2Driver</jdbc.driver>
<jdbc.url>jdbc:db2://dev32.qa.atl.jboss.com:50000/jbossqa</jdbc.url>
- <jdbc.user>hibbrtru</jdbc.user>
- <jdbc.pass>hibbrtru</jdbc.pass>
+ <jdbc.user>hibbr330</jdbc.user>
+ <jdbc.pass>hibbr330</jdbc.pass>
<jdbc.isolation/>
</properties>
</profile>
@@ -422,8 +422,8 @@
<db.dialect>org.hibernate.dialect.DB2Dialect</db.dialect>
<jdbc.driver>com.ibm.db2.jcc.DB2Driver</jdbc.driver>
<jdbc.url>jdbc:db2://dev67.qa.atl.jboss.com:50000/jbossqa</jdbc.url>
- <jdbc.user>hibbrtru</jdbc.user>
- <jdbc.pass>hibbrtru</jdbc.pass>
+ <jdbc.user>hibbr330</jdbc.user>
+ <jdbc.pass>hibbr330</jdbc.pass>
<jdbc.isolation/>
</properties>
</profile>
@@ -443,8 +443,8 @@
<db.dialect>org.hibernate.dialect.Oracle9iDialect</db.dialect>
<jdbc.driver>oracle.jdbc.driver.OracleDriver</jdbc.driver>
<jdbc.url>jdbc:oracle:thin:@dev20.qa.atl.jboss.com:1521:qa</jdbc.url>
- <jdbc.user>hibbrtru</jdbc.user>
- <jdbc.pass>hibbrtru</jdbc.pass>
+ <jdbc.user>hibbr330</jdbc.user>
+ <jdbc.pass>hibbr330</jdbc.pass>
<jdbc.isolation/>
</properties>
</profile>
@@ -464,8 +464,8 @@
<db.dialect>org.hibernate.dialect.Oracle10gDialect</db.dialect>
<jdbc.driver>oracle.jdbc.driver.OracleDriver</jdbc.driver>
<jdbc.url>jdbc:oracle:thin:@dev01.qa.atl.jboss.com:1521:qadb01</jdbc.url>
- <jdbc.user>hibbrtru</jdbc.user>
- <jdbc.pass>hibbrtru</jdbc.pass>
+ <jdbc.user>hibbr330</jdbc.user>
+ <jdbc.pass>hibbr330</jdbc.pass>
<jdbc.isolation/>
</properties>
</profile>
@@ -483,9 +483,9 @@
<properties>
<db.dialect>org.hibernate.dialect.SybaseASE15Dialect</db.dialect>
<jdbc.driver>com.sybase.jdbc3.jdbc.SybDriver</jdbc.driver>
- <jdbc.url>jdbc:sybase:Tds:dev77.qa.atl2.redhat.com:5000/hibbrtru</jdbc.url>
- <jdbc.user>hibbrtru</jdbc.user>
- <jdbc.pass>hibbrtru</jdbc.pass>
+ <jdbc.url>jdbc:sybase:Tds:dev77.qa.atl2.redhat.com:5000/hibbr330</jdbc.url>
+ <jdbc.user>hibbr330</jdbc.user>
+ <jdbc.pass>hibbr330</jdbc.pass>
<jdbc.isolation/>
</properties>
</profile>
@@ -504,8 +504,8 @@
<db.dialect>org.hibernate.dialect.SQLServerDialect</db.dialect>
<jdbc.driver>com.microsoft.sqlserver.jdbc.SQLServerDriver</jdbc.driver>
<jdbc.url>jdbc:sqlserver://dev30.qa.atl.jboss.com:3918</jdbc.url>
- <jdbc.user>hibbrtru</jdbc.user>
- <jdbc.pass>hibbrtru</jdbc.pass>
+ <jdbc.user>hibbr330</jdbc.user>
+ <jdbc.pass>hibbr330</jdbc.pass>
<jdbc.isolation>4096</jdbc.isolation>
</properties>
</profile>
15 years, 6 months
Hibernate SVN: r16569 - in core/trunk: testsuite/src/test/java/org/hibernate/test/cascade and 2 other directories.
by hibernate-commits@lists.jboss.org
Author: gbadner
Date: 2009-05-14 16:27:19 -0400 (Thu, 14 May 2009)
New Revision: 16569
Added:
core/trunk/core/src/main/java/org/hibernate/event/def/CopyCache.java
core/trunk/testsuite/src/test/java/org/hibernate/test/cascade/circle/
core/trunk/testsuite/src/test/java/org/hibernate/test/cascade/circle/CascadeMergeToChildBeforeParent.hbm.xml
core/trunk/testsuite/src/test/java/org/hibernate/test/cascade/circle/CascadeMergeToChildBeforeParentTest.java
core/trunk/testsuite/src/test/java/org/hibernate/test/cascade/circle/MultiPathCircleCascade.hbm.xml
core/trunk/testsuite/src/test/java/org/hibernate/test/cascade/circle/MultiPathCircleCascadeTest.java
core/trunk/testsuite/src/test/java/org/hibernate/test/cascade/circle/Node.java
core/trunk/testsuite/src/test/java/org/hibernate/test/cascade/circle/Route.java
core/trunk/testsuite/src/test/java/org/hibernate/test/cascade/circle/Tour.java
core/trunk/testsuite/src/test/java/org/hibernate/test/cascade/circle/Transport.java
core/trunk/testsuite/src/test/java/org/hibernate/test/cascade/circle/Vehicle.java
Modified:
core/trunk/core/src/main/java/org/hibernate/event/def/DefaultMergeEventListener.java
core/trunk/testsuite/src/test/java/org/hibernate/test/cascade/A.java
core/trunk/testsuite/src/test/java/org/hibernate/test/cascade/G.java
core/trunk/testsuite/src/test/java/org/hibernate/test/cascade/H.java
core/trunk/testsuite/src/test/java/org/hibernate/test/cascade/MultiPathCascadeTest.java
core/trunk/testsuite/src/test/java/org/hibernate/test/ops/MergeTest.java
Log:
HHH-3810 : Transient entities can be inserted twice on merge
Added: core/trunk/core/src/main/java/org/hibernate/event/def/CopyCache.java
===================================================================
--- core/trunk/core/src/main/java/org/hibernate/event/def/CopyCache.java (rev 0)
+++ core/trunk/core/src/main/java/org/hibernate/event/def/CopyCache.java 2009-05-14 20:27:19 UTC (rev 16569)
@@ -0,0 +1,244 @@
+//$Id: $
+/*
+ * Hibernate, Relational Persistence for Idiomatic Java
+ *
+ * Copyright (c) 2008, Red Hat Middleware LLC 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 Middleware LLC.
+ *
+ * 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.event.def;
+
+import java.util.Map;
+import java.util.Set;
+import java.util.Iterator;
+import java.util.Collection;
+
+import org.hibernate.util.IdentityMap;
+import org.hibernate.AssertionFailure;
+
+/**
+ * CopyCache is intended to be the Map implementation used by
+ * {@link DefaultMergeEventListener} to keep track of entities and their copies
+ * being merged into the session. This implementation also tracks whether a
+ * an entity in the CopyCache is included in the merge. This allows a
+ * an entity and its copy to be added to a CopyCache before merge has cascaded
+ * to that entity.
+ *
+ * @author Gail Badner
+ */
+class CopyCache implements Map {
+ private Map entityToCopyMap = IdentityMap.instantiate(10);
+ // key is an entity involved with the merge;
+ // value can be either a copy of the entity or the entity itself
+
+ private Map entityToIncludeInMergeFlagMap = IdentityMap.instantiate(10);
+ // key is an entity involved with the merge;
+ // value is a flag indicating if the entity is included in the merge
+
+ /**
+ * Clears the CopyCache.
+ */
+ public void clear() {
+ entityToCopyMap.clear();
+ entityToIncludeInMergeFlagMap.clear();
+ }
+
+ /**
+ * Returns true if this CopyCache contains a mapping for the specified entity.
+ * @param entity must be non-null
+ * @return true if this CopyCache contains a mapping for the specified entity
+ * @throws NullPointerException if entity is null
+ */
+ public boolean containsKey(Object entity) {
+ if ( entity == null ) {
+ throw new NullPointerException( "null entities are not supported by " + getClass().getName() );
+ }
+ return entityToCopyMap.containsKey( entity );
+ }
+
+ /**
+ * Returns true if this CopyCache maps one or more entities to the specified copy.
+ * @param copy must be non-null
+ * @return true if this CopyCache maps one or more entities to the specified copy
+ * @throws NullPointerException if copy is null
+ */
+ public boolean containsValue(Object copy) {
+ if ( copy == null ) {
+ throw new NullPointerException( "null copies are not supported by " + getClass().getName() );
+ }
+ return entityToCopyMap.containsValue( copy );
+ }
+
+ /**
+ * Returns a set view of the entity-to-copy mappings contained in this CopyCache.
+ * @return set view of the entity-to-copy mappings contained in this CopyCache
+ */
+ public Set entrySet() {
+ return entityToCopyMap.entrySet();
+ }
+
+ /**
+ * Returns the copy to which this CopyCache maps the specified entity.
+ * @param entity must be non-null
+ * @return the copy to which this CopyCache maps the specified entity
+ * @throws NullPointerException if entity is null
+ */
+ public Object get(Object entity) {
+ if ( entity == null ) {
+ throw new NullPointerException( "null entities are not supported by " + getClass().getName() );
+ }
+ return entityToCopyMap.get( entity );
+ }
+
+ /**
+ * Returns true if this CopyCache contains no entity-copy mappings.
+ * @return true if this CopyCache contains no entity-copy mappings
+ */
+ public boolean isEmpty() {
+ return entityToCopyMap.isEmpty();
+ }
+
+ /**
+ * Returns a set view of the entities contained in this CopyCache
+ * @return a set view of the entities contained in this CopyCache
+ */
+ public Set keySet() {
+ return entityToCopyMap.keySet();
+ }
+
+ /**
+ * Associates the specified entity with the specified copy in this CopyCache;
+ * @param entity must be non-null
+ * @param copy must be non- null
+ * @return previous copy associated with specified entity, or null if
+ * there was no mapping for entity.
+ * @throws NullPointerException if entity or copy is null
+ */
+ public Object put(Object entity, Object copy) {
+ if ( entity == null || copy == null ) {
+ throw new NullPointerException( "null entities and copies are not supported by " + getClass().getName() );
+ }
+ entityToIncludeInMergeFlagMap.put( entity, Boolean.FALSE );
+ return entityToCopyMap.put( entity, copy );
+ }
+
+ /**
+ * Associates the specified entity with the specified copy in this CopyCache;
+ * @param entity must be non-null
+ * @param copy must be non- null
+ * @param isIncludedInMerge indicates if the entity is included in merge
+ *
+ * @return previous copy associated with specified entity, or null if
+ * there was no mapping for entity.
+ * @throws NullPointerException if entity or copy is null
+ */
+ /* package-private */ Object put(Object entity, Object copy, boolean isIncludedInMerge) {
+ if ( entity == null || copy == null ) {
+ throw new NullPointerException( "null entities and copies are not supported by " + getClass().getName() );
+ }
+ entityToIncludeInMergeFlagMap.put( entity, Boolean.valueOf( isIncludedInMerge ) );
+ return entityToCopyMap.put( entity, copy );
+ }
+
+ /**
+ * Copies all of the mappings from the specified map to this CopyCache
+ * @param map keys and values must be non-null
+ * @throws NullPointerException if any map keys or values are null
+ */
+ public void putAll(Map map) {
+ for ( Iterator it=map.entrySet().iterator(); it.hasNext(); ) {
+ Map.Entry entry = ( Map.Entry ) it.next();
+ if ( entry.getKey() == null || entry.getValue() == null ) {
+ throw new NullPointerException( "null entities and copies are not supported by " + getClass().getName() );
+ }
+ entityToCopyMap.put( entry.getKey(), entry.getValue() );
+ entityToIncludeInMergeFlagMap.put( entry.getKey(), Boolean.FALSE );
+ }
+ }
+
+ /**
+ * Removes the mapping for this entity from this CopyCache if it is present
+ * @param entity must be non-null
+ * @return previous value associated with specified entity, or null if there was no mapping for entity.
+ * @throws NullPointerException if entity is null
+ */
+ public Object remove(Object entity) {
+ if ( entity == null ) {
+ throw new NullPointerException( "null entities are not supported by " + getClass().getName() );
+ }
+ entityToIncludeInMergeFlagMap.remove( entity );
+ return entityToCopyMap.remove( entity );
+ }
+
+ /**
+ * Returns the number of entity-copy mappings in this CopyCache
+ * @return the number of entity-copy mappings in this CopyCache
+ */
+ public int size() {
+ return entityToCopyMap.size();
+ }
+
+ /**
+ * Returns a collection view of the entity copies contained in this CopyCache.
+ * @return a collection view of the entity copies contained in this CopyCache
+ */
+ public Collection values() {
+ return entityToCopyMap.values();
+ }
+
+ /**
+ * Returns true if the specified entity is included in the merge.
+ * @param entity must be non-null
+ * @return true if the specified entity is included in the merge.
+ * @throws NullPointerException if entity is null
+ */
+ public boolean isIncludedInMerge(Object entity) {
+ if ( entity == null ) {
+ throw new NullPointerException( "null entities are not supported by " + getClass().getName() );
+ }
+ return ( ( Boolean ) entityToIncludeInMergeFlagMap.get( entity ) ).booleanValue();
+ }
+
+ /**
+ * Set flag to indicate if an entity is included in the merge.
+ * @param entity must be non-null and this CopyCache must contain a mapping for this entity
+ * @return true if the specified entity is included in the merge
+ * @throws NullPointerException if entity is null
+ * @throws AssertionFailure if this CopyCache does not contain a mapping for the specified entity
+ */
+ /* package-private */ void setIncludedInMerge(Object entity, boolean isIncludedInMerge) {
+ if ( entity == null ) {
+ throw new NullPointerException( "null entities are not supported by " + getClass().getName() );
+ }
+ if ( ! entityToIncludeInMergeFlagMap.containsKey( entity ) ||
+ ! entityToCopyMap.containsKey( entity ) ) {
+ throw new AssertionFailure( "called CopyCache.setInMergeProcess() for entity not found in CopyCache" );
+ }
+ entityToIncludeInMergeFlagMap.put( entity, Boolean.valueOf( isIncludedInMerge ) );
+ }
+
+ /**
+ * Returns the copy-entity mappings
+ * @return the copy-entity mappings
+ */
+ public Map getMergeMap() {
+ return IdentityMap.invert( entityToCopyMap );
+ }
+}
\ No newline at end of file
Modified: core/trunk/core/src/main/java/org/hibernate/event/def/DefaultMergeEventListener.java
===================================================================
--- core/trunk/core/src/main/java/org/hibernate/event/def/DefaultMergeEventListener.java 2009-05-14 19:57:00 UTC (rev 16568)
+++ core/trunk/core/src/main/java/org/hibernate/event/def/DefaultMergeEventListener.java 2009-05-14 20:27:19 UTC (rev 16569)
@@ -27,6 +27,8 @@
import java.io.Serializable;
import java.util.Iterator;
import java.util.Map;
+import java.util.Set;
+import java.util.HashSet;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -37,6 +39,7 @@
import org.hibernate.StaleObjectStateException;
import org.hibernate.TransientObjectException;
import org.hibernate.WrongClassException;
+import org.hibernate.PropertyValueException;
import org.hibernate.engine.Cascade;
import org.hibernate.engine.CascadingAction;
import org.hibernate.engine.EntityEntry;
@@ -53,7 +56,7 @@
import org.hibernate.proxy.LazyInitializer;
import org.hibernate.type.ForeignKeyDirection;
import org.hibernate.type.TypeFactory;
-import org.hibernate.util.IdentityMap;
+import org.hibernate.type.Type;
/**
* Defines the default copy event listener used by hibernate for copying entities
@@ -67,7 +70,7 @@
private static final Logger log = LoggerFactory.getLogger(DefaultMergeEventListener.class);
protected Map getMergeMap(Object anything) {
- return IdentityMap.invert( (Map) anything );
+ return ( ( CopyCache ) anything ).getMergeMap();
}
/**
@@ -77,36 +80,96 @@
* @throws HibernateException
*/
public void onMerge(MergeEvent event) throws HibernateException {
- Map copyCache = IdentityMap.instantiate(10);
+ CopyCache copyCache = new CopyCache();
onMerge( event, copyCache );
- for ( Iterator it=copyCache.values().iterator(); it.hasNext(); ) {
- Object entity = it.next();
- if ( entity instanceof HibernateProxy ) {
- entity = ( (HibernateProxy) entity ).getHibernateLazyInitializer().getImplementation();
+ // TODO: iteratively get transient entities and retry merge until one of the following conditions:
+ // 1) transientCopyCache.size() == 0
+ // 2) transientCopyCache.size() is not decreasing and copyCache.size() is not increasing
+ // TODO: find out if retrying can add entities to copyCache (don't think it can...)
+ // For now, just retry once; throw TransientObjectException if there are still any transient entities
+ Map transientCopyCache = getTransientCopyCache(event, copyCache );
+ if ( transientCopyCache.size() > 0 ) {
+ retryMergeTransientEntities( event, transientCopyCache, copyCache );
+ // find any entities that are still transient after retry
+ transientCopyCache = getTransientCopyCache(event, copyCache );
+ if ( transientCopyCache.size() > 0 ) {
+ Set transientEntityNames = new HashSet();
+ for( Iterator it=transientCopyCache.keySet().iterator(); it.hasNext(); ) {
+ Object transientEntity = it.next();
+ String transientEntityName = event.getSession().guessEntityName( transientEntity );
+ transientEntityNames.add( transientEntityName );
+ log.trace( "transient instance could not be processed by merge: " +
+ transientEntityName + "[" + transientEntity + "]" );
+ }
+ throw new TransientObjectException(
+ "one or more objects is an unsaved transient instance - save transient instance(s) before merging: " +
+ transientEntityNames );
}
- EntityEntry entry = event.getSession().getPersistenceContext().getEntry( entity );
- if ( entry == null ) {
+ }
+ copyCache.clear();
+ copyCache = null;
+ }
+
+ protected CopyCache getTransientCopyCache(MergeEvent event, CopyCache copyCache) {
+ CopyCache transientCopyCache = new CopyCache();
+ for ( Iterator it=copyCache.entrySet().iterator(); it.hasNext(); ) {
+ Map.Entry mapEntry = ( Map.Entry ) it.next();
+ Object entity = mapEntry.getKey();
+ Object copy = mapEntry.getValue();
+ if ( copy instanceof HibernateProxy ) {
+ copy = ( (HibernateProxy) copy ).getHibernateLazyInitializer().getImplementation();
+ }
+ EntityEntry copyEntry = event.getSession().getPersistenceContext().getEntry( copy );
+ if ( copyEntry == null ) {
+ // entity name will not be available for non-POJO entities
+ // TODO: cache the entity name somewhere so that it is available to this exception
+ log.trace( "transient instance could not be processed by merge: " +
+ event.getSession().guessEntityName( copy ) + "[" + entity + "]" );
throw new TransientObjectException(
- "object references an unsaved transient instance - save the transient instance before merging: " +
- event.getSession().guessEntityName( entity )
+ "object is an unsaved transient instance - save the transient instance before merging: " +
+ event.getSession().guessEntityName( copy )
);
- // TODO: cache the entity name somewhere so that it is available to this exception
- // entity name will not be available for non-POJO entities
}
- if ( entry.getStatus() != Status.MANAGED && entry.getStatus() != Status.READ_ONLY ) {
- throw new AssertionFailure( "Merged entity does not have status set to MANAGED or READ_ONLY; "+entry+" status="+entry.getStatus() );
+ else if ( copyEntry.getStatus() == Status.SAVING ) {
+ transientCopyCache.put( entity, copy, copyCache.isIncludedInMerge( entity ) );
}
+ else if ( copyEntry.getStatus() != Status.MANAGED && copyEntry.getStatus() != Status.READ_ONLY ) {
+ throw new AssertionFailure( "Merged entity does not have status set to MANAGED or READ_ONLY; "+copy+" status="+copyEntry.getStatus() );
+ }
}
+ return transientCopyCache;
}
+ protected void retryMergeTransientEntities(MergeEvent event, Map transientCopyCache, CopyCache copyCache) {
+ // TODO: The order in which entities are saved may matter (e.g., a particular transient entity
+ // may need to be saved before other transient entities can be saved;
+ // Keep retrying the batch of transient entities until either:
+ // 1) there are no transient entities left in transientCopyCache
+ // or 2) no transient entities were saved in the last batch
+ // For now, just run through the transient entities and retry the merge
+ for ( Iterator it=transientCopyCache.entrySet().iterator(); it.hasNext(); ) {
+ Map.Entry mapEntry = ( Map.Entry ) it.next();
+ Object entity = mapEntry.getKey();
+ Object copy = transientCopyCache.get( entity );
+ EntityEntry copyEntry = event.getSession().getPersistenceContext().getEntry( copy );
+ if ( entity == event.getEntity() ) {
+ mergeTransientEntity( entity, copyEntry.getEntityName(), event.getRequestedId(), event.getSession(), copyCache);
+ }
+ else {
+ mergeTransientEntity( entity, copyEntry.getEntityName(), copyEntry.getId(), event.getSession(), copyCache);
+ }
+ }
+ }
+
/**
* Handle the given merge event.
*
* @param event The merge event to be handled.
* @throws HibernateException
*/
- public void onMerge(MergeEvent event, Map copyCache) throws HibernateException {
+ public void onMerge(MergeEvent event, Map copiedAlready) throws HibernateException {
+ final CopyCache copyCache = ( CopyCache ) copiedAlready;
final EventSource source = event.getSession();
final Object original = event.getOriginal();
@@ -127,13 +190,17 @@
else {
entity = original;
}
-
- if ( copyCache.containsKey(entity) &&
- source.getContextEntityIdentifier( copyCache.get( entity ) ) != null ) {
- log.trace("already merged");
- event.setResult(entity);
+
+ if ( copyCache.containsKey( entity ) &&
+ ( copyCache.isIncludedInMerge( entity ) ) ) {
+ log.trace("already in merge process");
+ event.setResult( entity );
}
else {
+ if ( copyCache.containsKey( entity ) ) {
+ log.trace("already in copyCache; setting in merge process");
+ copyCache.setIncludedInMerge( entity, true );
+ }
event.setEntity( entity );
int entityState = -1;
@@ -175,7 +242,7 @@
default: //DELETED
throw new ObjectDeletedException(
"deleted instance passed to merge",
- null,
+ null,
getLoggableName( event.getEntityName(), entity )
);
}
@@ -193,15 +260,15 @@
final Object entity = event.getEntity();
final EventSource source = event.getSession();
final EntityPersister persister = source.getEntityPersister( event.getEntityName(), entity );
+
+ ( ( CopyCache ) copyCache ).put( entity, entity, true ); //before cascade!
- copyCache.put(entity, entity); //before cascade!
-
cascadeOnMerge(source, persister, entity, copyCache);
copyValues(persister, entity, entity, source, copyCache);
event.setResult(entity);
}
-
+
protected void entityIsTransient(MergeEvent event, Map copyCache) {
log.trace("merging transient instance");
@@ -211,7 +278,16 @@
final EntityPersister persister = source.getEntityPersister( event.getEntityName(), entity );
final String entityName = persister.getEntityName();
-
+
+ event.setResult( mergeTransientEntity( entity, entityName, event.getRequestedId(), source, copyCache ) );
+ }
+
+ protected Object mergeTransientEntity(Object entity, String entityName, Serializable requestedId, EventSource source, Map copyCache) {
+
+ log.trace("merging transient instance");
+
+ final EntityPersister persister = source.getEntityPersister( entityName, entity );
+
final Serializable id = persister.hasIdentifierProperty() ?
persister.getIdentifier( entity, source.getEntityMode() ) :
null;
@@ -219,7 +295,7 @@
persister.setIdentifier( copyCache.get( entity ), id, source.getEntityMode() );
}
else {
- copyCache.put(entity, persister.instantiate( id, source.getEntityMode() ) ); //before cascade!
+ ( ( CopyCache ) copyCache ).put( entity, persister.instantiate( id, source.getEntityMode() ), true ); //before cascade!
//TODO: should this be Session.instantiate(Persister, ...)?
}
final Object copy = copyCache.get( entity );
@@ -229,25 +305,54 @@
//cascadeOnMerge(event, persister, entity, copyCache, Cascades.CASCADE_BEFORE_MERGE);
super.cascadeBeforeSave(source, persister, entity, copyCache);
copyValues(persister, entity, copy, source, copyCache, ForeignKeyDirection.FOREIGN_KEY_FROM_PARENT);
-
- //this bit is only *really* absolutely necessary for handling
- //requestedId, but is also good if we merge multiple object
- //graphs, since it helps ensure uniqueness
- final Serializable requestedId = event.getRequestedId();
- if (requestedId==null) {
- saveWithGeneratedId( copy, entityName, copyCache, source, false );
+
+ try {
+ //this bit is only *really* absolutely necessary for handling
+ //requestedId, but is also good if we merge multiple object
+ //graphs, since it helps ensure uniqueness
+ if (requestedId==null) {
+ saveWithGeneratedId( copy, entityName, copyCache, source, false );
+ }
+ else {
+ saveWithRequestedId( copy, requestedId, entityName, copyCache, source );
+ }
}
- else {
- saveWithRequestedId( copy, requestedId, entityName, copyCache, source );
+ catch (PropertyValueException ex) {
+ String propertyName = ex.getPropertyName();
+ Object propertyFromCopy = persister.getPropertyValue( copy, propertyName, source.getEntityMode() );
+ Object propertyFromEntity = persister.getPropertyValue( entity, propertyName, source.getEntityMode() );
+ Type propertyType = persister.getPropertyType( propertyName );
+ EntityEntry copyEntry = source.getPersistenceContext().getEntry( copy );
+ if ( propertyFromCopy == null || ! propertyType.isEntityType() ) {
+ log.trace( "property '" + copyEntry.getEntityName() + "." + propertyName +
+ "' is null or not an entity; " + propertyName + " =["+propertyFromCopy+"]");
+ throw ex;
+ }
+ if ( ! copyCache.containsKey( propertyFromEntity ) ) {
+ log.trace( "property '" + copyEntry.getEntityName() + "." + propertyName +
+ "' from original entity is not in copyCache; " + propertyName + " =["+propertyFromEntity+"]");
+ throw ex;
+ }
+ if ( ( ( CopyCache ) copyCache ).isIncludedInMerge( propertyFromEntity ) ) {
+ log.trace( "property '" + copyEntry.getEntityName() + "." + propertyName +
+ "' from original entity is in copyCache and is in the process of being merged; " +
+ propertyName + " =["+propertyFromEntity+"]");
+ }
+ else {
+ log.trace( "property '" + copyEntry.getEntityName() + "." + propertyName +
+ "' from original entity is in copyCache and is not in the process of being merged; " +
+ propertyName + " =["+propertyFromEntity+"]");
+ }
+ // continue...; we'll find out if it ends up not getting saved later
}
-
- // cascade first, so that all unsaved objects get their
+
+ // cascade first, so that all unsaved objects get their
// copy created before we actually copy
super.cascadeAfterSave(source, persister, entity, copyCache);
copyValues(persister, entity, copy, source, copyCache, ForeignKeyDirection.FOREIGN_KEY_TO_PARENT);
-
- event.setResult(copy);
+ return copy;
+
}
protected void entityIsDetached(MergeEvent event, Map copyCache) {
@@ -259,7 +364,7 @@
final EntityPersister persister = source.getEntityPersister( event.getEntityName(), entity );
final String entityName = persister.getEntityName();
-
+
Serializable id = event.getRequestedId();
if ( id == null ) {
id = persister.getIdentifier( entity, source.getEntityMode() );
@@ -292,7 +397,7 @@
entityIsTransient(event, copyCache);
}
else {
- copyCache.put(entity, result); //before cascade!
+ ( ( CopyCache ) copyCache ).put( entity, result, true ); //before cascade!
final Object target = source.getPersistenceContext().unproxy(result);
if ( target == entity ) {
@@ -380,7 +485,7 @@
return entry.isExistsInDatabase();
}
}
-
+
protected void copyValues(
final EntityPersister persister,
final Object entity,
@@ -388,7 +493,6 @@
final SessionImplementor source,
final Map copyCache
) {
-
final Object[] copiedValues = TypeFactory.replace(
persister.getPropertyValues( entity, source.getEntityMode() ),
persister.getPropertyValues( target, source.getEntityMode() ),
Modified: core/trunk/testsuite/src/test/java/org/hibernate/test/cascade/A.java
===================================================================
--- core/trunk/testsuite/src/test/java/org/hibernate/test/cascade/A.java 2009-05-14 19:57:00 UTC (rev 16568)
+++ core/trunk/testsuite/src/test/java/org/hibernate/test/cascade/A.java 2009-05-14 20:27:19 UTC (rev 16569)
@@ -1,4 +1,28 @@
-// $Id$
+//$Id: $
+/*
+ * Hibernate, Relational Persistence for Idiomatic Java
+ *
+ * Copyright (c) 2008, Red Hat Middleware LLC 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 Middleware LLC.
+ *
+ * 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;
Modified: core/trunk/testsuite/src/test/java/org/hibernate/test/cascade/G.java
===================================================================
--- core/trunk/testsuite/src/test/java/org/hibernate/test/cascade/G.java 2009-05-14 19:57:00 UTC (rev 16568)
+++ core/trunk/testsuite/src/test/java/org/hibernate/test/cascade/G.java 2009-05-14 20:27:19 UTC (rev 16569)
@@ -1,3 +1,29 @@
+//$Id: $
+/*
+ * Hibernate, Relational Persistence for Idiomatic Java
+ *
+ * Copyright (c) 2008, Red Hat Middleware LLC 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 Middleware LLC.
+ *
+ * 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;
import java.util.Set;
Modified: core/trunk/testsuite/src/test/java/org/hibernate/test/cascade/H.java
===================================================================
--- core/trunk/testsuite/src/test/java/org/hibernate/test/cascade/H.java 2009-05-14 19:57:00 UTC (rev 16568)
+++ core/trunk/testsuite/src/test/java/org/hibernate/test/cascade/H.java 2009-05-14 20:27:19 UTC (rev 16569)
@@ -1,6 +1,29 @@
-// $Id$
+//$Id: $
+/*
+ * Hibernate, Relational Persistence for Idiomatic Java
+ *
+ * Copyright (c) 2008, Red Hat Middleware LLC 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 Middleware LLC.
+ *
+ * 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;
import java.util.Set;
Modified: core/trunk/testsuite/src/test/java/org/hibernate/test/cascade/MultiPathCascadeTest.java
===================================================================
--- core/trunk/testsuite/src/test/java/org/hibernate/test/cascade/MultiPathCascadeTest.java 2009-05-14 19:57:00 UTC (rev 16568)
+++ core/trunk/testsuite/src/test/java/org/hibernate/test/cascade/MultiPathCascadeTest.java 2009-05-14 20:27:19 UTC (rev 16569)
@@ -1,4 +1,28 @@
//$Id: $
+/*
+ * Hibernate, Relational Persistence for Idiomatic Java
+ *
+ * Copyright (c) 2008, Red Hat Middleware LLC 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 Middleware LLC.
+ *
+ * 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;
Added: core/trunk/testsuite/src/test/java/org/hibernate/test/cascade/circle/CascadeMergeToChildBeforeParent.hbm.xml
===================================================================
--- core/trunk/testsuite/src/test/java/org/hibernate/test/cascade/circle/CascadeMergeToChildBeforeParent.hbm.xml (rev 0)
+++ core/trunk/testsuite/src/test/java/org/hibernate/test/cascade/circle/CascadeMergeToChildBeforeParent.hbm.xml 2009-05-14 20:27:19 UTC (rev 16569)
@@ -0,0 +1,115 @@
+<?xml version="1.0"?>
+<!DOCTYPE hibernate-mapping SYSTEM "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd" >
+
+<hibernate-mapping package="org.hibernate.test.cascade.circle">
+
+ <class name="Route" table="HB_Route">
+
+ <id name="routeID" type="long"><generator class="native"/></id>
+ <version name="version" column="VERS" type="long" />
+
+ <property name="name" type="string" not-null="true"/>
+
+ <set name="nodes" inverse="true" cascade="persist,merge,refresh">
+ <key column="routeID"/>
+ <one-to-many class="Node"/>
+ </set>
+ <set name="vehicles" inverse="true" cascade="persist,merge,refresh">
+ <key column="routeID"/>
+ <one-to-many class="Vehicle"/>
+ </set>
+ </class>
+
+ <class name="Tour" table="HB_Tour">
+
+ <id name="tourID" type="long"><generator class="native"/></id>
+ <version name="version" column="VERS" type="long" />
+
+ <property name="name" type="string" not-null="true"/>
+
+ <set name="nodes" inverse="true" lazy="true" cascade="merge,refresh">
+ <key column="tourID"/>
+ <one-to-many class="Node"/>
+ </set>
+ </class>
+
+ <class name="Transport" table="HB_Transport">
+
+ <id name="transportID" type="long"><generator class="native"/></id>
+ <version name="version" column="VERS" type="long" />
+
+ <property name="name" type="string" not-null="true"/>
+
+ <many-to-one name="pickupNode"
+ column="pickupNodeID"
+ unique="true"
+ not-null="true"
+ cascade="merge,refresh"
+ lazy="false"/>
+
+ <many-to-one name="deliveryNode"
+ column="deliveryNodeID"
+ unique="true"
+ not-null="true"
+ cascade="merge,refresh"
+ lazy="false"/>
+
+ <many-to-one name="vehicle"
+ column="vehicleID"
+ unique="false"
+ not-null="true"
+ cascade="none"
+ lazy="false"/>
+ </class>
+
+ <class name="Vehicle" table="HB_Vehicle">
+ <id name="vehicleID" type="long"><generator class="native"/></id>
+ <version name="version" column="VERS" type="long" />
+
+ <property name="name"/>
+ <set name="transports" inverse="false" lazy="true" cascade="merge,refresh">
+ <key column="vehicleID"/>
+ <one-to-many class="Transport" not-found="exception"/>
+ </set>
+ <many-to-one name="route"
+ column="routeID"
+ unique="false"
+ not-null="true"
+ cascade="none"
+ lazy="false"/>
+ </class>
+
+
+ <class name="Node" table="HB_Node">
+
+ <id name="nodeID" type="long"><generator class="native"/></id>
+ <version name="version" column="VERS" type="long" />
+
+ <property name="name" type="string" not-null="true"/>
+
+ <set name="deliveryTransports" inverse="true" lazy="true" cascade="merge,refresh">
+ <key column="deliveryNodeID"/>
+ <one-to-many class="Transport"/>
+ </set>
+
+ <set name="pickupTransports" inverse="true" lazy="true" cascade="merge,refresh">
+ <key column="pickupNodeID"/>
+ <one-to-many class="Transport"/>
+ </set>
+
+ <many-to-one name="route"
+ column="routeID"
+ unique="false"
+ not-null="true"
+ cascade="none"
+ lazy="false"/>
+
+ <many-to-one name="tour"
+ column="tourID"
+ unique="false"
+ not-null="false"
+ cascade="merge,refresh"
+ lazy="false"/>
+ </class>
+
+</hibernate-mapping>
Added: core/trunk/testsuite/src/test/java/org/hibernate/test/cascade/circle/CascadeMergeToChildBeforeParentTest.java
===================================================================
--- core/trunk/testsuite/src/test/java/org/hibernate/test/cascade/circle/CascadeMergeToChildBeforeParentTest.java (rev 0)
+++ core/trunk/testsuite/src/test/java/org/hibernate/test/cascade/circle/CascadeMergeToChildBeforeParentTest.java 2009-05-14 20:27:19 UTC (rev 16569)
@@ -0,0 +1,288 @@
+//$Id: $
+/*
+ * Hibernate, Relational Persistence for Idiomatic Java
+ *
+ * Copyright (c) 2008, Red Hat Middleware LLC 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 Middleware LLC.
+ *
+ * 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 junit.framework.Test;
+
+import org.hibernate.Session;
+import org.hibernate.junit.functional.FunctionalTestCase;
+import org.hibernate.junit.functional.FunctionalTestClassTestSuite;
+
+/**
+ * The test case uses the following model:
+ *
+ * <- ->
+ * -- (N : 0,1) -- Tour
+ * | <- ->
+ * | -- (1 : N) -- (pickup) ----
+ * -> | | |
+ * Route -- (1 : N) - Node Transport
+ * | | <- -> | |
+ * | -- (1 : N) -- (delivery) -- |
+ * | |
+ * | -> -> |
+ * -------- (1 : N) ---- Vehicle--(1 : N)------------
+ *
+ * Arrows indicate the direction of cascade-merge.
+ *
+ * I believe it reproduces the following issue:
+ * http://opensource.atlassian.com/projects/hibernate/browse/HHH-3544
+ *
+ * @author Gail Badner (based on original model provided by Pavol Zibrita)
+ */
+public class CascadeMergeToChildBeforeParentTest extends FunctionalTestCase {
+
+ public CascadeMergeToChildBeforeParentTest(String string) {
+ super(string);
+ }
+
+ public String[] getMappings() {
+ return new String[] {
+ "cascade/circle/CascadeMergeToChildBeforeParent.hbm.xml"
+ };
+ }
+
+ public static Test suite() {
+ return new FunctionalTestClassTestSuite( CascadeMergeToChildBeforeParentTest.class );
+ }
+
+ 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" );
+ s.createQuery( "delete from Vehicle" );
+ }
+
+ public void testMerge()
+ {
+ Session s = openSession();
+ s.beginTransaction();
+
+ Route route = new Route();
+ route.setName("routeA");
+
+ s.save( route );
+ s.getTransaction().commit();
+ s.close();
+
+ s = openSession();
+ s.beginTransaction();
+
+ route = (Route) s.get(Route.class, new Long(1));
+
+ route.setTransientField(new String("sfnaouisrbn"));
+
+ Tour tour = new Tour();
+ tour.setName("tourB");
+
+ Node pickupNode = new Node();
+ pickupNode.setName("pickupNodeB");
+
+ Node deliveryNode = new Node();
+ deliveryNode.setName("deliveryNodeB");
+
+ pickupNode.setRoute(route);
+ pickupNode.setTour(tour);
+ pickupNode.setTransientField("pickup node aaaaaaaaaaa");
+
+ deliveryNode.setRoute(route);
+ deliveryNode.setTour(tour);
+ deliveryNode.setTransientField("delivery node aaaaaaaaa");
+
+ tour.getNodes().add(pickupNode);
+ tour.getNodes().add(deliveryNode);
+
+ route.getNodes().add(pickupNode);
+ route.getNodes().add(deliveryNode);
+
+ Route mergedRoute = (Route) s.merge(route);
+
+ s.getTransaction().commit();
+ s.close();
+ }
+
+ // This test fails because the merge algorithm tries to save a
+ // transient child (transport) before cascade-merge gets its
+ // transient parent (vehicle); merge does not cascade from the
+ // child to the parent.
+ public void testMergeTransientChildBeforeTransientParent()
+ {
+ Session s = openSession();
+ s.beginTransaction();
+
+ Route route = new Route();
+ route.setName("routeA");
+
+ s.save( route );
+ s.getTransaction().commit();
+ s.close();
+
+ s = openSession();
+ s.beginTransaction();
+
+ route = (Route) s.get(Route.class, new Long(1));
+
+ 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");
+
+ Vehicle vehicle = new Vehicle();
+ vehicle.setName("vehicleB");
+
+ 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);
+ route.getVehicles().add(vehicle);
+
+ transport.setPickupNode(pickupNode);
+ transport.setDeliveryNode(deliveryNode);
+ transport.setVehicle( vehicle );
+ transport.setTransientField("aaaaaaaaaaaaaa");
+
+ vehicle.getTransports().add(transport);
+ vehicle.setTransientField( "anewvalue" );
+ vehicle.setRoute( route );
+
+ Route mergedRoute = (Route) s.merge(route);
+
+ s.getTransaction().commit();
+ s.close();
+ }
+
+ public void testMergeData3Nodes()
+ {
+
+ Session s = openSession();
+ s.beginTransaction();
+
+ Route route = new Route();
+ route.setName("routeA");
+
+ s.save( route );
+ s.getTransaction().commit();
+ s.close();
+
+ s = openSession();
+ s.beginTransaction();
+
+ route = (Route) s.get(Route.class, new Long(1));
+
+ 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");
+
+ Vehicle vehicle = new Vehicle();
+ vehicle.setName("vehicleB");
+
+ 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);
+ route.getVehicles().add(vehicle);
+
+ transport1.setPickupNode(node1);
+ transport1.setDeliveryNode(node2);
+ transport1.setVehicle( vehicle );
+ transport1.setTransientField("aaaaaaaaaaaaaa");
+
+ transport2.setPickupNode(node2);
+ transport2.setDeliveryNode(node3);
+ transport2.setVehicle( vehicle );
+ transport2.setTransientField("bbbbbbbbbbbbb");
+
+ vehicle.getTransports().add(transport1);
+ vehicle.getTransports().add(transport2);
+ vehicle.setTransientField( "anewvalue" );
+ vehicle.setRoute( route );
+
+ Route mergedRoute = (Route) s.merge(route);
+
+ s.getTransaction().commit();
+ s.close();
+ }
+
+}
Added: core/trunk/testsuite/src/test/java/org/hibernate/test/cascade/circle/MultiPathCircleCascade.hbm.xml
===================================================================
--- core/trunk/testsuite/src/test/java/org/hibernate/test/cascade/circle/MultiPathCircleCascade.hbm.xml (rev 0)
+++ core/trunk/testsuite/src/test/java/org/hibernate/test/cascade/circle/MultiPathCircleCascade.hbm.xml 2009-05-14 20:27:19 UTC (rev 16569)
@@ -0,0 +1,82 @@
+<?xml version="1.0"?>
+<!DOCTYPE hibernate-mapping SYSTEM "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd" >
+
+<hibernate-mapping package="org.hibernate.test.cascade.circle">
+
+ <class name="Route" table="HB_Route">
+
+ <id name="routeID" type="long"><generator class="native"/></id>
+
+ <property name="name" type="string" not-null="true"/>
+
+ <set name="nodes" inverse="true" cascade="persist,merge,refresh">
+ <key column="routeID"/>
+ <one-to-many class="Node"/>
+ </set>
+ </class>
+
+ <class name="Tour" table="HB_Tour">
+
+ <id name="tourID" type="long"><generator class="native"/></id>
+
+ <property name="name" type="string" not-null="true"/>
+
+ <set name="nodes" inverse="true" lazy="true" cascade="merge,refresh">
+ <key column="tourID"/>
+ <one-to-many class="Node"/>
+ </set>
+ </class>
+
+ <class name="Transport" table="HB_Transport">
+
+ <id name="transportID" type="long"><generator class="native"/></id>
+
+ <property name="name" type="string" not-null="true"/>
+
+ <many-to-one name="pickupNode"
+ column="pickupNodeID"
+ unique="true"
+ not-null="true"
+ cascade="merge,refresh"
+ lazy="false"/>
+
+ <many-to-one name="deliveryNode"
+ column="deliveryNodeID"
+ unique="true"
+ not-null="true"
+ cascade="merge,refresh"
+ lazy="false"/>
+ </class>
+
+ <class name="Node" table="HB_Node">
+
+ <id name="nodeID" type="long"><generator class="native"/></id>
+
+ <property name="name" type="string" not-null="true"/>
+
+ <set name="deliveryTransports" inverse="true" lazy="true" cascade="merge,refresh">
+ <key column="deliveryNodeID"/>
+ <one-to-many class="Transport"/>
+ </set>
+
+ <set name="pickupTransports" inverse="true" lazy="true" cascade="merge,refresh">
+ <key column="pickupNodeID"/>
+ <one-to-many class="Transport"/>
+ </set>
+
+ <many-to-one name="route"
+ column="routeID"
+ unique="false"
+ not-null="true"
+ cascade="none"
+ lazy="false"/>
+
+ <many-to-one name="tour"
+ column="tourID"
+ unique="false"
+ not-null="false"
+ cascade="merge,refresh"
+ lazy="false"/>
+ </class>
+
+</hibernate-mapping>
Added: core/trunk/testsuite/src/test/java/org/hibernate/test/cascade/circle/MultiPathCircleCascadeTest.java
===================================================================
--- core/trunk/testsuite/src/test/java/org/hibernate/test/cascade/circle/MultiPathCircleCascadeTest.java (rev 0)
+++ core/trunk/testsuite/src/test/java/org/hibernate/test/cascade/circle/MultiPathCircleCascadeTest.java 2009-05-14 20:27:19 UTC (rev 16569)
@@ -0,0 +1,459 @@
+//$Id: $
+/*
+ * Hibernate, Relational Persistence for Idiomatic Java
+ *
+ * Copyright (c) 2008, Red Hat Middleware LLC 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 Middleware LLC.
+ *
+ * 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 junit.framework.Test;
+
+import org.hibernate.Session;
+import org.hibernate.cfg.Configuration;
+import org.hibernate.cfg.Environment;
+import org.hibernate.junit.functional.FunctionalTestCase;
+import org.hibernate.junit.functional.FunctionalTestClassTestSuite;
+
+/**
+ * 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
+ *
+ * This tests that merge is cascaded properly from each entity.
+ *
+ * @author Pavol Zibrita, Gail Badner
+ */
+public class MultiPathCircleCascadeTest extends FunctionalTestCase {
+
+ public MultiPathCircleCascadeTest(String string) {
+ super(string);
+ }
+
+ public void configure(Configuration cfg) {
+ cfg.setProperty( Environment.GENERATE_STATISTICS, "true");
+ cfg.setProperty( Environment.STATEMENT_BATCH_SIZE, "0" );
+ }
+
+ public String[] getMappings() {
+ return new String[] {
+ "cascade/circle/MultiPathCircleCascade.hbm.xml"
+ };
+ }
+
+ public static Test suite() {
+ return new FunctionalTestClassTestSuite( MultiPathCircleCascadeTest.class );
+ }
+
+ 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" );
+ }
+
+ 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();
+ }
+
+ 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();
+ }
+
+ 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();
+ }
+
+ 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();
+ }
+
+ 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() );
+ }
+
+ 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 clearCounts() {
+ getSessions().getStatistics().clear();
+ }
+
+ protected void assertInsertCount(int expected) {
+ int inserts = ( int ) getSessions().getStatistics().getEntityInsertCount();
+ assertEquals( "unexpected insert count", expected, inserts );
+ }
+
+ protected void assertUpdateCount(int expected) {
+ int updates = ( int ) getSessions().getStatistics().getEntityUpdateCount();
+ assertEquals( "unexpected update counts", expected, updates );
+ }
+
+ protected void assertDeleteCount(int expected) {
+ int deletes = ( int ) getSessions().getStatistics().getEntityDeleteCount();
+ assertEquals( "unexpected delete counts", expected, deletes );
+ }
+}
Added: core/trunk/testsuite/src/test/java/org/hibernate/test/cascade/circle/Node.java
===================================================================
--- core/trunk/testsuite/src/test/java/org/hibernate/test/cascade/circle/Node.java (rev 0)
+++ core/trunk/testsuite/src/test/java/org/hibernate/test/cascade/circle/Node.java 2009-05-14 20:27:19 UTC (rev 16569)
@@ -0,0 +1,154 @@
+//$Id: $
+/*
+ * Hibernate, Relational Persistence for Idiomatic Java
+ *
+ * Copyright (c) 2008, Red Hat Middleware LLC 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 Middleware LLC.
+ *
+ * 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.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+import java.util.HashSet;
+
+public class Node {
+
+// @Id
+// @SequenceGenerator(name="NODE_SEQ", sequenceName="NODE_SEQ", initialValue=1, allocationSize=1)
+// @GeneratedValue(strategy=GenerationType.SEQUENCE, generator="NODE_SEQ")
+ private Long nodeID;
+
+ private long version;
+
+ private String name;
+
+ /** the list of orders that are delivered at this node */
+// @OneToMany(fetch=FetchType.LAZY, cascade={CascadeType.MERGE, CascadeType.REFRESH}, mappedBy="deliveryNode")
+ private Set deliveryTransports = new HashSet();
+
+ /** the list of orders that are picked up at this node */
+// @OneToMany(fetch=FetchType.LAZY, cascade=CascadeType.ALL, mappedBy="pickupNode")
+ private Set pickupTransports = new HashSet();
+
+ /** the route to which this node belongs */
+// @ManyToOne(targetEntity=Route.class, optional=false, fetch=FetchType.EAGER)
+// @JoinColumn(name="ROUTEID", nullable=false, insertable=true, updatable=true)
+ private Route route = null;
+
+ /** the tour this node belongs to, null if this node does not belong to a tour (e.g first node of a route) */
+// @ManyToOne(targetEntity=Tour.class, cascade={CascadeType.MERGE, CascadeType.PERSIST, CascadeType.REFRESH}, optional=true, fetch=FetchType.LAZY)
+// @JoinColumn(name="TOURID", nullable=true, insertable=true, updatable=true)
+ private Tour tour;
+
+// @Transient
+ private String transientField = "node original value";
+
+ public Set getDeliveryTransports() {
+ return deliveryTransports;
+ }
+
+ public void setDeliveryTransports(Set deliveryTransports) {
+ this.deliveryTransports = deliveryTransports;
+ }
+
+ public Set getPickupTransports() {
+ return pickupTransports;
+ }
+
+ public void setPickupTransports(Set pickupTransports) {
+ this.pickupTransports = pickupTransports;
+ }
+
+ public Long getNodeID() {
+ return nodeID;
+ }
+
+ public long getVersion() {
+ return version;
+ }
+
+ protected void setVersion(long version) {
+ this.version = version;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public Route getRoute() {
+ return route;
+ }
+
+ public void setRoute(Route route) {
+ this.route = route;
+ }
+
+ public Tour getTour() {
+ return tour;
+ }
+
+ public void setTour(Tour tour) {
+ this.tour = tour;
+ }
+
+ public String toString()
+ {
+ StringBuffer buffer = new StringBuffer();
+
+ buffer.append( name + " id: " + nodeID );
+ if ( route != null ) {
+ buffer.append( " route name: " ).append( route.getName() ).append( " tour name: " ).append( tour.getName() );
+ }
+ if ( pickupTransports != null ) {
+ for (Iterator it = pickupTransports.iterator(); it.hasNext();) {
+ buffer.append("Pickup transports: " + it.next());
+ }
+ }
+
+ if ( deliveryTransports != null ) {
+ for (Iterator it = deliveryTransports.iterator(); it.hasNext();) {
+ buffer.append("Delviery transports: " + it.next());
+ }
+ }
+
+ return buffer.toString();
+ }
+
+ public String getTransientField() {
+ return transientField;
+ }
+
+ public void setTransientField(String transientField) {
+ this.transientField = transientField;
+ }
+
+ protected void setNodeID(Long nodeID) {
+ this.nodeID = nodeID;
+ }
+
+}
Added: core/trunk/testsuite/src/test/java/org/hibernate/test/cascade/circle/Route.java
===================================================================
--- core/trunk/testsuite/src/test/java/org/hibernate/test/cascade/circle/Route.java (rev 0)
+++ core/trunk/testsuite/src/test/java/org/hibernate/test/cascade/circle/Route.java 2009-05-14 20:27:19 UTC (rev 16569)
@@ -0,0 +1,119 @@
+//$Id: $
+/*
+ * Hibernate, Relational Persistence for Idiomatic Java
+ *
+ * Copyright (c) 2008, Red Hat Middleware LLC 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 Middleware LLC.
+ *
+ * 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.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+import java.util.HashSet;
+
+
+public class Route {
+
+// @Id
+// @SequenceGenerator(name="ROUTE_SEQ", sequenceName="ROUTE_SEQ", initialValue=1, allocationSize=1)
+// @GeneratedValue(strategy=GenerationType.SEQUENCE, generator="ROUTE_SEQ")
+ private Long routeID;
+
+ private long version;
+
+ /** A List of nodes contained in this route. */
+// @OneToMany(targetEntity=Node.class, fetch=FetchType.EAGER, cascade=CascadeType.ALL, mappedBy="route")
+ private Set nodes = new HashSet();
+
+ private Set vehicles = new HashSet();
+
+ private String name;
+
+// @Transient
+ private String transientField = null;
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ protected Set getNodes() {
+ return nodes;
+ }
+
+ protected void setNodes(Set nodes) {
+ this.nodes = nodes;
+ }
+
+ protected Set getVehicles() {
+ return vehicles;
+ }
+
+ protected void setVehicles(Set vehicles) {
+ this.vehicles = vehicles;
+ }
+
+ protected void setRouteID(Long routeID) {
+ this.routeID = routeID;
+ }
+
+ public Long getRouteID() {
+ return routeID;
+ }
+
+ public long getVersion() {
+ return version;
+ }
+
+ protected void setVersion(long version) {
+ this.version = version;
+ }
+
+ public String toString()
+ {
+ StringBuffer buffer = new StringBuffer();
+
+ buffer.append("Route name: " + name + " id: " + routeID + " transientField: " + transientField + "\n");
+ for (Iterator it = nodes.iterator(); it.hasNext();) {
+ buffer.append("Node: " + (Node)it.next());
+ }
+
+ for (Iterator it = vehicles.iterator(); it.hasNext();) {
+ buffer.append("Vehicle: " + (Vehicle)it.next());
+ }
+
+ return buffer.toString();
+ }
+
+ public String getTransientField() {
+ return transientField;
+ }
+
+ public void setTransientField(String transientField) {
+ this.transientField = transientField;
+ }
+}
Added: core/trunk/testsuite/src/test/java/org/hibernate/test/cascade/circle/Tour.java
===================================================================
--- core/trunk/testsuite/src/test/java/org/hibernate/test/cascade/circle/Tour.java (rev 0)
+++ core/trunk/testsuite/src/test/java/org/hibernate/test/cascade/circle/Tour.java 2009-05-14 20:27:19 UTC (rev 16569)
@@ -0,0 +1,80 @@
+//$Id: $
+/*
+ * Hibernate, Relational Persistence for Idiomatic Java
+ *
+ * Copyright (c) 2008, Red Hat Middleware LLC 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 Middleware LLC.
+ *
+ * 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.ArrayList;
+import java.util.List;
+import java.util.Set;
+import java.util.HashSet;
+
+
+public class Tour {
+
+// @Id
+// @SequenceGenerator(name="TOUR_SEQ", sequenceName="TOUR_SEQ", initialValue=1, allocationSize=1)
+// @GeneratedValue(strategy=GenerationType.SEQUENCE, generator="TOUR_SEQ")
+ private Long tourID;
+
+ private long version;
+
+ private String name;
+
+ /** A List of nodes contained in this tour. */
+// @OneToMany(targetEntity=Node.class, fetch=FetchType.LAZY, cascade={CascadeType.MERGE, CascadeType.REFRESH}, mappedBy="tour")
+ private Set nodes = new HashSet(0);
+
+ public String getName() {
+ return name;
+ }
+
+ protected void setTourID(Long tourID) {
+ this.tourID = tourID;
+ }
+
+ public long getVersion() {
+ return version;
+ }
+
+ protected void setVersion(long version) {
+ this.version = version;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public Set getNodes() {
+ return nodes;
+ }
+
+ public void setNodes(Set nodes) {
+ this.nodes = nodes;
+ }
+
+ public Long getTourID() {
+ return tourID;
+ }
+}
Added: core/trunk/testsuite/src/test/java/org/hibernate/test/cascade/circle/Transport.java
===================================================================
--- core/trunk/testsuite/src/test/java/org/hibernate/test/cascade/circle/Transport.java (rev 0)
+++ core/trunk/testsuite/src/test/java/org/hibernate/test/cascade/circle/Transport.java 2009-05-14 20:27:19 UTC (rev 16569)
@@ -0,0 +1,120 @@
+//$Id: $
+/*
+ * Hibernate, Relational Persistence for Idiomatic Java
+ *
+ * Copyright (c) 2008, Red Hat Middleware LLC 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 Middleware LLC.
+ *
+ * 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;
+
+
+public class Transport {
+
+// @Id
+// @SequenceGenerator(name="TRANSPORT_SEQ", sequenceName="TRANSPORT_SEQ", initialValue=1, allocationSize=1)
+// @GeneratedValue(strategy=GenerationType.SEQUENCE, generator="TRANSPORT_SEQ")
+ private Long transportID;
+
+ private long version;
+
+ private String name;
+
+ /** node value object at which the order is picked up */
+// @ManyToOne(optional=false, cascade={CascadeType.MERGE, CascadeType.PERSIST, CascadeType.REFRESH}, fetch=FetchType.EAGER)
+// @JoinColumn(name="PICKUPNODEID", /*nullable=false,*/insertable=true, updatable=true)
+ private Node pickupNode = null;
+
+ /** node value object at which the order is delivered */
+// @ManyToOne(optional=false, cascade={CascadeType.MERGE, CascadeType.PERSIST, CascadeType.REFRESH}, fetch=FetchType.EAGER)
+// @JoinColumn(name="DELIVERYNODEID", /*nullable=false,*/ insertable=true, updatable=true)
+ private Node deliveryNode = null;
+
+ private Vehicle vehicle;
+
+// @Transient
+ private String transientField = "transport original value";
+
+ public Node getDeliveryNode() {
+ return deliveryNode;
+ }
+
+ public void setDeliveryNode(Node deliveryNode) {
+ this.deliveryNode = deliveryNode;
+ }
+
+ public Node getPickupNode() {
+ return pickupNode;
+ }
+
+ protected void setTransportID(Long transportID) {
+ this.transportID = transportID;
+ }
+
+ public void setPickupNode(Node pickupNode) {
+ this.pickupNode = pickupNode;
+ }
+
+ public Vehicle getVehicle() {
+ return vehicle;
+ }
+
+ public void setVehicle(Vehicle vehicle) {
+ this.vehicle = vehicle;
+ }
+
+ public Long getTransportID() {
+ return transportID;
+ }
+
+ public long getVersion() {
+ return version;
+ }
+
+ protected void setVersion(long version) {
+ this.version = version;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public String toString()
+ {
+ StringBuffer buffer = new StringBuffer();
+
+ buffer.append(name + " id: " + transportID + "\n");
+
+ return buffer.toString();
+ }
+
+ public String getTransientField() {
+ return transientField;
+ }
+
+ public void setTransientField(String transientField) {
+ this.transientField = transientField;
+ }
+}
Added: core/trunk/testsuite/src/test/java/org/hibernate/test/cascade/circle/Vehicle.java
===================================================================
--- core/trunk/testsuite/src/test/java/org/hibernate/test/cascade/circle/Vehicle.java (rev 0)
+++ core/trunk/testsuite/src/test/java/org/hibernate/test/cascade/circle/Vehicle.java 2009-05-14 20:27:19 UTC (rev 16569)
@@ -0,0 +1,106 @@
+//$Id: $
+/*
+ * Hibernate, Relational Persistence for Idiomatic Java
+ *
+ * Copyright (c) 2008, Red Hat Middleware LLC 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 Middleware LLC.
+ *
+ * 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.Set;
+import java.util.HashSet;
+
+
+public class Vehicle {
+
+// @Id
+// @SequenceGenerator(name="TRANSPORT_SEQ", sequenceName="TRANSPORT_SEQ", initialValue=1, allocationSize=1)
+// @GeneratedValue(strategy=GenerationType.SEQUENCE, generator="TRANSPORT_SEQ")
+ private Long vehicleID;
+
+ private long version;
+
+ private String name;
+
+ private Set transports = new HashSet();
+
+ private Route route;
+
+ private String transientField = "vehicle original value";
+
+ protected void setVehicleID(Long vehicleID) {
+ this.vehicleID = vehicleID;
+ }
+
+ public Long getVehicleID() {
+ return vehicleID;
+ }
+
+ public long getVersion() {
+ return version;
+ }
+
+ protected void setVersion(long version) {
+ this.version = version;
+ }
+
+ public Set getTransports() {
+ return transports;
+ }
+
+ public void setTransports(Set transports) {
+ this.transports = transports;
+ }
+
+ public Route getRoute() {
+ return route;
+ }
+
+ public void setRoute(Route route) {
+ this.route = route;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public String toString()
+ {
+ StringBuffer buffer = new StringBuffer();
+
+ buffer.append(name + " id: " + vehicleID + "\n");
+
+ return buffer.toString();
+ }
+
+ public String getTransientField() {
+ return transientField;
+ }
+
+ public void setTransientField(String transientField) {
+ this.transientField = transientField;
+ }
+}
\ No newline at end of file
Modified: core/trunk/testsuite/src/test/java/org/hibernate/test/ops/MergeTest.java
===================================================================
--- core/trunk/testsuite/src/test/java/org/hibernate/test/ops/MergeTest.java 2009-05-14 19:57:00 UTC (rev 16568)
+++ core/trunk/testsuite/src/test/java/org/hibernate/test/ops/MergeTest.java 2009-05-14 20:27:19 UTC (rev 16569)
@@ -187,7 +187,7 @@
// as a control measure, now update the node while it is detached and
// make sure we get an update as a result...
( ( Node ) parent.getChildren().iterator().next() ).setDescription( "child's new description" );
- parent.getChildren().add( new Node( "second child" ) );
+ parent.addChild( new Node( "second child" ) );
s = openSession();
s.beginTransaction();
parent = ( Node ) s.merge( parent );
15 years, 6 months
Hibernate SVN: r16568 - in core/branches/Branch_3_3: testsuite/src/test/java/org/hibernate/test/cascade and 2 other directories.
by hibernate-commits@lists.jboss.org
Author: gbadner
Date: 2009-05-14 15:57:00 -0400 (Thu, 14 May 2009)
New Revision: 16568
Added:
core/branches/Branch_3_3/core/src/main/java/org/hibernate/event/def/CopyCache.java
core/branches/Branch_3_3/testsuite/src/test/java/org/hibernate/test/cascade/circle/
core/branches/Branch_3_3/testsuite/src/test/java/org/hibernate/test/cascade/circle/CascadeMergeToChildBeforeParent.hbm.xml
core/branches/Branch_3_3/testsuite/src/test/java/org/hibernate/test/cascade/circle/CascadeMergeToChildBeforeParentTest.java
core/branches/Branch_3_3/testsuite/src/test/java/org/hibernate/test/cascade/circle/MultiPathCircleCascade.hbm.xml
core/branches/Branch_3_3/testsuite/src/test/java/org/hibernate/test/cascade/circle/MultiPathCircleCascadeTest.java
core/branches/Branch_3_3/testsuite/src/test/java/org/hibernate/test/cascade/circle/Node.java
core/branches/Branch_3_3/testsuite/src/test/java/org/hibernate/test/cascade/circle/Route.java
core/branches/Branch_3_3/testsuite/src/test/java/org/hibernate/test/cascade/circle/Tour.java
core/branches/Branch_3_3/testsuite/src/test/java/org/hibernate/test/cascade/circle/Transport.java
core/branches/Branch_3_3/testsuite/src/test/java/org/hibernate/test/cascade/circle/Vehicle.java
Modified:
core/branches/Branch_3_3/core/src/main/java/org/hibernate/event/def/DefaultMergeEventListener.java
core/branches/Branch_3_3/testsuite/src/test/java/org/hibernate/test/cascade/A.java
core/branches/Branch_3_3/testsuite/src/test/java/org/hibernate/test/cascade/G.java
core/branches/Branch_3_3/testsuite/src/test/java/org/hibernate/test/cascade/H.java
core/branches/Branch_3_3/testsuite/src/test/java/org/hibernate/test/cascade/MultiPathCascadeTest.java
core/branches/Branch_3_3/testsuite/src/test/java/org/hibernate/test/ops/MergeTest.java
Log:
HHH-3810 : Transient entities can be inserted twice on merge
Added: core/branches/Branch_3_3/core/src/main/java/org/hibernate/event/def/CopyCache.java
===================================================================
--- core/branches/Branch_3_3/core/src/main/java/org/hibernate/event/def/CopyCache.java (rev 0)
+++ core/branches/Branch_3_3/core/src/main/java/org/hibernate/event/def/CopyCache.java 2009-05-14 19:57:00 UTC (rev 16568)
@@ -0,0 +1,244 @@
+//$Id: $
+/*
+ * Hibernate, Relational Persistence for Idiomatic Java
+ *
+ * Copyright (c) 2008, Red Hat Middleware LLC 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 Middleware LLC.
+ *
+ * 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.event.def;
+
+import java.util.Map;
+import java.util.Set;
+import java.util.Iterator;
+import java.util.Collection;
+
+import org.hibernate.util.IdentityMap;
+import org.hibernate.AssertionFailure;
+
+/**
+ * CopyCache is intended to be the Map implementation used by
+ * {@link DefaultMergeEventListener} to keep track of entities and their copies
+ * being merged into the session. This implementation also tracks whether a
+ * an entity in the CopyCache is included in the merge. This allows a
+ * an entity and its copy to be added to a CopyCache before merge has cascaded
+ * to that entity.
+ *
+ * @author Gail Badner
+ */
+class CopyCache implements Map {
+ private Map entityToCopyMap = IdentityMap.instantiate(10);
+ // key is an entity involved with the merge;
+ // value can be either a copy of the entity or the entity itself
+
+ private Map entityToIncludeInMergeFlagMap = IdentityMap.instantiate(10);
+ // key is an entity involved with the merge;
+ // value is a flag indicating if the entity is included in the merge
+
+ /**
+ * Clears the CopyCache.
+ */
+ public void clear() {
+ entityToCopyMap.clear();
+ entityToIncludeInMergeFlagMap.clear();
+ }
+
+ /**
+ * Returns true if this CopyCache contains a mapping for the specified entity.
+ * @param entity must be non-null
+ * @return true if this CopyCache contains a mapping for the specified entity
+ * @throws NullPointerException if entity is null
+ */
+ public boolean containsKey(Object entity) {
+ if ( entity == null ) {
+ throw new NullPointerException( "null entities are not supported by " + getClass().getName() );
+ }
+ return entityToCopyMap.containsKey( entity );
+ }
+
+ /**
+ * Returns true if this CopyCache maps one or more entities to the specified copy.
+ * @param copy must be non-null
+ * @return true if this CopyCache maps one or more entities to the specified copy
+ * @throws NullPointerException if copy is null
+ */
+ public boolean containsValue(Object copy) {
+ if ( copy == null ) {
+ throw new NullPointerException( "null copies are not supported by " + getClass().getName() );
+ }
+ return entityToCopyMap.containsValue( copy );
+ }
+
+ /**
+ * Returns a set view of the entity-to-copy mappings contained in this CopyCache.
+ * @return set view of the entity-to-copy mappings contained in this CopyCache
+ */
+ public Set entrySet() {
+ return entityToCopyMap.entrySet();
+ }
+
+ /**
+ * Returns the copy to which this CopyCache maps the specified entity.
+ * @param entity must be non-null
+ * @return the copy to which this CopyCache maps the specified entity
+ * @throws NullPointerException if entity is null
+ */
+ public Object get(Object entity) {
+ if ( entity == null ) {
+ throw new NullPointerException( "null entities are not supported by " + getClass().getName() );
+ }
+ return entityToCopyMap.get( entity );
+ }
+
+ /**
+ * Returns true if this CopyCache contains no entity-copy mappings.
+ * @return true if this CopyCache contains no entity-copy mappings
+ */
+ public boolean isEmpty() {
+ return entityToCopyMap.isEmpty();
+ }
+
+ /**
+ * Returns a set view of the entities contained in this CopyCache
+ * @return a set view of the entities contained in this CopyCache
+ */
+ public Set keySet() {
+ return entityToCopyMap.keySet();
+ }
+
+ /**
+ * Associates the specified entity with the specified copy in this CopyCache;
+ * @param entity must be non-null
+ * @param copy must be non- null
+ * @return previous copy associated with specified entity, or null if
+ * there was no mapping for entity.
+ * @throws NullPointerException if entity or copy is null
+ */
+ public Object put(Object entity, Object copy) {
+ if ( entity == null || copy == null ) {
+ throw new NullPointerException( "null entities and copies are not supported by " + getClass().getName() );
+ }
+ entityToIncludeInMergeFlagMap.put( entity, Boolean.FALSE );
+ return entityToCopyMap.put( entity, copy );
+ }
+
+ /**
+ * Associates the specified entity with the specified copy in this CopyCache;
+ * @param entity must be non-null
+ * @param copy must be non- null
+ * @param isIncludedInMerge indicates if the entity is included in merge
+ *
+ * @return previous copy associated with specified entity, or null if
+ * there was no mapping for entity.
+ * @throws NullPointerException if entity or copy is null
+ */
+ /* package-private */ Object put(Object entity, Object copy, boolean isIncludedInMerge) {
+ if ( entity == null || copy == null ) {
+ throw new NullPointerException( "null entities and copies are not supported by " + getClass().getName() );
+ }
+ entityToIncludeInMergeFlagMap.put( entity, Boolean.valueOf( isIncludedInMerge ) );
+ return entityToCopyMap.put( entity, copy );
+ }
+
+ /**
+ * Copies all of the mappings from the specified map to this CopyCache
+ * @param map keys and values must be non-null
+ * @throws NullPointerException if any map keys or values are null
+ */
+ public void putAll(Map map) {
+ for ( Iterator it=map.entrySet().iterator(); it.hasNext(); ) {
+ Map.Entry entry = ( Map.Entry ) it.next();
+ if ( entry.getKey() == null || entry.getValue() == null ) {
+ throw new NullPointerException( "null entities and copies are not supported by " + getClass().getName() );
+ }
+ entityToCopyMap.put( entry.getKey(), entry.getValue() );
+ entityToIncludeInMergeFlagMap.put( entry.getKey(), Boolean.FALSE );
+ }
+ }
+
+ /**
+ * Removes the mapping for this entity from this CopyCache if it is present
+ * @param entity must be non-null
+ * @return previous value associated with specified entity, or null if there was no mapping for entity.
+ * @throws NullPointerException if entity is null
+ */
+ public Object remove(Object entity) {
+ if ( entity == null ) {
+ throw new NullPointerException( "null entities are not supported by " + getClass().getName() );
+ }
+ entityToIncludeInMergeFlagMap.remove( entity );
+ return entityToCopyMap.remove( entity );
+ }
+
+ /**
+ * Returns the number of entity-copy mappings in this CopyCache
+ * @return the number of entity-copy mappings in this CopyCache
+ */
+ public int size() {
+ return entityToCopyMap.size();
+ }
+
+ /**
+ * Returns a collection view of the entity copies contained in this CopyCache.
+ * @return a collection view of the entity copies contained in this CopyCache
+ */
+ public Collection values() {
+ return entityToCopyMap.values();
+ }
+
+ /**
+ * Returns true if the specified entity is included in the merge.
+ * @param entity must be non-null
+ * @return true if the specified entity is included in the merge.
+ * @throws NullPointerException if entity is null
+ */
+ public boolean isIncludedInMerge(Object entity) {
+ if ( entity == null ) {
+ throw new NullPointerException( "null entities are not supported by " + getClass().getName() );
+ }
+ return ( ( Boolean ) entityToIncludeInMergeFlagMap.get( entity ) ).booleanValue();
+ }
+
+ /**
+ * Set flag to indicate if an entity is included in the merge.
+ * @param entity must be non-null and this CopyCache must contain a mapping for this entity
+ * @return true if the specified entity is included in the merge
+ * @throws NullPointerException if entity is null
+ * @throws AssertionFailure if this CopyCache does not contain a mapping for the specified entity
+ */
+ /* package-private */ void setIncludedInMerge(Object entity, boolean isIncludedInMerge) {
+ if ( entity == null ) {
+ throw new NullPointerException( "null entities are not supported by " + getClass().getName() );
+ }
+ if ( ! entityToIncludeInMergeFlagMap.containsKey( entity ) ||
+ ! entityToCopyMap.containsKey( entity ) ) {
+ throw new AssertionFailure( "called CopyCache.setInMergeProcess() for entity not found in CopyCache" );
+ }
+ entityToIncludeInMergeFlagMap.put( entity, Boolean.valueOf( isIncludedInMerge ) );
+ }
+
+ /**
+ * Returns the copy-entity mappings
+ * @return the copy-entity mappings
+ */
+ public Map getMergeMap() {
+ return IdentityMap.invert( entityToCopyMap );
+ }
+}
\ No newline at end of file
Modified: core/branches/Branch_3_3/core/src/main/java/org/hibernate/event/def/DefaultMergeEventListener.java
===================================================================
--- core/branches/Branch_3_3/core/src/main/java/org/hibernate/event/def/DefaultMergeEventListener.java 2009-05-14 15:40:17 UTC (rev 16567)
+++ core/branches/Branch_3_3/core/src/main/java/org/hibernate/event/def/DefaultMergeEventListener.java 2009-05-14 19:57:00 UTC (rev 16568)
@@ -27,6 +27,8 @@
import java.io.Serializable;
import java.util.Iterator;
import java.util.Map;
+import java.util.Set;
+import java.util.HashSet;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -37,6 +39,7 @@
import org.hibernate.StaleObjectStateException;
import org.hibernate.TransientObjectException;
import org.hibernate.WrongClassException;
+import org.hibernate.PropertyValueException;
import org.hibernate.engine.Cascade;
import org.hibernate.engine.CascadingAction;
import org.hibernate.engine.EntityEntry;
@@ -53,7 +56,7 @@
import org.hibernate.proxy.LazyInitializer;
import org.hibernate.type.ForeignKeyDirection;
import org.hibernate.type.TypeFactory;
-import org.hibernate.util.IdentityMap;
+import org.hibernate.type.Type;
/**
* Defines the default copy event listener used by hibernate for copying entities
@@ -65,9 +68,9 @@
implements MergeEventListener {
private static final Logger log = LoggerFactory.getLogger(DefaultMergeEventListener.class);
-
+
protected Map getMergeMap(Object anything) {
- return IdentityMap.invert( (Map) anything );
+ return ( ( CopyCache ) anything ).getMergeMap();
}
/**
@@ -77,36 +80,96 @@
* @throws HibernateException
*/
public void onMerge(MergeEvent event) throws HibernateException {
- Map copyCache = IdentityMap.instantiate(10);
+ CopyCache copyCache = new CopyCache();
onMerge( event, copyCache );
- for ( Iterator it=copyCache.values().iterator(); it.hasNext(); ) {
- Object entity = it.next();
- if ( entity instanceof HibernateProxy ) {
- entity = ( (HibernateProxy) entity ).getHibernateLazyInitializer().getImplementation();
+ // TODO: iteratively get transient entities and retry merge until one of the following conditions:
+ // 1) transientCopyCache.size() == 0
+ // 2) transientCopyCache.size() is not decreasing and copyCache.size() is not increasing
+ // TODO: find out if retrying can add entities to copyCache (don't think it can...)
+ // For now, just retry once; throw TransientObjectException if there are still any transient entities
+ Map transientCopyCache = getTransientCopyCache(event, copyCache );
+ if ( transientCopyCache.size() > 0 ) {
+ retryMergeTransientEntities( event, transientCopyCache, copyCache );
+ // find any entities that are still transient after retry
+ transientCopyCache = getTransientCopyCache(event, copyCache );
+ if ( transientCopyCache.size() > 0 ) {
+ Set transientEntityNames = new HashSet();
+ for( Iterator it=transientCopyCache.keySet().iterator(); it.hasNext(); ) {
+ Object transientEntity = it.next();
+ String transientEntityName = event.getSession().guessEntityName( transientEntity );
+ transientEntityNames.add( transientEntityName );
+ log.trace( "transient instance could not be processed by merge: " +
+ transientEntityName + "[" + transientEntity + "]" );
+ }
+ throw new TransientObjectException(
+ "one or more objects is an unsaved transient instance - save transient instance(s) before merging: " +
+ transientEntityNames );
}
- EntityEntry entry = event.getSession().getPersistenceContext().getEntry( entity );
- if ( entry == null ) {
+ }
+ copyCache.clear();
+ copyCache = null;
+ }
+
+ protected CopyCache getTransientCopyCache(MergeEvent event, CopyCache copyCache) {
+ CopyCache transientCopyCache = new CopyCache();
+ for ( Iterator it=copyCache.entrySet().iterator(); it.hasNext(); ) {
+ Map.Entry mapEntry = ( Map.Entry ) it.next();
+ Object entity = mapEntry.getKey();
+ Object copy = mapEntry.getValue();
+ if ( copy instanceof HibernateProxy ) {
+ copy = ( (HibernateProxy) copy ).getHibernateLazyInitializer().getImplementation();
+ }
+ EntityEntry copyEntry = event.getSession().getPersistenceContext().getEntry( copy );
+ if ( copyEntry == null ) {
+ // entity name will not be available for non-POJO entities
+ // TODO: cache the entity name somewhere so that it is available to this exception
+ log.trace( "transient instance could not be processed by merge: " +
+ event.getSession().guessEntityName( copy ) + "[" + entity + "]" );
throw new TransientObjectException(
- "object references an unsaved transient instance - save the transient instance before merging: " +
- event.getSession().guessEntityName( entity )
+ "object is an unsaved transient instance - save the transient instance before merging: " +
+ event.getSession().guessEntityName( copy )
);
- // TODO: cache the entity name somewhere so that it is available to this exception
- // entity name will not be available for non-POJO entities
}
- if ( entry.getStatus() != Status.MANAGED && entry.getStatus() != Status.READ_ONLY ) {
- throw new AssertionFailure( "Merged entity does not have status set to MANAGED or READ_ONLY; "+entry+" status="+entry.getStatus() );
+ else if ( copyEntry.getStatus() == Status.SAVING ) {
+ transientCopyCache.put( entity, copy, copyCache.isIncludedInMerge( entity ) );
}
+ else if ( copyEntry.getStatus() != Status.MANAGED && copyEntry.getStatus() != Status.READ_ONLY ) {
+ throw new AssertionFailure( "Merged entity does not have status set to MANAGED or READ_ONLY; "+copy+" status="+copyEntry.getStatus() );
+ }
}
+ return transientCopyCache;
}
+ protected void retryMergeTransientEntities(MergeEvent event, Map transientCopyCache, CopyCache copyCache) {
+ // TODO: The order in which entities are saved may matter (e.g., a particular transient entity
+ // may need to be saved before other transient entities can be saved;
+ // Keep retrying the batch of transient entities until either:
+ // 1) there are no transient entities left in transientCopyCache
+ // or 2) no transient entities were saved in the last batch
+ // For now, just run through the transient entities and retry the merge
+ for ( Iterator it=transientCopyCache.entrySet().iterator(); it.hasNext(); ) {
+ Map.Entry mapEntry = ( Map.Entry ) it.next();
+ Object entity = mapEntry.getKey();
+ Object copy = transientCopyCache.get( entity );
+ EntityEntry copyEntry = event.getSession().getPersistenceContext().getEntry( copy );
+ if ( entity == event.getEntity() ) {
+ mergeTransientEntity( entity, copyEntry.getEntityName(), event.getRequestedId(), event.getSession(), copyCache);
+ }
+ else {
+ mergeTransientEntity( entity, copyEntry.getEntityName(), copyEntry.getId(), event.getSession(), copyCache);
+ }
+ }
+ }
+
/**
* Handle the given merge event.
*
* @param event The merge event to be handled.
* @throws HibernateException
*/
- public void onMerge(MergeEvent event, Map copyCache) throws HibernateException {
+ public void onMerge(MergeEvent event, Map copiedAlready) throws HibernateException {
+ final CopyCache copyCache = ( CopyCache ) copiedAlready;
final EventSource source = event.getSession();
final Object original = event.getOriginal();
@@ -127,13 +190,17 @@
else {
entity = original;
}
-
- if ( copyCache.containsKey(entity) &&
- source.getContextEntityIdentifier( copyCache.get( entity ) ) != null ) {
- log.trace("already merged");
- event.setResult(entity);
+
+ if ( copyCache.containsKey( entity ) &&
+ ( copyCache.isIncludedInMerge( entity ) ) ) {
+ log.trace("already in merge process");
+ event.setResult( entity );
}
else {
+ if ( copyCache.containsKey( entity ) ) {
+ log.trace("already in copyCache; setting in merge process");
+ copyCache.setIncludedInMerge( entity, true );
+ }
event.setEntity( entity );
int entityState = -1;
@@ -175,7 +242,7 @@
default: //DELETED
throw new ObjectDeletedException(
"deleted instance passed to merge",
- null,
+ null,
getLoggableName( event.getEntityName(), entity )
);
}
@@ -193,15 +260,15 @@
final Object entity = event.getEntity();
final EventSource source = event.getSession();
final EntityPersister persister = source.getEntityPersister( event.getEntityName(), entity );
+
+ ( ( CopyCache ) copyCache ).put( entity, entity, true ); //before cascade!
- copyCache.put(entity, entity); //before cascade!
-
cascadeOnMerge(source, persister, entity, copyCache);
copyValues(persister, entity, entity, source, copyCache);
event.setResult(entity);
}
-
+
protected void entityIsTransient(MergeEvent event, Map copyCache) {
log.trace("merging transient instance");
@@ -211,7 +278,16 @@
final EntityPersister persister = source.getEntityPersister( event.getEntityName(), entity );
final String entityName = persister.getEntityName();
-
+
+ event.setResult( mergeTransientEntity( entity, entityName, event.getRequestedId(), source, copyCache ) );
+ }
+
+ protected Object mergeTransientEntity(Object entity, String entityName, Serializable requestedId, EventSource source, Map copyCache) {
+
+ log.trace("merging transient instance");
+
+ final EntityPersister persister = source.getEntityPersister( entityName, entity );
+
final Serializable id = persister.hasIdentifierProperty() ?
persister.getIdentifier( entity, source.getEntityMode() ) :
null;
@@ -219,7 +295,7 @@
persister.setIdentifier( copyCache.get( entity ), id, source.getEntityMode() );
}
else {
- copyCache.put(entity, persister.instantiate( id, source.getEntityMode() ) ); //before cascade!
+ ( ( CopyCache ) copyCache ).put( entity, persister.instantiate( id, source.getEntityMode() ), true ); //before cascade!
//TODO: should this be Session.instantiate(Persister, ...)?
}
final Object copy = copyCache.get( entity );
@@ -229,25 +305,54 @@
//cascadeOnMerge(event, persister, entity, copyCache, Cascades.CASCADE_BEFORE_MERGE);
super.cascadeBeforeSave(source, persister, entity, copyCache);
copyValues(persister, entity, copy, source, copyCache, ForeignKeyDirection.FOREIGN_KEY_FROM_PARENT);
-
- //this bit is only *really* absolutely necessary for handling
- //requestedId, but is also good if we merge multiple object
- //graphs, since it helps ensure uniqueness
- final Serializable requestedId = event.getRequestedId();
- if (requestedId==null) {
- saveWithGeneratedId( copy, entityName, copyCache, source, false );
+
+ try {
+ //this bit is only *really* absolutely necessary for handling
+ //requestedId, but is also good if we merge multiple object
+ //graphs, since it helps ensure uniqueness
+ if (requestedId==null) {
+ saveWithGeneratedId( copy, entityName, copyCache, source, false );
+ }
+ else {
+ saveWithRequestedId( copy, requestedId, entityName, copyCache, source );
+ }
}
- else {
- saveWithRequestedId( copy, requestedId, entityName, copyCache, source );
+ catch (PropertyValueException ex) {
+ String propertyName = ex.getPropertyName();
+ Object propertyFromCopy = persister.getPropertyValue( copy, propertyName, source.getEntityMode() );
+ Object propertyFromEntity = persister.getPropertyValue( entity, propertyName, source.getEntityMode() );
+ Type propertyType = persister.getPropertyType( propertyName );
+ EntityEntry copyEntry = source.getPersistenceContext().getEntry( copy );
+ if ( propertyFromCopy == null || ! propertyType.isEntityType() ) {
+ log.trace( "property '" + copyEntry.getEntityName() + "." + propertyName +
+ "' is null or not an entity; " + propertyName + " =["+propertyFromCopy+"]");
+ throw ex;
+ }
+ if ( ! copyCache.containsKey( propertyFromEntity ) ) {
+ log.trace( "property '" + copyEntry.getEntityName() + "." + propertyName +
+ "' from original entity is not in copyCache; " + propertyName + " =["+propertyFromEntity+"]");
+ throw ex;
+ }
+ if ( ( ( CopyCache ) copyCache ).isIncludedInMerge( propertyFromEntity ) ) {
+ log.trace( "property '" + copyEntry.getEntityName() + "." + propertyName +
+ "' from original entity is in copyCache and is in the process of being merged; " +
+ propertyName + " =["+propertyFromEntity+"]");
+ }
+ else {
+ log.trace( "property '" + copyEntry.getEntityName() + "." + propertyName +
+ "' from original entity is in copyCache and is not in the process of being merged; " +
+ propertyName + " =["+propertyFromEntity+"]");
+ }
+ // continue...; we'll find out if it ends up not getting saved later
}
-
- // cascade first, so that all unsaved objects get their
+
+ // cascade first, so that all unsaved objects get their
// copy created before we actually copy
super.cascadeAfterSave(source, persister, entity, copyCache);
copyValues(persister, entity, copy, source, copyCache, ForeignKeyDirection.FOREIGN_KEY_TO_PARENT);
-
- event.setResult(copy);
+ return copy;
+
}
protected void entityIsDetached(MergeEvent event, Map copyCache) {
@@ -259,7 +364,7 @@
final EntityPersister persister = source.getEntityPersister( event.getEntityName(), entity );
final String entityName = persister.getEntityName();
-
+
Serializable id = event.getRequestedId();
if ( id == null ) {
id = persister.getIdentifier( entity, source.getEntityMode() );
@@ -292,7 +397,7 @@
entityIsTransient(event, copyCache);
}
else {
- copyCache.put(entity, result); //before cascade!
+ ( ( CopyCache ) copyCache ).put( entity, result, true ); //before cascade!
final Object target = source.getPersistenceContext().unproxy(result);
if ( target == entity ) {
@@ -380,7 +485,7 @@
return entry.isExistsInDatabase();
}
}
-
+
protected void copyValues(
final EntityPersister persister,
final Object entity,
@@ -388,7 +493,6 @@
final SessionImplementor source,
final Map copyCache
) {
-
final Object[] copiedValues = TypeFactory.replace(
persister.getPropertyValues( entity, source.getEntityMode() ),
persister.getPropertyValues( target, source.getEntityMode() ),
Modified: core/branches/Branch_3_3/testsuite/src/test/java/org/hibernate/test/cascade/A.java
===================================================================
--- core/branches/Branch_3_3/testsuite/src/test/java/org/hibernate/test/cascade/A.java 2009-05-14 15:40:17 UTC (rev 16567)
+++ core/branches/Branch_3_3/testsuite/src/test/java/org/hibernate/test/cascade/A.java 2009-05-14 19:57:00 UTC (rev 16568)
@@ -1,4 +1,28 @@
-// $Id$
+//$Id: $
+/*
+ * Hibernate, Relational Persistence for Idiomatic Java
+ *
+ * Copyright (c) 2008, Red Hat Middleware LLC 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 Middleware LLC.
+ *
+ * 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;
Modified: core/branches/Branch_3_3/testsuite/src/test/java/org/hibernate/test/cascade/G.java
===================================================================
--- core/branches/Branch_3_3/testsuite/src/test/java/org/hibernate/test/cascade/G.java 2009-05-14 15:40:17 UTC (rev 16567)
+++ core/branches/Branch_3_3/testsuite/src/test/java/org/hibernate/test/cascade/G.java 2009-05-14 19:57:00 UTC (rev 16568)
@@ -1,3 +1,29 @@
+//$Id: $
+/*
+ * Hibernate, Relational Persistence for Idiomatic Java
+ *
+ * Copyright (c) 2008, Red Hat Middleware LLC 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 Middleware LLC.
+ *
+ * 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;
import java.util.Set;
Modified: core/branches/Branch_3_3/testsuite/src/test/java/org/hibernate/test/cascade/H.java
===================================================================
--- core/branches/Branch_3_3/testsuite/src/test/java/org/hibernate/test/cascade/H.java 2009-05-14 15:40:17 UTC (rev 16567)
+++ core/branches/Branch_3_3/testsuite/src/test/java/org/hibernate/test/cascade/H.java 2009-05-14 19:57:00 UTC (rev 16568)
@@ -1,6 +1,29 @@
-// $Id$
+//$Id: $
+/*
+ * Hibernate, Relational Persistence for Idiomatic Java
+ *
+ * Copyright (c) 2008, Red Hat Middleware LLC 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 Middleware LLC.
+ *
+ * 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;
import java.util.Set;
Modified: core/branches/Branch_3_3/testsuite/src/test/java/org/hibernate/test/cascade/MultiPathCascadeTest.java
===================================================================
--- core/branches/Branch_3_3/testsuite/src/test/java/org/hibernate/test/cascade/MultiPathCascadeTest.java 2009-05-14 15:40:17 UTC (rev 16567)
+++ core/branches/Branch_3_3/testsuite/src/test/java/org/hibernate/test/cascade/MultiPathCascadeTest.java 2009-05-14 19:57:00 UTC (rev 16568)
@@ -1,4 +1,28 @@
//$Id: $
+/*
+ * Hibernate, Relational Persistence for Idiomatic Java
+ *
+ * Copyright (c) 2008, Red Hat Middleware LLC 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 Middleware LLC.
+ *
+ * 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;
Added: core/branches/Branch_3_3/testsuite/src/test/java/org/hibernate/test/cascade/circle/CascadeMergeToChildBeforeParent.hbm.xml
===================================================================
--- core/branches/Branch_3_3/testsuite/src/test/java/org/hibernate/test/cascade/circle/CascadeMergeToChildBeforeParent.hbm.xml (rev 0)
+++ core/branches/Branch_3_3/testsuite/src/test/java/org/hibernate/test/cascade/circle/CascadeMergeToChildBeforeParent.hbm.xml 2009-05-14 19:57:00 UTC (rev 16568)
@@ -0,0 +1,115 @@
+<?xml version="1.0"?>
+<!DOCTYPE hibernate-mapping SYSTEM "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd" >
+
+<hibernate-mapping package="org.hibernate.test.cascade.circle">
+
+ <class name="Route" table="HB_Route">
+
+ <id name="routeID" type="long"><generator class="native"/></id>
+ <version name="version" column="VERS" type="long" />
+
+ <property name="name" type="string" not-null="true"/>
+
+ <set name="nodes" inverse="true" cascade="persist,merge,refresh">
+ <key column="routeID"/>
+ <one-to-many class="Node"/>
+ </set>
+ <set name="vehicles" inverse="true" cascade="persist,merge,refresh">
+ <key column="routeID"/>
+ <one-to-many class="Vehicle"/>
+ </set>
+ </class>
+
+ <class name="Tour" table="HB_Tour">
+
+ <id name="tourID" type="long"><generator class="native"/></id>
+ <version name="version" column="VERS" type="long" />
+
+ <property name="name" type="string" not-null="true"/>
+
+ <set name="nodes" inverse="true" lazy="true" cascade="merge,refresh">
+ <key column="tourID"/>
+ <one-to-many class="Node"/>
+ </set>
+ </class>
+
+ <class name="Transport" table="HB_Transport">
+
+ <id name="transportID" type="long"><generator class="native"/></id>
+ <version name="version" column="VERS" type="long" />
+
+ <property name="name" type="string" not-null="true"/>
+
+ <many-to-one name="pickupNode"
+ column="pickupNodeID"
+ unique="true"
+ not-null="true"
+ cascade="merge,refresh"
+ lazy="false"/>
+
+ <many-to-one name="deliveryNode"
+ column="deliveryNodeID"
+ unique="true"
+ not-null="true"
+ cascade="merge,refresh"
+ lazy="false"/>
+
+ <many-to-one name="vehicle"
+ column="vehicleID"
+ unique="false"
+ not-null="true"
+ cascade="none"
+ lazy="false"/>
+ </class>
+
+ <class name="Vehicle" table="HB_Vehicle">
+ <id name="vehicleID" type="long"><generator class="native"/></id>
+ <version name="version" column="VERS" type="long" />
+
+ <property name="name"/>
+ <set name="transports" inverse="false" lazy="true" cascade="merge,refresh">
+ <key column="vehicleID"/>
+ <one-to-many class="Transport" not-found="exception"/>
+ </set>
+ <many-to-one name="route"
+ column="routeID"
+ unique="false"
+ not-null="true"
+ cascade="none"
+ lazy="false"/>
+ </class>
+
+
+ <class name="Node" table="HB_Node">
+
+ <id name="nodeID" type="long"><generator class="native"/></id>
+ <version name="version" column="VERS" type="long" />
+
+ <property name="name" type="string" not-null="true"/>
+
+ <set name="deliveryTransports" inverse="true" lazy="true" cascade="merge,refresh">
+ <key column="deliveryNodeID"/>
+ <one-to-many class="Transport"/>
+ </set>
+
+ <set name="pickupTransports" inverse="true" lazy="true" cascade="merge,refresh">
+ <key column="pickupNodeID"/>
+ <one-to-many class="Transport"/>
+ </set>
+
+ <many-to-one name="route"
+ column="routeID"
+ unique="false"
+ not-null="true"
+ cascade="none"
+ lazy="false"/>
+
+ <many-to-one name="tour"
+ column="tourID"
+ unique="false"
+ not-null="false"
+ cascade="merge,refresh"
+ lazy="false"/>
+ </class>
+
+</hibernate-mapping>
Added: core/branches/Branch_3_3/testsuite/src/test/java/org/hibernate/test/cascade/circle/CascadeMergeToChildBeforeParentTest.java
===================================================================
--- core/branches/Branch_3_3/testsuite/src/test/java/org/hibernate/test/cascade/circle/CascadeMergeToChildBeforeParentTest.java (rev 0)
+++ core/branches/Branch_3_3/testsuite/src/test/java/org/hibernate/test/cascade/circle/CascadeMergeToChildBeforeParentTest.java 2009-05-14 19:57:00 UTC (rev 16568)
@@ -0,0 +1,288 @@
+//$Id: $
+/*
+ * Hibernate, Relational Persistence for Idiomatic Java
+ *
+ * Copyright (c) 2008, Red Hat Middleware LLC 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 Middleware LLC.
+ *
+ * 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 junit.framework.Test;
+
+import org.hibernate.Session;
+import org.hibernate.junit.functional.FunctionalTestCase;
+import org.hibernate.junit.functional.FunctionalTestClassTestSuite;
+
+/**
+ * The test case uses the following model:
+ *
+ * <- ->
+ * -- (N : 0,1) -- Tour
+ * | <- ->
+ * | -- (1 : N) -- (pickup) ----
+ * -> | | |
+ * Route -- (1 : N) - Node Transport
+ * | | <- -> | |
+ * | -- (1 : N) -- (delivery) -- |
+ * | |
+ * | -> -> |
+ * -------- (1 : N) ---- Vehicle--(1 : N)------------
+ *
+ * Arrows indicate the direction of cascade-merge.
+ *
+ * I believe it reproduces the following issue:
+ * http://opensource.atlassian.com/projects/hibernate/browse/HHH-3544
+ *
+ * @author Gail Badner (based on original model provided by Pavol Zibrita)
+ */
+public class CascadeMergeToChildBeforeParentTest extends FunctionalTestCase {
+
+ public CascadeMergeToChildBeforeParentTest(String string) {
+ super(string);
+ }
+
+ public String[] getMappings() {
+ return new String[] {
+ "cascade/circle/CascadeMergeToChildBeforeParent.hbm.xml"
+ };
+ }
+
+ public static Test suite() {
+ return new FunctionalTestClassTestSuite( CascadeMergeToChildBeforeParentTest.class );
+ }
+
+ 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" );
+ s.createQuery( "delete from Vehicle" );
+ }
+
+ public void testMerge()
+ {
+ Session s = openSession();
+ s.beginTransaction();
+
+ Route route = new Route();
+ route.setName("routeA");
+
+ s.save( route );
+ s.getTransaction().commit();
+ s.close();
+
+ s = openSession();
+ s.beginTransaction();
+
+ route = (Route) s.get(Route.class, new Long(1));
+
+ route.setTransientField(new String("sfnaouisrbn"));
+
+ Tour tour = new Tour();
+ tour.setName("tourB");
+
+ Node pickupNode = new Node();
+ pickupNode.setName("pickupNodeB");
+
+ Node deliveryNode = new Node();
+ deliveryNode.setName("deliveryNodeB");
+
+ pickupNode.setRoute(route);
+ pickupNode.setTour(tour);
+ pickupNode.setTransientField("pickup node aaaaaaaaaaa");
+
+ deliveryNode.setRoute(route);
+ deliveryNode.setTour(tour);
+ deliveryNode.setTransientField("delivery node aaaaaaaaa");
+
+ tour.getNodes().add(pickupNode);
+ tour.getNodes().add(deliveryNode);
+
+ route.getNodes().add(pickupNode);
+ route.getNodes().add(deliveryNode);
+
+ Route mergedRoute = (Route) s.merge(route);
+
+ s.getTransaction().commit();
+ s.close();
+ }
+
+ // This test fails because the merge algorithm tries to save a
+ // transient child (transport) before cascade-merge gets its
+ // transient parent (vehicle); merge does not cascade from the
+ // child to the parent.
+ public void testMergeTransientChildBeforeTransientParent()
+ {
+ Session s = openSession();
+ s.beginTransaction();
+
+ Route route = new Route();
+ route.setName("routeA");
+
+ s.save( route );
+ s.getTransaction().commit();
+ s.close();
+
+ s = openSession();
+ s.beginTransaction();
+
+ route = (Route) s.get(Route.class, new Long(1));
+
+ 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");
+
+ Vehicle vehicle = new Vehicle();
+ vehicle.setName("vehicleB");
+
+ 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);
+ route.getVehicles().add(vehicle);
+
+ transport.setPickupNode(pickupNode);
+ transport.setDeliveryNode(deliveryNode);
+ transport.setVehicle( vehicle );
+ transport.setTransientField("aaaaaaaaaaaaaa");
+
+ vehicle.getTransports().add(transport);
+ vehicle.setTransientField( "anewvalue" );
+ vehicle.setRoute( route );
+
+ Route mergedRoute = (Route) s.merge(route);
+
+ s.getTransaction().commit();
+ s.close();
+ }
+
+ public void testMergeData3Nodes()
+ {
+
+ Session s = openSession();
+ s.beginTransaction();
+
+ Route route = new Route();
+ route.setName("routeA");
+
+ s.save( route );
+ s.getTransaction().commit();
+ s.close();
+
+ s = openSession();
+ s.beginTransaction();
+
+ route = (Route) s.get(Route.class, new Long(1));
+
+ 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");
+
+ Vehicle vehicle = new Vehicle();
+ vehicle.setName("vehicleB");
+
+ 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);
+ route.getVehicles().add(vehicle);
+
+ transport1.setPickupNode(node1);
+ transport1.setDeliveryNode(node2);
+ transport1.setVehicle( vehicle );
+ transport1.setTransientField("aaaaaaaaaaaaaa");
+
+ transport2.setPickupNode(node2);
+ transport2.setDeliveryNode(node3);
+ transport2.setVehicle( vehicle );
+ transport2.setTransientField("bbbbbbbbbbbbb");
+
+ vehicle.getTransports().add(transport1);
+ vehicle.getTransports().add(transport2);
+ vehicle.setTransientField( "anewvalue" );
+ vehicle.setRoute( route );
+
+ Route mergedRoute = (Route) s.merge(route);
+
+ s.getTransaction().commit();
+ s.close();
+ }
+
+}
Added: core/branches/Branch_3_3/testsuite/src/test/java/org/hibernate/test/cascade/circle/MultiPathCircleCascade.hbm.xml
===================================================================
--- core/branches/Branch_3_3/testsuite/src/test/java/org/hibernate/test/cascade/circle/MultiPathCircleCascade.hbm.xml (rev 0)
+++ core/branches/Branch_3_3/testsuite/src/test/java/org/hibernate/test/cascade/circle/MultiPathCircleCascade.hbm.xml 2009-05-14 19:57:00 UTC (rev 16568)
@@ -0,0 +1,82 @@
+<?xml version="1.0"?>
+<!DOCTYPE hibernate-mapping SYSTEM "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd" >
+
+<hibernate-mapping package="org.hibernate.test.cascade.circle">
+
+ <class name="Route" table="HB_Route">
+
+ <id name="routeID" type="long"><generator class="native"/></id>
+
+ <property name="name" type="string" not-null="true"/>
+
+ <set name="nodes" inverse="true" cascade="persist,merge,refresh">
+ <key column="routeID"/>
+ <one-to-many class="Node"/>
+ </set>
+ </class>
+
+ <class name="Tour" table="HB_Tour">
+
+ <id name="tourID" type="long"><generator class="native"/></id>
+
+ <property name="name" type="string" not-null="true"/>
+
+ <set name="nodes" inverse="true" lazy="true" cascade="merge,refresh">
+ <key column="tourID"/>
+ <one-to-many class="Node"/>
+ </set>
+ </class>
+
+ <class name="Transport" table="HB_Transport">
+
+ <id name="transportID" type="long"><generator class="native"/></id>
+
+ <property name="name" type="string" not-null="true"/>
+
+ <many-to-one name="pickupNode"
+ column="pickupNodeID"
+ unique="true"
+ not-null="true"
+ cascade="merge,refresh"
+ lazy="false"/>
+
+ <many-to-one name="deliveryNode"
+ column="deliveryNodeID"
+ unique="true"
+ not-null="true"
+ cascade="merge,refresh"
+ lazy="false"/>
+ </class>
+
+ <class name="Node" table="HB_Node">
+
+ <id name="nodeID" type="long"><generator class="native"/></id>
+
+ <property name="name" type="string" not-null="true"/>
+
+ <set name="deliveryTransports" inverse="true" lazy="true" cascade="merge,refresh">
+ <key column="deliveryNodeID"/>
+ <one-to-many class="Transport"/>
+ </set>
+
+ <set name="pickupTransports" inverse="true" lazy="true" cascade="merge,refresh">
+ <key column="pickupNodeID"/>
+ <one-to-many class="Transport"/>
+ </set>
+
+ <many-to-one name="route"
+ column="routeID"
+ unique="false"
+ not-null="true"
+ cascade="none"
+ lazy="false"/>
+
+ <many-to-one name="tour"
+ column="tourID"
+ unique="false"
+ not-null="false"
+ cascade="merge,refresh"
+ lazy="false"/>
+ </class>
+
+</hibernate-mapping>
Added: core/branches/Branch_3_3/testsuite/src/test/java/org/hibernate/test/cascade/circle/MultiPathCircleCascadeTest.java
===================================================================
--- core/branches/Branch_3_3/testsuite/src/test/java/org/hibernate/test/cascade/circle/MultiPathCircleCascadeTest.java (rev 0)
+++ core/branches/Branch_3_3/testsuite/src/test/java/org/hibernate/test/cascade/circle/MultiPathCircleCascadeTest.java 2009-05-14 19:57:00 UTC (rev 16568)
@@ -0,0 +1,459 @@
+//$Id: $
+/*
+ * Hibernate, Relational Persistence for Idiomatic Java
+ *
+ * Copyright (c) 2008, Red Hat Middleware LLC 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 Middleware LLC.
+ *
+ * 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 junit.framework.Test;
+
+import org.hibernate.Session;
+import org.hibernate.cfg.Configuration;
+import org.hibernate.cfg.Environment;
+import org.hibernate.junit.functional.FunctionalTestCase;
+import org.hibernate.junit.functional.FunctionalTestClassTestSuite;
+
+/**
+ * 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
+ *
+ * This tests that merge is cascaded properly from each entity.
+ *
+ * @author Pavol Zibrita, Gail Badner
+ */
+public class MultiPathCircleCascadeTest extends FunctionalTestCase {
+
+ public MultiPathCircleCascadeTest(String string) {
+ super(string);
+ }
+
+ public void configure(Configuration cfg) {
+ cfg.setProperty( Environment.GENERATE_STATISTICS, "true");
+ cfg.setProperty( Environment.STATEMENT_BATCH_SIZE, "0" );
+ }
+
+ public String[] getMappings() {
+ return new String[] {
+ "cascade/circle/MultiPathCircleCascade.hbm.xml"
+ };
+ }
+
+ public static Test suite() {
+ return new FunctionalTestClassTestSuite( MultiPathCircleCascadeTest.class );
+ }
+
+ 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" );
+ }
+
+ 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();
+ }
+
+ 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();
+ }
+
+ 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();
+ }
+
+ 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();
+ }
+
+ 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() );
+ }
+
+ 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 clearCounts() {
+ getSessions().getStatistics().clear();
+ }
+
+ protected void assertInsertCount(int expected) {
+ int inserts = ( int ) getSessions().getStatistics().getEntityInsertCount();
+ assertEquals( "unexpected insert count", expected, inserts );
+ }
+
+ protected void assertUpdateCount(int expected) {
+ int updates = ( int ) getSessions().getStatistics().getEntityUpdateCount();
+ assertEquals( "unexpected update counts", expected, updates );
+ }
+
+ protected void assertDeleteCount(int expected) {
+ int deletes = ( int ) getSessions().getStatistics().getEntityDeleteCount();
+ assertEquals( "unexpected delete counts", expected, deletes );
+ }
+}
Added: core/branches/Branch_3_3/testsuite/src/test/java/org/hibernate/test/cascade/circle/Node.java
===================================================================
--- core/branches/Branch_3_3/testsuite/src/test/java/org/hibernate/test/cascade/circle/Node.java (rev 0)
+++ core/branches/Branch_3_3/testsuite/src/test/java/org/hibernate/test/cascade/circle/Node.java 2009-05-14 19:57:00 UTC (rev 16568)
@@ -0,0 +1,154 @@
+//$Id: $
+/*
+ * Hibernate, Relational Persistence for Idiomatic Java
+ *
+ * Copyright (c) 2008, Red Hat Middleware LLC 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 Middleware LLC.
+ *
+ * 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.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+import java.util.HashSet;
+
+public class Node {
+
+// @Id
+// @SequenceGenerator(name="NODE_SEQ", sequenceName="NODE_SEQ", initialValue=1, allocationSize=1)
+// @GeneratedValue(strategy=GenerationType.SEQUENCE, generator="NODE_SEQ")
+ private Long nodeID;
+
+ private long version;
+
+ private String name;
+
+ /** the list of orders that are delivered at this node */
+// @OneToMany(fetch=FetchType.LAZY, cascade={CascadeType.MERGE, CascadeType.REFRESH}, mappedBy="deliveryNode")
+ private Set deliveryTransports = new HashSet();
+
+ /** the list of orders that are picked up at this node */
+// @OneToMany(fetch=FetchType.LAZY, cascade=CascadeType.ALL, mappedBy="pickupNode")
+ private Set pickupTransports = new HashSet();
+
+ /** the route to which this node belongs */
+// @ManyToOne(targetEntity=Route.class, optional=false, fetch=FetchType.EAGER)
+// @JoinColumn(name="ROUTEID", nullable=false, insertable=true, updatable=true)
+ private Route route = null;
+
+ /** the tour this node belongs to, null if this node does not belong to a tour (e.g first node of a route) */
+// @ManyToOne(targetEntity=Tour.class, cascade={CascadeType.MERGE, CascadeType.PERSIST, CascadeType.REFRESH}, optional=true, fetch=FetchType.LAZY)
+// @JoinColumn(name="TOURID", nullable=true, insertable=true, updatable=true)
+ private Tour tour;
+
+// @Transient
+ private String transientField = "node original value";
+
+ public Set getDeliveryTransports() {
+ return deliveryTransports;
+ }
+
+ public void setDeliveryTransports(Set deliveryTransports) {
+ this.deliveryTransports = deliveryTransports;
+ }
+
+ public Set getPickupTransports() {
+ return pickupTransports;
+ }
+
+ public void setPickupTransports(Set pickupTransports) {
+ this.pickupTransports = pickupTransports;
+ }
+
+ public Long getNodeID() {
+ return nodeID;
+ }
+
+ public long getVersion() {
+ return version;
+ }
+
+ protected void setVersion(long version) {
+ this.version = version;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public Route getRoute() {
+ return route;
+ }
+
+ public void setRoute(Route route) {
+ this.route = route;
+ }
+
+ public Tour getTour() {
+ return tour;
+ }
+
+ public void setTour(Tour tour) {
+ this.tour = tour;
+ }
+
+ public String toString()
+ {
+ StringBuffer buffer = new StringBuffer();
+
+ buffer.append( name + " id: " + nodeID );
+ if ( route != null ) {
+ buffer.append( " route name: " ).append( route.getName() ).append( " tour name: " ).append( tour.getName() );
+ }
+ if ( pickupTransports != null ) {
+ for (Iterator it = pickupTransports.iterator(); it.hasNext();) {
+ buffer.append("Pickup transports: " + it.next());
+ }
+ }
+
+ if ( deliveryTransports != null ) {
+ for (Iterator it = deliveryTransports.iterator(); it.hasNext();) {
+ buffer.append("Delviery transports: " + it.next());
+ }
+ }
+
+ return buffer.toString();
+ }
+
+ public String getTransientField() {
+ return transientField;
+ }
+
+ public void setTransientField(String transientField) {
+ this.transientField = transientField;
+ }
+
+ protected void setNodeID(Long nodeID) {
+ this.nodeID = nodeID;
+ }
+
+}
Added: core/branches/Branch_3_3/testsuite/src/test/java/org/hibernate/test/cascade/circle/Route.java
===================================================================
--- core/branches/Branch_3_3/testsuite/src/test/java/org/hibernate/test/cascade/circle/Route.java (rev 0)
+++ core/branches/Branch_3_3/testsuite/src/test/java/org/hibernate/test/cascade/circle/Route.java 2009-05-14 19:57:00 UTC (rev 16568)
@@ -0,0 +1,119 @@
+//$Id: $
+/*
+ * Hibernate, Relational Persistence for Idiomatic Java
+ *
+ * Copyright (c) 2008, Red Hat Middleware LLC 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 Middleware LLC.
+ *
+ * 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.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+import java.util.HashSet;
+
+
+public class Route {
+
+// @Id
+// @SequenceGenerator(name="ROUTE_SEQ", sequenceName="ROUTE_SEQ", initialValue=1, allocationSize=1)
+// @GeneratedValue(strategy=GenerationType.SEQUENCE, generator="ROUTE_SEQ")
+ private Long routeID;
+
+ private long version;
+
+ /** A List of nodes contained in this route. */
+// @OneToMany(targetEntity=Node.class, fetch=FetchType.EAGER, cascade=CascadeType.ALL, mappedBy="route")
+ private Set nodes = new HashSet();
+
+ private Set vehicles = new HashSet();
+
+ private String name;
+
+// @Transient
+ private String transientField = null;
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ protected Set getNodes() {
+ return nodes;
+ }
+
+ protected void setNodes(Set nodes) {
+ this.nodes = nodes;
+ }
+
+ protected Set getVehicles() {
+ return vehicles;
+ }
+
+ protected void setVehicles(Set vehicles) {
+ this.vehicles = vehicles;
+ }
+
+ protected void setRouteID(Long routeID) {
+ this.routeID = routeID;
+ }
+
+ public Long getRouteID() {
+ return routeID;
+ }
+
+ public long getVersion() {
+ return version;
+ }
+
+ protected void setVersion(long version) {
+ this.version = version;
+ }
+
+ public String toString()
+ {
+ StringBuffer buffer = new StringBuffer();
+
+ buffer.append("Route name: " + name + " id: " + routeID + " transientField: " + transientField + "\n");
+ for (Iterator it = nodes.iterator(); it.hasNext();) {
+ buffer.append("Node: " + (Node)it.next());
+ }
+
+ for (Iterator it = vehicles.iterator(); it.hasNext();) {
+ buffer.append("Vehicle: " + (Vehicle)it.next());
+ }
+
+ return buffer.toString();
+ }
+
+ public String getTransientField() {
+ return transientField;
+ }
+
+ public void setTransientField(String transientField) {
+ this.transientField = transientField;
+ }
+}
Added: core/branches/Branch_3_3/testsuite/src/test/java/org/hibernate/test/cascade/circle/Tour.java
===================================================================
--- core/branches/Branch_3_3/testsuite/src/test/java/org/hibernate/test/cascade/circle/Tour.java (rev 0)
+++ core/branches/Branch_3_3/testsuite/src/test/java/org/hibernate/test/cascade/circle/Tour.java 2009-05-14 19:57:00 UTC (rev 16568)
@@ -0,0 +1,80 @@
+//$Id: $
+/*
+ * Hibernate, Relational Persistence for Idiomatic Java
+ *
+ * Copyright (c) 2008, Red Hat Middleware LLC 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 Middleware LLC.
+ *
+ * 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.ArrayList;
+import java.util.List;
+import java.util.Set;
+import java.util.HashSet;
+
+
+public class Tour {
+
+// @Id
+// @SequenceGenerator(name="TOUR_SEQ", sequenceName="TOUR_SEQ", initialValue=1, allocationSize=1)
+// @GeneratedValue(strategy=GenerationType.SEQUENCE, generator="TOUR_SEQ")
+ private Long tourID;
+
+ private long version;
+
+ private String name;
+
+ /** A List of nodes contained in this tour. */
+// @OneToMany(targetEntity=Node.class, fetch=FetchType.LAZY, cascade={CascadeType.MERGE, CascadeType.REFRESH}, mappedBy="tour")
+ private Set nodes = new HashSet(0);
+
+ public String getName() {
+ return name;
+ }
+
+ protected void setTourID(Long tourID) {
+ this.tourID = tourID;
+ }
+
+ public long getVersion() {
+ return version;
+ }
+
+ protected void setVersion(long version) {
+ this.version = version;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public Set getNodes() {
+ return nodes;
+ }
+
+ public void setNodes(Set nodes) {
+ this.nodes = nodes;
+ }
+
+ public Long getTourID() {
+ return tourID;
+ }
+}
Added: core/branches/Branch_3_3/testsuite/src/test/java/org/hibernate/test/cascade/circle/Transport.java
===================================================================
--- core/branches/Branch_3_3/testsuite/src/test/java/org/hibernate/test/cascade/circle/Transport.java (rev 0)
+++ core/branches/Branch_3_3/testsuite/src/test/java/org/hibernate/test/cascade/circle/Transport.java 2009-05-14 19:57:00 UTC (rev 16568)
@@ -0,0 +1,120 @@
+//$Id: $
+/*
+ * Hibernate, Relational Persistence for Idiomatic Java
+ *
+ * Copyright (c) 2008, Red Hat Middleware LLC 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 Middleware LLC.
+ *
+ * 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;
+
+
+public class Transport {
+
+// @Id
+// @SequenceGenerator(name="TRANSPORT_SEQ", sequenceName="TRANSPORT_SEQ", initialValue=1, allocationSize=1)
+// @GeneratedValue(strategy=GenerationType.SEQUENCE, generator="TRANSPORT_SEQ")
+ private Long transportID;
+
+ private long version;
+
+ private String name;
+
+ /** node value object at which the order is picked up */
+// @ManyToOne(optional=false, cascade={CascadeType.MERGE, CascadeType.PERSIST, CascadeType.REFRESH}, fetch=FetchType.EAGER)
+// @JoinColumn(name="PICKUPNODEID", /*nullable=false,*/insertable=true, updatable=true)
+ private Node pickupNode = null;
+
+ /** node value object at which the order is delivered */
+// @ManyToOne(optional=false, cascade={CascadeType.MERGE, CascadeType.PERSIST, CascadeType.REFRESH}, fetch=FetchType.EAGER)
+// @JoinColumn(name="DELIVERYNODEID", /*nullable=false,*/ insertable=true, updatable=true)
+ private Node deliveryNode = null;
+
+ private Vehicle vehicle;
+
+// @Transient
+ private String transientField = "transport original value";
+
+ public Node getDeliveryNode() {
+ return deliveryNode;
+ }
+
+ public void setDeliveryNode(Node deliveryNode) {
+ this.deliveryNode = deliveryNode;
+ }
+
+ public Node getPickupNode() {
+ return pickupNode;
+ }
+
+ protected void setTransportID(Long transportID) {
+ this.transportID = transportID;
+ }
+
+ public void setPickupNode(Node pickupNode) {
+ this.pickupNode = pickupNode;
+ }
+
+ public Vehicle getVehicle() {
+ return vehicle;
+ }
+
+ public void setVehicle(Vehicle vehicle) {
+ this.vehicle = vehicle;
+ }
+
+ public Long getTransportID() {
+ return transportID;
+ }
+
+ public long getVersion() {
+ return version;
+ }
+
+ protected void setVersion(long version) {
+ this.version = version;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public String toString()
+ {
+ StringBuffer buffer = new StringBuffer();
+
+ buffer.append(name + " id: " + transportID + "\n");
+
+ return buffer.toString();
+ }
+
+ public String getTransientField() {
+ return transientField;
+ }
+
+ public void setTransientField(String transientField) {
+ this.transientField = transientField;
+ }
+}
Added: core/branches/Branch_3_3/testsuite/src/test/java/org/hibernate/test/cascade/circle/Vehicle.java
===================================================================
--- core/branches/Branch_3_3/testsuite/src/test/java/org/hibernate/test/cascade/circle/Vehicle.java (rev 0)
+++ core/branches/Branch_3_3/testsuite/src/test/java/org/hibernate/test/cascade/circle/Vehicle.java 2009-05-14 19:57:00 UTC (rev 16568)
@@ -0,0 +1,106 @@
+//$Id: $
+/*
+ * Hibernate, Relational Persistence for Idiomatic Java
+ *
+ * Copyright (c) 2008, Red Hat Middleware LLC 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 Middleware LLC.
+ *
+ * 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.Set;
+import java.util.HashSet;
+
+
+public class Vehicle {
+
+// @Id
+// @SequenceGenerator(name="TRANSPORT_SEQ", sequenceName="TRANSPORT_SEQ", initialValue=1, allocationSize=1)
+// @GeneratedValue(strategy=GenerationType.SEQUENCE, generator="TRANSPORT_SEQ")
+ private Long vehicleID;
+
+ private long version;
+
+ private String name;
+
+ private Set transports = new HashSet();
+
+ private Route route;
+
+ private String transientField = "vehicle original value";
+
+ protected void setVehicleID(Long vehicleID) {
+ this.vehicleID = vehicleID;
+ }
+
+ public Long getVehicleID() {
+ return vehicleID;
+ }
+
+ public long getVersion() {
+ return version;
+ }
+
+ protected void setVersion(long version) {
+ this.version = version;
+ }
+
+ public Set getTransports() {
+ return transports;
+ }
+
+ public void setTransports(Set transports) {
+ this.transports = transports;
+ }
+
+ public Route getRoute() {
+ return route;
+ }
+
+ public void setRoute(Route route) {
+ this.route = route;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public String toString()
+ {
+ StringBuffer buffer = new StringBuffer();
+
+ buffer.append(name + " id: " + vehicleID + "\n");
+
+ return buffer.toString();
+ }
+
+ public String getTransientField() {
+ return transientField;
+ }
+
+ public void setTransientField(String transientField) {
+ this.transientField = transientField;
+ }
+}
\ No newline at end of file
Modified: core/branches/Branch_3_3/testsuite/src/test/java/org/hibernate/test/ops/MergeTest.java
===================================================================
--- core/branches/Branch_3_3/testsuite/src/test/java/org/hibernate/test/ops/MergeTest.java 2009-05-14 15:40:17 UTC (rev 16567)
+++ core/branches/Branch_3_3/testsuite/src/test/java/org/hibernate/test/ops/MergeTest.java 2009-05-14 19:57:00 UTC (rev 16568)
@@ -187,7 +187,7 @@
// as a control measure, now update the node while it is detached and
// make sure we get an update as a result...
( ( Node ) parent.getChildren().iterator().next() ).setDescription( "child's new description" );
- parent.getChildren().add( new Node( "second child" ) );
+ parent.addChild( new Node( "second child" ) );
s = openSession();
s.beginTransaction();
parent = ( Node ) s.merge( parent );
15 years, 6 months
Hibernate SVN: r16567 - in search/branches/Branch_3_1/src: main/java/org/hibernate/search/query and 2 other directories.
by hibernate-commits@lists.jboss.org
Author: hardy.ferentschik
Date: 2009-05-14 11:40:17 -0400 (Thu, 14 May 2009)
New Revision: 16567
Added:
search/branches/Branch_3_1/src/test/java/org/hibernate/search/test/query/criteria/
search/branches/Branch_3_1/src/test/java/org/hibernate/search/test/query/criteria/AbstractCar.java
search/branches/Branch_3_1/src/test/java/org/hibernate/search/test/query/criteria/Bike.java
search/branches/Branch_3_1/src/test/java/org/hibernate/search/test/query/criteria/CombiCar.java
search/branches/Branch_3_1/src/test/java/org/hibernate/search/test/query/criteria/MixedCriteriaTest.java
search/branches/Branch_3_1/src/test/java/org/hibernate/search/test/query/criteria/SportCar.java
Modified:
search/branches/Branch_3_1/src/main/java/org/hibernate/search/engine/ObjectLoaderHelper.java
search/branches/Branch_3_1/src/main/java/org/hibernate/search/query/FullTextQueryImpl.java
Log:
backported HSEARCH-360
Modified: search/branches/Branch_3_1/src/main/java/org/hibernate/search/engine/ObjectLoaderHelper.java
===================================================================
--- search/branches/Branch_3_1/src/main/java/org/hibernate/search/engine/ObjectLoaderHelper.java 2009-05-14 15:33:16 UTC (rev 16566)
+++ search/branches/Branch_3_1/src/main/java/org/hibernate/search/engine/ObjectLoaderHelper.java 2009-05-14 15:40:17 UTC (rev 16567)
@@ -4,6 +4,7 @@
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
+import java.util.Set;
import org.slf4j.Logger;
@@ -46,7 +47,8 @@
final int maxResults = entityInfos.length;
if ( maxResults == 0 ) return;
- DocumentBuilderIndexedEntity<?> builder = searchFactoryImplementor.getDocumentBuilderIndexedEntity( entityType );
+ Set<Class<?>> indexedEntities = searchFactoryImplementor.getIndexedTypesPolymorphic( new Class<?>[]{entityType} );
+ DocumentBuilderIndexedEntity<?> builder = searchFactoryImplementor.getDocumentBuilderIndexedEntity( indexedEntities.iterator().next() );
String idName = builder.getIdentifierName();
int loop = maxResults / MAX_IN_CLAUSE;
boolean exact = maxResults % MAX_IN_CLAUSE == 0;
Modified: search/branches/Branch_3_1/src/main/java/org/hibernate/search/query/FullTextQueryImpl.java
===================================================================
--- search/branches/Branch_3_1/src/main/java/org/hibernate/search/query/FullTextQueryImpl.java 2009-05-14 15:33:16 UTC (rev 16566)
+++ search/branches/Branch_3_1/src/main/java/org/hibernate/search/query/FullTextQueryImpl.java 2009-05-14 15:40:17 UTC (rev 16567)
@@ -4,6 +4,7 @@
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
@@ -68,12 +69,13 @@
*
* @author Emmanuel Bernard
* @author Hardy Ferentschik
+ * @todo Implements setParameter()
*/
-//TODO implements setParameter()
public class FullTextQueryImpl extends AbstractQueryImpl implements FullTextQuery {
private static final Logger log = LoggerFactory.make();
private final org.apache.lucene.search.Query luceneQuery;
- private Set<Class<?>> targetedEntities;
+ private Set<Class<?>> indexedTargetedEntities;
+ private List<Class<?>> targetedEntities;
private Set<Class<?>> classesAndSubclasses;
//optimization: if we can avoid the filter clause (we can most of the time) do it as it has a significant perf impact
private boolean needClassFilterClause;
@@ -99,13 +101,15 @@
* @param session Access to the Hibernate session.
* @param parameterMetadata Additional query metadata.
*/
- public FullTextQueryImpl(org.apache.lucene.search.Query query, Class[] classes, SessionImplementor session,
+ public FullTextQueryImpl(org.apache.lucene.search.Query query, Class<?>[] classes, SessionImplementor session,
ParameterMetadata parameterMetadata) {
//TODO handle flushMode
super( query.toString(), null, session, parameterMetadata );
this.luceneQuery = query;
- this.targetedEntities = getSearchFactoryImplementor().getIndexedTypesPolymorphic( classes );
- if ( classes != null && classes.length > 0 && targetedEntities.size() == 0 ) {
+ this.targetedEntities = Arrays.asList( classes );
+ searchFactoryImplementor = getSearchFactoryImplementor();
+ this.indexedTargetedEntities = searchFactoryImplementor.getIndexedTypesPolymorphic( classes );
+ if ( classes != null && classes.length > 0 && indexedTargetedEntities.size() == 0 ) {
String msg = "None of the specified entity types or any of their subclasses are indexed.";
throw new IllegalArgumentException( msg );
}
@@ -137,7 +141,6 @@
//user stop using it
//scrollable is better in this area
- SearchFactoryImplementor searchFactoryImplementor = getSearchFactoryImplementor();
//find the directories
IndexSearcher searcher = buildSearcher( searchFactoryImplementor );
if ( searcher == null ) {
@@ -157,7 +160,7 @@
for ( int index = first; index <= max; index++ ) {
infos.add( extractor.extract( index ) );
}
- Loader loader = getLoader( sess, searchFactoryImplementor );
+ Loader loader = getLoader();
return new IteratorImpl( infos, loader );
}
catch ( IOException e ) {
@@ -173,79 +176,100 @@
}
}
- private Loader getLoader(Session session, SearchFactoryImplementor searchFactoryImplementor) {
+ /**
+ * Decide which object loader to use depending on the targeted entities. If there is only a single entity targeted
+ * a <code>QueryLoader</code> can be used which will only execute a single query to load the entities. If more than
+ * one entity is targeted a <code>MultiClassesQueryLoader</code> must be used. We also have to consider whether
+ * projections or <code>Criteria</code> are used.
+ *
+ * @return The loader instance to use to load the results of the query.
+ */
+ private Loader getLoader() {
+ Loader loader;
if ( indexProjection != null ) {
- ProjectionLoader loader = new ProjectionLoader();
- loader.init( session, searchFactoryImplementor, resultTransformer, indexProjection );
- loader.setEntityTypes( targetedEntities );
- return loader;
+ loader = getProjectionLoader();
}
- if ( criteria != null ) {
- if ( targetedEntities.size() > 1 ) {
- throw new SearchException( "Cannot mix criteria and multiple entity types" );
- }
- if ( criteria instanceof CriteriaImpl ) {
- String targetEntity = ( ( CriteriaImpl ) criteria ).getEntityOrClassName();
- if ( targetedEntities.size() == 1 && !targetedEntities.iterator()
- .next()
- .getName()
- .equals( targetEntity ) ) {
- throw new SearchException( "Criteria query entity should match query entity" );
- }
- else {
- try {
- Class entityType = ReflectHelper.classForName( targetEntity );
- targetedEntities = new HashSet<Class<?>>( 1 );
- targetedEntities.add( entityType );
- }
- catch ( ClassNotFoundException e ) {
- throw new SearchException( "Unable to load entity class from criteria: " + targetEntity, e );
- }
- }
- }
- QueryLoader loader = new QueryLoader();
- loader.init( session, searchFactoryImplementor );
- loader.setEntityType( targetedEntities.iterator().next() );
- loader.setCriteria( criteria );
- return loader;
+ else if ( criteria != null ) {
+ loader = getCriteriaLoader();
}
else if ( targetedEntities.size() == 1 ) {
- final QueryLoader loader = new QueryLoader();
- loader.init( session, searchFactoryImplementor );
- loader.setEntityType( targetedEntities.iterator().next() );
- return loader;
+ loader = getSingleEntityLoader();
}
else {
- final MultiClassesQueryLoader loader = new MultiClassesQueryLoader();
- loader.init( session, searchFactoryImplementor );
- loader.setEntityTypes( targetedEntities );
- return loader;
+ loader = getMultipleEntitiesLoader();
}
+ return loader;
}
+ private Loader getMultipleEntitiesLoader() {
+ final MultiClassesQueryLoader multiClassesLoader = new MultiClassesQueryLoader();
+ multiClassesLoader.init( ( Session ) session, searchFactoryImplementor );
+ multiClassesLoader.setEntityTypes( indexedTargetedEntities );
+ return multiClassesLoader;
+ }
+
+ private Loader getSingleEntityLoader() {
+ final QueryLoader queryLoader = new QueryLoader();
+ queryLoader.init( ( Session ) session, searchFactoryImplementor );
+ queryLoader.setEntityType( targetedEntities.iterator().next() );
+ return queryLoader;
+ }
+
+ private Loader getCriteriaLoader() {
+ if ( targetedEntities.size() > 1 ) {
+ throw new SearchException( "Cannot mix criteria and multiple entity types" );
+ }
+ Class entityType = targetedEntities.size() == 0 ? null : targetedEntities.iterator().next();
+ if ( criteria instanceof CriteriaImpl ) {
+ String targetEntity = ( ( CriteriaImpl ) criteria ).getEntityOrClassName();
+ if ( entityType != null && !entityType.getName().equals( targetEntity ) ) {
+ throw new SearchException( "Criteria query entity should match query entity" );
+ }
+ else {
+ try {
+ entityType = ReflectHelper.classForName( targetEntity );
+ }
+ catch ( ClassNotFoundException e ) {
+ throw new SearchException( "Unable to load entity class from criteria: " + targetEntity, e );
+ }
+ }
+ }
+ QueryLoader queryLoader = new QueryLoader();
+ queryLoader.init( ( Session ) session, searchFactoryImplementor );
+ queryLoader.setEntityType( entityType );
+ queryLoader.setCriteria( criteria );
+ return queryLoader;
+ }
+
+ private Loader getProjectionLoader() {
+ ProjectionLoader loader = new ProjectionLoader();
+ loader.init( ( Session ) session, searchFactoryImplementor, resultTransformer, indexProjection );
+ loader.setEntityTypes( indexedTargetedEntities );
+ return loader;
+ }
+
public ScrollableResults scroll() throws HibernateException {
//keep the searcher open until the resultset is closed
- SearchFactoryImplementor searchFactory = getSearchFactoryImplementor();
//find the directories
- IndexSearcher searcher = buildSearcher( searchFactory );
+ IndexSearcher searcher = buildSearcher( searchFactoryImplementor );
//FIXME: handle null searcher
try {
QueryHits queryHits = getQueryHits( searcher, calculateTopDocsRetrievalSize() );
int first = first();
int max = max( first, queryHits.totalHits );
DocumentExtractor extractor = new DocumentExtractor(
- queryHits, searchFactory, indexProjection, idFieldNames, allowFieldSelectionInProjection
+ queryHits, searchFactoryImplementor, indexProjection, idFieldNames, allowFieldSelectionInProjection
);
- Loader loader = getLoader( ( Session ) this.session, searchFactory );
+ Loader loader = getLoader();
return new ScrollableResultsImpl(
- searcher, first, max, fetchSize, extractor, loader, searchFactory, this.session
+ searcher, first, max, fetchSize, extractor, loader, searchFactoryImplementor, this.session
);
}
catch ( IOException e ) {
//close only in case of exception
try {
- closeSearcher( searcher, searchFactory.getReaderProvider() );
+ closeSearcher( searcher, searchFactoryImplementor.getReaderProvider() );
}
catch ( SearchException ee ) {
//we have the initial issue already
@@ -260,7 +284,6 @@
}
public List list() throws HibernateException {
- SearchFactoryImplementor searchFactoryImplementor = getSearchFactoryImplementor();
//find the directories
IndexSearcher searcher = buildSearcher( searchFactoryImplementor );
if ( searcher == null ) {
@@ -280,7 +303,7 @@
for ( int index = first; index <= max; index++ ) {
infos.add( extractor.extract( index ) );
}
- Loader loader = getLoader( sess, searchFactoryImplementor );
+ Loader loader = getLoader();
List list = loader.load( infos.toArray( new EntityInfo[infos.size()] ) );
if ( resultTransformer == null || loader instanceof ProjectionLoader ) {
//stay consistent with transformTuple which can only be executed during a projection
@@ -305,7 +328,6 @@
public Explanation explain(int documentId) {
Explanation explanation = null;
- SearchFactoryImplementor searchFactoryImplementor = getSearchFactoryImplementor();
Searcher searcher = buildSearcher( searchFactoryImplementor );
if ( searcher == null ) {
throw new SearchException(
@@ -367,13 +389,13 @@
return null;
}
else {
- long tmpMaxResult = (long) first() + maxResults;
+ long tmpMaxResult = ( long ) first() + maxResults;
if ( tmpMaxResult >= Integer.MAX_VALUE ) {
// don't return just Integer.MAX_VALUE due to a bug in Lucene - see HSEARCH-330
return Integer.MAX_VALUE - 1;
}
else {
- return (int) tmpMaxResult;
+ return ( int ) tmpMaxResult;
}
}
}
@@ -405,8 +427,6 @@
*/
private Filter buildLuceneFilter(FullTextFilterImpl fullTextFilter) {
- SearchFactoryImplementor searchFactoryImplementor = getSearchFactoryImplementor();
-
/*
* FilterKey implementations and Filter(Factory) do not have to be threadsafe wrt their parameter injection
* as FilterCachingStrategy ensure a memory barrier between concurrent thread calls
@@ -484,7 +504,7 @@
*/
private Filter addCachingWrapperFilter(Filter filter, FilterDef def) {
if ( cacheResults( def.getCacheMode() ) ) {
- int cachingWrapperFilterSize = getSearchFactoryImplementor().getFilterCacheBitResultsSize();
+ int cachingWrapperFilterSize = searchFactoryImplementor.getFilterCacheBitResultsSize();
filter = new org.hibernate.search.filter.CachingWrapperFilter( filter, cachingWrapperFilterSize );
}
@@ -619,9 +639,9 @@
Set<String> idFieldNames = new HashSet<String>();
Similarity searcherSimilarity = null;
- //TODO check if caching this work for the last n list of targetedEntities makes a perf boost
- if ( targetedEntities.size() == 0 ) {
- // empty targetedEntities array means search over all indexed enities,
+ //TODO check if caching this work for the last n list of indexedTargetedEntities makes a perf boost
+ if ( indexedTargetedEntities.size() == 0 ) {
+ // empty indexedTargetedEntities array means search over all indexed enities,
// but we have to make sure there is at least one
if ( builders.isEmpty() ) {
throw new HibernateException(
@@ -642,9 +662,9 @@
classesAndSubclasses = null;
}
else {
- Set<Class<?>> involvedClasses = new HashSet<Class<?>>( targetedEntities.size() );
- involvedClasses.addAll( targetedEntities );
- for ( Class<?> clazz : targetedEntities ) {
+ Set<Class<?>> involvedClasses = new HashSet<Class<?>>( indexedTargetedEntities.size() );
+ involvedClasses.addAll( indexedTargetedEntities );
+ for ( Class<?> clazz : indexedTargetedEntities ) {
DocumentBuilderIndexedEntity<?> builder = builders.get( clazz );
if ( builder != null ) {
involvedClasses.addAll( builder.getMappedSubclasses() );
@@ -737,7 +757,6 @@
public int getResultSize() {
if ( resultSize == null ) {
//get result size without object initialization
- SearchFactoryImplementor searchFactoryImplementor = getSearchFactoryImplementor();
IndexSearcher searcher = buildSearcher( searchFactoryImplementor );
if ( searcher == null ) {
resultSize = 0;
@@ -836,7 +855,7 @@
filterDefinition = new FullTextFilterImpl();
filterDefinition.setName( name );
- FilterDef filterDef = getSearchFactoryImplementor().getFilterDefinition( name );
+ FilterDef filterDef = searchFactoryImplementor.getFilterDefinition( name );
if ( filterDef == null ) {
throw new SearchException( "Unkown @FullTextFilter: " + name );
}
Added: search/branches/Branch_3_1/src/test/java/org/hibernate/search/test/query/criteria/AbstractCar.java
===================================================================
--- search/branches/Branch_3_1/src/test/java/org/hibernate/search/test/query/criteria/AbstractCar.java (rev 0)
+++ search/branches/Branch_3_1/src/test/java/org/hibernate/search/test/query/criteria/AbstractCar.java 2009-05-14 15:40:17 UTC (rev 16567)
@@ -0,0 +1,52 @@
+//$Id$
+package org.hibernate.search.test.query.criteria;
+
+import javax.persistence.DiscriminatorColumn;
+import javax.persistence.DiscriminatorType;
+import javax.persistence.Entity;
+import javax.persistence.GeneratedValue;
+import javax.persistence.Id;
+import javax.persistence.Inheritance;
+import javax.persistence.InheritanceType;
+import javax.persistence.Table;
+
+import org.hibernate.search.annotations.Field;
+
+@Entity
+@Table(name = "Car")
+@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
+@DiscriminatorColumn(name = "DISC", discriminatorType = DiscriminatorType.STRING, length = 5)
+public abstract class AbstractCar {
+
+ @Id
+ @GeneratedValue
+ private Integer id;
+
+ @Field
+ private String kurztext;
+
+ private boolean hasColor = false;
+
+ protected AbstractCar() {
+ }
+
+ public Integer getId() {
+ return id;
+ }
+
+ public void setId(Integer id) {
+ this.id = id;
+ }
+
+ public String getKurztext() {
+ return kurztext;
+ }
+
+ public void setKurztext(final String kurztext) {
+ this.kurztext = kurztext;
+ }
+
+ public boolean isHasColor() {
+ return hasColor;
+ }
+}
Added: search/branches/Branch_3_1/src/test/java/org/hibernate/search/test/query/criteria/Bike.java
===================================================================
--- search/branches/Branch_3_1/src/test/java/org/hibernate/search/test/query/criteria/Bike.java (rev 0)
+++ search/branches/Branch_3_1/src/test/java/org/hibernate/search/test/query/criteria/Bike.java 2009-05-14 15:40:17 UTC (rev 16567)
@@ -0,0 +1,49 @@
+//$Id$
+package org.hibernate.search.test.query.criteria;
+
+import javax.persistence.DiscriminatorColumn;
+import javax.persistence.DiscriminatorType;
+import javax.persistence.Entity;
+import javax.persistence.GeneratedValue;
+import javax.persistence.Id;
+import javax.persistence.Inheritance;
+import javax.persistence.InheritanceType;
+import javax.persistence.Table;
+
+import org.hibernate.search.annotations.Field;
+
+@Entity
+public class Bike {
+
+ @Id
+ @GeneratedValue
+ private Integer id;
+
+ @Field
+ private String kurztext;
+
+ private boolean hasColor = false;
+
+ protected Bike() {
+ }
+
+ public Integer getId() {
+ return id;
+ }
+
+ public void setId(Integer id) {
+ this.id = id;
+ }
+
+ public String getKurztext() {
+ return kurztext;
+ }
+
+ public void setKurztext(final String kurztext) {
+ this.kurztext = kurztext;
+ }
+
+ public boolean isHasColor() {
+ return hasColor;
+ }
+}
\ No newline at end of file
Added: search/branches/Branch_3_1/src/test/java/org/hibernate/search/test/query/criteria/CombiCar.java
===================================================================
--- search/branches/Branch_3_1/src/test/java/org/hibernate/search/test/query/criteria/CombiCar.java (rev 0)
+++ search/branches/Branch_3_1/src/test/java/org/hibernate/search/test/query/criteria/CombiCar.java 2009-05-14 15:40:17 UTC (rev 16567)
@@ -0,0 +1,13 @@
+//$Id$
+package org.hibernate.search.test.query.criteria;
+
+import javax.persistence.DiscriminatorValue;
+import javax.persistence.Entity;
+
+import org.hibernate.search.annotations.Indexed;
+
+@Entity
+@DiscriminatorValue(value = "Combi")
+@Indexed
+public class CombiCar extends AbstractCar {
+}
Added: search/branches/Branch_3_1/src/test/java/org/hibernate/search/test/query/criteria/MixedCriteriaTest.java
===================================================================
--- search/branches/Branch_3_1/src/test/java/org/hibernate/search/test/query/criteria/MixedCriteriaTest.java (rev 0)
+++ search/branches/Branch_3_1/src/test/java/org/hibernate/search/test/query/criteria/MixedCriteriaTest.java 2009-05-14 15:40:17 UTC (rev 16567)
@@ -0,0 +1,128 @@
+//$Id$
+package org.hibernate.search.test.query.criteria;
+
+import java.util.List;
+
+import org.apache.lucene.analysis.standard.StandardAnalyzer;
+import org.apache.lucene.queryParser.MultiFieldQueryParser;
+import org.apache.lucene.search.Query;
+
+import org.hibernate.Criteria;
+import org.hibernate.Session;
+import org.hibernate.Transaction;
+import org.hibernate.criterion.Restrictions;
+import org.hibernate.search.FullTextSession;
+import org.hibernate.search.Search;
+import org.hibernate.search.SearchException;
+import org.hibernate.search.test.SearchTestCase;
+
+/**
+ * @author Hardy Ferentschik
+ */
+public class MixedCriteriaTest extends SearchTestCase {
+ /**
+ * HSEARCH-360
+ */
+ public void testCriteriaWithFilteredEntity() throws Exception {
+ indexTestData();
+
+ // Search
+ Session session = openSession();
+ Transaction tx = session.beginTransaction();
+ FullTextSession fullTextSession = Search.getFullTextSession( session );
+
+ MultiFieldQueryParser parser = new MultiFieldQueryParser(
+ new String[] { "kurztext" }, new StandardAnalyzer()
+ );
+ Query query = parser.parse( "combi OR sport" );
+
+ Criteria criteria = session.createCriteria( AbstractCar.class );
+ criteria.add( Restrictions.eq( "hasColor", Boolean.FALSE ) );
+
+ org.hibernate.Query hibQuery = fullTextSession.createFullTextQuery( query, AbstractCar.class )
+ .setCriteriaQuery( criteria );
+ List result = hibQuery.list();
+ assertEquals( 2, result.size() );
+ tx.commit();
+ session.close();
+ }
+
+ public void testCriteriaWithoutFilteredEntity() throws Exception {
+ indexTestData();
+
+ // Search
+ Session session = openSession();
+ Transaction tx = session.beginTransaction();
+ FullTextSession fullTextSession = Search.getFullTextSession( session );
+
+ MultiFieldQueryParser parser = new MultiFieldQueryParser(
+ new String[] { "kurztext" }, new StandardAnalyzer()
+ );
+ Query query = parser.parse( "combi OR sport" );
+
+ Criteria criteria = session.createCriteria( AbstractCar.class );
+ criteria.add( Restrictions.eq( "hasColor", Boolean.FALSE ) );
+
+ org.hibernate.Query hibQuery = fullTextSession.createFullTextQuery( query )
+ .setCriteriaQuery( criteria );
+ List result = hibQuery.list();
+ assertEquals( 2, result.size() );
+ tx.commit();
+ session.close();
+ }
+
+ public void testCriteriaWithMultipleEntities() throws Exception {
+ indexTestData();
+
+ // Search
+ Session session = openSession();
+ Transaction tx = session.beginTransaction();
+ FullTextSession fullTextSession = Search.getFullTextSession( session );
+
+ MultiFieldQueryParser parser = new MultiFieldQueryParser(
+ new String[] { "kurztext" }, new StandardAnalyzer()
+ );
+ Query query = parser.parse( "combi OR sport" );
+
+ Criteria criteria = session.createCriteria( AbstractCar.class );
+ criteria.add( Restrictions.eq( "hasColor", Boolean.FALSE ) );
+
+ try {
+ org.hibernate.Query hibQuery = fullTextSession.createFullTextQuery( query, AbstractCar.class, Bike.class )
+ .setCriteriaQuery( criteria );
+ hibQuery.list();
+ fail();
+ }
+ catch ( SearchException se ) {
+ assertEquals( "Cannot mix criteria and multiple entity types", se.getMessage() );
+ }
+ tx.commit();
+ session.close();
+ }
+
+ private void indexTestData() {
+ Session s = openSession();
+ Transaction tx = s.beginTransaction();
+
+ CombiCar combi = new CombiCar();
+ combi.setKurztext( "combi" );
+ s.persist( combi );
+
+ SportCar sport = new SportCar();
+ sport.setKurztext( "sport" );
+ s.persist( sport );
+
+ Bike bike = new Bike();
+ bike.setKurztext( "bike" );
+ s.persist( bike );
+ tx.commit();
+ s.close();
+ }
+
+
+ protected Class[] getMappings() {
+ return new Class[] {
+ AbstractCar.class, CombiCar.class, SportCar.class, Bike.class
+ };
+ }
+}
Added: search/branches/Branch_3_1/src/test/java/org/hibernate/search/test/query/criteria/SportCar.java
===================================================================
--- search/branches/Branch_3_1/src/test/java/org/hibernate/search/test/query/criteria/SportCar.java (rev 0)
+++ search/branches/Branch_3_1/src/test/java/org/hibernate/search/test/query/criteria/SportCar.java 2009-05-14 15:40:17 UTC (rev 16567)
@@ -0,0 +1,13 @@
+//$Id$
+package org.hibernate.search.test.query.criteria;
+
+import javax.persistence.DiscriminatorValue;
+import javax.persistence.Entity;
+
+import org.hibernate.search.annotations.Indexed;
+
+@Entity
+@DiscriminatorValue(value = "Sport")
+@Indexed
+public class SportCar extends AbstractCar {
+}
15 years, 6 months
Hibernate SVN: r16566 - in search/trunk/src: main/java/org/hibernate/search/query and 2 other directories.
by hibernate-commits@lists.jboss.org
Author: hardy.ferentschik
Date: 2009-05-14 11:33:16 -0400 (Thu, 14 May 2009)
New Revision: 16566
Added:
search/trunk/src/test/java/org/hibernate/search/test/query/criteria/
search/trunk/src/test/java/org/hibernate/search/test/query/criteria/AbstractCar.java
search/trunk/src/test/java/org/hibernate/search/test/query/criteria/Bike.java
search/trunk/src/test/java/org/hibernate/search/test/query/criteria/CombiCar.java
search/trunk/src/test/java/org/hibernate/search/test/query/criteria/MixedCriteriaTest.java
search/trunk/src/test/java/org/hibernate/search/test/query/criteria/SportCar.java
Modified:
search/trunk/src/main/java/org/hibernate/search/engine/ObjectLoaderHelper.java
search/trunk/src/main/java/org/hibernate/search/query/FullTextQueryImpl.java
Log:
HSEARCH-360 Refactored getLoader() in FullTextQueryImpl
Modified: search/trunk/src/main/java/org/hibernate/search/engine/ObjectLoaderHelper.java
===================================================================
--- search/trunk/src/main/java/org/hibernate/search/engine/ObjectLoaderHelper.java 2009-05-13 22:48:45 UTC (rev 16565)
+++ search/trunk/src/main/java/org/hibernate/search/engine/ObjectLoaderHelper.java 2009-05-14 15:33:16 UTC (rev 16566)
@@ -4,6 +4,7 @@
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
+import java.util.Set;
import org.slf4j.Logger;
@@ -46,7 +47,8 @@
final int maxResults = entityInfos.length;
if ( maxResults == 0 ) return;
- DocumentBuilderIndexedEntity<?> builder = searchFactoryImplementor.getDocumentBuilderIndexedEntity( entityType );
+ Set<Class<?>> indexedEntities = searchFactoryImplementor.getIndexedTypesPolymorphic( new Class<?>[]{entityType} );
+ DocumentBuilderIndexedEntity<?> builder = searchFactoryImplementor.getDocumentBuilderIndexedEntity( indexedEntities.iterator().next() );
String idName = builder.getIdentifierName();
int loop = maxResults / MAX_IN_CLAUSE;
boolean exact = maxResults % MAX_IN_CLAUSE == 0;
Modified: search/trunk/src/main/java/org/hibernate/search/query/FullTextQueryImpl.java
===================================================================
--- search/trunk/src/main/java/org/hibernate/search/query/FullTextQueryImpl.java 2009-05-13 22:48:45 UTC (rev 16565)
+++ search/trunk/src/main/java/org/hibernate/search/query/FullTextQueryImpl.java 2009-05-14 15:33:16 UTC (rev 16566)
@@ -4,6 +4,7 @@
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
@@ -68,12 +69,13 @@
*
* @author Emmanuel Bernard
* @author Hardy Ferentschik
+ * @todo Implements setParameter()
*/
-//TODO implements setParameter()
public class FullTextQueryImpl extends AbstractQueryImpl implements FullTextQuery {
private static final Logger log = LoggerFactory.make();
private final org.apache.lucene.search.Query luceneQuery;
- private Set<Class<?>> targetedEntities;
+ private Set<Class<?>> indexedTargetedEntities;
+ private List<Class<?>> targetedEntities;
private Set<Class<?>> classesAndSubclasses;
//optimization: if we can avoid the filter clause (we can most of the time) do it as it has a significant perf impact
private boolean needClassFilterClause;
@@ -99,13 +101,15 @@
* @param session Access to the Hibernate session.
* @param parameterMetadata Additional query metadata.
*/
- public FullTextQueryImpl(org.apache.lucene.search.Query query, Class[] classes, SessionImplementor session,
+ public FullTextQueryImpl(org.apache.lucene.search.Query query, Class<?>[] classes, SessionImplementor session,
ParameterMetadata parameterMetadata) {
//TODO handle flushMode
super( query.toString(), null, session, parameterMetadata );
this.luceneQuery = query;
- this.targetedEntities = getSearchFactoryImplementor().getIndexedTypesPolymorphic( classes );
- if ( classes != null && classes.length > 0 && targetedEntities.size() == 0 ) {
+ this.targetedEntities = Arrays.asList( classes );
+ searchFactoryImplementor = getSearchFactoryImplementor();
+ this.indexedTargetedEntities = searchFactoryImplementor.getIndexedTypesPolymorphic( classes );
+ if ( classes != null && classes.length > 0 && indexedTargetedEntities.size() == 0 ) {
String msg = "None of the specified entity types or any of their subclasses are indexed.";
throw new IllegalArgumentException( msg );
}
@@ -137,7 +141,6 @@
//user stop using it
//scrollable is better in this area
- SearchFactoryImplementor searchFactoryImplementor = getSearchFactoryImplementor();
//find the directories
IndexSearcher searcher = buildSearcher( searchFactoryImplementor );
if ( searcher == null ) {
@@ -157,7 +160,7 @@
for ( int index = first; index <= max; index++ ) {
infos.add( extractor.extract( index ) );
}
- Loader loader = getLoader( sess, searchFactoryImplementor );
+ Loader loader = getLoader();
return new IteratorImpl( infos, loader );
}
catch ( IOException e ) {
@@ -173,79 +176,100 @@
}
}
- private Loader getLoader(Session session, SearchFactoryImplementor searchFactoryImplementor) {
+ /**
+ * Decide which object loader to use depending on the targeted entities. If there is only a single entity targeted
+ * a <code>QueryLoader</code> can be used which will only execute a single query to load the entities. If more than
+ * one entity is targeted a <code>MultiClassesQueryLoader</code> must be used. We also have to consider whether
+ * projections or <code>Criteria</code> are used.
+ *
+ * @return The loader instance to use to load the results of the query.
+ */
+ private Loader getLoader() {
+ Loader loader;
if ( indexProjection != null ) {
- ProjectionLoader loader = new ProjectionLoader();
- loader.init( session, searchFactoryImplementor, resultTransformer, indexProjection );
- loader.setEntityTypes( targetedEntities );
- return loader;
+ loader = getProjectionLoader();
}
- if ( criteria != null ) {
- if ( targetedEntities.size() > 1 ) {
- throw new SearchException( "Cannot mix criteria and multiple entity types" );
- }
- if ( criteria instanceof CriteriaImpl ) {
- String targetEntity = ( ( CriteriaImpl ) criteria ).getEntityOrClassName();
- if ( targetedEntities.size() == 1 && !targetedEntities.iterator()
- .next()
- .getName()
- .equals( targetEntity ) ) {
- throw new SearchException( "Criteria query entity should match query entity" );
- }
- else {
- try {
- Class entityType = ReflectHelper.classForName( targetEntity );
- targetedEntities = new HashSet<Class<?>>( 1 );
- targetedEntities.add( entityType );
- }
- catch ( ClassNotFoundException e ) {
- throw new SearchException( "Unable to load entity class from criteria: " + targetEntity, e );
- }
- }
- }
- QueryLoader loader = new QueryLoader();
- loader.init( session, searchFactoryImplementor );
- loader.setEntityType( targetedEntities.iterator().next() );
- loader.setCriteria( criteria );
- return loader;
+ else if ( criteria != null ) {
+ loader = getCriteriaLoader();
}
else if ( targetedEntities.size() == 1 ) {
- final QueryLoader loader = new QueryLoader();
- loader.init( session, searchFactoryImplementor );
- loader.setEntityType( targetedEntities.iterator().next() );
- return loader;
+ loader = getSingleEntityLoader();
}
else {
- final MultiClassesQueryLoader loader = new MultiClassesQueryLoader();
- loader.init( session, searchFactoryImplementor );
- loader.setEntityTypes( targetedEntities );
- return loader;
+ loader = getMultipleEntitiesLoader();
}
+ return loader;
}
+ private Loader getMultipleEntitiesLoader() {
+ final MultiClassesQueryLoader multiClassesLoader = new MultiClassesQueryLoader();
+ multiClassesLoader.init( ( Session ) session, searchFactoryImplementor );
+ multiClassesLoader.setEntityTypes( indexedTargetedEntities );
+ return multiClassesLoader;
+ }
+
+ private Loader getSingleEntityLoader() {
+ final QueryLoader queryLoader = new QueryLoader();
+ queryLoader.init( ( Session ) session, searchFactoryImplementor );
+ queryLoader.setEntityType( targetedEntities.iterator().next() );
+ return queryLoader;
+ }
+
+ private Loader getCriteriaLoader() {
+ if ( targetedEntities.size() > 1 ) {
+ throw new SearchException( "Cannot mix criteria and multiple entity types" );
+ }
+ Class entityType = targetedEntities.size() == 0 ? null : targetedEntities.iterator().next();
+ if ( criteria instanceof CriteriaImpl ) {
+ String targetEntity = ( ( CriteriaImpl ) criteria ).getEntityOrClassName();
+ if ( entityType != null && !entityType.getName().equals( targetEntity ) ) {
+ throw new SearchException( "Criteria query entity should match query entity" );
+ }
+ else {
+ try {
+ entityType = ReflectHelper.classForName( targetEntity );
+ }
+ catch ( ClassNotFoundException e ) {
+ throw new SearchException( "Unable to load entity class from criteria: " + targetEntity, e );
+ }
+ }
+ }
+ QueryLoader queryLoader = new QueryLoader();
+ queryLoader.init( ( Session ) session, searchFactoryImplementor );
+ queryLoader.setEntityType( entityType );
+ queryLoader.setCriteria( criteria );
+ return queryLoader;
+ }
+
+ private Loader getProjectionLoader() {
+ ProjectionLoader loader = new ProjectionLoader();
+ loader.init( ( Session ) session, searchFactoryImplementor, resultTransformer, indexProjection );
+ loader.setEntityTypes( indexedTargetedEntities );
+ return loader;
+ }
+
public ScrollableResults scroll() throws HibernateException {
//keep the searcher open until the resultset is closed
- SearchFactoryImplementor searchFactory = getSearchFactoryImplementor();
//find the directories
- IndexSearcher searcher = buildSearcher( searchFactory );
+ IndexSearcher searcher = buildSearcher( searchFactoryImplementor );
//FIXME: handle null searcher
try {
QueryHits queryHits = getQueryHits( searcher, calculateTopDocsRetrievalSize() );
int first = first();
int max = max( first, queryHits.totalHits );
DocumentExtractor extractor = new DocumentExtractor(
- queryHits, searchFactory, indexProjection, idFieldNames, allowFieldSelectionInProjection
+ queryHits, searchFactoryImplementor, indexProjection, idFieldNames, allowFieldSelectionInProjection
);
- Loader loader = getLoader( ( Session ) this.session, searchFactory );
+ Loader loader = getLoader();
return new ScrollableResultsImpl(
- searcher, first, max, fetchSize, extractor, loader, searchFactory, this.session
+ searcher, first, max, fetchSize, extractor, loader, searchFactoryImplementor, this.session
);
}
catch ( IOException e ) {
//close only in case of exception
try {
- closeSearcher( searcher, searchFactory.getReaderProvider() );
+ closeSearcher( searcher, searchFactoryImplementor.getReaderProvider() );
}
catch ( SearchException ee ) {
//we have the initial issue already
@@ -260,7 +284,6 @@
}
public List list() throws HibernateException {
- SearchFactoryImplementor searchFactoryImplementor = getSearchFactoryImplementor();
//find the directories
IndexSearcher searcher = buildSearcher( searchFactoryImplementor );
if ( searcher == null ) {
@@ -280,7 +303,7 @@
for ( int index = first; index <= max; index++ ) {
infos.add( extractor.extract( index ) );
}
- Loader loader = getLoader( sess, searchFactoryImplementor );
+ Loader loader = getLoader();
List list = loader.load( infos.toArray( new EntityInfo[infos.size()] ) );
if ( resultTransformer == null || loader instanceof ProjectionLoader ) {
//stay consistent with transformTuple which can only be executed during a projection
@@ -305,7 +328,6 @@
public Explanation explain(int documentId) {
Explanation explanation = null;
- SearchFactoryImplementor searchFactoryImplementor = getSearchFactoryImplementor();
Searcher searcher = buildSearcher( searchFactoryImplementor );
if ( searcher == null ) {
throw new SearchException(
@@ -367,13 +389,13 @@
return null;
}
else {
- long tmpMaxResult = (long) first() + maxResults;
+ long tmpMaxResult = ( long ) first() + maxResults;
if ( tmpMaxResult >= Integer.MAX_VALUE ) {
// don't return just Integer.MAX_VALUE due to a bug in Lucene - see HSEARCH-330
return Integer.MAX_VALUE - 1;
}
else {
- return (int) tmpMaxResult;
+ return ( int ) tmpMaxResult;
}
}
}
@@ -405,8 +427,6 @@
*/
private Filter buildLuceneFilter(FullTextFilterImpl fullTextFilter) {
- SearchFactoryImplementor searchFactoryImplementor = getSearchFactoryImplementor();
-
/*
* FilterKey implementations and Filter(Factory) do not have to be threadsafe wrt their parameter injection
* as FilterCachingStrategy ensure a memory barrier between concurrent thread calls
@@ -484,7 +504,7 @@
*/
private Filter addCachingWrapperFilter(Filter filter, FilterDef def) {
if ( cacheResults( def.getCacheMode() ) ) {
- int cachingWrapperFilterSize = getSearchFactoryImplementor().getFilterCacheBitResultsSize();
+ int cachingWrapperFilterSize = searchFactoryImplementor.getFilterCacheBitResultsSize();
filter = new org.hibernate.search.filter.CachingWrapperFilter( filter, cachingWrapperFilterSize );
}
@@ -619,9 +639,9 @@
Set<String> idFieldNames = new HashSet<String>();
Similarity searcherSimilarity = null;
- //TODO check if caching this work for the last n list of targetedEntities makes a perf boost
- if ( targetedEntities.size() == 0 ) {
- // empty targetedEntities array means search over all indexed enities,
+ //TODO check if caching this work for the last n list of indexedTargetedEntities makes a perf boost
+ if ( indexedTargetedEntities.size() == 0 ) {
+ // empty indexedTargetedEntities array means search over all indexed enities,
// but we have to make sure there is at least one
if ( builders.isEmpty() ) {
throw new HibernateException(
@@ -642,9 +662,9 @@
classesAndSubclasses = null;
}
else {
- Set<Class<?>> involvedClasses = new HashSet<Class<?>>( targetedEntities.size() );
- involvedClasses.addAll( targetedEntities );
- for ( Class<?> clazz : targetedEntities ) {
+ Set<Class<?>> involvedClasses = new HashSet<Class<?>>( indexedTargetedEntities.size() );
+ involvedClasses.addAll( indexedTargetedEntities );
+ for ( Class<?> clazz : indexedTargetedEntities ) {
DocumentBuilderIndexedEntity<?> builder = builders.get( clazz );
if ( builder != null ) {
involvedClasses.addAll( builder.getMappedSubclasses() );
@@ -737,7 +757,6 @@
public int getResultSize() {
if ( resultSize == null ) {
//get result size without object initialization
- SearchFactoryImplementor searchFactoryImplementor = getSearchFactoryImplementor();
IndexSearcher searcher = buildSearcher( searchFactoryImplementor );
if ( searcher == null ) {
resultSize = 0;
@@ -838,7 +857,7 @@
filterDefinition = new FullTextFilterImpl();
filterDefinition.setName( name );
- FilterDef filterDef = getSearchFactoryImplementor().getFilterDefinition( name );
+ FilterDef filterDef = searchFactoryImplementor.getFilterDefinition( name );
if ( filterDef == null ) {
throw new SearchException( "Unkown @FullTextFilter: " + name );
}
Added: search/trunk/src/test/java/org/hibernate/search/test/query/criteria/AbstractCar.java
===================================================================
--- search/trunk/src/test/java/org/hibernate/search/test/query/criteria/AbstractCar.java (rev 0)
+++ search/trunk/src/test/java/org/hibernate/search/test/query/criteria/AbstractCar.java 2009-05-14 15:33:16 UTC (rev 16566)
@@ -0,0 +1,52 @@
+//$Id$
+package org.hibernate.search.test.query.criteria;
+
+import javax.persistence.DiscriminatorColumn;
+import javax.persistence.DiscriminatorType;
+import javax.persistence.Entity;
+import javax.persistence.GeneratedValue;
+import javax.persistence.Id;
+import javax.persistence.Inheritance;
+import javax.persistence.InheritanceType;
+import javax.persistence.Table;
+
+import org.hibernate.search.annotations.Field;
+
+@Entity
+@Table(name = "Car")
+@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
+@DiscriminatorColumn(name = "DISC", discriminatorType = DiscriminatorType.STRING, length = 5)
+public abstract class AbstractCar {
+
+ @Id
+ @GeneratedValue
+ private Integer id;
+
+ @Field
+ private String kurztext;
+
+ private boolean hasColor = false;
+
+ protected AbstractCar() {
+ }
+
+ public Integer getId() {
+ return id;
+ }
+
+ public void setId(Integer id) {
+ this.id = id;
+ }
+
+ public String getKurztext() {
+ return kurztext;
+ }
+
+ public void setKurztext(final String kurztext) {
+ this.kurztext = kurztext;
+ }
+
+ public boolean isHasColor() {
+ return hasColor;
+ }
+}
Property changes on: search/trunk/src/test/java/org/hibernate/search/test/query/criteria/AbstractCar.java
___________________________________________________________________
Name: svn:executable
+ *
Name: svn:keywords
+ Id
Added: search/trunk/src/test/java/org/hibernate/search/test/query/criteria/Bike.java
===================================================================
--- search/trunk/src/test/java/org/hibernate/search/test/query/criteria/Bike.java (rev 0)
+++ search/trunk/src/test/java/org/hibernate/search/test/query/criteria/Bike.java 2009-05-14 15:33:16 UTC (rev 16566)
@@ -0,0 +1,49 @@
+//$Id$
+package org.hibernate.search.test.query.criteria;
+
+import javax.persistence.DiscriminatorColumn;
+import javax.persistence.DiscriminatorType;
+import javax.persistence.Entity;
+import javax.persistence.GeneratedValue;
+import javax.persistence.Id;
+import javax.persistence.Inheritance;
+import javax.persistence.InheritanceType;
+import javax.persistence.Table;
+
+import org.hibernate.search.annotations.Field;
+
+@Entity
+public class Bike {
+
+ @Id
+ @GeneratedValue
+ private Integer id;
+
+ @Field
+ private String kurztext;
+
+ private boolean hasColor = false;
+
+ protected Bike() {
+ }
+
+ public Integer getId() {
+ return id;
+ }
+
+ public void setId(Integer id) {
+ this.id = id;
+ }
+
+ public String getKurztext() {
+ return kurztext;
+ }
+
+ public void setKurztext(final String kurztext) {
+ this.kurztext = kurztext;
+ }
+
+ public boolean isHasColor() {
+ return hasColor;
+ }
+}
\ No newline at end of file
Property changes on: search/trunk/src/test/java/org/hibernate/search/test/query/criteria/Bike.java
___________________________________________________________________
Name: svn:keywords
+ Id
Name: svn:eol-style
+ native
Added: search/trunk/src/test/java/org/hibernate/search/test/query/criteria/CombiCar.java
===================================================================
--- search/trunk/src/test/java/org/hibernate/search/test/query/criteria/CombiCar.java (rev 0)
+++ search/trunk/src/test/java/org/hibernate/search/test/query/criteria/CombiCar.java 2009-05-14 15:33:16 UTC (rev 16566)
@@ -0,0 +1,13 @@
+//$Id$
+package org.hibernate.search.test.query.criteria;
+
+import javax.persistence.DiscriminatorValue;
+import javax.persistence.Entity;
+
+import org.hibernate.search.annotations.Indexed;
+
+@Entity
+@DiscriminatorValue(value = "Combi")
+@Indexed
+public class CombiCar extends AbstractCar {
+}
Property changes on: search/trunk/src/test/java/org/hibernate/search/test/query/criteria/CombiCar.java
___________________________________________________________________
Name: svn:executable
+ *
Name: svn:keywords
+ Id
Added: search/trunk/src/test/java/org/hibernate/search/test/query/criteria/MixedCriteriaTest.java
===================================================================
--- search/trunk/src/test/java/org/hibernate/search/test/query/criteria/MixedCriteriaTest.java (rev 0)
+++ search/trunk/src/test/java/org/hibernate/search/test/query/criteria/MixedCriteriaTest.java 2009-05-14 15:33:16 UTC (rev 16566)
@@ -0,0 +1,128 @@
+//$Id$
+package org.hibernate.search.test.query.criteria;
+
+import java.util.List;
+
+import org.apache.lucene.analysis.standard.StandardAnalyzer;
+import org.apache.lucene.queryParser.MultiFieldQueryParser;
+import org.apache.lucene.search.Query;
+
+import org.hibernate.Criteria;
+import org.hibernate.Session;
+import org.hibernate.Transaction;
+import org.hibernate.criterion.Restrictions;
+import org.hibernate.search.FullTextSession;
+import org.hibernate.search.Search;
+import org.hibernate.search.SearchException;
+import org.hibernate.search.test.SearchTestCase;
+
+/**
+ * @author Hardy Ferentschik
+ */
+public class MixedCriteriaTest extends SearchTestCase {
+ /**
+ * HSEARCH-360
+ */
+ public void testCriteriaWithFilteredEntity() throws Exception {
+ indexTestData();
+
+ // Search
+ Session session = openSession();
+ Transaction tx = session.beginTransaction();
+ FullTextSession fullTextSession = Search.getFullTextSession( session );
+
+ MultiFieldQueryParser parser = new MultiFieldQueryParser(
+ new String[] { "kurztext" }, new StandardAnalyzer()
+ );
+ Query query = parser.parse( "combi OR sport" );
+
+ Criteria criteria = session.createCriteria( AbstractCar.class );
+ criteria.add( Restrictions.eq( "hasColor", Boolean.FALSE ) );
+
+ org.hibernate.Query hibQuery = fullTextSession.createFullTextQuery( query, AbstractCar.class )
+ .setCriteriaQuery( criteria );
+ List result = hibQuery.list();
+ assertEquals( 2, result.size() );
+ tx.commit();
+ session.close();
+ }
+
+ public void testCriteriaWithoutFilteredEntity() throws Exception {
+ indexTestData();
+
+ // Search
+ Session session = openSession();
+ Transaction tx = session.beginTransaction();
+ FullTextSession fullTextSession = Search.getFullTextSession( session );
+
+ MultiFieldQueryParser parser = new MultiFieldQueryParser(
+ new String[] { "kurztext" }, new StandardAnalyzer()
+ );
+ Query query = parser.parse( "combi OR sport" );
+
+ Criteria criteria = session.createCriteria( AbstractCar.class );
+ criteria.add( Restrictions.eq( "hasColor", Boolean.FALSE ) );
+
+ org.hibernate.Query hibQuery = fullTextSession.createFullTextQuery( query )
+ .setCriteriaQuery( criteria );
+ List result = hibQuery.list();
+ assertEquals( 2, result.size() );
+ tx.commit();
+ session.close();
+ }
+
+ public void testCriteriaWithMultipleEntities() throws Exception {
+ indexTestData();
+
+ // Search
+ Session session = openSession();
+ Transaction tx = session.beginTransaction();
+ FullTextSession fullTextSession = Search.getFullTextSession( session );
+
+ MultiFieldQueryParser parser = new MultiFieldQueryParser(
+ new String[] { "kurztext" }, new StandardAnalyzer()
+ );
+ Query query = parser.parse( "combi OR sport" );
+
+ Criteria criteria = session.createCriteria( AbstractCar.class );
+ criteria.add( Restrictions.eq( "hasColor", Boolean.FALSE ) );
+
+ try {
+ org.hibernate.Query hibQuery = fullTextSession.createFullTextQuery( query, AbstractCar.class, Bike.class )
+ .setCriteriaQuery( criteria );
+ hibQuery.list();
+ fail();
+ }
+ catch ( SearchException se ) {
+ assertEquals( "Cannot mix criteria and multiple entity types", se.getMessage() );
+ }
+ tx.commit();
+ session.close();
+ }
+
+ private void indexTestData() {
+ Session s = openSession();
+ Transaction tx = s.beginTransaction();
+
+ CombiCar combi = new CombiCar();
+ combi.setKurztext( "combi" );
+ s.persist( combi );
+
+ SportCar sport = new SportCar();
+ sport.setKurztext( "sport" );
+ s.persist( sport );
+
+ Bike bike = new Bike();
+ bike.setKurztext( "bike" );
+ s.persist( bike );
+ tx.commit();
+ s.close();
+ }
+
+
+ protected Class[] getMappings() {
+ return new Class[] {
+ AbstractCar.class, CombiCar.class, SportCar.class, Bike.class
+ };
+ }
+}
Property changes on: search/trunk/src/test/java/org/hibernate/search/test/query/criteria/MixedCriteriaTest.java
___________________________________________________________________
Name: svn:executable
+ *
Name: svn:keywords
+ Id
Added: search/trunk/src/test/java/org/hibernate/search/test/query/criteria/SportCar.java
===================================================================
--- search/trunk/src/test/java/org/hibernate/search/test/query/criteria/SportCar.java (rev 0)
+++ search/trunk/src/test/java/org/hibernate/search/test/query/criteria/SportCar.java 2009-05-14 15:33:16 UTC (rev 16566)
@@ -0,0 +1,13 @@
+//$Id$
+package org.hibernate.search.test.query.criteria;
+
+import javax.persistence.DiscriminatorValue;
+import javax.persistence.Entity;
+
+import org.hibernate.search.annotations.Indexed;
+
+@Entity
+@DiscriminatorValue(value = "Sport")
+@Indexed
+public class SportCar extends AbstractCar {
+}
Property changes on: search/trunk/src/test/java/org/hibernate/search/test/query/criteria/SportCar.java
___________________________________________________________________
Name: svn:executable
+ *
Name: svn:keywords
+ Id
15 years, 6 months
Hibernate SVN: r16565 - core/branches/Branch_3_2/src/org/hibernate/dialect.
by hibernate-commits@lists.jboss.org
Author: steve.ebersole(a)jboss.com
Date: 2009-05-13 18:48:45 -0400 (Wed, 13 May 2009)
New Revision: 16565
Modified:
core/branches/Branch_3_2/src/org/hibernate/dialect/Oracle10gDialect.java
core/branches/Branch_3_2/src/org/hibernate/dialect/Oracle8iDialect.java
Log:
HHH-3912 - Change for HHH-3159 causes InstantiationException
Modified: core/branches/Branch_3_2/src/org/hibernate/dialect/Oracle10gDialect.java
===================================================================
--- core/branches/Branch_3_2/src/org/hibernate/dialect/Oracle10gDialect.java 2009-05-13 22:48:28 UTC (rev 16564)
+++ core/branches/Branch_3_2/src/org/hibernate/dialect/Oracle10gDialect.java 2009-05-13 22:48:45 UTC (rev 16565)
@@ -24,13 +24,4 @@
public JoinFragment createOuterJoinFragment() {
return new ANSIJoinFragment();
}
-
- /*
- * The package "oracle.jdbc.driver" was retired in 9.0.1 but works fine up
- * through 10g. So as not to mess with 9i, we're changing it in 10g -- we
- * may not need an 11g Dialect at all.
- */
- String getOracleTypesClassName() {
- return "oracle.jdbc.OracleTypes";
- }
}
\ No newline at end of file
Modified: core/branches/Branch_3_2/src/org/hibernate/dialect/Oracle8iDialect.java
===================================================================
--- core/branches/Branch_3_2/src/org/hibernate/dialect/Oracle8iDialect.java 2009-05-13 22:48:28 UTC (rev 16564)
+++ core/branches/Branch_3_2/src/org/hibernate/dialect/Oracle8iDialect.java 2009-05-13 22:48:45 UTC (rev 16565)
@@ -359,24 +359,48 @@
}
};
-
- String getOracleTypesClassName() {
- return "oracle.jdbc.driver.OracleTypes";
+
+ public static final String ORACLE_TYPES_CLASS_NAME = "oracle.jdbc.OracleTypes";
+ public static final String DEPRECATED_ORACLE_TYPES_CLASS_NAME = "oracle.jdbc.driver.OracleTypes";
+
+ public static final int INIT_ORACLETYPES_CURSOR_VALUE = -99;
+
+ // not final-static to avoid possible classcast exceptions if using different oracle drivers.
+ private int oracleCursorTypeSqlType = INIT_ORACLETYPES_CURSOR_VALUE;
+
+ public int getOracleCursorTypeSqlType() {
+ if ( oracleCursorTypeSqlType == INIT_ORACLETYPES_CURSOR_VALUE ) {
+ // todo : is there really any reason to kkeep trying if this fails once?
+ oracleCursorTypeSqlType = extractOracleCursorTypeValue();
+ }
+ return oracleCursorTypeSqlType;
}
- // not final-static to avoid possible classcast exceptions if using different oracle drivers.
- int oracletypes_cursor_value = 0;
- public int registerResultSetOutParameter(java.sql.CallableStatement statement,int col) throws SQLException {
- if(oracletypes_cursor_value==0) {
+ protected int extractOracleCursorTypeValue() {
+ Class oracleTypesClass;
+ try {
+ oracleTypesClass = ReflectHelper.classForName( ORACLE_TYPES_CLASS_NAME );
+ }
+ catch ( ClassNotFoundException cnfe ) {
try {
- Class types = ReflectHelper.classForName(getOracleTypesClassName());
- oracletypes_cursor_value = types.getField("CURSOR").getInt(types.newInstance());
- } catch (Exception se) {
- throw new HibernateException("Problem while trying to load or access OracleTypes.CURSOR value",se);
+ oracleTypesClass = ReflectHelper.classForName( DEPRECATED_ORACLE_TYPES_CLASS_NAME );
}
+ catch ( ClassNotFoundException e ) {
+ throw new HibernateException( "Unable to locate OracleTypes class", e );
+ }
}
+
+ try {
+ return oracleTypesClass.getField( "CURSOR" ).getInt( null );
+ }
+ catch ( Exception se ) {
+ throw new HibernateException( "Unable to access OracleTypes.CURSOR value", se );
+ }
+ }
+
+ public int registerResultSetOutParameter(CallableStatement statement, int col) throws SQLException {
// register the type of the out param - an Oracle specific type
- statement.registerOutParameter(col, oracletypes_cursor_value);
+ statement.registerOutParameter( col, getOracleCursorTypeSqlType() );
col++;
return col;
}
15 years, 6 months
Hibernate SVN: r16564 - core/branches/Branch_3_3/core/src/main/java/org/hibernate/dialect.
by hibernate-commits@lists.jboss.org
Author: steve.ebersole(a)jboss.com
Date: 2009-05-13 18:48:28 -0400 (Wed, 13 May 2009)
New Revision: 16564
Modified:
core/branches/Branch_3_3/core/src/main/java/org/hibernate/dialect/Oracle10gDialect.java
core/branches/Branch_3_3/core/src/main/java/org/hibernate/dialect/Oracle8iDialect.java
Log:
HHH-3912 - Change for HHH-3159 causes InstantiationException
Modified: core/branches/Branch_3_3/core/src/main/java/org/hibernate/dialect/Oracle10gDialect.java
===================================================================
--- core/branches/Branch_3_3/core/src/main/java/org/hibernate/dialect/Oracle10gDialect.java 2009-05-13 22:43:29 UTC (rev 16563)
+++ core/branches/Branch_3_3/core/src/main/java/org/hibernate/dialect/Oracle10gDialect.java 2009-05-13 22:48:28 UTC (rev 16564)
@@ -47,13 +47,4 @@
public JoinFragment createOuterJoinFragment() {
return new ANSIJoinFragment();
}
-
- /*
- * The package "oracle.jdbc.driver" was retired in 9.0.1 but works fine up
- * through 10g. So as not to mess with 9i, we're changing it in 10g -- we
- * may not need an 11g Dialect at all.
- */
- String getOracleTypesClassName() {
- return "oracle.jdbc.OracleTypes";
- }
}
Modified: core/branches/Branch_3_3/core/src/main/java/org/hibernate/dialect/Oracle8iDialect.java
===================================================================
--- core/branches/Branch_3_3/core/src/main/java/org/hibernate/dialect/Oracle8iDialect.java 2009-05-13 22:43:29 UTC (rev 16563)
+++ core/branches/Branch_3_3/core/src/main/java/org/hibernate/dialect/Oracle8iDialect.java 2009-05-13 22:48:28 UTC (rev 16564)
@@ -387,24 +387,48 @@
}
};
-
- String getOracleTypesClassName() {
- return "oracle.jdbc.driver.OracleTypes";
+
+ public static final String ORACLE_TYPES_CLASS_NAME = "oracle.jdbc.OracleTypes";
+ public static final String DEPRECATED_ORACLE_TYPES_CLASS_NAME = "oracle.jdbc.driver.OracleTypes";
+
+ public static final int INIT_ORACLETYPES_CURSOR_VALUE = -99;
+
+ // not final-static to avoid possible classcast exceptions if using different oracle drivers.
+ private int oracleCursorTypeSqlType = INIT_ORACLETYPES_CURSOR_VALUE;
+
+ public int getOracleCursorTypeSqlType() {
+ if ( oracleCursorTypeSqlType == INIT_ORACLETYPES_CURSOR_VALUE ) {
+ // todo : is there really any reason to kkeep trying if this fails once?
+ oracleCursorTypeSqlType = extractOracleCursorTypeValue();
+ }
+ return oracleCursorTypeSqlType;
}
- // not final-static to avoid possible classcast exceptions if using different oracle drivers.
- int oracletypes_cursor_value = 0;
- public int registerResultSetOutParameter(java.sql.CallableStatement statement,int col) throws SQLException {
- if(oracletypes_cursor_value==0) {
+ protected int extractOracleCursorTypeValue() {
+ Class oracleTypesClass;
+ try {
+ oracleTypesClass = ReflectHelper.classForName( ORACLE_TYPES_CLASS_NAME );
+ }
+ catch ( ClassNotFoundException cnfe ) {
try {
- Class types = ReflectHelper.classForName(getOracleTypesClassName());
- oracletypes_cursor_value = types.getField("CURSOR").getInt(types.newInstance());
- } catch (Exception se) {
- throw new HibernateException("Problem while trying to load or access OracleTypes.CURSOR value",se);
+ oracleTypesClass = ReflectHelper.classForName( DEPRECATED_ORACLE_TYPES_CLASS_NAME );
}
+ catch ( ClassNotFoundException e ) {
+ throw new HibernateException( "Unable to locate OracleTypes class", e );
+ }
}
+
+ try {
+ return oracleTypesClass.getField( "CURSOR" ).getInt( null );
+ }
+ catch ( Exception se ) {
+ throw new HibernateException( "Unable to access OracleTypes.CURSOR value", se );
+ }
+ }
+
+ public int registerResultSetOutParameter(CallableStatement statement, int col) throws SQLException {
// register the type of the out param - an Oracle specific type
- statement.registerOutParameter(col, oracletypes_cursor_value);
+ statement.registerOutParameter( col, getOracleCursorTypeSqlType() );
col++;
return col;
}
15 years, 6 months
Hibernate SVN: r16563 - core/trunk/core/src/main/java/org/hibernate/dialect.
by hibernate-commits@lists.jboss.org
Author: steve.ebersole(a)jboss.com
Date: 2009-05-13 18:43:29 -0400 (Wed, 13 May 2009)
New Revision: 16563
Modified:
core/trunk/core/src/main/java/org/hibernate/dialect/Oracle10gDialect.java
core/trunk/core/src/main/java/org/hibernate/dialect/Oracle8iDialect.java
Log:
HHH-3912 - Change for HHH-3159 causes InstantiationException
Modified: core/trunk/core/src/main/java/org/hibernate/dialect/Oracle10gDialect.java
===================================================================
--- core/trunk/core/src/main/java/org/hibernate/dialect/Oracle10gDialect.java 2009-05-13 22:22:59 UTC (rev 16562)
+++ core/trunk/core/src/main/java/org/hibernate/dialect/Oracle10gDialect.java 2009-05-13 22:43:29 UTC (rev 16563)
@@ -47,13 +47,4 @@
public JoinFragment createOuterJoinFragment() {
return new ANSIJoinFragment();
}
-
- /*
- * The package "oracle.jdbc.driver" was retired in 9.0.1 but works fine up
- * through 10g. So as not to mess with 9i, we're changing it in 10g -- we
- * may not need an 11g Dialect at all.
- */
- String getOracleTypesClassName() {
- return "oracle.jdbc.OracleTypes";
- }
}
Modified: core/trunk/core/src/main/java/org/hibernate/dialect/Oracle8iDialect.java
===================================================================
--- core/trunk/core/src/main/java/org/hibernate/dialect/Oracle8iDialect.java 2009-05-13 22:22:59 UTC (rev 16562)
+++ core/trunk/core/src/main/java/org/hibernate/dialect/Oracle8iDialect.java 2009-05-13 22:43:29 UTC (rev 16563)
@@ -390,24 +390,48 @@
}
};
-
- String getOracleTypesClassName() {
- return "oracle.jdbc.driver.OracleTypes";
+
+ public static final String ORACLE_TYPES_CLASS_NAME = "oracle.jdbc.OracleTypes";
+ public static final String DEPRECATED_ORACLE_TYPES_CLASS_NAME = "oracle.jdbc.driver.OracleTypes";
+
+ public static final int INIT_ORACLETYPES_CURSOR_VALUE = -99;
+
+ // not final-static to avoid possible classcast exceptions if using different oracle drivers.
+ private int oracleCursorTypeSqlType = INIT_ORACLETYPES_CURSOR_VALUE;
+
+ public int getOracleCursorTypeSqlType() {
+ if ( oracleCursorTypeSqlType == INIT_ORACLETYPES_CURSOR_VALUE ) {
+ // todo : is there really any reason to kkeep trying if this fails once?
+ oracleCursorTypeSqlType = extractOracleCursorTypeValue();
+ }
+ return oracleCursorTypeSqlType;
}
- // not final-static to avoid possible classcast exceptions if using different oracle drivers.
- int oracletypes_cursor_value = 0;
- public int registerResultSetOutParameter(java.sql.CallableStatement statement,int col) throws SQLException {
- if(oracletypes_cursor_value==0) {
+ protected int extractOracleCursorTypeValue() {
+ Class oracleTypesClass;
+ try {
+ oracleTypesClass = ReflectHelper.classForName( ORACLE_TYPES_CLASS_NAME );
+ }
+ catch ( ClassNotFoundException cnfe ) {
try {
- Class types = ReflectHelper.classForName(getOracleTypesClassName());
- oracletypes_cursor_value = types.getField("CURSOR").getInt(types.newInstance());
- } catch (Exception se) {
- throw new HibernateException("Problem while trying to load or access OracleTypes.CURSOR value",se);
+ oracleTypesClass = ReflectHelper.classForName( DEPRECATED_ORACLE_TYPES_CLASS_NAME );
}
+ catch ( ClassNotFoundException e ) {
+ throw new HibernateException( "Unable to locate OracleTypes class", e );
+ }
}
+
+ try {
+ return oracleTypesClass.getField( "CURSOR" ).getInt( null );
+ }
+ catch ( Exception se ) {
+ throw new HibernateException( "Unable to access OracleTypes.CURSOR value", se );
+ }
+ }
+
+ public int registerResultSetOutParameter(CallableStatement statement, int col) throws SQLException {
// register the type of the out param - an Oracle specific type
- statement.registerOutParameter(col, oracletypes_cursor_value);
+ statement.registerOutParameter( col, getOracleCursorTypeSqlType() );
col++;
return col;
}
15 years, 6 months
Hibernate SVN: r16562 - in core/branches/Branch_3_2_4_SP1_CP: test/org/hibernate/test/cascade and 2 other directories.
by hibernate-commits@lists.jboss.org
Author: gbadner
Date: 2009-05-13 18:22:59 -0400 (Wed, 13 May 2009)
New Revision: 16562
Added:
core/branches/Branch_3_2_4_SP1_CP/src/org/hibernate/event/def/CopyCache.java
core/branches/Branch_3_2_4_SP1_CP/test/org/hibernate/test/cascade/circle/
core/branches/Branch_3_2_4_SP1_CP/test/org/hibernate/test/cascade/circle/CascadeMergeToChildBeforeParent.hbm.xml
core/branches/Branch_3_2_4_SP1_CP/test/org/hibernate/test/cascade/circle/CascadeMergeToChildBeforeParentTest.java
core/branches/Branch_3_2_4_SP1_CP/test/org/hibernate/test/cascade/circle/MultiPathCircleCascade.hbm.xml
core/branches/Branch_3_2_4_SP1_CP/test/org/hibernate/test/cascade/circle/MultiPathCircleCascadeTest.java
core/branches/Branch_3_2_4_SP1_CP/test/org/hibernate/test/cascade/circle/Node.java
core/branches/Branch_3_2_4_SP1_CP/test/org/hibernate/test/cascade/circle/Route.java
core/branches/Branch_3_2_4_SP1_CP/test/org/hibernate/test/cascade/circle/Tour.java
core/branches/Branch_3_2_4_SP1_CP/test/org/hibernate/test/cascade/circle/Transport.java
core/branches/Branch_3_2_4_SP1_CP/test/org/hibernate/test/cascade/circle/Vehicle.java
Modified:
core/branches/Branch_3_2_4_SP1_CP/src/org/hibernate/event/def/DefaultMergeEventListener.java
core/branches/Branch_3_2_4_SP1_CP/test/org/hibernate/test/cascade/A.java
core/branches/Branch_3_2_4_SP1_CP/test/org/hibernate/test/cascade/CascadeSuite.java
core/branches/Branch_3_2_4_SP1_CP/test/org/hibernate/test/cascade/G.java
core/branches/Branch_3_2_4_SP1_CP/test/org/hibernate/test/cascade/H.java
core/branches/Branch_3_2_4_SP1_CP/test/org/hibernate/test/cascade/MultiPathCascadeTest.java
core/branches/Branch_3_2_4_SP1_CP/test/org/hibernate/test/ops/MergeTest.java
Log:
JBPAPP-1797 HHH-3810 : Transient entities can be inserted twice on merge
Added: core/branches/Branch_3_2_4_SP1_CP/src/org/hibernate/event/def/CopyCache.java
===================================================================
--- core/branches/Branch_3_2_4_SP1_CP/src/org/hibernate/event/def/CopyCache.java (rev 0)
+++ core/branches/Branch_3_2_4_SP1_CP/src/org/hibernate/event/def/CopyCache.java 2009-05-13 22:22:59 UTC (rev 16562)
@@ -0,0 +1,244 @@
+//$Id: $
+/*
+ * Hibernate, Relational Persistence for Idiomatic Java
+ *
+ * Copyright (c) 2008, Red Hat Middleware LLC 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 Middleware LLC.
+ *
+ * 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.event.def;
+
+import java.util.Map;
+import java.util.Set;
+import java.util.Iterator;
+import java.util.Collection;
+
+import org.hibernate.util.IdentityMap;
+import org.hibernate.AssertionFailure;
+
+/**
+ * CopyCache is intended to be the Map implementation used by
+ * {@link DefaultMergeEventListener} to keep track of entities and their copies
+ * being merged into the session. This implementation also tracks whether a
+ * an entity in the CopyCache is included in the merge. This allows a
+ * an entity and its copy to be added to a CopyCache before merge has cascaded
+ * to that entity.
+ *
+ * @author Gail Badner
+ */
+class CopyCache implements Map {
+ private Map entityToCopyMap = IdentityMap.instantiate(10);
+ // key is an entity involved with the merge;
+ // value can be either a copy of the entity or the entity itself
+
+ private Map entityToIncludeInMergeFlagMap = IdentityMap.instantiate(10);
+ // key is an entity involved with the merge;
+ // value is a flag indicating if the entity is included in the merge
+
+ /**
+ * Clears the CopyCache.
+ */
+ public void clear() {
+ entityToCopyMap.clear();
+ entityToIncludeInMergeFlagMap.clear();
+ }
+
+ /**
+ * Returns true if this CopyCache contains a mapping for the specified entity.
+ * @param entity must be non-null
+ * @return true if this CopyCache contains a mapping for the specified entity
+ * @throws NullPointerException if entity is null
+ */
+ public boolean containsKey(Object entity) {
+ if ( entity == null ) {
+ throw new NullPointerException( "null entities are not supported by " + getClass().getName() );
+ }
+ return entityToCopyMap.containsKey( entity );
+ }
+
+ /**
+ * Returns true if this CopyCache maps one or more entities to the specified copy.
+ * @param copy must be non-null
+ * @return true if this CopyCache maps one or more entities to the specified copy
+ * @throws NullPointerException if copy is null
+ */
+ public boolean containsValue(Object copy) {
+ if ( copy == null ) {
+ throw new NullPointerException( "null copies are not supported by " + getClass().getName() );
+ }
+ return entityToCopyMap.containsValue( copy );
+ }
+
+ /**
+ * Returns a set view of the entity-to-copy mappings contained in this CopyCache.
+ * @return set view of the entity-to-copy mappings contained in this CopyCache
+ */
+ public Set entrySet() {
+ return entityToCopyMap.entrySet();
+ }
+
+ /**
+ * Returns the copy to which this CopyCache maps the specified entity.
+ * @param entity must be non-null
+ * @return the copy to which this CopyCache maps the specified entity
+ * @throws NullPointerException if entity is null
+ */
+ public Object get(Object entity) {
+ if ( entity == null ) {
+ throw new NullPointerException( "null entities are not supported by " + getClass().getName() );
+ }
+ return entityToCopyMap.get( entity );
+ }
+
+ /**
+ * Returns true if this CopyCache contains no entity-copy mappings.
+ * @return true if this CopyCache contains no entity-copy mappings
+ */
+ public boolean isEmpty() {
+ return entityToCopyMap.isEmpty();
+ }
+
+ /**
+ * Returns a set view of the entities contained in this CopyCache
+ * @return a set view of the entities contained in this CopyCache
+ */
+ public Set keySet() {
+ return entityToCopyMap.keySet();
+ }
+
+ /**
+ * Associates the specified entity with the specified copy in this CopyCache;
+ * @param entity must be non-null
+ * @param copy must be non- null
+ * @return previous copy associated with specified entity, or null if
+ * there was no mapping for entity.
+ * @throws NullPointerException if entity or copy is null
+ */
+ public Object put(Object entity, Object copy) {
+ if ( entity == null || copy == null ) {
+ throw new NullPointerException( "null entities and copies are not supported by " + getClass().getName() );
+ }
+ entityToIncludeInMergeFlagMap.put( entity, Boolean.FALSE );
+ return entityToCopyMap.put( entity, copy );
+ }
+
+ /**
+ * Associates the specified entity with the specified copy in this CopyCache;
+ * @param entity must be non-null
+ * @param copy must be non- null
+ * @param isIncludedInMerge indicates if the entity is included in merge
+ *
+ * @return previous copy associated with specified entity, or null if
+ * there was no mapping for entity.
+ * @throws NullPointerException if entity or copy is null
+ */
+ /* package-private */ Object put(Object entity, Object copy, boolean isIncludedInMerge) {
+ if ( entity == null || copy == null ) {
+ throw new NullPointerException( "null entities and copies are not supported by " + getClass().getName() );
+ }
+ entityToIncludeInMergeFlagMap.put( entity, Boolean.valueOf( isIncludedInMerge ) );
+ return entityToCopyMap.put( entity, copy );
+ }
+
+ /**
+ * Copies all of the mappings from the specified map to this CopyCache
+ * @param map keys and values must be non-null
+ * @throws NullPointerException if any map keys or values are null
+ */
+ public void putAll(Map map) {
+ for ( Iterator it=map.entrySet().iterator(); it.hasNext(); ) {
+ Map.Entry entry = ( Map.Entry ) it.next();
+ if ( entry.getKey() == null || entry.getValue() == null ) {
+ throw new NullPointerException( "null entities and copies are not supported by " + getClass().getName() );
+ }
+ entityToCopyMap.put( entry.getKey(), entry.getValue() );
+ entityToIncludeInMergeFlagMap.put( entry.getKey(), Boolean.FALSE );
+ }
+ }
+
+ /**
+ * Removes the mapping for this entity from this CopyCache if it is present
+ * @param entity must be non-null
+ * @return previous value associated with specified entity, or null if there was no mapping for entity.
+ * @throws NullPointerException if entity is null
+ */
+ public Object remove(Object entity) {
+ if ( entity == null ) {
+ throw new NullPointerException( "null entities are not supported by " + getClass().getName() );
+ }
+ entityToIncludeInMergeFlagMap.remove( entity );
+ return entityToCopyMap.remove( entity );
+ }
+
+ /**
+ * Returns the number of entity-copy mappings in this CopyCache
+ * @return the number of entity-copy mappings in this CopyCache
+ */
+ public int size() {
+ return entityToCopyMap.size();
+ }
+
+ /**
+ * Returns a collection view of the entity copies contained in this CopyCache.
+ * @return a collection view of the entity copies contained in this CopyCache
+ */
+ public Collection values() {
+ return entityToCopyMap.values();
+ }
+
+ /**
+ * Returns true if the specified entity is included in the merge.
+ * @param entity must be non-null
+ * @return true if the specified entity is included in the merge.
+ * @throws NullPointerException if entity is null
+ */
+ public boolean isIncludedInMerge(Object entity) {
+ if ( entity == null ) {
+ throw new NullPointerException( "null entities are not supported by " + getClass().getName() );
+ }
+ return ( ( Boolean ) entityToIncludeInMergeFlagMap.get( entity ) ).booleanValue();
+ }
+
+ /**
+ * Set flag to indicate if an entity is included in the merge.
+ * @param entity must be non-null and this CopyCache must contain a mapping for this entity
+ * @return true if the specified entity is included in the merge
+ * @throws NullPointerException if entity is null
+ * @throws AssertionFailure if this CopyCache does not contain a mapping for the specified entity
+ */
+ /* package-private */ void setIncludedInMerge(Object entity, boolean isIncludedInMerge) {
+ if ( entity == null ) {
+ throw new NullPointerException( "null entities are not supported by " + getClass().getName() );
+ }
+ if ( ! entityToIncludeInMergeFlagMap.containsKey( entity ) ||
+ ! entityToCopyMap.containsKey( entity ) ) {
+ throw new AssertionFailure( "called CopyCache.setInMergeProcess() for entity not found in CopyCache" );
+ }
+ entityToIncludeInMergeFlagMap.put( entity, Boolean.valueOf( isIncludedInMerge ) );
+ }
+
+ /**
+ * Returns the copy-entity mappings
+ * @return the copy-entity mappings
+ */
+ public Map getMergeMap() {
+ return IdentityMap.invert( entityToCopyMap );
+ }
+}
\ No newline at end of file
Modified: core/branches/Branch_3_2_4_SP1_CP/src/org/hibernate/event/def/DefaultMergeEventListener.java
===================================================================
--- core/branches/Branch_3_2_4_SP1_CP/src/org/hibernate/event/def/DefaultMergeEventListener.java 2009-05-13 22:17:08 UTC (rev 16561)
+++ core/branches/Branch_3_2_4_SP1_CP/src/org/hibernate/event/def/DefaultMergeEventListener.java 2009-05-13 22:22:59 UTC (rev 16562)
@@ -28,6 +28,8 @@
import java.io.Serializable;
import java.util.Iterator;
import java.util.Map;
+import java.util.Set;
+import java.util.HashSet;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
@@ -38,6 +40,7 @@
import org.hibernate.StaleObjectStateException;
import org.hibernate.TransientObjectException;
import org.hibernate.WrongClassException;
+import org.hibernate.PropertyValueException;
import org.hibernate.engine.Cascade;
import org.hibernate.engine.CascadingAction;
import org.hibernate.engine.EntityEntry;
@@ -54,7 +57,7 @@
import org.hibernate.proxy.LazyInitializer;
import org.hibernate.type.ForeignKeyDirection;
import org.hibernate.type.TypeFactory;
-import org.hibernate.util.IdentityMap;
+import org.hibernate.type.Type;
/**
* Defines the default copy event listener used by hibernate for copying entities
@@ -66,9 +69,9 @@
implements MergeEventListener {
private static final Log log = LogFactory.getLog(DefaultMergeEventListener.class);
-
+
protected Map getMergeMap(Object anything) {
- return IdentityMap.invert( (Map) anything );
+ return ( ( CopyCache ) anything ).getMergeMap();
}
/**
@@ -78,36 +81,96 @@
* @throws HibernateException
*/
public void onMerge(MergeEvent event) throws HibernateException {
- Map copyCache = IdentityMap.instantiate(10);
+ CopyCache copyCache = new CopyCache();
onMerge( event, copyCache );
- for ( Iterator it=copyCache.values().iterator(); it.hasNext(); ) {
- Object entity = it.next();
- if ( entity instanceof HibernateProxy ) {
- entity = ( (HibernateProxy) entity ).getHibernateLazyInitializer().getImplementation();
+ // TODO: iteratively get transient entities and retry merge until one of the following conditions:
+ // 1) transientCopyCache.size() == 0
+ // 2) transientCopyCache.size() is not decreasing and copyCache.size() is not increasing
+ // TODO: find out if retrying can add entities to copyCache (don't think it can...)
+ // For now, just retry once; throw TransientObjectException if there are still any transient entities
+ Map transientCopyCache = getTransientCopyCache(event, copyCache );
+ if ( transientCopyCache.size() > 0 ) {
+ retryMergeTransientEntities( event, transientCopyCache, copyCache );
+ // find any entities that are still transient after retry
+ transientCopyCache = getTransientCopyCache(event, copyCache );
+ if ( transientCopyCache.size() > 0 ) {
+ Set transientEntityNames = new HashSet();
+ for( Iterator it=transientCopyCache.keySet().iterator(); it.hasNext(); ) {
+ Object transientEntity = it.next();
+ String transientEntityName = event.getSession().guessEntityName( transientEntity );
+ transientEntityNames.add( transientEntityName );
+ log.trace( "transient instance could not be processed by merge: " +
+ transientEntityName + "[" + transientEntity + "]" );
+ }
+ throw new TransientObjectException(
+ "one or more objects is an unsaved transient instance - save transient instance(s) before merging: " +
+ transientEntityNames );
}
- EntityEntry entry = event.getSession().getPersistenceContext().getEntry( entity );
- if ( entry == null ) {
+ }
+ copyCache.clear();
+ copyCache = null;
+ }
+
+ protected CopyCache getTransientCopyCache(MergeEvent event, CopyCache copyCache) {
+ CopyCache transientCopyCache = new CopyCache();
+ for ( Iterator it=copyCache.entrySet().iterator(); it.hasNext(); ) {
+ Map.Entry mapEntry = ( Map.Entry ) it.next();
+ Object entity = mapEntry.getKey();
+ Object copy = mapEntry.getValue();
+ if ( copy instanceof HibernateProxy ) {
+ copy = ( (HibernateProxy) copy ).getHibernateLazyInitializer().getImplementation();
+ }
+ EntityEntry copyEntry = event.getSession().getPersistenceContext().getEntry( copy );
+ if ( copyEntry == null ) {
+ // entity name will not be available for non-POJO entities
+ // TODO: cache the entity name somewhere so that it is available to this exception
+ log.trace( "transient instance could not be processed by merge: " +
+ event.getSession().guessEntityName( copy ) + "[" + entity + "]" );
throw new TransientObjectException(
- "object references an unsaved transient instance - save the transient instance before merging: " +
- event.getSession().guessEntityName( entity )
+ "object is an unsaved transient instance - save the transient instance before merging: " +
+ event.getSession().guessEntityName( copy )
);
- // TODO: cache the entity name somewhere so that it is available to this exception
- // entity name will not be available for non-POJO entities
}
- if ( entry.getStatus() != Status.MANAGED && entry.getStatus() != Status.READ_ONLY ) {
- throw new AssertionFailure( "Merged entity does not have status set to MANAGED or READ_ONLY; "+entry+" status="+entry.getStatus() );
+ else if ( copyEntry.getStatus() == Status.SAVING ) {
+ transientCopyCache.put( entity, copy, copyCache.isIncludedInMerge( entity ) );
}
+ else if ( copyEntry.getStatus() != Status.MANAGED && copyEntry.getStatus() != Status.READ_ONLY ) {
+ throw new AssertionFailure( "Merged entity does not have status set to MANAGED or READ_ONLY; "+copy+" status="+copyEntry.getStatus() );
+ }
}
+ return transientCopyCache;
}
+ protected void retryMergeTransientEntities(MergeEvent event, Map transientCopyCache, CopyCache copyCache) {
+ // TODO: The order in which entities are saved may matter (e.g., a particular transient entity
+ // may need to be saved before other transient entities can be saved;
+ // Keep retrying the batch of transient entities until either:
+ // 1) there are no transient entities left in transientCopyCache
+ // or 2) no transient entities were saved in the last batch
+ // For now, just run through the transient entities and retry the merge
+ for ( Iterator it=transientCopyCache.entrySet().iterator(); it.hasNext(); ) {
+ Map.Entry mapEntry = ( Map.Entry ) it.next();
+ Object entity = mapEntry.getKey();
+ Object copy = transientCopyCache.get( entity );
+ EntityEntry copyEntry = event.getSession().getPersistenceContext().getEntry( copy );
+ if ( entity == event.getEntity() ) {
+ mergeTransientEntity( entity, copyEntry.getEntityName(), event.getRequestedId(), event.getSession(), copyCache);
+ }
+ else {
+ mergeTransientEntity( entity, copyEntry.getEntityName(), copyEntry.getId(), event.getSession(), copyCache);
+ }
+ }
+ }
+
/**
* Handle the given merge event.
*
* @param event The merge event to be handled.
* @throws HibernateException
*/
- public void onMerge(MergeEvent event, Map copyCache) throws HibernateException {
+ public void onMerge(MergeEvent event, Map copiedAlready) throws HibernateException {
+ final CopyCache copyCache = ( CopyCache ) copiedAlready;
final EventSource source = event.getSession();
final Object original = event.getOriginal();
@@ -128,13 +191,17 @@
else {
entity = original;
}
-
- if ( copyCache.containsKey(entity) &&
- source.getContextEntityIdentifier( copyCache.get( entity ) ) != null ) {
- log.trace("already merged");
- event.setResult(entity);
+
+ if ( copyCache.containsKey( entity ) &&
+ ( copyCache.isIncludedInMerge( entity ) ) ) {
+ log.trace("already in merge process");
+ event.setResult( entity );
}
else {
+ if ( copyCache.containsKey( entity ) ) {
+ log.trace("already in copyCache; setting in merge process");
+ copyCache.setIncludedInMerge( entity, true );
+ }
event.setEntity( entity );
int entityState = -1;
@@ -176,7 +243,7 @@
default: //DELETED
throw new ObjectDeletedException(
"deleted instance passed to merge",
- null,
+ null,
getLoggableName( event.getEntityName(), entity )
);
}
@@ -194,15 +261,15 @@
final Object entity = event.getEntity();
final EventSource source = event.getSession();
final EntityPersister persister = source.getEntityPersister( event.getEntityName(), entity );
+
+ ( ( CopyCache ) copyCache ).put( entity, entity, true ); //before cascade!
- copyCache.put(entity, entity); //before cascade!
-
cascadeOnMerge(source, persister, entity, copyCache);
copyValues(persister, entity, entity, source, copyCache);
event.setResult(entity);
}
-
+
protected void entityIsTransient(MergeEvent event, Map copyCache) {
log.trace("merging transient instance");
@@ -212,7 +279,16 @@
final EntityPersister persister = source.getEntityPersister( event.getEntityName(), entity );
final String entityName = persister.getEntityName();
-
+
+ event.setResult( mergeTransientEntity( entity, entityName, event.getRequestedId(), source, copyCache ) );
+ }
+
+ protected Object mergeTransientEntity(Object entity, String entityName, Serializable requestedId, EventSource source, Map copyCache) {
+
+ log.trace("merging transient instance");
+
+ final EntityPersister persister = source.getEntityPersister( entityName, entity );
+
final Serializable id = persister.hasIdentifierProperty() ?
persister.getIdentifier( entity, source.getEntityMode() ) :
null;
@@ -220,7 +296,7 @@
persister.setIdentifier( copyCache.get( entity ), id, source.getEntityMode() );
}
else {
- copyCache.put(entity, persister.instantiate( id, source.getEntityMode() ) ); //before cascade!
+ ( ( CopyCache ) copyCache ).put( entity, persister.instantiate( id, source.getEntityMode() ), true ); //before cascade!
//TODO: should this be Session.instantiate(Persister, ...)?
}
final Object copy = copyCache.get( entity );
@@ -230,25 +306,54 @@
//cascadeOnMerge(event, persister, entity, copyCache, Cascades.CASCADE_BEFORE_MERGE);
super.cascadeBeforeSave(source, persister, entity, copyCache);
copyValues(persister, entity, copy, source, copyCache, ForeignKeyDirection.FOREIGN_KEY_FROM_PARENT);
-
- //this bit is only *really* absolutely necessary for handling
- //requestedId, but is also good if we merge multiple object
- //graphs, since it helps ensure uniqueness
- final Serializable requestedId = event.getRequestedId();
- if (requestedId==null) {
- saveWithGeneratedId( copy, entityName, copyCache, source, false );
+
+ try {
+ //this bit is only *really* absolutely necessary for handling
+ //requestedId, but is also good if we merge multiple object
+ //graphs, since it helps ensure uniqueness
+ if (requestedId==null) {
+ saveWithGeneratedId( copy, entityName, copyCache, source, false );
+ }
+ else {
+ saveWithRequestedId( copy, requestedId, entityName, copyCache, source );
+ }
}
- else {
- saveWithRequestedId( copy, requestedId, entityName, copyCache, source );
+ catch (PropertyValueException ex) {
+ String propertyName = ex.getPropertyName();
+ Object propertyFromCopy = persister.getPropertyValue( copy, propertyName, source.getEntityMode() );
+ Object propertyFromEntity = persister.getPropertyValue( entity, propertyName, source.getEntityMode() );
+ Type propertyType = persister.getPropertyType( propertyName );
+ EntityEntry copyEntry = source.getPersistenceContext().getEntry( copy );
+ if ( propertyFromCopy == null || ! propertyType.isEntityType() ) {
+ log.trace( "property '" + copyEntry.getEntityName() + "." + propertyName +
+ "' is null or not an entity; " + propertyName + " =["+propertyFromCopy+"]");
+ throw ex;
+ }
+ if ( ! copyCache.containsKey( propertyFromEntity ) ) {
+ log.trace( "property '" + copyEntry.getEntityName() + "." + propertyName +
+ "' from original entity is not in copyCache; " + propertyName + " =["+propertyFromEntity+"]");
+ throw ex;
+ }
+ if ( ( ( CopyCache ) copyCache ).isIncludedInMerge( propertyFromEntity ) ) {
+ log.trace( "property '" + copyEntry.getEntityName() + "." + propertyName +
+ "' from original entity is in copyCache and is in the process of being merged; " +
+ propertyName + " =["+propertyFromEntity+"]");
+ }
+ else {
+ log.trace( "property '" + copyEntry.getEntityName() + "." + propertyName +
+ "' from original entity is in copyCache and is not in the process of being merged; " +
+ propertyName + " =["+propertyFromEntity+"]");
+ }
+ // continue...; we'll find out if it ends up not getting saved later
}
-
- // cascade first, so that all unsaved objects get their
+
+ // cascade first, so that all unsaved objects get their
// copy created before we actually copy
super.cascadeAfterSave(source, persister, entity, copyCache);
copyValues(persister, entity, copy, source, copyCache, ForeignKeyDirection.FOREIGN_KEY_TO_PARENT);
-
- event.setResult(copy);
+ return copy;
+
}
protected void entityIsDetached(MergeEvent event, Map copyCache) {
@@ -260,7 +365,7 @@
final EntityPersister persister = source.getEntityPersister( event.getEntityName(), entity );
final String entityName = persister.getEntityName();
-
+
Serializable id = event.getRequestedId();
if ( id == null ) {
id = persister.getIdentifier( entity, source.getEntityMode() );
@@ -293,7 +398,7 @@
entityIsTransient(event, copyCache);
}
else {
- copyCache.put(entity, result); //before cascade!
+ ( ( CopyCache ) copyCache ).put( entity, result, true ); //before cascade!
final Object target = source.getPersistenceContext().unproxy(result);
if ( target == entity ) {
@@ -381,7 +486,7 @@
return entry.isExistsInDatabase();
}
}
-
+
protected void copyValues(
final EntityPersister persister,
final Object entity,
@@ -389,7 +494,6 @@
final SessionImplementor source,
final Map copyCache
) {
-
final Object[] copiedValues = TypeFactory.replace(
persister.getPropertyValues( entity, source.getEntityMode() ),
persister.getPropertyValues( target, source.getEntityMode() ),
Modified: core/branches/Branch_3_2_4_SP1_CP/test/org/hibernate/test/cascade/A.java
===================================================================
--- core/branches/Branch_3_2_4_SP1_CP/test/org/hibernate/test/cascade/A.java 2009-05-13 22:17:08 UTC (rev 16561)
+++ core/branches/Branch_3_2_4_SP1_CP/test/org/hibernate/test/cascade/A.java 2009-05-13 22:22:59 UTC (rev 16562)
@@ -1,4 +1,28 @@
-// $Id$
+//$Id: $
+/*
+ * Hibernate, Relational Persistence for Idiomatic Java
+ *
+ * Copyright (c) 2008, Red Hat Middleware LLC 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 Middleware LLC.
+ *
+ * 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;
Modified: core/branches/Branch_3_2_4_SP1_CP/test/org/hibernate/test/cascade/CascadeSuite.java
===================================================================
--- core/branches/Branch_3_2_4_SP1_CP/test/org/hibernate/test/cascade/CascadeSuite.java 2009-05-13 22:17:08 UTC (rev 16561)
+++ core/branches/Branch_3_2_4_SP1_CP/test/org/hibernate/test/cascade/CascadeSuite.java 2009-05-13 22:22:59 UTC (rev 16562)
@@ -1,5 +1,34 @@
+//$Id: $
+/*
+ * Hibernate, Relational Persistence for Idiomatic Java
+ *
+ * Copyright (c) 2008, Red Hat Middleware LLC 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 Middleware LLC.
+ *
+ * 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;
+import org.hibernate.test.cascade.circle.MultiPathCircleCascadeTest;
+import org.hibernate.test.cascade.circle.CascadeMergeToChildBeforeParentTest;
+
import junit.framework.Test;
import junit.framework.TestSuite;
@@ -12,6 +41,8 @@
TestSuite suite = new TestSuite( "Cascade tests" );
suite.addTest( RefreshTest.suite() );
suite.addTest( MultiPathCascadeTest.suite() );
+ suite.addTest( MultiPathCircleCascadeTest.suite() );
+ suite.addTest( CascadeMergeToChildBeforeParentTest.suite() );
return suite;
}
}
Modified: core/branches/Branch_3_2_4_SP1_CP/test/org/hibernate/test/cascade/G.java
===================================================================
--- core/branches/Branch_3_2_4_SP1_CP/test/org/hibernate/test/cascade/G.java 2009-05-13 22:17:08 UTC (rev 16561)
+++ core/branches/Branch_3_2_4_SP1_CP/test/org/hibernate/test/cascade/G.java 2009-05-13 22:22:59 UTC (rev 16562)
@@ -1,3 +1,29 @@
+//$Id: $
+/*
+ * Hibernate, Relational Persistence for Idiomatic Java
+ *
+ * Copyright (c) 2008, Red Hat Middleware LLC 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 Middleware LLC.
+ *
+ * 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;
import java.util.Set;
Modified: core/branches/Branch_3_2_4_SP1_CP/test/org/hibernate/test/cascade/H.java
===================================================================
--- core/branches/Branch_3_2_4_SP1_CP/test/org/hibernate/test/cascade/H.java 2009-05-13 22:17:08 UTC (rev 16561)
+++ core/branches/Branch_3_2_4_SP1_CP/test/org/hibernate/test/cascade/H.java 2009-05-13 22:22:59 UTC (rev 16562)
@@ -1,6 +1,29 @@
-// $Id$
+//$Id: $
+/*
+ * Hibernate, Relational Persistence for Idiomatic Java
+ *
+ * Copyright (c) 2008, Red Hat Middleware LLC 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 Middleware LLC.
+ *
+ * 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;
import java.util.Set;
Modified: core/branches/Branch_3_2_4_SP1_CP/test/org/hibernate/test/cascade/MultiPathCascadeTest.java
===================================================================
--- core/branches/Branch_3_2_4_SP1_CP/test/org/hibernate/test/cascade/MultiPathCascadeTest.java 2009-05-13 22:17:08 UTC (rev 16561)
+++ core/branches/Branch_3_2_4_SP1_CP/test/org/hibernate/test/cascade/MultiPathCascadeTest.java 2009-05-13 22:22:59 UTC (rev 16562)
@@ -1,4 +1,28 @@
//$Id: $
+/*
+ * Hibernate, Relational Persistence for Idiomatic Java
+ *
+ * Copyright (c) 2008, Red Hat Middleware LLC 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 Middleware LLC.
+ *
+ * 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;
Added: core/branches/Branch_3_2_4_SP1_CP/test/org/hibernate/test/cascade/circle/CascadeMergeToChildBeforeParent.hbm.xml
===================================================================
--- core/branches/Branch_3_2_4_SP1_CP/test/org/hibernate/test/cascade/circle/CascadeMergeToChildBeforeParent.hbm.xml (rev 0)
+++ core/branches/Branch_3_2_4_SP1_CP/test/org/hibernate/test/cascade/circle/CascadeMergeToChildBeforeParent.hbm.xml 2009-05-13 22:22:59 UTC (rev 16562)
@@ -0,0 +1,115 @@
+<?xml version="1.0"?>
+<!DOCTYPE hibernate-mapping SYSTEM "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd" >
+
+<hibernate-mapping package="org.hibernate.test.cascade.circle">
+
+ <class name="Route" table="HB_Route">
+
+ <id name="routeID" type="long"><generator class="native"/></id>
+ <version name="version" column="VERS" type="long" />
+
+ <property name="name" type="string" not-null="true"/>
+
+ <set name="nodes" inverse="true" cascade="persist,merge,refresh">
+ <key column="routeID"/>
+ <one-to-many class="Node"/>
+ </set>
+ <set name="vehicles" inverse="true" cascade="persist,merge,refresh">
+ <key column="routeID"/>
+ <one-to-many class="Vehicle"/>
+ </set>
+ </class>
+
+ <class name="Tour" table="HB_Tour">
+
+ <id name="tourID" type="long"><generator class="native"/></id>
+ <version name="version" column="VERS" type="long" />
+
+ <property name="name" type="string" not-null="true"/>
+
+ <set name="nodes" inverse="true" lazy="true" cascade="merge,refresh">
+ <key column="tourID"/>
+ <one-to-many class="Node"/>
+ </set>
+ </class>
+
+ <class name="Transport" table="HB_Transport">
+
+ <id name="transportID" type="long"><generator class="native"/></id>
+ <version name="version" column="VERS" type="long" />
+
+ <property name="name" type="string" not-null="true"/>
+
+ <many-to-one name="pickupNode"
+ column="pickupNodeID"
+ unique="true"
+ not-null="true"
+ cascade="merge,refresh"
+ lazy="false"/>
+
+ <many-to-one name="deliveryNode"
+ column="deliveryNodeID"
+ unique="true"
+ not-null="true"
+ cascade="merge,refresh"
+ lazy="false"/>
+
+ <many-to-one name="vehicle"
+ column="vehicleID"
+ unique="false"
+ not-null="true"
+ cascade="none"
+ lazy="false"/>
+ </class>
+
+ <class name="Vehicle" table="HB_Vehicle">
+ <id name="vehicleID" type="long"><generator class="native"/></id>
+ <version name="version" column="VERS" type="long" />
+
+ <property name="name"/>
+ <set name="transports" inverse="false" lazy="true" cascade="merge,refresh">
+ <key column="vehicleID"/>
+ <one-to-many class="Transport" not-found="exception"/>
+ </set>
+ <many-to-one name="route"
+ column="routeID"
+ unique="false"
+ not-null="true"
+ cascade="none"
+ lazy="false"/>
+ </class>
+
+
+ <class name="Node" table="HB_Node">
+
+ <id name="nodeID" type="long"><generator class="native"/></id>
+ <version name="version" column="VERS" type="long" />
+
+ <property name="name" type="string" not-null="true"/>
+
+ <set name="deliveryTransports" inverse="true" lazy="true" cascade="merge,refresh">
+ <key column="deliveryNodeID"/>
+ <one-to-many class="Transport"/>
+ </set>
+
+ <set name="pickupTransports" inverse="true" lazy="true" cascade="merge,refresh">
+ <key column="pickupNodeID"/>
+ <one-to-many class="Transport"/>
+ </set>
+
+ <many-to-one name="route"
+ column="routeID"
+ unique="false"
+ not-null="true"
+ cascade="none"
+ lazy="false"/>
+
+ <many-to-one name="tour"
+ column="tourID"
+ unique="false"
+ not-null="false"
+ cascade="merge,refresh"
+ lazy="false"/>
+ </class>
+
+</hibernate-mapping>
Added: core/branches/Branch_3_2_4_SP1_CP/test/org/hibernate/test/cascade/circle/CascadeMergeToChildBeforeParentTest.java
===================================================================
--- core/branches/Branch_3_2_4_SP1_CP/test/org/hibernate/test/cascade/circle/CascadeMergeToChildBeforeParentTest.java (rev 0)
+++ core/branches/Branch_3_2_4_SP1_CP/test/org/hibernate/test/cascade/circle/CascadeMergeToChildBeforeParentTest.java 2009-05-13 22:22:59 UTC (rev 16562)
@@ -0,0 +1,288 @@
+//$Id: $
+/*
+ * Hibernate, Relational Persistence for Idiomatic Java
+ *
+ * Copyright (c) 2008, Red Hat Middleware LLC 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 Middleware LLC.
+ *
+ * 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 junit.framework.Test;
+
+import org.hibernate.Session;
+import org.hibernate.junit.functional.FunctionalTestCase;
+import org.hibernate.junit.functional.FunctionalTestClassTestSuite;
+
+/**
+ * The test case uses the following model:
+ *
+ * <- ->
+ * -- (N : 0,1) -- Tour
+ * | <- ->
+ * | -- (1 : N) -- (pickup) ----
+ * -> | | |
+ * Route -- (1 : N) - Node Transport
+ * | | <- -> | |
+ * | -- (1 : N) -- (delivery) -- |
+ * | |
+ * | -> -> |
+ * -------- (1 : N) ---- Vehicle--(1 : N)------------
+ *
+ * Arrows indicate the direction of cascade-merge.
+ *
+ * I believe it reproduces the following issue:
+ * http://opensource.atlassian.com/projects/hibernate/browse/HHH-3544
+ *
+ * @author Gail Badner (based on original model provided by Pavol Zibrita)
+ */
+public class CascadeMergeToChildBeforeParentTest extends FunctionalTestCase {
+
+ public CascadeMergeToChildBeforeParentTest(String string) {
+ super(string);
+ }
+
+ public String[] getMappings() {
+ return new String[] {
+ "cascade/circle/CascadeMergeToChildBeforeParent.hbm.xml"
+ };
+ }
+
+ public static Test suite() {
+ return new FunctionalTestClassTestSuite( CascadeMergeToChildBeforeParentTest.class );
+ }
+
+ 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" );
+ s.createQuery( "delete from Vehicle" );
+ }
+
+ public void testMerge()
+ {
+ Session s = openSession();
+ s.beginTransaction();
+
+ Route route = new Route();
+ route.setName("routeA");
+
+ s.save( route );
+ s.getTransaction().commit();
+ s.close();
+
+ s = openSession();
+ s.beginTransaction();
+
+ route = (Route) s.get(Route.class, new Long(1));
+
+ route.setTransientField(new String("sfnaouisrbn"));
+
+ Tour tour = new Tour();
+ tour.setName("tourB");
+
+ Node pickupNode = new Node();
+ pickupNode.setName("pickupNodeB");
+
+ Node deliveryNode = new Node();
+ deliveryNode.setName("deliveryNodeB");
+
+ pickupNode.setRoute(route);
+ pickupNode.setTour(tour);
+ pickupNode.setTransientField("pickup node aaaaaaaaaaa");
+
+ deliveryNode.setRoute(route);
+ deliveryNode.setTour(tour);
+ deliveryNode.setTransientField("delivery node aaaaaaaaa");
+
+ tour.getNodes().add(pickupNode);
+ tour.getNodes().add(deliveryNode);
+
+ route.getNodes().add(pickupNode);
+ route.getNodes().add(deliveryNode);
+
+ Route mergedRoute = (Route) s.merge(route);
+
+ s.getTransaction().commit();
+ s.close();
+ }
+
+ // This test fails because the merge algorithm tries to save a
+ // transient child (transport) before cascade-merge gets its
+ // transient parent (vehicle); merge does not cascade from the
+ // child to the parent.
+ public void testMergeTransientChildBeforeTransientParent()
+ {
+ Session s = openSession();
+ s.beginTransaction();
+
+ Route route = new Route();
+ route.setName("routeA");
+
+ s.save( route );
+ s.getTransaction().commit();
+ s.close();
+
+ s = openSession();
+ s.beginTransaction();
+
+ route = (Route) s.get(Route.class, new Long(1));
+
+ 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");
+
+ Vehicle vehicle = new Vehicle();
+ vehicle.setName("vehicleB");
+
+ 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);
+ route.getVehicles().add(vehicle);
+
+ transport.setPickupNode(pickupNode);
+ transport.setDeliveryNode(deliveryNode);
+ transport.setVehicle( vehicle );
+ transport.setTransientField("aaaaaaaaaaaaaa");
+
+ vehicle.getTransports().add(transport);
+ vehicle.setTransientField( "anewvalue" );
+ vehicle.setRoute( route );
+
+ Route mergedRoute = (Route) s.merge(route);
+
+ s.getTransaction().commit();
+ s.close();
+ }
+
+ public void testMergeData3Nodes()
+ {
+
+ Session s = openSession();
+ s.beginTransaction();
+
+ Route route = new Route();
+ route.setName("routeA");
+
+ s.save( route );
+ s.getTransaction().commit();
+ s.close();
+
+ s = openSession();
+ s.beginTransaction();
+
+ route = (Route) s.get(Route.class, new Long(1));
+
+ 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");
+
+ Vehicle vehicle = new Vehicle();
+ vehicle.setName("vehicleB");
+
+ 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);
+ route.getVehicles().add(vehicle);
+
+ transport1.setPickupNode(node1);
+ transport1.setDeliveryNode(node2);
+ transport1.setVehicle( vehicle );
+ transport1.setTransientField("aaaaaaaaaaaaaa");
+
+ transport2.setPickupNode(node2);
+ transport2.setDeliveryNode(node3);
+ transport2.setVehicle( vehicle );
+ transport2.setTransientField("bbbbbbbbbbbbb");
+
+ vehicle.getTransports().add(transport1);
+ vehicle.getTransports().add(transport2);
+ vehicle.setTransientField( "anewvalue" );
+ vehicle.setRoute( route );
+
+ Route mergedRoute = (Route) s.merge(route);
+
+ s.getTransaction().commit();
+ s.close();
+ }
+
+}
Added: core/branches/Branch_3_2_4_SP1_CP/test/org/hibernate/test/cascade/circle/MultiPathCircleCascade.hbm.xml
===================================================================
--- core/branches/Branch_3_2_4_SP1_CP/test/org/hibernate/test/cascade/circle/MultiPathCircleCascade.hbm.xml (rev 0)
+++ core/branches/Branch_3_2_4_SP1_CP/test/org/hibernate/test/cascade/circle/MultiPathCircleCascade.hbm.xml 2009-05-13 22:22:59 UTC (rev 16562)
@@ -0,0 +1,82 @@
+<?xml version="1.0"?>
+<!DOCTYPE hibernate-mapping SYSTEM "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd" >
+
+<hibernate-mapping package="org.hibernate.test.cascade.circle">
+
+ <class name="Route" table="HB_Route">
+
+ <id name="routeID" type="long"><generator class="native"/></id>
+
+ <property name="name" type="string" not-null="true"/>
+
+ <set name="nodes" inverse="true" cascade="persist,merge,refresh">
+ <key column="routeID"/>
+ <one-to-many class="Node"/>
+ </set>
+ </class>
+
+ <class name="Tour" table="HB_Tour">
+
+ <id name="tourID" type="long"><generator class="native"/></id>
+
+ <property name="name" type="string" not-null="true"/>
+
+ <set name="nodes" inverse="true" lazy="true" cascade="merge,refresh">
+ <key column="tourID"/>
+ <one-to-many class="Node"/>
+ </set>
+ </class>
+
+ <class name="Transport" table="HB_Transport">
+
+ <id name="transportID" type="long"><generator class="native"/></id>
+
+ <property name="name" type="string" not-null="true"/>
+
+ <many-to-one name="pickupNode"
+ column="pickupNodeID"
+ unique="true"
+ not-null="true"
+ cascade="merge,refresh"
+ lazy="false"/>
+
+ <many-to-one name="deliveryNode"
+ column="deliveryNodeID"
+ unique="true"
+ not-null="true"
+ cascade="merge,refresh"
+ lazy="false"/>
+ </class>
+
+ <class name="Node" table="HB_Node">
+
+ <id name="nodeID" type="long"><generator class="native"/></id>
+
+ <property name="name" type="string" not-null="true"/>
+
+ <set name="deliveryTransports" inverse="true" lazy="true" cascade="merge,refresh">
+ <key column="deliveryNodeID"/>
+ <one-to-many class="Transport"/>
+ </set>
+
+ <set name="pickupTransports" inverse="true" lazy="true" cascade="merge,refresh">
+ <key column="pickupNodeID"/>
+ <one-to-many class="Transport"/>
+ </set>
+
+ <many-to-one name="route"
+ column="routeID"
+ unique="false"
+ not-null="true"
+ cascade="none"
+ lazy="false"/>
+
+ <many-to-one name="tour"
+ column="tourID"
+ unique="false"
+ not-null="false"
+ cascade="merge,refresh"
+ lazy="false"/>
+ </class>
+
+</hibernate-mapping>
Added: core/branches/Branch_3_2_4_SP1_CP/test/org/hibernate/test/cascade/circle/MultiPathCircleCascadeTest.java
===================================================================
--- core/branches/Branch_3_2_4_SP1_CP/test/org/hibernate/test/cascade/circle/MultiPathCircleCascadeTest.java (rev 0)
+++ core/branches/Branch_3_2_4_SP1_CP/test/org/hibernate/test/cascade/circle/MultiPathCircleCascadeTest.java 2009-05-13 22:22:59 UTC (rev 16562)
@@ -0,0 +1,459 @@
+//$Id: $
+/*
+ * Hibernate, Relational Persistence for Idiomatic Java
+ *
+ * Copyright (c) 2008, Red Hat Middleware LLC 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 Middleware LLC.
+ *
+ * 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 junit.framework.Test;
+
+import org.hibernate.Session;
+import org.hibernate.cfg.Configuration;
+import org.hibernate.cfg.Environment;
+import org.hibernate.junit.functional.FunctionalTestCase;
+import org.hibernate.junit.functional.FunctionalTestClassTestSuite;
+
+/**
+ * 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
+ *
+ * This tests that merge is cascaded properly from each entity.
+ *
+ * @author Pavol Zibrita, Gail Badner
+ */
+public class MultiPathCircleCascadeTest extends FunctionalTestCase {
+
+ public MultiPathCircleCascadeTest(String string) {
+ super(string);
+ }
+
+ public void configure(Configuration cfg) {
+ cfg.setProperty( Environment.GENERATE_STATISTICS, "true");
+ cfg.setProperty( Environment.STATEMENT_BATCH_SIZE, "0" );
+ }
+
+ public String[] getMappings() {
+ return new String[] {
+ "cascade/circle/MultiPathCircleCascade.hbm.xml"
+ };
+ }
+
+ public static Test suite() {
+ return new FunctionalTestClassTestSuite( MultiPathCircleCascadeTest.class );
+ }
+
+ 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" );
+ }
+
+ 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();
+ }
+
+ 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();
+ }
+
+ 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();
+ }
+
+ 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();
+ }
+
+ 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() );
+ }
+
+ 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 clearCounts() {
+ getSessions().getStatistics().clear();
+ }
+
+ protected void assertInsertCount(int expected) {
+ int inserts = ( int ) getSessions().getStatistics().getEntityInsertCount();
+ assertEquals( "unexpected insert count", expected, inserts );
+ }
+
+ protected void assertUpdateCount(int expected) {
+ int updates = ( int ) getSessions().getStatistics().getEntityUpdateCount();
+ assertEquals( "unexpected update counts", expected, updates );
+ }
+
+ protected void assertDeleteCount(int expected) {
+ int deletes = ( int ) getSessions().getStatistics().getEntityDeleteCount();
+ assertEquals( "unexpected delete counts", expected, deletes );
+ }
+}
Added: core/branches/Branch_3_2_4_SP1_CP/test/org/hibernate/test/cascade/circle/Node.java
===================================================================
--- core/branches/Branch_3_2_4_SP1_CP/test/org/hibernate/test/cascade/circle/Node.java (rev 0)
+++ core/branches/Branch_3_2_4_SP1_CP/test/org/hibernate/test/cascade/circle/Node.java 2009-05-13 22:22:59 UTC (rev 16562)
@@ -0,0 +1,154 @@
+//$Id: $
+/*
+ * Hibernate, Relational Persistence for Idiomatic Java
+ *
+ * Copyright (c) 2008, Red Hat Middleware LLC 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 Middleware LLC.
+ *
+ * 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.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+import java.util.HashSet;
+
+public class Node {
+
+// @Id
+// @SequenceGenerator(name="NODE_SEQ", sequenceName="NODE_SEQ", initialValue=1, allocationSize=1)
+// @GeneratedValue(strategy=GenerationType.SEQUENCE, generator="NODE_SEQ")
+ private Long nodeID;
+
+ private long version;
+
+ private String name;
+
+ /** the list of orders that are delivered at this node */
+// @OneToMany(fetch=FetchType.LAZY, cascade={CascadeType.MERGE, CascadeType.REFRESH}, mappedBy="deliveryNode")
+ private Set deliveryTransports = new HashSet();
+
+ /** the list of orders that are picked up at this node */
+// @OneToMany(fetch=FetchType.LAZY, cascade=CascadeType.ALL, mappedBy="pickupNode")
+ private Set pickupTransports = new HashSet();
+
+ /** the route to which this node belongs */
+// @ManyToOne(targetEntity=Route.class, optional=false, fetch=FetchType.EAGER)
+// @JoinColumn(name="ROUTEID", nullable=false, insertable=true, updatable=true)
+ private Route route = null;
+
+ /** the tour this node belongs to, null if this node does not belong to a tour (e.g first node of a route) */
+// @ManyToOne(targetEntity=Tour.class, cascade={CascadeType.MERGE, CascadeType.PERSIST, CascadeType.REFRESH}, optional=true, fetch=FetchType.LAZY)
+// @JoinColumn(name="TOURID", nullable=true, insertable=true, updatable=true)
+ private Tour tour;
+
+// @Transient
+ private String transientField = "node original value";
+
+ public Set getDeliveryTransports() {
+ return deliveryTransports;
+ }
+
+ public void setDeliveryTransports(Set deliveryTransports) {
+ this.deliveryTransports = deliveryTransports;
+ }
+
+ public Set getPickupTransports() {
+ return pickupTransports;
+ }
+
+ public void setPickupTransports(Set pickupTransports) {
+ this.pickupTransports = pickupTransports;
+ }
+
+ public Long getNodeID() {
+ return nodeID;
+ }
+
+ public long getVersion() {
+ return version;
+ }
+
+ protected void setVersion(long version) {
+ this.version = version;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public Route getRoute() {
+ return route;
+ }
+
+ public void setRoute(Route route) {
+ this.route = route;
+ }
+
+ public Tour getTour() {
+ return tour;
+ }
+
+ public void setTour(Tour tour) {
+ this.tour = tour;
+ }
+
+ public String toString()
+ {
+ StringBuffer buffer = new StringBuffer();
+
+ buffer.append( name + " id: " + nodeID );
+ if ( route != null ) {
+ buffer.append( " route name: " ).append( route.getName() ).append( " tour name: " ).append( tour.getName() );
+ }
+ if ( pickupTransports != null ) {
+ for (Iterator it = pickupTransports.iterator(); it.hasNext();) {
+ buffer.append("Pickup transports: " + it.next());
+ }
+ }
+
+ if ( deliveryTransports != null ) {
+ for (Iterator it = deliveryTransports.iterator(); it.hasNext();) {
+ buffer.append("Delviery transports: " + it.next());
+ }
+ }
+
+ return buffer.toString();
+ }
+
+ public String getTransientField() {
+ return transientField;
+ }
+
+ public void setTransientField(String transientField) {
+ this.transientField = transientField;
+ }
+
+ protected void setNodeID(Long nodeID) {
+ this.nodeID = nodeID;
+ }
+
+}
Added: core/branches/Branch_3_2_4_SP1_CP/test/org/hibernate/test/cascade/circle/Route.java
===================================================================
--- core/branches/Branch_3_2_4_SP1_CP/test/org/hibernate/test/cascade/circle/Route.java (rev 0)
+++ core/branches/Branch_3_2_4_SP1_CP/test/org/hibernate/test/cascade/circle/Route.java 2009-05-13 22:22:59 UTC (rev 16562)
@@ -0,0 +1,119 @@
+//$Id: $
+/*
+ * Hibernate, Relational Persistence for Idiomatic Java
+ *
+ * Copyright (c) 2008, Red Hat Middleware LLC 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 Middleware LLC.
+ *
+ * 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.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+import java.util.HashSet;
+
+
+public class Route {
+
+// @Id
+// @SequenceGenerator(name="ROUTE_SEQ", sequenceName="ROUTE_SEQ", initialValue=1, allocationSize=1)
+// @GeneratedValue(strategy=GenerationType.SEQUENCE, generator="ROUTE_SEQ")
+ private Long routeID;
+
+ private long version;
+
+ /** A List of nodes contained in this route. */
+// @OneToMany(targetEntity=Node.class, fetch=FetchType.EAGER, cascade=CascadeType.ALL, mappedBy="route")
+ private Set nodes = new HashSet();
+
+ private Set vehicles = new HashSet();
+
+ private String name;
+
+// @Transient
+ private String transientField = null;
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ protected Set getNodes() {
+ return nodes;
+ }
+
+ protected void setNodes(Set nodes) {
+ this.nodes = nodes;
+ }
+
+ protected Set getVehicles() {
+ return vehicles;
+ }
+
+ protected void setVehicles(Set vehicles) {
+ this.vehicles = vehicles;
+ }
+
+ protected void setRouteID(Long routeID) {
+ this.routeID = routeID;
+ }
+
+ public Long getRouteID() {
+ return routeID;
+ }
+
+ public long getVersion() {
+ return version;
+ }
+
+ protected void setVersion(long version) {
+ this.version = version;
+ }
+
+ public String toString()
+ {
+ StringBuffer buffer = new StringBuffer();
+
+ buffer.append("Route name: " + name + " id: " + routeID + " transientField: " + transientField + "\n");
+ for (Iterator it = nodes.iterator(); it.hasNext();) {
+ buffer.append("Node: " + (Node)it.next());
+ }
+
+ for (Iterator it = vehicles.iterator(); it.hasNext();) {
+ buffer.append("Vehicle: " + (Vehicle)it.next());
+ }
+
+ return buffer.toString();
+ }
+
+ public String getTransientField() {
+ return transientField;
+ }
+
+ public void setTransientField(String transientField) {
+ this.transientField = transientField;
+ }
+}
Added: core/branches/Branch_3_2_4_SP1_CP/test/org/hibernate/test/cascade/circle/Tour.java
===================================================================
--- core/branches/Branch_3_2_4_SP1_CP/test/org/hibernate/test/cascade/circle/Tour.java (rev 0)
+++ core/branches/Branch_3_2_4_SP1_CP/test/org/hibernate/test/cascade/circle/Tour.java 2009-05-13 22:22:59 UTC (rev 16562)
@@ -0,0 +1,80 @@
+//$Id: $
+/*
+ * Hibernate, Relational Persistence for Idiomatic Java
+ *
+ * Copyright (c) 2008, Red Hat Middleware LLC 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 Middleware LLC.
+ *
+ * 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.ArrayList;
+import java.util.List;
+import java.util.Set;
+import java.util.HashSet;
+
+
+public class Tour {
+
+// @Id
+// @SequenceGenerator(name="TOUR_SEQ", sequenceName="TOUR_SEQ", initialValue=1, allocationSize=1)
+// @GeneratedValue(strategy=GenerationType.SEQUENCE, generator="TOUR_SEQ")
+ private Long tourID;
+
+ private long version;
+
+ private String name;
+
+ /** A List of nodes contained in this tour. */
+// @OneToMany(targetEntity=Node.class, fetch=FetchType.LAZY, cascade={CascadeType.MERGE, CascadeType.REFRESH}, mappedBy="tour")
+ private Set nodes = new HashSet(0);
+
+ public String getName() {
+ return name;
+ }
+
+ protected void setTourID(Long tourID) {
+ this.tourID = tourID;
+ }
+
+ public long getVersion() {
+ return version;
+ }
+
+ protected void setVersion(long version) {
+ this.version = version;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public Set getNodes() {
+ return nodes;
+ }
+
+ public void setNodes(Set nodes) {
+ this.nodes = nodes;
+ }
+
+ public Long getTourID() {
+ return tourID;
+ }
+}
Added: core/branches/Branch_3_2_4_SP1_CP/test/org/hibernate/test/cascade/circle/Transport.java
===================================================================
--- core/branches/Branch_3_2_4_SP1_CP/test/org/hibernate/test/cascade/circle/Transport.java (rev 0)
+++ core/branches/Branch_3_2_4_SP1_CP/test/org/hibernate/test/cascade/circle/Transport.java 2009-05-13 22:22:59 UTC (rev 16562)
@@ -0,0 +1,120 @@
+//$Id: $
+/*
+ * Hibernate, Relational Persistence for Idiomatic Java
+ *
+ * Copyright (c) 2008, Red Hat Middleware LLC 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 Middleware LLC.
+ *
+ * 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;
+
+
+public class Transport {
+
+// @Id
+// @SequenceGenerator(name="TRANSPORT_SEQ", sequenceName="TRANSPORT_SEQ", initialValue=1, allocationSize=1)
+// @GeneratedValue(strategy=GenerationType.SEQUENCE, generator="TRANSPORT_SEQ")
+ private Long transportID;
+
+ private long version;
+
+ private String name;
+
+ /** node value object at which the order is picked up */
+// @ManyToOne(optional=false, cascade={CascadeType.MERGE, CascadeType.PERSIST, CascadeType.REFRESH}, fetch=FetchType.EAGER)
+// @JoinColumn(name="PICKUPNODEID", /*nullable=false,*/insertable=true, updatable=true)
+ private Node pickupNode = null;
+
+ /** node value object at which the order is delivered */
+// @ManyToOne(optional=false, cascade={CascadeType.MERGE, CascadeType.PERSIST, CascadeType.REFRESH}, fetch=FetchType.EAGER)
+// @JoinColumn(name="DELIVERYNODEID", /*nullable=false,*/ insertable=true, updatable=true)
+ private Node deliveryNode = null;
+
+ private Vehicle vehicle;
+
+// @Transient
+ private String transientField = "transport original value";
+
+ public Node getDeliveryNode() {
+ return deliveryNode;
+ }
+
+ public void setDeliveryNode(Node deliveryNode) {
+ this.deliveryNode = deliveryNode;
+ }
+
+ public Node getPickupNode() {
+ return pickupNode;
+ }
+
+ protected void setTransportID(Long transportID) {
+ this.transportID = transportID;
+ }
+
+ public void setPickupNode(Node pickupNode) {
+ this.pickupNode = pickupNode;
+ }
+
+ public Vehicle getVehicle() {
+ return vehicle;
+ }
+
+ public void setVehicle(Vehicle vehicle) {
+ this.vehicle = vehicle;
+ }
+
+ public Long getTransportID() {
+ return transportID;
+ }
+
+ public long getVersion() {
+ return version;
+ }
+
+ protected void setVersion(long version) {
+ this.version = version;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public String toString()
+ {
+ StringBuffer buffer = new StringBuffer();
+
+ buffer.append(name + " id: " + transportID + "\n");
+
+ return buffer.toString();
+ }
+
+ public String getTransientField() {
+ return transientField;
+ }
+
+ public void setTransientField(String transientField) {
+ this.transientField = transientField;
+ }
+}
Added: core/branches/Branch_3_2_4_SP1_CP/test/org/hibernate/test/cascade/circle/Vehicle.java
===================================================================
--- core/branches/Branch_3_2_4_SP1_CP/test/org/hibernate/test/cascade/circle/Vehicle.java (rev 0)
+++ core/branches/Branch_3_2_4_SP1_CP/test/org/hibernate/test/cascade/circle/Vehicle.java 2009-05-13 22:22:59 UTC (rev 16562)
@@ -0,0 +1,106 @@
+//$Id: $
+/*
+ * Hibernate, Relational Persistence for Idiomatic Java
+ *
+ * Copyright (c) 2008, Red Hat Middleware LLC 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 Middleware LLC.
+ *
+ * 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.Set;
+import java.util.HashSet;
+
+
+public class Vehicle {
+
+// @Id
+// @SequenceGenerator(name="TRANSPORT_SEQ", sequenceName="TRANSPORT_SEQ", initialValue=1, allocationSize=1)
+// @GeneratedValue(strategy=GenerationType.SEQUENCE, generator="TRANSPORT_SEQ")
+ private Long vehicleID;
+
+ private long version;
+
+ private String name;
+
+ private Set transports = new HashSet();
+
+ private Route route;
+
+ private String transientField = "vehicle original value";
+
+ protected void setVehicleID(Long vehicleID) {
+ this.vehicleID = vehicleID;
+ }
+
+ public Long getVehicleID() {
+ return vehicleID;
+ }
+
+ public long getVersion() {
+ return version;
+ }
+
+ protected void setVersion(long version) {
+ this.version = version;
+ }
+
+ public Set getTransports() {
+ return transports;
+ }
+
+ public void setTransports(Set transports) {
+ this.transports = transports;
+ }
+
+ public Route getRoute() {
+ return route;
+ }
+
+ public void setRoute(Route route) {
+ this.route = route;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public String toString()
+ {
+ StringBuffer buffer = new StringBuffer();
+
+ buffer.append(name + " id: " + vehicleID + "\n");
+
+ return buffer.toString();
+ }
+
+ public String getTransientField() {
+ return transientField;
+ }
+
+ public void setTransientField(String transientField) {
+ this.transientField = transientField;
+ }
+}
\ No newline at end of file
Modified: core/branches/Branch_3_2_4_SP1_CP/test/org/hibernate/test/ops/MergeTest.java
===================================================================
--- core/branches/Branch_3_2_4_SP1_CP/test/org/hibernate/test/ops/MergeTest.java 2009-05-13 22:17:08 UTC (rev 16561)
+++ core/branches/Branch_3_2_4_SP1_CP/test/org/hibernate/test/ops/MergeTest.java 2009-05-13 22:22:59 UTC (rev 16562)
@@ -152,7 +152,7 @@
// as a control measure, now update the node while it is detached and
// make sure we get an update as a result...
( ( Node ) parent.getChildren().iterator().next() ).setDescription( "child's new description" );
- parent.getChildren().add( new Node( "second child" ) );
+ parent.addChild( new Node( "second child" ) );
s = openSession();
s.beginTransaction();
parent = ( Node ) s.merge( parent );
15 years, 6 months