Project

General

Profile

Download (38.8 KB) Statistics
| Branch: | Tag: | Revision:
1
/*
2
 * Hibernate, Relational Persistence for Idiomatic Java
3
 *
4
 * License: GNU Lesser General Public License (LGPL), version 2.1 or later.
5
 * See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
6
 */
7
package org.hibernate.collection.internal;
8

    
9
import java.io.Serializable;
10
import java.lang.reflect.Field;
11
import java.util.ArrayList;
12
import java.util.Collection;
13
import java.util.Collections;
14
import java.util.HashSet;
15
import java.util.Iterator;
16
import java.util.List;
17
import java.util.ListIterator;
18
import java.util.Map;
19

    
20
import org.hibernate.AssertionFailure;
21
import org.hibernate.HibernateException;
22
import org.hibernate.LazyInitializationException;
23
import org.hibernate.Session;
24
import org.hibernate.collection.spi.PersistentCollection;
25
import org.hibernate.engine.internal.ForeignKeys;
26
import org.hibernate.engine.spi.CollectionEntry;
27
import org.hibernate.engine.spi.EntityEntry;
28
import org.hibernate.engine.spi.SessionFactoryImplementor;
29
import org.hibernate.engine.spi.SessionImplementor;
30
import org.hibernate.engine.spi.Status;
31
import org.hibernate.engine.spi.TypedValue;
32
import org.hibernate.internal.CoreLogging;
33
import org.hibernate.internal.CoreMessageLogger;
34
import org.hibernate.internal.SessionFactoryRegistry;
35
import org.hibernate.internal.util.MarkerObject;
36
import org.hibernate.internal.util.collections.EmptyIterator;
37
import org.hibernate.internal.util.collections.IdentitySet;
38
import org.hibernate.persister.collection.CollectionPersister;
39
import org.hibernate.persister.entity.EntityPersister;
40
import org.hibernate.pretty.MessageHelper;
41
import org.hibernate.type.CompositeType;
42
import org.hibernate.type.IntegerType;
43
import org.hibernate.type.LongType;
44
import org.hibernate.type.PostgresUUIDType;
45
import org.hibernate.type.StringType;
46
import org.hibernate.type.Type;
47
import org.hibernate.type.UUIDBinaryType;
48
import org.hibernate.type.UUIDCharType;
49

    
50
import eu.etaxonomy.cdm.api.application.CdmApplicationRemoteConfiguration;
51
import eu.etaxonomy.cdm.cache.ProxyUtils;
52
import eu.etaxonomy.cdm.model.common.CdmBase;
53
import eu.etaxonomy.taxeditor.remoting.CdmEagerLoadingException;
54
import eu.etaxonomy.taxeditor.service.ICachedCommonService;
55

    
56
/**
57
 * Base class implementing {@link org.hibernate.collection.spi.PersistentCollection}
58
 *
59
 * This is an extended copy of the original class from hibernate. It has been extended to
60
 * allow making remote service calls to spring httpinvoker services (see section at the bottom
61
 * of this class).
62
 *
63
 * NOTE: For updating this class to the latest hibernate version, update the serialVersionUID as
64
 *       described above, copy the new class to the old class BUT keep those 5 places marked with
65
 *       ##REMOTING-KEEP##
66
 *
67
 * @author Gavin King
68
 * @author Cherian Mathew
69
 */
