1 /*
2 * Hibernate, Relational Persistence for Idiomatic Java
3 *
4 * Copyright (c) 2008, Red Hat Middleware LLC or third-party contributors as
5 * indicated by the @author tags or express copyright attribution
6 * statements applied by the authors. All third-party contributions are
7 * distributed under license by Red Hat Middleware LLC.
8 *
9 * This copyrighted material is made available to anyone wishing to use, modify,
10 * copy, or redistribute it subject to the terms and conditions of the GNU
11 * Lesser General Public License, as published by the Free Software Foundation.
12 *
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
15 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
16 * for more details.
17 *
18 * You should have received a copy of the GNU Lesser General Public License
19 * along with this distribution; if not, write to:
20 * Free Software Foundation, Inc.
21 * 51 Franklin Street, Fifth Floor
22 * Boston, MA 02110-1301 USA
23 *
24 */
25 package org.hibernate.engine;
26
27 import org.slf4j.Logger;
28 import org.slf4j.LoggerFactory;
29 import org.hibernate.AssertionFailure;
30 import org.hibernate.EntityMode;
31 import org.hibernate.HibernateException;
32 import org.hibernate.collection.PersistentCollection;
33 import org.hibernate.persister.collection.CollectionPersister;
34 import org.hibernate.pretty.MessageHelper;
35 import org.hibernate.type.CollectionType;
36
37 import java.io.Serializable;
38
39 /**
40 * Implements book-keeping for the collection persistence by reachability algorithm
41 * @author Gavin King
42 */
43 public final class Collections {
44
45 private Collections() {}
46
47 private static final Logger log = LoggerFactory.getLogger(Collections.class);
48
49 /**
50 * record the fact that this collection was dereferenced
51 *
52 * @param coll The collection to be updated by unreachability.
53 * @throws HibernateException
54 */
55 public static void processUnreachableCollection(PersistentCollection coll, SessionImplementor session)
56 throws HibernateException {
57
58 if ( coll.getOwner()==null ) {
59 processNeverReferencedCollection(coll, session);
60 }
61 else {
62 processDereferencedCollection(coll, session);
63 }
64
65 }
66
67 private static void processDereferencedCollection(PersistentCollection coll, SessionImplementor session)
68 throws HibernateException {
69
70 final PersistenceContext persistenceContext = session.getPersistenceContext();
71 CollectionEntry entry = persistenceContext.getCollectionEntry(coll);
72 final CollectionPersister loadedPersister = entry.getLoadedPersister();
73
74 if ( log.isDebugEnabled() && loadedPersister != null )
75 log.debug(
76 "Collection dereferenced: " +
77 MessageHelper.collectionInfoString(
78 loadedPersister,
79 entry.getLoadedKey(),
80 session.getFactory()
81 )
82 );
83
84 // do a check
85 boolean hasOrphanDelete = loadedPersister != null &&
86 loadedPersister.hasOrphanDelete();
87 if (hasOrphanDelete) {
88 Serializable ownerId = loadedPersister.getOwnerEntityPersister()
89 .getIdentifier( coll.getOwner(), session.getEntityMode() );
90 if ( ownerId == null ) {
91 // the owning entity may have been deleted and its identifier unset due to
92 // identifier-rollback; in which case, try to look up its identifier from
93 // the persistence context
94 if ( session.getFactory().getSettings().isIdentifierRollbackEnabled() ) {
95 EntityEntry ownerEntry = persistenceContext.getEntry( coll.getOwner() );
96 if ( ownerEntry != null ) {
97 ownerId = ownerEntry.getId();
98 }
99 }
100 if ( ownerId == null ) {
101 throw new AssertionFailure( "Unable to determine collection owner identifier for orphan-delete processing" );
102 }
103 }
104 EntityKey key = new EntityKey(
105 ownerId,
106 loadedPersister.getOwnerEntityPersister(),
107 session.getEntityMode()
108 );
109 Object owner = persistenceContext.getEntity(key);
110 if ( owner == null ) {
111 throw new AssertionFailure(
112 "collection owner not associated with session: " +
113 loadedPersister.getRole()
114 );
115 }
116 EntityEntry e = persistenceContext.getEntry(owner);
117 //only collections belonging to deleted entities are allowed to be dereferenced in the case of orphan delete
118 if ( e != null && e.getStatus() != Status.DELETED && e.getStatus() != Status.GONE ) {
119 throw new HibernateException(
120 "A collection with cascade=\"all-delete-orphan\" was no longer referenced by the owning entity instance: " +
121 loadedPersister.getRole()
122 );
123 }
124 }
125
126 // do the work
127 entry.setCurrentPersister(null);
128 entry.setCurrentKey(null);
129 prepareCollectionForUpdate( coll, entry, session.getEntityMode(), session.getFactory() );
130
131 }
132
133 private static void processNeverReferencedCollection(PersistentCollection coll, SessionImplementor session)
134 throws HibernateException {
135
136 final PersistenceContext persistenceContext = session.getPersistenceContext();
137 CollectionEntry entry = persistenceContext.getCollectionEntry(coll);
138
139 log.debug(
140 "Found collection with unloaded owner: " +
141 MessageHelper.collectionInfoString(
142 entry.getLoadedPersister(),
143 entry.getLoadedKey(),
144 session.getFactory()
145 )
146 );
147
148 entry.setCurrentPersister( entry.getLoadedPersister() );
149 entry.setCurrentKey( entry.getLoadedKey() );
150
151 prepareCollectionForUpdate( coll, entry, session.getEntityMode(), session.getFactory() );
152
153 }
154
155 /**
156 * Initialize the role of the collection.
157 *
158 * @param collection The collection to be updated by reachibility.
159 * @param type The type of the collection.
160 * @param entity The owner of the collection.
161 * @throws HibernateException
162 */
163 public static void processReachableCollection(
164 PersistentCollection collection,
165 CollectionType type,
166 Object entity,
167 SessionImplementor session)
168 throws HibernateException {
169
170 collection.setOwner(entity);
171
172 CollectionEntry ce = session.getPersistenceContext().getCollectionEntry(collection);
173
174 if ( ce == null ) {
175 // refer to comment in StatefulPersistenceContext.addCollection()
176 throw new HibernateException(
177 "Found two representations of same collection: " +
178 type.getRole()
179 );
180 }
181
182 // The CollectionEntry.isReached() stuff is just to detect any silly users
183 // who set up circular or shared references between/to collections.
184 if ( ce.isReached() ) {
185 // We've been here before
186 throw new HibernateException(
187 "Found shared references to a collection: " +
188 type.getRole()
189 );
190 }
191 ce.setReached(true);
192
193 SessionFactoryImplementor factory = session.getFactory();
194 CollectionPersister persister = factory.getCollectionPersister( type.getRole() );
195 ce.setCurrentPersister(persister);
196 ce.setCurrentKey( type.getKeyOfOwner(entity, session) ); //TODO: better to pass the id in as an argument?
197
198 if ( log.isDebugEnabled() ) {
199 log.debug(
200 "Collection found: " +
201 MessageHelper.collectionInfoString( persister, ce.getCurrentKey(), factory ) +
202 ", was: " +
203 MessageHelper.collectionInfoString( ce.getLoadedPersister(), ce.getLoadedKey(), factory ) +
204 ( collection.wasInitialized() ? " (initialized)" : " (uninitialized)" )
205 );
206 }
207
208 prepareCollectionForUpdate( collection, ce, session.getEntityMode(), factory );
209
210 }
211
212 /**
213 * 1. record the collection role that this collection is referenced by
214 * 2. decide if the collection needs deleting/creating/updating (but
215 * don't actually schedule the action yet)
216 */
217 private static void prepareCollectionForUpdate(
218 PersistentCollection collection,
219 CollectionEntry entry,
220 EntityMode entityMode,
221 SessionFactoryImplementor factory)
222 throws HibernateException {
223
224 if ( entry.isProcessed() ) {
225 throw new AssertionFailure( "collection was processed twice by flush()" );
226 }
227 entry.setProcessed(true);
228
229 final CollectionPersister loadedPersister = entry.getLoadedPersister();
230 final CollectionPersister currentPersister = entry.getCurrentPersister();
231 if ( loadedPersister != null || currentPersister != null ) { // it is or was referenced _somewhere_
232
233 boolean ownerChanged = loadedPersister != currentPersister || // if either its role changed,
234 !currentPersister
235 .getKeyType().isEqual( // or its key changed
236 entry.getLoadedKey(),
237 entry.getCurrentKey(),
238 entityMode, factory
239 );
240
241 if (ownerChanged) {
242
243 // do a check
244 final boolean orphanDeleteAndRoleChanged = loadedPersister != null &&
245 currentPersister != null &&
246 loadedPersister.hasOrphanDelete();
247
248 if (orphanDeleteAndRoleChanged) {
249 throw new HibernateException(
250 "Don't change the reference to a collection with cascade=\"all-delete-orphan\": " +
251 loadedPersister.getRole()
252 );
253 }
254
255 // do the work
256 if ( currentPersister != null ) {
257 entry.setDorecreate(true); // we will need to create new entries
258 }
259
260 if ( loadedPersister != null ) {
261 entry.setDoremove(true); // we will need to remove ye olde entries
262 if ( entry.isDorecreate() ) {
263 log.trace( "Forcing collection initialization" );
264 collection.forceInitialization(); // force initialize!
265 }
266 }
267
268 }
269 else if ( collection.isDirty() ) { // else if it's elements changed
270 entry.setDoupdate(true);
271 }
272
273 }
274
275 }
276
277 }