#10009 add spring-security-remoting again to try to solve serialVersionUID issue
[taxeditor.git] / eu.etaxonomy.taxeditor.cdmlib / src / main / java / org / hibernate / collection / internal / AbstractPersistentCollection.java
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.FlushMode;
22 import org.hibernate.HibernateException;
23 import org.hibernate.LazyInitializationException;
24 import org.hibernate.Session;
25 import org.hibernate.collection.spi.PersistentCollection;
26 import org.hibernate.engine.internal.ForeignKeys;
27 import org.hibernate.engine.spi.CollectionEntry;
28 import org.hibernate.engine.spi.EntityEntry;
29 import org.hibernate.engine.spi.PersistenceContext;
30 import org.hibernate.engine.spi.SessionFactoryImplementor;
31 import org.hibernate.engine.spi.SessionImplementor;
32 import org.hibernate.engine.spi.SharedSessionContractImplementor;
33 import org.hibernate.engine.spi.Status;
34 import org.hibernate.engine.spi.TypedValue;
35 import org.hibernate.internal.CoreLogging;
36 import org.hibernate.internal.CoreMessageLogger;
37 import org.hibernate.internal.SessionFactoryRegistry;
38 import org.hibernate.internal.util.MarkerObject;
39 import org.hibernate.internal.util.collections.IdentitySet;
40 import org.hibernate.persister.collection.CollectionPersister;
41 import org.hibernate.persister.entity.EntityPersister;
42 import org.hibernate.pretty.MessageHelper;
43 import org.hibernate.resource.transaction.spi.TransactionStatus;
44 import org.hibernate.type.CompositeType;
45 import org.hibernate.type.IntegerType;
46 import org.hibernate.type.LongType;
47 import org.hibernate.type.PostgresUUIDType;
48 import org.hibernate.type.StringType;
49 import org.hibernate.type.Type;
50 import org.hibernate.type.UUIDBinaryType;
51 import org.hibernate.type.UUIDCharType;
52
53 import eu.etaxonomy.cdm.api.application.CdmApplicationRemoteConfiguration;
54 import eu.etaxonomy.cdm.cache.ProxyUtils;
55 import eu.etaxonomy.cdm.model.common.CdmBase;
56 import eu.etaxonomy.taxeditor.remoting.CdmEagerLoadingException;
57 import eu.etaxonomy.taxeditor.service.ICachedCommonService;
58
59 /**
60 * Base class implementing {@link org.hibernate.collection.spi.PersistentCollection}
61 *
62 * This is an extended copy of the original class from hibernate. It has been extended to
63 * allow making remote service calls to spring httpinvoker services (see section at the bottom
64 * of this class).
65 *
66 * NOTE: For updating this class to the latest hibernate version, update the serialVersionUID as
67 * described above, copy the new class to the old class BUT keep those 5 places marked with
68 * ##REMOTING-KEEP##
69 *
70 * @author Gavin King
71 * @author Cherian Mathew
72 */
73 public abstract class AbstractPersistentCollection implements Serializable, PersistentCollection {
74 private static final CoreMessageLogger LOG = CoreLogging.messageLogger( AbstractPersistentCollection.class );
75
76 /**
77 * <b>IMPORTANT:</b><br>
78 * This serialVersionUID must be kept in sync with the serialVersionUID which is generated
79 * on the fly for serialized AbstractPersistentCollection objects coming from the httpInvoker
80 * service.
81 * This is most probably necessary after updating hibernate to a newer version. In any case
82 * it the need for updating this <code>serialVersionUID</code> becomes obvious when the attempt
83 * to connect to the server side fails with an <code>InvalidClassException</code>:
84 *
85 * <pre>
86 * java.io.InvalidClassException: org.hibernate.collection.internal.AbstractPersistentCollection;
87 * local class incompatible:
88 * stream classdesc serialVersionUID = 2742261122392386159,
89 * local class serialVersionUID = -7238232378593030571
90 * </pre>
91 * The correct <code>serialVersionUID</code> is the <code>stream classdesc serialVersionUID</code>
92 * from the error message.
93 */
94 private static final long serialVersionUID = 7094296207968006972L;
95
96 private transient SharedSessionContractImplementor session;
97 private boolean isTempSession = false;
98 private boolean initialized;
99 private transient List<DelayedOperation> operationQueue;
100 private transient boolean directlyAccessible;
101 private transient boolean initializing;
102 private Object owner;
103 private int cachedSize = -1;
104
105 private String role;
106 private Serializable key;
107 // collections detect changes made via their public interface and mark
108 // themselves as dirty as a performance optimization
109 private boolean dirty;
110 protected boolean elementRemoved;
111 private Serializable storedSnapshot;
112
113 private String sessionFactoryUuid;
114 private boolean allowLoadOutsideTransaction;
115
116 /**
117 * Not called by Hibernate, but used by non-JDK serialization,
118 * eg. SOAP libraries.
119 */
120 public AbstractPersistentCollection() {
121 }
122
123 protected AbstractPersistentCollection(SharedSessionContractImplementor session) {
124 this.session = session;
125 }
126
127 /**
128 * @deprecated {@link #AbstractPersistentCollection(SharedSessionContractImplementor)} should be used instead.
129 */
130 @Deprecated
131 protected AbstractPersistentCollection(SessionImplementor session) {
132 this( (SharedSessionContractImplementor) session );
133 }
134
135 @Override
136 public final String getRole() {
137 return role;
138 }
139
140 @Override
141 public final Serializable getKey() {
142 return key;
143 }
144
145 @Override
146 public final boolean isUnreferenced() {
147 return role == null;
148 }
149
150 @Override
151 public final boolean isDirty() {
152 return dirty;
153 }
154
155 @Override
156 public boolean isElementRemoved() {
157 return elementRemoved;
158 }
159
160 @Override
161 public final void clearDirty() {
162 dirty = false;
163 elementRemoved = false;
164 }
165
166 @Override
167 public final void dirty() {
168 dirty = true;
169 }
170
171 @Override
172 public final Serializable getStoredSnapshot() {
173 return storedSnapshot;
174 }
175
176 //Careful: these methods do not initialize the collection.
177
178 @Override
179 public abstract boolean empty();
180
181 /**
182 * Called by any read-only method of the collection interface
183 */
184 protected final void read() {
185 initialize( false );
186 }
187
188 /**
189 * Called by the {@link Collection#size} method
190 */
191 @SuppressWarnings({"JavaDoc"})
192 protected boolean readSize() {
193 if ( !initialized ) {
194 if ( cachedSize != -1 && !hasQueuedOperations() ) {
195 return true;
196 }
197 else {
198 //##REMOTING-KEEP##
199 // In remoting we are sure that session is null
200 // both when using property paths and switching off conversations
201 if(session == null && remoting) {
202 LOG.info("--> readSize, of " + getRole() + " with key " + getKey());
203 read();
204 } else {
205 //keep formatting below to ease update to newer hibernate version
206 //##REMOTING-KEEP END##
207 final boolean isExtraLazy = withTemporarySessionIfNeeded(
208 new LazyInitializationWork<Boolean>() {
209 @Override
210 public Boolean doWork() {
211 final CollectionEntry entry = session.getPersistenceContextInternal().getCollectionEntry( AbstractPersistentCollection.this );
212
213 if ( entry != null ) {
214 final CollectionPersister persister = entry.getLoadedPersister();
215 if ( persister.isExtraLazy() ) {
216 if ( hasQueuedOperations() ) {
217 session.flush();
218 }
219 cachedSize = persister.getSize( entry.getLoadedKey(), session );
220 return true;
221 }
222 else {
223 read();
224 }
225 }
226 else{
227 throwLazyInitializationExceptionIfNotConnected();
228 }
229 return false;
230 }
231 }
232 );
233 if ( isExtraLazy ) {
234 return true;
235 }
236 }
237 }
238 }
239 return false;
240 }
241
242 /**
243 * TBH not sure why this is public
244 *
245 * @param <T> The java type of the return for this LazyInitializationWork
246 */
247 public static interface LazyInitializationWork<T> {
248 /**
249 * Do the represented work and return the result.
250 *
251 * @return The result
252 */
253 public T doWork();
254 }
255
256 private <T> T withTemporarySessionIfNeeded(LazyInitializationWork<T> lazyInitializationWork) {
257 SharedSessionContractImplementor tempSession = null;
258
259 if ( session == null ) {
260 if ( allowLoadOutsideTransaction ) {
261 tempSession = openTemporarySessionForLoading();
262 }
263 else {
264 throwLazyInitializationException( "could not initialize proxy - no Session" );
265 }
266 }
267 else if ( !session.isOpenOrWaitingForAutoClose() ) {
268 if ( allowLoadOutsideTransaction ) {
269 tempSession = openTemporarySessionForLoading();
270 }
271 else {
272 throwLazyInitializationException( "could not initialize proxy - the owning Session was closed" );
273 }
274 }
275 else if ( !session.isConnected() ) {
276 if ( allowLoadOutsideTransaction ) {
277 tempSession = openTemporarySessionForLoading();
278 }
279 else {
280 throwLazyInitializationException( "could not initialize proxy - the owning Session is disconnected" );
281 }
282 }
283
284 SharedSessionContractImplementor originalSession = null;
285 boolean isJTA = false;
286
287 if ( tempSession != null ) {
288 isTempSession = true;
289 originalSession = session;
290 session = tempSession;
291
292 isJTA = session.getTransactionCoordinator().getTransactionCoordinatorBuilder().isJta();
293
294 if ( !isJTA ) {
295 // Explicitly handle the transactions only if we're not in
296 // a JTA environment. A lazy loading temporary session can
297 // be created even if a current session and transaction are
298 // open (ex: session.clear() was used). We must prevent
299 // multiple transactions.
300 ( (Session) session ).beginTransaction();
301 }
302
303 session.getPersistenceContextInternal().addUninitializedDetachedCollection(
304 session.getFactory().getCollectionPersister( getRole() ),
305 this
306 );
307 }
308
309 try {
310 return lazyInitializationWork.doWork();
311 }
312 finally {
313 if ( tempSession != null ) {
314 // make sure the just opened temp session gets closed!
315 isTempSession = false;
316 session = originalSession;
317
318 try {
319 if ( !isJTA ) {
320 ( (Session) tempSession ).getTransaction().commit();
321 }
322 ( (Session) tempSession ).close();
323 }
324 catch (Exception e) {
325 LOG.warn( "Unable to close temporary session used to load lazy collection associated to no session" );
326 }
327 }
328 }
329 }
330
331 private SharedSessionContractImplementor openTemporarySessionForLoading() {
332 if ( sessionFactoryUuid == null ) {
333 throwLazyInitializationException( "SessionFactory UUID not known to create temporary Session for loading" );
334 }
335
336 final SessionFactoryImplementor sf = (SessionFactoryImplementor)
337 SessionFactoryRegistry.INSTANCE.getSessionFactory( sessionFactoryUuid );
338 final SharedSessionContractImplementor session = (SharedSessionContractImplementor) sf.openSession();
339 session.getPersistenceContextInternal().setDefaultReadOnly( true );
340 session.setFlushMode( FlushMode.MANUAL );
341 return session;
342 }
343
344 protected Boolean readIndexExistence(final Object index) {
345 if ( !initialized ) {
346 //##REMOTING-KEEP##
347 // In remoting we are sure that session is null
348 // both when using property paths and switching off conversations
349 if(session == null && remoting) {
350 LOG.info("--> readIndexExistence, of " + getRole() + " with key " + getKey());
351 read();
352 } else {
353 //keep formatting below to ease update to newer hibernate version
354 //##REMOTING-KEEP END##
355 final Boolean extraLazyExistenceCheck = withTemporarySessionIfNeeded(
356 new LazyInitializationWork<Boolean>() {
357 @Override
358 public Boolean doWork() {
359 final CollectionEntry entry = session.getPersistenceContextInternal().getCollectionEntry( AbstractPersistentCollection.this );
360 final CollectionPersister persister = entry.getLoadedPersister();
361 if ( persister.isExtraLazy() ) {
362 if ( hasQueuedOperations() ) {
363 session.flush();
364 }
365 return persister.indexExists( entry.getLoadedKey(), index, session );
366 }
367 else {
368 read();
369 }
370 return null;
371 }
372 }
373 );
374 if ( extraLazyExistenceCheck != null ) {
375 return extraLazyExistenceCheck;
376 }
377 }
378 }
379 return null;
380 }
381
382 protected Boolean readElementExistence(final Object element) {
383 if ( !initialized ) {
384 //##REMOTING-KEEP##
385 // In remoting we are sure that session is null
386 // both when using property paths and switching off conversations
387 if(session == null && remoting) {
388 LOG.info("--> readElementExistence, of " + getRole() + " with key " + getKey());
389 read();
390
391 } else {
392 //keep formatting below to ease update to newer hibernate version
393 //##REMOTING-KEEP END##
394 final Boolean extraLazyExistenceCheck = withTemporarySessionIfNeeded(
395 new LazyInitializationWork<Boolean>() {
396 @Override
397 public Boolean doWork() {
398 final CollectionEntry entry = session.getPersistenceContextInternal().getCollectionEntry( AbstractPersistentCollection.this );
399 final CollectionPersister persister = entry.getLoadedPersister();
400 if ( persister.isExtraLazy() ) {
401 if ( hasQueuedOperations() ) {
402 session.flush();
403 }
404 return persister.elementExists( entry.getLoadedKey(), element, session );
405 }
406 else {
407 read();
408 }
409 return null;
410 }
411 }
412 );
413 if ( extraLazyExistenceCheck != null ) {
414 return extraLazyExistenceCheck;
415 }
416 }
417 }
418 return null;
419 }
420
421 protected static final Object UNKNOWN = new MarkerObject( "UNKNOWN" );
422
423 protected Object readElementByIndex(final Object index) {
424 if ( !initialized ) {
425 //##REMOTING-KEEP##
426 // In remoting we are sure that session is null
427 // both when using property paths and switching off conversations
428 if(session == null && remoting) {
429 LOG.info("--> readElementByIndex, of " + getRole() + " with key " + getKey());
430 read();
431
432 } else {
433 //keep formatting below to ease update to newer hibernate version
434 //##REMOTING-KEEP END##
435 class ExtraLazyElementByIndexReader implements LazyInitializationWork {
436 private boolean isExtraLazy;
437 private Object element;
438
439 @Override
440 public Object doWork() {
441 final CollectionEntry entry = session.getPersistenceContextInternal().getCollectionEntry( AbstractPersistentCollection.this );
442 final CollectionPersister persister = entry.getLoadedPersister();
443 isExtraLazy = persister.isExtraLazy();
444 if ( isExtraLazy ) {
445 if ( hasQueuedOperations() ) {
446 session.flush();
447 }
448 element = persister.getElementByIndex( entry.getLoadedKey(), index, session, owner );
449 }
450 else {
451 read();
452 }
453 return null;
454 }
455 }
456
457 final ExtraLazyElementByIndexReader reader = new ExtraLazyElementByIndexReader();
458 //noinspection unchecked
459 withTemporarySessionIfNeeded( reader );
460 if ( reader.isExtraLazy ) {
461 return reader.element;
462 }
463 }
464 }
465 return UNKNOWN;
466
467 }
468
469 protected int getCachedSize() {
470 return cachedSize;
471 }
472
473 protected boolean isConnectedToSession() {
474 return session != null
475 && session.isOpen()
476 && session.getPersistenceContextInternal().containsCollection( this );
477 }
478
479 protected boolean isInitialized() {
480 return initialized;
481 }
482
483 /**
484 * Called by any writer method of the collection interface
485 */
486 protected final void write() {
487 initialize( true );
488 dirty();
489 }
490
491 /**
492 * Is this collection in a state that would allow us to
493 * "queue" operations?
494 */
495 @SuppressWarnings({"JavaDoc"})
496 protected boolean isOperationQueueEnabled() {
497 return !initialized
498 && isConnectedToSession()
499 && isInverseCollection();
500 }
501
502 /**
503 * Is this collection in a state that would allow us to
504 * "queue" puts? This is a special case, because of orphan
505 * delete.
506 */
507 @SuppressWarnings({"JavaDoc"})
508 protected boolean isPutQueueEnabled() {
509 return !initialized
510 && isConnectedToSession()
511 && isInverseOneToManyOrNoOrphanDelete();
512 }
513
514 /**
515 * Is this collection in a state that would allow us to
516 * "queue" clear? This is a special case, because of orphan
517 * delete.
518 */
519 @SuppressWarnings({"JavaDoc"})
520 protected boolean isClearQueueEnabled() {
521 return !initialized
522 && isConnectedToSession()
523 && isInverseCollectionNoOrphanDelete();
524 }
525
526 /**
527 * Is this the "inverse" end of a bidirectional association?
528 */
529 @SuppressWarnings({"JavaDoc"})
530 protected boolean isInverseCollection() {
531 final CollectionEntry ce = session.getPersistenceContextInternal().getCollectionEntry( this );
532 return ce != null && ce.getLoadedPersister().isInverse();
533 }
534
535 /**
536 * Is this the "inverse" end of a bidirectional association with
537 * no orphan delete enabled?
538 */
539 @SuppressWarnings({"JavaDoc"})
540 protected boolean isInverseCollectionNoOrphanDelete() {
541 final CollectionEntry ce = session.getPersistenceContextInternal().getCollectionEntry( this );
542 if ( ce == null ) {
543 return false;
544 }
545 final CollectionPersister loadedPersister = ce.getLoadedPersister();
546 return loadedPersister.isInverse() && !loadedPersister.hasOrphanDelete();
547 }
548
549 /**
550 * Is this the "inverse" end of a bidirectional one-to-many, or
551 * of a collection with no orphan delete?
552 */
553 @SuppressWarnings({"JavaDoc"})
554 protected boolean isInverseOneToManyOrNoOrphanDelete() {
555 final CollectionEntry ce = session.getPersistenceContextInternal().getCollectionEntry( this );
556 if ( ce == null ) {
557 return false;
558 }
559 final CollectionPersister loadedPersister = ce.getLoadedPersister();
560 return loadedPersister.isInverse() && ( loadedPersister.isOneToMany() || !loadedPersister.hasOrphanDelete() );
561 }
562
563 /**
564 * Queue an addition
565 */
566 @SuppressWarnings({"JavaDoc"})
567 protected final void queueOperation(DelayedOperation operation) {
568 if ( operationQueue == null ) {
569 operationQueue = new ArrayList<DelayedOperation>( 10 );
570 }
571 operationQueue.add( operation );
572 //needed so that we remove this collection from the second-level cache
573 dirty = true;
574 }
575
576 /**
577 * Replace entity instances with copy in {@code copyCache}/.
578 *
579 * @param copyCache - mapping from entity in the process of being
580 * merged to managed copy.
581 */
582 public final void replaceQueuedOperationValues(CollectionPersister persister, Map copyCache) {
583 for ( DelayedOperation operation : operationQueue ) {
584 if ( ValueDelayedOperation.class.isInstance( operation ) ) {
585 ( (ValueDelayedOperation) operation ).replace( persister, copyCache );
586 }
587 }
588 }
589
590 /**
591 * After reading all existing elements from the database,
592 * add the queued elements to the underlying collection.
593 */
594 protected final void performQueuedOperations() {
595 for ( DelayedOperation operation : operationQueue ) {
596 operation.operate();
597 }
598 clearOperationQueue();
599 }
600
601 @Override
602 public void setSnapshot(Serializable key, String role, Serializable snapshot) {
603 this.key = key;
604 this.role = role;
605 this.storedSnapshot = snapshot;
606 }
607
608 @Override
609 public void postAction() {
610 clearOperationQueue();
611 cachedSize = -1;
612 clearDirty();
613 }
614
615 public final void clearOperationQueue() {
616 operationQueue = null;
617 }
618
619 @Override
620 public Object getValue() {
621 return this;
622 }
623
624 @Override
625 public void beginRead() {
626 // override on some subclasses
627 initializing = true;
628 }
629
630 @Override
631 public boolean endRead() {
632 //override on some subclasses
633 return afterInitialize();
634 }
635
636 @Override
637 public boolean afterInitialize() {
638 setInitialized();
639 //do this bit after setting initialized to true or it will recurse
640 if ( hasQueuedOperations() ) {
641 performQueuedOperations();
642 cachedSize = -1;
643 return false;
644 }
645 else {
646 return true;
647 }
648 }
649
650 /**
651 * Initialize the collection, if possible, wrapping any exceptions
652 * in a runtime exception
653 *
654 * @param writing currently obsolete
655 *
656 * @throws LazyInitializationException if we cannot initialize
657 */
658 protected final void initialize(final boolean writing) {
659 if ( initialized ) {
660 return;
661 }
662 //##REMOTING-KEEP##
663 // In remoting we are sure that session is null
664 // both when using property paths and switching off conversations
665 if(session == null && remoting) {
666 remoteInitialize();
667 } else {
668 //keep formatting below to ease update to newer hibernate version
669 //##REMOTING-KEEP END##
670 withTemporarySessionIfNeeded(
671 new LazyInitializationWork<Object>() {
672 @Override
673 public Object doWork() {
674 session.initializeCollection( AbstractPersistentCollection.this, writing );
675 return null;
676 }
677 }
678 );
679 }
680 }
681
682 private void throwLazyInitializationExceptionIfNotConnected() {
683 if ( !isConnectedToSession() ) {
684 throwLazyInitializationException( "no session or session was closed" );
685 }
686 if ( !session.isConnected() ) {
687 throwLazyInitializationException( "session is disconnected" );
688 }
689 }
690
691 private void throwLazyInitializationException(String message) {
692 throw new LazyInitializationException(
693 "failed to lazily initialize a collection" +
694 (role == null ? "" : " of role: " + role) +
695 ", " + message
696 );
697 }
698
699 protected final void setInitialized() {
700 this.initializing = false;
701 this.initialized = true;
702 }
703
704 protected final void setDirectlyAccessible(boolean directlyAccessible) {
705 this.directlyAccessible = directlyAccessible;
706 }
707
708 @Override
709 public boolean isDirectlyAccessible() {
710 return directlyAccessible;
711 }
712
713 @Override
714 public final boolean unsetSession(SharedSessionContractImplementor currentSession) {
715 prepareForPossibleLoadingOutsideTransaction();
716 if ( currentSession == this.session ) {
717 if ( !isTempSession ) {
718 if ( hasQueuedOperations() ) {
719 final String collectionInfoString = MessageHelper.collectionInfoString( getRole(), getKey() );
720 try {
721 final TransactionStatus transactionStatus =
722 session.getTransactionCoordinator().getTransactionDriverControl().getStatus();
723 if ( transactionStatus.isOneOf(
724 TransactionStatus.ROLLED_BACK,
725 TransactionStatus.MARKED_ROLLBACK,
726 TransactionStatus.FAILED_COMMIT,
727 TransactionStatus.FAILED_ROLLBACK,
728 TransactionStatus.ROLLING_BACK
729 ) ) {
730 // It was due to a rollback.
731 LOG.queuedOperationWhenDetachFromSessionOnRollback( collectionInfoString );
732 }
733 else {
734 // We don't know why the collection is being detached.
735 // Just log the info.
736 LOG.queuedOperationWhenDetachFromSession( collectionInfoString );
737 }
738 }
739 catch (Exception e) {
740 // We don't know why the collection is being detached.
741 // Just log the info.
742 LOG.queuedOperationWhenDetachFromSession( collectionInfoString );
743 }
744 }
745 if ( allowLoadOutsideTransaction && !initialized && session.getLoadQueryInfluencers().hasEnabledFilters() ) {
746 final String collectionInfoString = MessageHelper.collectionInfoString( getRole(), getKey() );
747 LOG.enabledFiltersWhenDetachFromSession( collectionInfoString );
748 }
749 this.session = null;
750 }
751 return true;
752 }
753 else {
754 if ( this.session != null ) {
755 LOG.logCannotUnsetUnexpectedSessionInCollection( generateUnexpectedSessionStateMessage( currentSession ) );
756 }
757 return false;
758 }
759 }
760
761 protected void prepareForPossibleLoadingOutsideTransaction() {
762 if ( session != null ) {
763 allowLoadOutsideTransaction = session.getFactory().getSessionFactoryOptions().isInitializeLazyStateOutsideTransactionsEnabled();
764
765 if ( allowLoadOutsideTransaction && sessionFactoryUuid == null ) {
766 sessionFactoryUuid = session.getFactory().getUuid();
767 }
768 }
769 }
770
771 @Override
772 public final boolean setCurrentSession(SharedSessionContractImplementor session) throws HibernateException {
773 if ( session == this.session ) {
774 return false;
775 }
776 else if ( this.session != null ) {
777 final String msg = generateUnexpectedSessionStateMessage( session );
778 if ( isConnectedToSession() ) {
779 throw new HibernateException(
780 "Illegal attempt to associate a collection with two open sessions. " + msg
781 );
782 }
783 else {
784 LOG.logUnexpectedSessionInCollectionNotConnected( msg );
785 }
786 }
787 if ( hasQueuedOperations() ) {
788 LOG.queuedOperationWhenAttachToSession( MessageHelper.collectionInfoString( getRole(), getKey() ) );
789 }
790 this.session = session;
791 return true;
792 }
793
794 private String generateUnexpectedSessionStateMessage(SharedSessionContractImplementor session) {
795 // NOTE: If this.session != null, this.session may be operating on this collection
796 // (e.g., by changing this.role, this.key, or even this.session) in a different thread.
797
798 // Grab the current role and key (it can still get changed by this.session...)
799 // If this collection is connected to this.session, then this.role and this.key should
800 // be consistent with the CollectionEntry in this.session (as long as this.session doesn't
801 // change it). Don't access the CollectionEntry in this.session because that could result
802 // in multi-threaded access to this.session.
803 final String roleCurrent = role;
804 final Serializable keyCurrent = key;
805
806 final StringBuilder sb = new StringBuilder( "Collection : " );
807 if ( roleCurrent != null ) {
808 sb.append( MessageHelper.collectionInfoString( roleCurrent, keyCurrent ) );
809 }
810 else {
811 final CollectionEntry ce = session.getPersistenceContextInternal().getCollectionEntry( this );
812 if ( ce != null ) {
813 sb.append(
814 MessageHelper.collectionInfoString(
815 ce.getLoadedPersister(),
816 this,
817 ce.getLoadedKey(),
818 session
819 )
820 );
821 }
822 else {
823 sb.append( "<unknown>" );
824 }
825 }
826 // only include the collection contents if debug logging
827 if ( LOG.isDebugEnabled() ) {
828 final String collectionContents = wasInitialized() ? toString() : "<uninitialized>";
829 sb.append( "\nCollection contents: [" ).append( collectionContents ).append( "]" );
830 }
831 return sb.toString();
832 }
833
834 @Override
835 public boolean needsRecreate(CollectionPersister persister) {
836 // Workaround for situations like HHH-7072. If the collection element is a component that consists entirely
837 // of nullable properties, we currently have to forcefully recreate the entire collection. See the use
838 // of hasNotNullableColumns in the AbstractCollectionPersister constructor for more info. In order to delete
839 // row-by-row, that would require SQL like "WHERE ( COL = ? OR ( COL is null AND ? is null ) )", rather than
840 // the current "WHERE COL = ?" (fails for null for most DBs). Note that
841 // the param would have to be bound twice. Until we eventually add "parameter bind points" concepts to the
842 // AST in ORM 5+, handling this type of condition is either extremely difficult or impossible. Forcing
843 // recreation isn't ideal, but not really any other option in ORM 4.
844 // Selecting a type used in where part of update statement
845 // (must match condition in org.hibernate.persister.collection.BasicCollectionPersister#doUpdateRows).
846 // See HHH-9474
847 Type whereType;
848 if ( persister.hasIndex() ) {
849 whereType = persister.getIndexType();
850 }
851 else {
852 whereType = persister.getElementType();
853 }
854 if ( whereType instanceof CompositeType ) {
855 CompositeType componentIndexType = (CompositeType) whereType;
856 return !componentIndexType.hasNotNullProperty();
857 }
858 return false;
859 }
860
861 @Override
862 public final void forceInitialization() throws HibernateException {
863 if ( !initialized ) {
864 if ( initializing ) {
865 throw new AssertionFailure( "force initialize loading collection" );
866 }
867 initialize( false );
868 }
869 }
870
871
872 /**
873 * Get the current snapshot from the session
874 */
875 @SuppressWarnings({"JavaDoc"})
876 protected final Serializable getSnapshot() {
877 return session.getPersistenceContext().getSnapshot( this );
878 }
879
880 @Override
881 public final boolean wasInitialized() {
882 return initialized;
883 }
884
885 @Override
886 public boolean isRowUpdatePossible() {
887 return true;
888 }
889
890 @Override
891 public final boolean hasQueuedOperations() {
892 return operationQueue != null;
893 }
894
895 @Override
896 public final Iterator queuedAdditionIterator() {
897 if ( hasQueuedOperations() ) {
898 return new Iterator() {
899 private int index;
900
901 @Override
902 public Object next() {
903 return operationQueue.get( index++ ).getAddedInstance();
904 }
905
906 @Override
907 public boolean hasNext() {
908 return index < operationQueue.size();
909 }
910
911 @Override
912 public void remove() {
913 throw new UnsupportedOperationException();
914 }
915 };
916 }
917 else {
918 return Collections.emptyIterator();
919 }
920 }
921
922 @Override
923 @SuppressWarnings({"unchecked"})
924 public final Collection getQueuedOrphans(String entityName) {
925 if ( hasQueuedOperations() ) {
926 final Collection additions = new ArrayList( operationQueue.size() );
927 final Collection removals = new ArrayList( operationQueue.size() );
928 for ( DelayedOperation operation : operationQueue ) {
929 additions.add( operation.getAddedInstance() );
930 removals.add( operation.getOrphan() );
931 }
932 return getOrphans( removals, additions, entityName, session );
933 }
934 else {
935 return Collections.EMPTY_LIST;
936 }
937 }
938
939 @Override
940 public void preInsert(CollectionPersister persister) throws HibernateException {
941 }
942
943 @Override
944 public void afterRowInsert(CollectionPersister persister, Object entry, int i) throws HibernateException {
945 }
946
947 @Override
948 public abstract Collection getOrphans(Serializable snapshot, String entityName) throws HibernateException;
949
950 /**
951 * Get the session currently associated with this collection.
952 *
953 * @return The session
954 */
955 public final SharedSessionContractImplementor getSession() {
956 return session;
957 }
958
959 protected final class IteratorProxy implements Iterator {
960 protected final Iterator itr;
961
962 public IteratorProxy(Iterator itr) {
963 this.itr = itr;
964 }
965
966 @Override
967 public boolean hasNext() {
968 return itr.hasNext();
969 }
970
971 @Override
972 public Object next() {
973 return itr.next();
974 }
975
976 @Override
977 public void remove() {
978 write();
979 itr.remove();
980 }
981 }
982
983 protected final class ListIteratorProxy implements ListIterator {
984 protected final ListIterator itr;
985
986 public ListIteratorProxy(ListIterator itr) {
987 this.itr = itr;
988 }
989
990 @Override
991 @SuppressWarnings({"unchecked"})
992 public void add(Object o) {
993 write();
994 itr.add( o );
995 }
996
997 @Override
998 public boolean hasNext() {
999 return itr.hasNext();
1000 }
1001
1002 @Override
1003 public boolean hasPrevious() {
1004 return itr.hasPrevious();
1005 }
1006
1007 @Override
1008 public Object next() {
1009 return itr.next();
1010 }
1011
1012 @Override
1013 public int nextIndex() {
1014 return itr.nextIndex();
1015 }
1016
1017 @Override
1018 public Object previous() {
1019 return itr.previous();
1020 }
1021
1022 @Override
1023 public int previousIndex() {
1024 return itr.previousIndex();
1025 }
1026
1027 @Override
1028 public void remove() {
1029 write();
1030 itr.remove();
1031 }
1032
1033 @Override
1034 @SuppressWarnings({"unchecked"})
1035 public void set(Object o) {
1036 write();
1037 itr.set( o );
1038 }
1039 }
1040
1041 protected class SetProxy implements java.util.Set {
1042 protected final Collection set;
1043
1044 public SetProxy(Collection set) {
1045 this.set = set;
1046 }
1047
1048 @Override
1049 @SuppressWarnings({"unchecked"})
1050 public boolean add(Object o) {
1051 write();
1052 return set.add( o );
1053 }
1054
1055 @Override
1056 @SuppressWarnings({"unchecked"})
1057 public boolean addAll(Collection c) {
1058 write();
1059 return set.addAll( c );
1060 }
1061
1062 @Override
1063 public void clear() {
1064 write();
1065 set.clear();
1066 }
1067
1068 @Override
1069 public boolean contains(Object o) {
1070 return set.contains( o );
1071 }
1072
1073 @Override
1074 @SuppressWarnings("unchecked")
1075 public boolean containsAll(Collection c) {
1076 return set.containsAll( c );
1077 }
1078
1079 @Override
1080 public boolean isEmpty() {
1081 return set.isEmpty();
1082 }
1083
1084 @Override
1085 public Iterator iterator() {
1086 return new IteratorProxy( set.iterator() );
1087 }
1088
1089 @Override
1090 public boolean remove(Object o) {
1091 write();
1092 return set.remove( o );
1093 }
1094
1095 @Override
1096 @SuppressWarnings("unchecked")
1097 public boolean removeAll(Collection c) {
1098 write();
1099 return set.removeAll( c );
1100 }
1101
1102 @Override
1103 @SuppressWarnings("unchecked")
1104 public boolean retainAll(Collection c) {
1105 write();
1106 return set.retainAll( c );
1107 }
1108
1109 @Override
1110 public int size() {
1111 return set.size();
1112 }
1113
1114 @Override
1115 public Object[] toArray() {
1116 return set.toArray();
1117 }
1118
1119 @Override
1120 @SuppressWarnings({"unchecked"})
1121 public Object[] toArray(Object[] array) {
1122 return set.toArray( array );
1123 }
1124 }
1125
1126 protected final class ListProxy implements java.util.List {
1127 protected final List list;
1128
1129 public ListProxy(List list) {
1130 this.list = list;
1131 }
1132
1133 @Override
1134 @SuppressWarnings({"unchecked"})
1135 public void add(int index, Object value) {
1136 write();
1137 list.add( index, value );
1138 }
1139
1140 @Override
1141 @SuppressWarnings({"unchecked"})
1142 public boolean add(Object o) {
1143 write();
1144 return list.add( o );
1145 }
1146
1147 @Override
1148 @SuppressWarnings({"unchecked"})
1149 public boolean addAll(Collection c) {
1150 write();
1151 return list.addAll( c );
1152 }
1153
1154 @Override
1155 @SuppressWarnings({"unchecked"})
1156 public boolean addAll(int i, Collection c) {
1157 write();
1158 return list.addAll( i, c );
1159 }
1160
1161 @Override
1162 public void clear() {
1163 write();
1164 list.clear();
1165 }
1166
1167 @Override
1168 public boolean contains(Object o) {
1169 return list.contains( o );
1170 }
1171
1172 @Override
1173 @SuppressWarnings("unchecked")
1174 public boolean containsAll(Collection c) {
1175 return list.containsAll( c );
1176 }
1177
1178 @Override
1179 public Object get(int i) {
1180 return list.get( i );
1181 }
1182
1183 @Override
1184 public int indexOf(Object o) {
1185 return list.indexOf( o );
1186 }
1187
1188 @Override
1189 public boolean isEmpty() {
1190 return list.isEmpty();
1191 }
1192
1193 @Override
1194 public Iterator iterator() {
1195 return new IteratorProxy( list.iterator() );
1196 }
1197
1198 @Override
1199 public int lastIndexOf(Object o) {
1200 return list.lastIndexOf( o );
1201 }
1202
1203 @Override
1204 public ListIterator listIterator() {
1205 return new ListIteratorProxy( list.listIterator() );
1206 }
1207
1208 @Override
1209 public ListIterator listIterator(int i) {
1210 return new ListIteratorProxy( list.listIterator( i ) );
1211 }
1212
1213 @Override
1214 public Object remove(int i) {
1215 write();
1216 return list.remove( i );
1217 }
1218
1219 @Override
1220 public boolean remove(Object o) {
1221 write();
1222 return list.remove( o );
1223 }
1224
1225 @Override
1226 @SuppressWarnings("unchecked")
1227 public boolean removeAll(Collection c) {
1228 write();
1229 return list.removeAll( c );
1230 }
1231
1232 @Override
1233 @SuppressWarnings("unchecked")
1234 public boolean retainAll(Collection c) {
1235 write();
1236 return list.retainAll( c );
1237 }
1238
1239 @Override
1240 @SuppressWarnings({"unchecked"})
1241 public Object set(int i, Object o) {
1242 write();
1243 return list.set( i, o );
1244 }
1245
1246 @Override
1247 public int size() {
1248 return list.size();
1249 }
1250
1251 @Override
1252 public List subList(int i, int j) {
1253 return list.subList( i, j );
1254 }
1255
1256 @Override
1257 public Object[] toArray() {
1258 return list.toArray();
1259 }
1260
1261 @Override
1262 @SuppressWarnings({"unchecked"})
1263 public Object[] toArray(Object[] array) {
1264 return list.toArray( array );
1265 }
1266
1267 }
1268
1269 /**
1270 * Contract for operations which are part of a collection's operation queue.
1271 */
1272 protected interface DelayedOperation {
1273 public void operate();
1274
1275 public Object getAddedInstance();
1276
1277 public Object getOrphan();
1278 }
1279
1280 protected interface ValueDelayedOperation extends DelayedOperation {
1281 void replace(CollectionPersister collectionPersister, Map copyCache);
1282 }
1283
1284 protected abstract class AbstractValueDelayedOperation implements ValueDelayedOperation {
1285 private Object addedValue;
1286 private Object orphan;
1287
1288 protected AbstractValueDelayedOperation(Object addedValue, Object orphan) {
1289 this.addedValue = addedValue;
1290 this.orphan = orphan;
1291 }
1292
1293 @Override
1294 public void replace(CollectionPersister persister, Map copyCache) {
1295 if ( addedValue != null ) {
1296 addedValue = getReplacement( persister.getElementType(), addedValue, copyCache );
1297 }
1298 }
1299
1300 protected final Object getReplacement(Type type, Object current, Map copyCache) {
1301 return type.replace( current, null, session, owner, copyCache );
1302 }
1303
1304 @Override
1305 public final Object getAddedInstance() {
1306 return addedValue;
1307 }
1308
1309 @Override
1310 public final Object getOrphan() {
1311 return orphan;
1312 }
1313 }
1314
1315 /**
1316 * Given a collection of entity instances that used to
1317 * belong to the collection, and a collection of instances
1318 * that currently belong, return a collection of orphans
1319 */
1320 @SuppressWarnings({"JavaDoc", "unchecked"})
1321 protected static Collection getOrphans(
1322 Collection oldElements,
1323 Collection currentElements,
1324 String entityName,
1325 SharedSessionContractImplementor session) throws HibernateException {
1326
1327 // short-circuit(s)
1328 if ( currentElements.size() == 0 ) {
1329 // no new elements, the old list contains only Orphans
1330 return oldElements;
1331 }
1332 if ( oldElements.size() == 0 ) {
1333 // no old elements, so no Orphans neither
1334 return oldElements;
1335 }
1336
1337 final EntityPersister entityPersister = session.getFactory().getEntityPersister( entityName );
1338 final Type idType = entityPersister.getIdentifierType();
1339 final boolean useIdDirect = mayUseIdDirect( idType );
1340
1341 // create the collection holding the Orphans
1342 final Collection res = new ArrayList();
1343
1344 // collect EntityIdentifier(s) of the *current* elements - add them into a HashSet for fast access
1345 final java.util.Set currentIds = new HashSet();
1346 final java.util.Set currentSaving = new IdentitySet();
1347 final PersistenceContext persistenceContext = session.getPersistenceContextInternal();
1348 for ( Object current : currentElements ) {
1349 if ( current != null && ForeignKeys.isNotTransient( entityName, current, null, session ) ) {
1350 final EntityEntry ee = persistenceContext.getEntry( current );
1351 if ( ee != null && ee.getStatus() == Status.SAVING ) {
1352 currentSaving.add( current );
1353 }
1354 else {
1355 final Serializable currentId = ForeignKeys.getEntityIdentifierIfNotUnsaved(
1356 entityName,
1357 current,
1358 session
1359 );
1360 currentIds.add( useIdDirect ? currentId : new TypedValue( idType, currentId ) );
1361 }
1362 }
1363 }
1364
1365 // iterate over the *old* list
1366 for ( Object old : oldElements ) {
1367 if ( !currentSaving.contains( old ) ) {
1368 final Serializable oldId = ForeignKeys.getEntityIdentifierIfNotUnsaved( entityName, old, session );
1369 if ( !currentIds.contains( useIdDirect ? oldId : new TypedValue( idType, oldId ) ) ) {
1370 res.add( old );
1371 }
1372 }
1373 }
1374
1375 return res;
1376 }
1377
1378 private static boolean mayUseIdDirect(Type idType) {
1379 return idType == StringType.INSTANCE
1380 || idType == IntegerType.INSTANCE
1381 || idType == LongType.INSTANCE
1382 || idType == UUIDBinaryType.INSTANCE
1383 || idType == UUIDCharType.INSTANCE
1384 || idType == PostgresUUIDType.INSTANCE;
1385 }
1386
1387 /**
1388 * Removes entity entries that have an equal identifier with the incoming entity instance
1389 *
1390 * @param list The list containing the entity instances
1391 * @param entityInstance The entity instance to match elements.
1392 * @param entityName The entity name
1393 * @param session The session
1394 */
1395 public static void identityRemove(
1396 Collection list,
1397 Object entityInstance,
1398 String entityName,
1399 SharedSessionContractImplementor session) {
1400
1401 if ( entityInstance != null && ForeignKeys.isNotTransient( entityName, entityInstance, null, session ) ) {
1402 final EntityPersister entityPersister = session.getFactory().getEntityPersister( entityName );
1403 final Type idType = entityPersister.getIdentifierType();
1404
1405 final Serializable idOfCurrent = ForeignKeys.getEntityIdentifierIfNotUnsaved( entityName, entityInstance, session );
1406 final Iterator itr = list.iterator();
1407 while ( itr.hasNext() ) {
1408 final Serializable idOfOld = ForeignKeys.getEntityIdentifierIfNotUnsaved( entityName, itr.next(), session );
1409 if ( idType.isEqual( idOfCurrent, idOfOld, session.getFactory() ) ) {
1410 itr.remove();
1411 break;
1412 }
1413 }
1414
1415 }
1416 }
1417
1418 /**
1419 * Removes entity entries that have an equal identifier with the incoming entity instance
1420 *
1421 * @param list The list containing the entity instances
1422 * @param entityInstance The entity instance to match elements.
1423 * @param entityName The entity name
1424 * @param session The session
1425 *
1426 * @deprecated {@link #identityRemove(Collection, Object, String, SharedSessionContractImplementor)}
1427 * should be used instead.
1428 */
1429 @Deprecated
1430 public static void identityRemove(
1431 Collection list,
1432 Object entityInstance,
1433 String entityName,
1434 SessionImplementor session) {
1435 identityRemove( list, entityInstance, entityName, (SharedSessionContractImplementor) session );
1436 }
1437
1438 @Override
1439 public Object getIdentifier(Object entry, int i) {
1440 throw new UnsupportedOperationException();
1441 }
1442
1443 @Override
1444 public Object getOwner() {
1445 return owner;
1446 }
1447
1448 @Override
1449 public void setOwner(Object owner) {
1450 this.owner = owner;
1451 }
1452
1453
1454 /** ##REMOTING-KEEP## #######################################################
1455
1456 ADDED PART: Below is the section of code which makes remote service calls.
1457 Keeps this code when upgrading to newer hibernate version. Also keep
1458 other code marked with ##REMOTING-KEEP## in the following 5 methods:
1459
1460 {@link #initialize(boolean)}
1461 {@link #readSize()}
1462 {@link #readIndexExistence(Object)}
1463 {@link #readElementExistence(Object)}
1464 {@link #readElementByIndex(Object)}
1465
1466 ######################################################################### */
1467
1468 private static CdmApplicationRemoteConfiguration configuration;
1469 private static boolean remoting = false;
1470
1471 public static void setConfiguration(CdmApplicationRemoteConfiguration conf) {
1472 remoting = true;
1473 configuration = conf;
1474 }
1475
1476 private void remoteInitialize() {
1477
1478 if (getOwner() != null && !initialized) {
1479 Object collectionType = null;
1480 Field field = null;
1481 Class<?> clazz = null;
1482 try {
1483 String role = getRole();
1484 String fieldName = role.substring(role.lastIndexOf(".") + 1);
1485 LOG.info("--> Remote Lazy Initializing Collection " + getRole() + " , owner : " + getOwner().getClass() + "/" + getKey() + " , field : " + fieldName);
1486 Object owner = getOwner();
1487 CdmBase cdmBase;
1488 if(owner instanceof CdmBase) {
1489 cdmBase = (CdmBase)owner;
1490 } else {
1491 throw new HibernateException("Owner of persistent collection is not a cdm entity");
1492 }
1493 if(configuration == null) {
1494 throw new HibernateException("CdmApplicationRemoteConfiguration not initialized (null)");
1495 }
1496 ICachedCommonService cachedCommonService = configuration.getCachedCommonService();
1497 if(cachedCommonService == null) {
1498 throw new HibernateException("commonService not initialized (null)");
1499 }
1500
1501 //Object obj = ProxyUtils.deproxyIfInitialized(cachedCommonService.initializeCollection(this));
1502 Object obj = ProxyUtils.deproxyIfInitialized(cachedCommonService.initializeCollection(cdmBase.getUuid(), fieldName));
1503 if(ProxyUtils.isUninitializedProxy(obj)) {
1504 throw new HibernateException("Persistent Collection initialized but is still a proxy");
1505 }
1506 afterInitialize();
1507
1508 clazz = getClass();
1509 if (clazz != null) {
1510 collectionType = ProxyUtils.getCollectionType(obj, clazz);
1511 field = clazz.getDeclaredField(collectionType.toString());
1512 field.setAccessible(true);
1513 field.set(this, obj);
1514 ProxyUtils.setRoleValueInOwner(owner, role, obj);
1515
1516 }
1517 } catch (Exception ex) {
1518 String originalMessage = ex.getMessage();
1519 String message = originalMessage + "clazz: " + (clazz == null? "" :clazz.getSimpleName())+ "- field: " + field + " - collectionType: " + collectionType;
1520 throw new CdmEagerLoadingException(message);
1521 }
1522 }
1523 }
1524 // ##REMOTING-KEEP END##
1525 }