70
public abstract class AbstractPersistentCollection implements Serializable, PersistentCollection {
71
	private static final CoreMessageLogger LOG = CoreLogging.messageLogger( AbstractPersistentCollection.class );
72

    
73
	/**
74
	 * <b>IMPORTANT:</b><br>
75
	 * This serialVersionUID must be kept in sync with the serialVersionUID which is generated
76
	 * on the fly for serialized AbstractPersistentCollection objects coming from the httpInvoker
77
	 * service.
78
	 * This is most probably necessary after updating hibernate to a newer version. In any case
79
	 * it the need for updating this <code>serialVersionUID</code> becomes obvious when the attempt
80
	 * to connect to the server side fails with an  <code>InvalidClassException</code>:
81
	 *
82
	 * <pre>
83
	 * java.io.InvalidClassException: org.hibernate.collection.internal.AbstractPersistentCollection;
84
	 * local class incompatible:
85
	 * stream classdesc serialVersionUID = 2742261122392386159,
86
	 * local class serialVersionUID = -7238232378593030571
87
	 * </pre>
88
	 * The correct <code>serialVersionUID</code> is the <code>stream classdesc serialVersionUID</code>
89
	 * from the error message.
90
	 */
91
	private static final long serialVersionUID = 7094296207968006972L;
92

    
93
	private transient SessionImplementor session;
94
	private boolean isTempSession = false;
95
	private boolean initialized;
96
	private transient List<DelayedOperation> operationQueue;
97
	private transient boolean directlyAccessible;
98
	private transient boolean initializing;
99
	private Object owner;
100
	private int cachedSize = -1;
101

    
102
	private String role;
103
	private Serializable key;
104
	// collections detect changes made via their public interface and mark
105
	// themselves as dirty as a performance optimization
106
	private boolean dirty;
107
	private Serializable storedSnapshot;
108

    
109
	private String sessionFactoryUuid;
110
	private boolean allowLoadOutsideTransaction;
111

    
112
	/**
113
	 * Not called by Hibernate, but used by non-JDK serialization,
114
	 * eg. SOAP libraries.
115
	 */
116
	public AbstractPersistentCollection() {
117
	}
118

    
119
	protected AbstractPersistentCollection(SessionImplementor session) {
120
		this.session = session;
121
	}
122

    
123
	@Override
124
	public final String getRole() {
125
		return role;
126
	}
127

    
128
	@Override
129
	public final Serializable getKey() {
130
		return key;
131
	}
132

    
133
	@Override
134
	public final boolean isUnreferenced() {
135
		return role == null;
136
	}
137

    
138
	@Override
139
	public final boolean isDirty() {
140
		return dirty;
141
	}
142

    
143
	@Override
144
	public final void clearDirty() {
145
		dirty = false;
146
	}
147

    
148
	@Override
149
	public final void dirty() {
150
		dirty = true;
151
	}
152

    
153
	@Override
154
	public final Serializable getStoredSnapshot() {
155
		return storedSnapshot;
156
	}
157

    
158
	//Careful: these methods do not initialize the collection.
159

    
160
	@Override
161
	public abstract boolean empty();
162

    
163
	/**
164
	 * Called by any read-only method of the collection interface
165
	 */
166
	protected final void read() {
167
		initialize( false );
168
	}
169

    
170
	/**
171
	 * Called by the {@link Collection#size} method
172
	 */
173
	@SuppressWarnings({"JavaDoc"})
174
	protected boolean readSize() {
175
		if ( !initialized ) {
176
			if ( cachedSize != -1 && !hasQueuedOperations() ) {
177
				return true;
178
			}
179
			else {
180
				//##REMOTING-KEEP##
181
				// In remoting we are sure that session is null
182
				// both when using property paths and switching off conversations
183
				if(session == null && remoting) {
184
					LOG.info("--> readSize, of " + getRole() + " with key " + getKey());
185
					read();
186
				} else {
187
			    //keep formatting below to ease update to newer hibernate version
188
				//##REMOTING-KEEP END##
189
				final boolean isExtraLazy = withTemporarySessionIfNeeded(
190
						new LazyInitializationWork<Boolean>() {
191
							@Override
192
							public Boolean doWork() {
193
								final CollectionEntry entry = session.getPersistenceContext().getCollectionEntry( AbstractPersistentCollection.this );
194

    
195
								if ( entry != null ) {
196
									final CollectionPersister persister = entry.getLoadedPersister();
197
									if ( persister.isExtraLazy() ) {
198
										if ( hasQueuedOperations() ) {
199
											session.flush();
200
										}
201
										cachedSize = persister.getSize( entry.getLoadedKey(), session );
202
										return true;
203
									}
204
									else {
205
										read();
206
									}
207
								}
208
								else{
209
									throwLazyInitializationExceptionIfNotConnected();
210
								}
211
								return false;
212
							}
213
						}
214
				);
215
				if ( isExtraLazy ) {
216
					return true;
217
				}
218
				}
219
			}
220
		}
221
		return false;
222
	}
223

    
224
	/**
225
	 * TBH not sure why this is public
226
	 *
227
	 * @param <T> The java type of the return for this LazyInitializationWork
228
	 */
229
	public static interface LazyInitializationWork<T> {
230
		/**
231
		 * Do the represented work and return the result.
232
		 *
233
		 * @return The result
234
		 */
235
		public T doWork();
236
	}
237

    
238
	private <T> T withTemporarySessionIfNeeded(LazyInitializationWork<T> lazyInitializationWork) {
239
		SessionImplementor tempSession = null;
240

    
241
		if ( session == null ) {
242
			if ( allowLoadOutsideTransaction ) {
243
				tempSession = openTemporarySessionForLoading();
244
			}
245
			else {
246
				throwLazyInitializationException( "could not initialize proxy - no Session" );
247
			}
248
		}
249
		else if ( !session.isOpen() ) {
250
			if ( allowLoadOutsideTransaction ) {
251
				tempSession = openTemporarySessionForLoading();
252
			}
253
			else {
254
				throwLazyInitializationException( "could not initialize proxy - the owning Session was closed" );
255
			}
256
		}
257
		else if ( !session.isConnected() ) {
258
			if ( allowLoadOutsideTransaction ) {
259
				tempSession = openTemporarySessionForLoading();
260
			}
261
			else {
262
				throwLazyInitializationException( "could not initialize proxy - the owning Session is disconnected" );
263
			}
264
		}
265

    
266

    
267
		SessionImplementor originalSession = null;
268
		boolean isJTA = false;
269

    
270
		if ( tempSession != null ) {
271
			isTempSession = true;
272
			originalSession = session;
273
			session = tempSession;
274

    
275

    
276
			isJTA = session.getTransactionCoordinator().getTransactionCoordinatorBuilder().isJta();
277

    
278
			if ( !isJTA ) {
279
				// Explicitly handle the transactions only if we're not in
280
				// a JTA environment.  A lazy loading temporary session can
281
				// be created even if a current session and transaction are
282
				// open (ex: session.clear() was used).  We must prevent
283
				// multiple transactions.
284
				( (Session) session ).beginTransaction();
285
			}
286

    
287
			session.getPersistenceContext().addUninitializedDetachedCollection(
288
					session.getFactory().getCollectionPersister( getRole() ),
289
					this
290
			);
291
		}
292

    
293
		try {
294
			return lazyInitializationWork.doWork();
295
		}
296
		finally {
297
			if ( tempSession != null ) {
298
				// make sure the just opened temp session gets closed!
299
				isTempSession = false;
300
				session = originalSession;
301

    
302
				try {
303
					if ( !isJTA ) {
304
						( (Session) tempSession ).getTransaction().commit();
305
					}
306
					( (Session) tempSession ).close();
307
				}
308
				catch (Exception e) {
309
					LOG.warn( "Unable to close temporary session used to load lazy collection associated to no session" );
310
				}
311
			}
312
		}
313
	}
314

    
315
	private SessionImplementor openTemporarySessionForLoading() {
316
		if ( sessionFactoryUuid == null ) {
317
			throwLazyInitializationException( "SessionFactory UUID not known to create temporary Session for loading" );
318
		}
319

    
320
		final SessionFactoryImplementor sf = (SessionFactoryImplementor)
321
				SessionFactoryRegistry.INSTANCE.getSessionFactory( sessionFactoryUuid );
322
		return (SessionImplementor) sf.openSession();
323
	}
324

    
325
	protected Boolean readIndexExistence(final Object index) {
326
		if ( !initialized ) {
327
			//##REMOTING-KEEP##
328
			// In remoting we are sure that session is null
329
			// both when using property paths and switching off conversations
330
			if(session == null && remoting) {
331
				LOG.info("--> readIndexExistence, of " + getRole() + " with key " + getKey());
332
				read();
333
			} else {
334
		    //keep formatting below to ease update to newer hibernate version
335
			//##REMOTING-KEEP END##
336
			final Boolean extraLazyExistenceCheck = withTemporarySessionIfNeeded(
337
					new LazyInitializationWork<Boolean>() {
338
						@Override
339
						public Boolean doWork() {
340
							final CollectionEntry entry = session.getPersistenceContext().getCollectionEntry( AbstractPersistentCollection.this );
341
							final CollectionPersister persister = entry.getLoadedPersister();
342
							if ( persister.isExtraLazy() ) {
343
								if ( hasQueuedOperations() ) {
344
									session.flush();
345
								}
346
								return persister.indexExists( entry.getLoadedKey(), index, session );
347
							}
348
							else {
349
								read();
350
							}
351
							return null;
352
						}
353
					}
354
			);
355
			if ( extraLazyExistenceCheck != null ) {
356
				return extraLazyExistenceCheck;
357
			}
358
			}
359
		}
360
		return null;
361
	}
362

    
363
	protected Boolean readElementExistence(final Object element) {
364
		if ( !initialized ) {
365
			//##REMOTING-KEEP##
366
			// In remoting we are sure that session is null
367
			// both when using property paths and switching off conversations
368
			if(session == null && remoting) {
369
				LOG.info("--> readElementExistence, of " + getRole() + " with key " + getKey());
370
				read();
371

    
372
			} else {
373
		    //keep formatting below to ease update to newer hibernate version
374
			//##REMOTING-KEEP END##
375
			final Boolean extraLazyExistenceCheck = withTemporarySessionIfNeeded(
376
					new LazyInitializationWork<Boolean>() {
377
						@Override
378
						public Boolean doWork() {
379
							final CollectionEntry entry = session.getPersistenceContext().getCollectionEntry( AbstractPersistentCollection.this );
380
							final CollectionPersister persister = entry.getLoadedPersister();
381
							if ( persister.isExtraLazy() ) {
382
								if ( hasQueuedOperations() ) {
383
									session.flush();
384
								}
385
								return persister.elementExists( entry.getLoadedKey(), element, session );
386
							}
387
							else {
388
								read();
389
							}
390
							return null;
391
						}
392
					}
393
			);
394
			if ( extraLazyExistenceCheck != null ) {
395
				return extraLazyExistenceCheck;
396
			}
397
			}
398
		}
399
		return null;
400
	}
401

    
402
	protected static final Object UNKNOWN = new MarkerObject( "UNKNOWN" );
403

    
404
	protected Object readElementByIndex(final Object index) {
405
		if ( !initialized ) {
406
			//##REMOTING-KEEP##
407
			// In remoting we are sure that session is null
408
			// both when using property paths and switching off conversations
409
			if(session == null && remoting) {
410
				LOG.info("--> readElementByIndex, of " + getRole() + " with key " + getKey());
411
				read();
412

    
413
			} else {
414
		    //keep formatting below to ease update to newer hibernate version
415
			//##REMOTING-KEEP END##
416
			class ExtraLazyElementByIndexReader implements LazyInitializationWork {
417
				private boolean isExtraLazy;
418
				private Object element;
419

    
420
				@Override
421
				public Object doWork() {
422
					final CollectionEntry entry = session.getPersistenceContext().getCollectionEntry( AbstractPersistentCollection.this );
423
					final CollectionPersister persister = entry.getLoadedPersister();
424
					isExtraLazy = persister.isExtraLazy();
425
					if ( isExtraLazy ) {
426
						if ( hasQueuedOperations() ) {
427
							session.flush();
428
						}
429
						element = persister.getElementByIndex( entry.getLoadedKey(), index, session, owner );
430
					}
431
					else {
432
						read();
433
					}
434
					return null;
435
				}
436
			}
437

    
438
			final ExtraLazyElementByIndexReader reader = new ExtraLazyElementByIndexReader();
439
			//noinspection unchecked
440
			withTemporarySessionIfNeeded( reader );
441
			if ( reader.isExtraLazy ) {
442
				return reader.element;
443
			}
444
			}
445
		}
446
		return UNKNOWN;
447

    
448
	}
449

    
450
	protected int getCachedSize() {
451
		return cachedSize;
452
	}
453

    
454
	protected boolean isConnectedToSession() {
455
		return session != null
456
				&& session.isOpen()
457
				&& session.getPersistenceContext().containsCollection( this );
458
	}
459

    
460
	protected boolean isInitialized() {
461
		return initialized;
462
	}
463

    
464
	/**
465
	 * Called by any writer method of the collection interface
466
	 */
467
	protected final void write() {
468
		initialize( true );
469
		dirty();
470
	}
471

    
472
	/**
473
	 * Is this collection in a state that would allow us to
474
	 * "queue" operations?
475
	 */
476
	@SuppressWarnings({"JavaDoc"})
477
	protected boolean isOperationQueueEnabled() {
478
		return !initialized
479
				&& isConnectedToSession()
480
				&& isInverseCollection();
481
	}
482

    
483
	/**
484
	 * Is this collection in a state that would allow us to
485
	 * "queue" puts? This is a special case, because of orphan
486
	 * delete.
487
	 */
488
	@SuppressWarnings({"JavaDoc"})
489
	protected boolean isPutQueueEnabled() {
490
		return !initialized
491
				&& isConnectedToSession()
492
				&& isInverseOneToManyOrNoOrphanDelete();
493
	}
494

    
495
	/**
496
	 * Is this collection in a state that would allow us to
497
	 * "queue" clear? This is a special case, because of orphan
498
	 * delete.
499
	 */
500
	@SuppressWarnings({"JavaDoc"})
501
	protected boolean isClearQueueEnabled() {
502
		return !initialized
503
				&& isConnectedToSession()
504
				&& isInverseCollectionNoOrphanDelete();
505
	}
506

    
507
	/**
508
	 * Is this the "inverse" end of a bidirectional association?
509
	 */
510
	@SuppressWarnings({"JavaDoc"})
511
	protected boolean isInverseCollection() {
512
		final CollectionEntry ce = session.getPersistenceContext().getCollectionEntry( this );
513
		return ce != null && ce.getLoadedPersister().isInverse();
514
	}
515

    
516
	/**
517
	 * Is this the "inverse" end of a bidirectional association with
518
	 * no orphan delete enabled?
519
	 */
520
	@SuppressWarnings({"JavaDoc"})
521
	protected boolean isInverseCollectionNoOrphanDelete() {
522
		final CollectionEntry ce = session.getPersistenceContext().getCollectionEntry( this );
523
		return ce != null
524
				&&
525
				ce.getLoadedPersister().isInverse() &&
526
				!ce.getLoadedPersister().hasOrphanDelete();
527
	}
528

    
529
	/**
530
	 * Is this the "inverse" end of a bidirectional one-to-many, or
531
	 * of a collection with no orphan delete?
532
	 */
533
	@SuppressWarnings({"JavaDoc"})
534
	protected boolean isInverseOneToManyOrNoOrphanDelete() {
535
		final CollectionEntry ce = session.getPersistenceContext().getCollectionEntry( this );
536
		return ce != null
537
				&& ce.getLoadedPersister().isInverse()
538
				&& ( ce.getLoadedPersister().isOneToMany() || !ce.getLoadedPersister().hasOrphanDelete() );
539
	}
540

    
541
	/**
542
	 * Queue an addition
543
	 */
544
	@SuppressWarnings({"JavaDoc"})
545
	protected final void queueOperation(DelayedOperation operation) {
546
		if ( operationQueue == null ) {
547
			operationQueue = new ArrayList<DelayedOperation>( 10 );
548
		}
549
		operationQueue.add( operation );
550
		//needed so that we remove this collection from the second-level cache
551
		dirty = true;
552
	}
553

    
554
	/**
555
	 * Replace entity instances with copy in {@code copyCache}/.
556
	 *
557
	 * @param copyCache - mapping from entity in the process of being
558
	 *                    merged to managed copy.
559
	 */
560
	public final void replaceQueuedOperationValues(CollectionPersister persister, Map copyCache) {
561
		for ( DelayedOperation operation : operationQueue ) {
562
			if ( ValueDelayedOperation.class.isInstance( operation ) ) {
563
				( (ValueDelayedOperation) operation ).replace( persister, copyCache );
564
			}
565
		}
566
	}
567

    
568
	/**
569
	 * After reading all existing elements from the database,
570
	 * add the queued elements to the underlying collection.
571
	 */
572
	protected final void performQueuedOperations() {
573
		for ( DelayedOperation operation : operationQueue ) {
574
			operation.operate();
575
		}
576
	}
577

    
578
	@Override
579
	public void setSnapshot(Serializable key, String role, Serializable snapshot) {
580
		this.key = key;
581
		this.role = role;
582
		this.storedSnapshot = snapshot;
583
	}
584

    
585
	@Override
586
	public void postAction() {
587
		operationQueue = null;
588
		cachedSize = -1;
589
		clearDirty();
590
	}
591

    
592
	@Override
593
	public Object getValue() {
594
		return this;
595
	}
596

    
597
	@Override
598
	public void beginRead() {
599
		// override on some subclasses
600
		initializing = true;
601
	}
602

    
603
	@Override
604
	public boolean endRead() {
605
		//override on some subclasses
606
		return afterInitialize();
607
	}
608

    
609
	@Override
610
	public boolean afterInitialize() {
611
		setInitialized();
612
		//do this bit after setting initialized to true or it will recurse
613
		if ( operationQueue != null ) {
614
			performQueuedOperations();
615
			operationQueue = null;
616
			cachedSize = -1;
617
			return false;
618
		}
619
		else {
620
			return true;
621
		}
622
	}
623

    
624
	/**
625
	 * Initialize the collection, if possible, wrapping any exceptions
626
	 * in a runtime exception
627
	 *
628
	 * @param writing currently obsolete
629
	 *
630
	 * @throws LazyInitializationException if we cannot initialize
631
	 */
632
	protected final void initialize(final boolean writing) {
633
		if ( initialized ) {
634
			return;
635
		}
636
		//##REMOTING-KEEP##
637
	    // In remoting we are sure that session is null
638
	    // both when using property paths and switching off conversations
639
	    if(session == null && remoting) {
640
	        remoteInitialize();
641
	    } else {
642
	    //keep formatting below to ease update to newer hibernate version
643
		//##REMOTING-KEEP END##
644
	    withTemporarySessionIfNeeded(
645
				new LazyInitializationWork<Object>() {
646
					@Override
647
					public Object doWork() {
648
						session.initializeCollection( AbstractPersistentCollection.this, writing );
649
						return null;
650
					}
651
				}
652
		);
653
	    }
654
	}
655

    
656
	private void throwLazyInitializationExceptionIfNotConnected() {
657
		if ( !isConnectedToSession() ) {
658
			throwLazyInitializationException( "no session or session was closed" );
659
		}
660
		if ( !session.isConnected() ) {
661
			throwLazyInitializationException( "session is disconnected" );
662
		}
663
	}
664

    
665
	private void throwLazyInitializationException(String message) {
666
		throw new LazyInitializationException(
667
				"failed to lazily initialize a collection" +
668
						(role == null ? "" : " of role: " + role) +
669
						", " + message
670
		);
671
	}
672

    
673
	protected final void setInitialized() {
674
		this.initializing = false;
675
		this.initialized = true;
676
	}
677

    
678
	protected final void setDirectlyAccessible(boolean directlyAccessible) {
679
		this.directlyAccessible = directlyAccessible;
680
	}
681

    
682
	@Override
683
	public boolean isDirectlyAccessible() {
684
		return directlyAccessible;
685
	}
686

    
687
	@Override
688
	public final boolean unsetSession(SessionImplementor currentSession) {
689
		prepareForPossibleLoadingOutsideTransaction();
690
		if ( currentSession == this.session ) {
691
			if ( !isTempSession ) {
692
				this.session = null;
693
			}
694
			return true;
695
		}
696
		else {
697
			if ( this.session != null ) {
698
				LOG.logCannotUnsetUnexpectedSessionInCollection( generateUnexpectedSessionStateMessage( currentSession ) );
699
			}
700
			return false;
701
		}
702
	}
703

    
704
	protected void prepareForPossibleLoadingOutsideTransaction() {
705
		if ( session != null ) {
706
			allowLoadOutsideTransaction = session.getFactory().getSessionFactoryOptions().isInitializeLazyStateOutsideTransactionsEnabled();
707

    
708
			if ( allowLoadOutsideTransaction && sessionFactoryUuid == null ) {
709
				sessionFactoryUuid = session.getFactory().getUuid();
710
			}
711
		}
712
	}
713

    
714
	@Override
715
	public final boolean setCurrentSession(SessionImplementor session) throws HibernateException {
716
		if ( session == this.session ) {
717
			return false;
718
		}
719
		else {
720
			if ( this.session != null ) {
721
				final String msg = generateUnexpectedSessionStateMessage( session );
722
				if ( isConnectedToSession() ) {
723
					throw new HibernateException(
724
							"Illegal attempt to associate a collection with two open sessions. " + msg
725
					);
726
				}
727
				else {
728
					LOG.logUnexpectedSessionInCollectionNotConnected( msg );
729
					this.session = session;
730
					return true;
731
				}
732
			}
733
			else {
734
				this.session = session;
735
				return true;
736
			}
737
		}
738
	}
739

    
740
	private String generateUnexpectedSessionStateMessage(SessionImplementor session) {
741
		// NOTE: If this.session != null, this.session may be operating on this collection
742
		// (e.g., by changing this.role, this.key, or even this.session) in a different thread.
743

    
744
		// Grab the current role and key (it can still get changed by this.session...)
745
		// If this collection is connected to this.session, then this.role and this.key should
746
		// be consistent with the CollectionEntry in this.session (as long as this.session doesn't
747
		// change it). Don't access the CollectionEntry in this.session because that could result
748
		// in multi-threaded access to this.session.
749
		final String roleCurrent = role;
750
		final Serializable keyCurrent = key;
751

    
752
		final StringBuilder sb = new StringBuilder( "Collection : " );
753
		if ( roleCurrent != null ) {
754
			sb.append( MessageHelper.collectionInfoString( roleCurrent, keyCurrent ) );
755
		}
756
		else {
757
			final CollectionEntry ce = session.getPersistenceContext().getCollectionEntry( this );
758
			if ( ce != null ) {
759
				sb.append(
760
						MessageHelper.collectionInfoString(
761
								ce.getLoadedPersister(),
762
								this,
763
								ce.getLoadedKey(),
764
								session
765
						)
766
				);
767
			}
768
			else {
769
				sb.append( "<unknown>" );
770
			}
771
		}
772
		// only include the collection contents if debug logging
773
		if ( LOG.isDebugEnabled() ) {
774
			final String collectionContents = wasInitialized() ? toString() : "<uninitialized>";
775
			sb.append( "\nCollection contents: [" ).append( collectionContents ).append( "]" );
776
		}
777
		return sb.toString();
778
	}
779

    
780
	@Override
781
	public boolean needsRecreate(CollectionPersister persister) {
782
		// Workaround for situations like HHH-7072.  If the collection element is a component that consists entirely
783
		// of nullable properties, we currently have to forcefully recreate the entire collection.  See the use
784
		// of hasNotNullableColumns in the AbstractCollectionPersister constructor for more info.  In order to delete
785
		// row-by-row, that would require SQL like "WHERE ( COL = ? OR ( COL is null AND ? is null ) )", rather than
786
		// the current "WHERE COL = ?" (fails for null for most DBs).  Note that
787
		// the param would have to be bound twice.  Until we eventually add "parameter bind points" concepts to the
788
		// AST in ORM 5+, handling this type of condition is either extremely difficult or impossible.  Forcing
789
		// recreation isn't ideal, but not really any other option in ORM 4.
790
		// Selecting a type used in where part of update statement
791
		// (must match condidion in org.hibernate.persister.collection.BasicCollectionPersister.doUpdateRows).
792
		// See HHH-9474
793
		Type whereType;
794
		if ( persister.hasIndex() ) {
795
			whereType = persister.getIndexType();
796
		}
797
		else {
798
			whereType = persister.getElementType();
799
		}
800
		if ( whereType instanceof CompositeType ) {
801
			CompositeType componentIndexType = (CompositeType) whereType;
802
			return !componentIndexType.hasNotNullProperty();
803
		}
804
		return false;
805
	}
806

    
807
	@Override
808
	public final void forceInitialization() throws HibernateException {
809
		if ( !initialized ) {
810
			if ( initializing ) {
811
				throw new AssertionFailure( "force initialize loading collection" );
812
			}
813
			initialize( false );
814
		}
815
	}
816

    
817

    
818
	/**
819
	 * Get the current snapshot from the session
820
	 */
821
	@SuppressWarnings({"JavaDoc"})
822
	protected final Serializable getSnapshot() {
823
		return session.getPersistenceContext().getSnapshot( this );
824
	}
825

    
826
	@Override
827
	public final boolean wasInitialized() {
828
		return initialized;
829
	}
830

    
831
	@Override
832
	public boolean isRowUpdatePossible() {
833
		return true;
834
	}
835

    
836
	@Override
837
	public final boolean hasQueuedOperations() {
838
		return operationQueue != null;
839
	}
840

    
841
	@Override
842
	public final Iterator queuedAdditionIterator() {
843
		if ( hasQueuedOperations() ) {
844
			return new Iterator() {
845
				private int index;
846

    
847
				@Override
848
				public Object next() {
849
					return operationQueue.get( index++ ).getAddedInstance();
850
				}
851

    
852
				@Override
853
				public boolean hasNext() {
854
					return index < operationQueue.size();
855
				}
856

    
857
				@Override
858
				public void remove() {
859
					throw new UnsupportedOperationException();
860
				}
861
			};
862
		}
863
		else {
864
			return EmptyIterator.INSTANCE;
865
		}
866
	}
867

    
868
	@Override
869
	@SuppressWarnings({"unchecked"})
870
	public final Collection getQueuedOrphans(String entityName) {
871
		if ( hasQueuedOperations() ) {
872
			final Collection additions = new ArrayList( operationQueue.size() );
873
			final Collection removals = new ArrayList( operationQueue.size() );
874
			for ( DelayedOperation operation : operationQueue ) {
875
				additions.add( operation.getAddedInstance() );
876
				removals.add( operation.getOrphan() );
877
			}
878
			return getOrphans( removals, additions, entityName, session );
879
		}
880
		else {
881
			return Collections.EMPTY_LIST;
882
		}
883
	}
884

    
885
	@Override
886
	public void preInsert(CollectionPersister persister) throws HibernateException {
887
	}
888

    
889
	@Override
890
	public void afterRowInsert(CollectionPersister persister, Object entry, int i) throws HibernateException {
891
	}
892

    
893
	@Override
894
	public abstract Collection getOrphans(Serializable snapshot, String entityName) throws HibernateException;
895

    
896
	/**
897
	 * Get the session currently associated with this collection.
898
	 *
899
	 * @return The session
900
	 */
901
	public final SessionImplementor getSession() {
902
		return session;
903
	}
904

    
905
	protected final class IteratorProxy implements Iterator {
906
		protected final Iterator itr;
907

    
908
		public IteratorProxy(Iterator itr) {
909
			this.itr = itr;
910
		}
911

    
912
		@Override
913
		public boolean hasNext() {
914
			return itr.hasNext();
915
		}
916

    
917
		@Override
918
		public Object next() {
919
			return itr.next();
920
		}
921

    
922
		@Override
923
		public void remove() {
924
			write();
925
			itr.remove();
926
		}
927
	}
928

    
929
	protected final class ListIteratorProxy implements ListIterator {
930
		protected final ListIterator itr;
931

    
932
		public ListIteratorProxy(ListIterator itr) {
933
			this.itr = itr;
934
		}
935

    
936
		@Override
937
		@SuppressWarnings({"unchecked"})
938
		public void add(Object o) {
939
			write();
940
			itr.add( o );
941
		}
942

    
943
		@Override
944
		public boolean hasNext() {
945
			return itr.hasNext();
946
		}
947

    
948
		@Override
949
		public boolean hasPrevious() {
950
			return itr.hasPrevious();
951
		}
952

    
953
		@Override
954
		public Object next() {
955
			return itr.next();
956
		}
957

    
958
		@Override
959
		public int nextIndex() {
960
			return itr.nextIndex();
961
		}
962

    
963
		@Override
964
		public Object previous() {
965
			return itr.previous();
966
		}
967

    
968
		@Override
969
		public int previousIndex() {
970
			return itr.previousIndex();
971
		}
972

    
973
		@Override
974
		public void remove() {
975
			write();
976
			itr.remove();
977
		}
978

    
979
		@Override
980
		@SuppressWarnings({"unchecked"})
981
		public void set(Object o) {
982
			write();
983
			itr.set( o );
984
		}
985
	}
986

    
987
	protected class SetProxy implements java.util.Set {
988
		protected final Collection set;
989

    
990
		public SetProxy(Collection set) {
991
			this.set = set;
992
		}
993

    
994
		@Override
995
		@SuppressWarnings({"unchecked"})
996
		public boolean add(Object o) {
997
			write();
998
			return set.add( o );
999
		}
1000

    
1001
		@Override
1002
		@SuppressWarnings({"unchecked"})
1003
		public boolean addAll(Collection c) {
1004
			write();
1005
			return set.addAll( c );
1006
		}
1007

    
1008
		@Override
1009
		public void clear() {
1010
			write();
1011
			set.clear();
1012
		}
1013

    
1014
		@Override
1015
		public boolean contains(Object o) {
1016
			return set.contains( o );
1017
		}
1018

    
1019
		@Override
1020
		@SuppressWarnings("unchecked")
1021
		public boolean containsAll(Collection c) {
1022
			return set.containsAll( c );
1023
		}
1024

    
1025
		@Override
1026
		public boolean isEmpty() {
1027
			return set.isEmpty();
1028
		}
1029

    
1030
		@Override
1031
		public Iterator iterator() {
1032
			return new IteratorProxy( set.iterator() );
1033
		}
1034

    
1035
		@Override
1036
		public boolean remove(Object o) {
1037
			write();
1038
			return set.remove( o );
1039
		}
1040

    
1041
		@Override
1042
		@SuppressWarnings("unchecked")
1043
		public boolean removeAll(Collection c) {
1044
			write();
1045
			return set.removeAll( c );
1046
		}
1047

    
1048
		@Override
1049
		@SuppressWarnings("unchecked")
1050
		public boolean retainAll(Collection c) {
1051
			write();
1052
			return set.retainAll( c );
1053
		}
1054

    
1055
		@Override
1056
		public int size() {
1057
			return set.size();
1058
		}
1059

    
1060
		@Override
1061
		public Object[] toArray() {
1062
			return set.toArray();
1063
		}
1064

    
1065
		@Override
1066
		@SuppressWarnings({"unchecked"})
1067
		public Object[] toArray(Object[] array) {
1068
			return set.toArray( array );
1069
		}
1070
	}
1071

    
1072
	protected final class ListProxy implements java.util.List {
1073
		protected final List list;
1074

    
1075
		public ListProxy(List list) {
1076
			this.list = list;
1077
		}
1078

    
1079
		@Override
1080
		@SuppressWarnings({"unchecked"})
1081
		public void add(int index, Object value) {
1082
			write();
1083
			list.add( index, value );
1084
		}
1085

    
1086
		@Override
1087
		@SuppressWarnings({"unchecked"})
1088
		public boolean add(Object o) {
1089
			write();
1090
			return list.add( o );
1091
		}
1092

    
1093
		@Override
1094
		@SuppressWarnings({"unchecked"})
1095
		public boolean addAll(Collection c) {
1096
			write();
1097
			return list.addAll( c );
1098
		}
1099

    
1100
		@Override
1101
		@SuppressWarnings({"unchecked"})
1102
		public boolean addAll(int i, Collection c) {
1103
			write();
1104
			return list.addAll( i, c );
1105
		}
1106

    
1107
		@Override
1108
		public void clear() {
1109
			write();
1110
			list.clear();
1111
		}
1112

    
1113
		@Override
1114
		public boolean contains(Object o) {
1115
			return list.contains( o );
1116
		}
1117

    
1118
		@Override
1119
		@SuppressWarnings("unchecked")
1120
		public boolean containsAll(Collection c) {
1121
			return list.containsAll( c );
1122
		}
1123

    
1124
		@Override
1125
		public Object get(int i) {
1126
			return list.get( i );
1127
		}
1128

    
1129
		@Override
1130
		public int indexOf(Object o) {
1131
			return list.indexOf( o );
1132
		}
1133

    
1134
		@Override
1135
		public boolean isEmpty() {
1136
			return list.isEmpty();
1137
		}
1138

    
1139
		@Override
1140
		public Iterator iterator() {
1141
			return new IteratorProxy( list.iterator() );
1142
		}
1143

    
1144
		@Override
1145
		public int lastIndexOf(Object o) {
1146
			return list.lastIndexOf( o );
1147
		}
1148

    
1149
		@Override
1150
		public ListIterator listIterator() {
1151
			return new ListIteratorProxy( list.listIterator() );
1152
		}
1153

    
1154
		@Override
1155
		public ListIterator listIterator(int i) {
1156
			return new ListIteratorProxy( list.listIterator( i ) );
1157
		}
1158

    
1159
		@Override
1160
		public Object remove(int i) {
1161
			write();
1162
			return list.remove( i );
1163
		}
1164

    
1165
		@Override
1166
		public boolean remove(Object o) {
1167
			write();
1168
			return list.remove( o );
1169
		}
1170

    
1171
		@Override
1172
		@SuppressWarnings("unchecked")
1173
		public boolean removeAll(Collection c) {
1174
			write();
1175
			return list.removeAll( c );
1176
		}
1177

    
1178
		@Override
1179
		@SuppressWarnings("unchecked")
1180
		public boolean retainAll(Collection c) {
1181
			write();
1182
			return list.retainAll( c );
1183
		}
1184

    
1185
		@Override
1186
		@SuppressWarnings({"unchecked"})
1187
		public Object set(int i, Object o) {
1188
			write();
1189
			return list.set( i, o );
1190
		}
1191

    
1192
		@Override
1193
		public int size() {
1194
			return list.size();
1195
		}
1196

    
1197
		@Override
1198
		public List subList(int i, int j) {
1199
			return list.subList( i, j );
1200
		}
1201

    
1202
		@Override
1203
		public Object[] toArray() {
1204
			return list.toArray();
1205
		}
1206

    
1207
		@Override
1208
		@SuppressWarnings({"unchecked"})
1209
		public Object[] toArray(Object[] array) {
1210
			return list.toArray( array );
1211
		}
1212

    
1213
	}
1214

    
1215
	/**
1216
	 * Contract for operations which are part of a collection's operation queue.
1217
	 */
1218
	protected interface DelayedOperation {
1219
		public void operate();
1220

    
1221
		public Object getAddedInstance();
1222

    
1223
		public Object getOrphan();
1224
	}
1225

    
1226
	protected interface ValueDelayedOperation extends DelayedOperation {
1227
		void replace(CollectionPersister collectionPersister, Map copyCache);
1228
	}
1229

    
1230
	protected abstract class AbstractValueDelayedOperation implements ValueDelayedOperation {
1231
		private Object addedValue;
1232
		private Object orphan;
1233

    
1234
		protected AbstractValueDelayedOperation(Object addedValue, Object orphan) {
1235
			this.addedValue = addedValue;
1236
			this.orphan = orphan;
1237
		}
1238

    
1239
		@Override
1240
        public void replace(CollectionPersister persister, Map copyCache) {
1241
			if ( addedValue != null ) {
1242
				addedValue = getReplacement( persister.getElementType(), addedValue, copyCache );
1243
			}
1244
		}
1245

    
1246
		protected final Object getReplacement(Type type, Object current, Map copyCache) {
1247
			return type.replace( current, null, session, owner, copyCache );
1248
		}
1249

    
1250
		@Override
1251
		public final Object getAddedInstance() {
1252
			return addedValue;
1253
		}
1254

    
1255
		@Override
1256
		public final Object getOrphan() {
1257
			return orphan;
1258
		}
1259
	}
1260

    
1261
	/**
1262
	 * Given a collection of entity instances that used to
1263
	 * belong to the collection, and a collection of instances
1264
	 * that currently belong, return a collection of orphans
1265
	 */
1266
	@SuppressWarnings({"JavaDoc", "unchecked"})
1267
	protected static Collection getOrphans(
1268
			Collection oldElements,
1269
			Collection currentElements,
1270
			String entityName,
1271
			SessionImplementor session) throws HibernateException {
1272

    
1273
		// short-circuit(s)
1274
		if ( currentElements.size() == 0 ) {
1275
			// no new elements, the old list contains only Orphans
1276
			return oldElements;
1277
		}
1278
		if ( oldElements.size() == 0 ) {
1279
			// no old elements, so no Orphans neither
1280
			return oldElements;
1281
		}
1282

    
1283
		final EntityPersister entityPersister = session.getFactory().getEntityPersister( entityName );
1284
		final Type idType = entityPersister.getIdentifierType();
1285
		final boolean useIdDirect = mayUseIdDirect( idType );
1286

    
1287
		// create the collection holding the Orphans
1288
		final Collection res = new ArrayList();
1289

    
1290
		// collect EntityIdentifier(s) of the *current* elements - add them into a HashSet for fast access
1291
		final java.util.Set currentIds = new HashSet();
1292
		final java.util.Set currentSaving = new IdentitySet();
1293
		for ( Object current : currentElements ) {
1294
			if ( current != null && ForeignKeys.isNotTransient( entityName, current, null, session ) ) {
1295
				final EntityEntry ee = session.getPersistenceContext().getEntry( current );
1296
				if ( ee != null && ee.getStatus() == Status.SAVING ) {
1297
					currentSaving.add( current );
1298
				}
1299
				else {
1300
					final Serializable currentId = ForeignKeys.getEntityIdentifierIfNotUnsaved(
1301
							entityName,
1302
							current,
1303
							session
1304
					);
1305
					currentIds.add( useIdDirect ? currentId : new TypedValue( idType, currentId ) );
1306
				}
1307
			}
1308
		}
1309

    
1310
		// iterate over the *old* list
1311
		for ( Object old : oldElements ) {
1312
			if ( !currentSaving.contains( old ) ) {
1313
				final Serializable oldId = ForeignKeys.getEntityIdentifierIfNotUnsaved( entityName, old, session );
1314
				if ( !currentIds.contains( useIdDirect ? oldId : new TypedValue( idType, oldId ) ) ) {
1315
					res.add( old );
1316
				}
1317
			}
1318
		}
1319

    
1320
		return res;
1321
	}
1322

    
1323
	private static boolean mayUseIdDirect(Type idType) {
1324
		return idType == StringType.INSTANCE
1325
			|| idType == IntegerType.INSTANCE
1326
			|| idType == LongType.INSTANCE
1327
			|| idType == UUIDBinaryType.INSTANCE
1328
			|| idType == UUIDCharType.INSTANCE
1329
			|| idType == PostgresUUIDType.INSTANCE;
1330
	}
1331

    
1332
	/**
1333
	 * Removes entity entries that have an equal identifier with the incoming entity instance
1334
	 *
1335
	 * @param list The list containing the entity instances
1336
	 * @param entityInstance The entity instance to match elements.
1337
	 * @param entityName The entity name
1338
	 * @param session The session
1339
	 */
1340
	public static void identityRemove(
1341
			Collection list,
1342
			Object entityInstance,
1343
			String entityName,
1344
			SessionImplementor session) {
1345

    
1346
		if ( entityInstance != null && ForeignKeys.isNotTransient( entityName, entityInstance, null, session ) ) {
1347
			final EntityPersister entityPersister = session.getFactory().getEntityPersister( entityName );
1348
			final Type idType = entityPersister.getIdentifierType();
1349

    
1350
			final Serializable idOfCurrent = ForeignKeys.getEntityIdentifierIfNotUnsaved( entityName, entityInstance, session );
1351
			final Iterator itr = list.iterator();
1352
			while ( itr.hasNext() ) {
1353
				final Serializable idOfOld = ForeignKeys.getEntityIdentifierIfNotUnsaved( entityName, itr.next(), session );
1354
				if ( idType.isEqual( idOfCurrent, idOfOld, session.getFactory() ) ) {
1355
					itr.remove();
1356
					break;
1357
				}
1358
			}
1359

    
1360
		}
1361
	}
1362

    
1363
	@Override
1364
	public Object getIdentifier(Object entry, int i) {
1365
		throw new UnsupportedOperationException();
1366
	}
1367

    
1368
	@Override
1369
	public Object getOwner() {
1370
		return owner;
1371
	}
1372

    
1373
	@Override
1374
	public void setOwner(Object owner) {
1375
		this.owner = owner;
1376
	}
1377

    
1378

    
1379
/** ##REMOTING-KEEP## #######################################################
1380

    
1381
    ADDED PART: Below is the section of code which makes remote service calls.
1382
    Keeps this code when upgrading to newer hibernate version. Also keep
1383
    other code marked with ##REMOTING-KEEP## in the following 5 methods:
1384

    
1385
    {@link #initialize(boolean)}
1386
	{@link #readSize()}
1387
	{@link #readIndexExistence(Object)}
1388
	{@link #readElementExistence(Object)}
1389
	{@link #readElementByIndex(Object)}
1390

    
1391
    ######################################################################### */
1392

    
1393
	private static CdmApplicationRemoteConfiguration configuration;
1394
	private static boolean remoting = false;
1395

    
1396
	public static void setConfiguration(CdmApplicationRemoteConfiguration conf) {
1397
	    remoting = true;
1398
		configuration = conf;
1399
	}
1400

    
1401
	private void remoteInitialize() {
1402

    
1403
		if (getOwner() != null && !initialized) {
1404
		    Object collectionType = null;
1405
		    Field field = null;
1406
		    Class<?> clazz = null;
1407
			try {
1408
				String role = getRole();
1409
				String fieldName = role.substring(role.lastIndexOf(".") + 1);
1410
				LOG.info("--> Remote Lazy Initializing Collection " + getRole() + " , owner : " + getOwner().getClass() + "/" + getKey() + " , field : " + fieldName);
1411
				Object owner = getOwner();
1412
				CdmBase cdmBase;
1413
				if(owner instanceof CdmBase) {
1414
				    cdmBase = (CdmBase)owner;
1415
				} else {
1416
				    throw new HibernateException("Owner of persistent collection is not a cdm entity");
1417
				}
1418
				if(configuration == null) {
1419
					throw new HibernateException("CdmApplicationRemoteConfiguration not initialized (null)");
1420
				}
1421
				ICachedCommonService cachedCommonService = configuration.getCachedCommonService();
1422
				if(cachedCommonService == null) {
1423
					throw new HibernateException("commonService not initialized (null)");
1424
				}
1425

    
1426
				//Object obj = ProxyUtils.deproxyIfInitialized(cachedCommonService.initializeCollection(this));
1427
				Object obj = ProxyUtils.deproxyIfInitialized(cachedCommonService.initializeCollection(cdmBase.getUuid(), fieldName));
1428
				if(ProxyUtils.isUninitializedProxy(obj)) {
1429
				    throw new HibernateException("Persistent Collection initialized but is still a proxy");
1430
				}
1431
				afterInitialize();
1432

    
1433
				clazz = getClass();
1434
				if (clazz != null) {
1435
					collectionType = ProxyUtils.getCollectionType(obj, clazz);
1436
				    field = clazz.getDeclaredField(collectionType.toString());
1437
					field.setAccessible(true);
1438
					field.set(this, obj);
1439
					ProxyUtils.setRoleValueInOwner(owner, role, obj);
1440

    
1441
				}
1442
			} catch (Exception ex) {
1443
			    String originalMessage = ex.getMessage();
1444
			    String message = originalMessage + "clazz: " + (clazz == null? "" :clazz.getSimpleName())+ "- field: " + field + " - collectionType: " + collectionType;
1445
			    throw new CdmEagerLoadingException(message);
1446
			}
1447
		}
1448
	}
1449
    // ##REMOTING-KEEP END##
1450
}
    (1-1/1)