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 java.io.Serializable;
28
29 import org.hibernate.HibernateException;
30 import org.hibernate.TransientObjectException;
31 import org.hibernate.intercept.LazyPropertyInitializer;
32 import org.hibernate.persister.entity.EntityPersister;
33 import org.hibernate.proxy.HibernateProxy;
34 import org.hibernate.proxy.LazyInitializer;
35 import org.hibernate.type.AbstractComponentType;
36 import org.hibernate.type.EntityType;
37 import org.hibernate.type.Type;
38
39 /**
40 * Algorithms related to foreign key constraint transparency
41 *
42 * @author Gavin King
43 */
44 public final class ForeignKeys {
45
46 private ForeignKeys() {}
47
48 public static class Nullifier {
49
50 private final boolean isDelete;
51 private final boolean isEarlyInsert;
52 private final SessionImplementor session;
53 private final Object self;
54
55 public Nullifier(Object self, boolean isDelete, boolean isEarlyInsert, SessionImplementor session) {
56 this.isDelete = isDelete;
57 this.isEarlyInsert = isEarlyInsert;
58 this.session = session;
59 this.self = self;
60 }
61
62 /**
63 * Nullify all references to entities that have not yet
64 * been inserted in the database, where the foreign key
65 * points toward that entity
66 */
67 public void nullifyTransientReferences(final Object[] values, final Type[] types)
68 throws HibernateException {
69 for ( int i = 0; i < types.length; i++ ) {
70 values[i] = nullifyTransientReferences( values[i], types[i] );
71 }
72 }
73
74 /**
75 * Return null if the argument is an "unsaved" entity (ie.
76 * one with no existing database row), or the input argument
77 * otherwise. This is how Hibernate avoids foreign key constraint
78 * violations.
79 */
80 private Object nullifyTransientReferences(final Object value, final Type type)
81 throws HibernateException {
82 if ( value == null ) {
83 return null;
84 }
85 else if ( type.isEntityType() ) {
86 EntityType entityType = (EntityType) type;
87 if ( entityType.isOneToOne() ) {
88 return value;
89 }
90 else {
91 String entityName = entityType.getAssociatedEntityName();
92 return isNullifiable(entityName, value) ? null : value;
93 }
94 }
95 else if ( type.isAnyType() ) {
96 return isNullifiable(null, value) ? null : value;
97 }
98 else if ( type.isComponentType() ) {
99 AbstractComponentType actype = (AbstractComponentType) type;
100 Object[] subvalues = actype.getPropertyValues(value, session);
101 Type[] subtypes = actype.getSubtypes();
102 boolean substitute = false;
103 for ( int i = 0; i < subvalues.length; i++ ) {
104 Object replacement = nullifyTransientReferences( subvalues[i], subtypes[i] );
105 if ( replacement != subvalues[i] ) {
106 substitute = true;
107 subvalues[i] = replacement;
108 }
109 }
110 if (substitute) actype.setPropertyValues( value, subvalues, session.getEntityMode() );
111 return value;
112 }
113 else {
114 return value;
115 }
116 }
117
118 /**
119 * Determine if the object already exists in the database,
120 * using a "best guess"
121 */
122 private boolean isNullifiable(final String entityName, Object object)
123 throws HibernateException {
124
125 if (object==LazyPropertyInitializer.UNFETCHED_PROPERTY) return false; //this is kinda the best we can do...
126
127 if ( object instanceof HibernateProxy ) {
128 // if its an uninitialized proxy it can't be transient
129 LazyInitializer li = ( (HibernateProxy) object ).getHibernateLazyInitializer();
130 if ( li.getImplementation(session) == null ) {
131 return false;
132 // ie. we never have to null out a reference to
133 // an uninitialized proxy
134 }
135 else {
136 //unwrap it
137 object = li.getImplementation();
138 }
139 }
140
141 // if it was a reference to self, don't need to nullify
142 // unless we are using native id generation, in which
143 // case we definitely need to nullify
144 if ( object == self ) {
145 return isEarlyInsert || (
146 isDelete &&
147 session.getFactory()
148 .getDialect()
149 .hasSelfReferentialForeignKeyBug()
150 );
151 }
152
153 // See if the entity is already bound to this session, if not look at the
154 // entity identifier and assume that the entity is persistent if the
155 // id is not "unsaved" (that is, we rely on foreign keys to keep
156 // database integrity)
157
158 EntityEntry entityEntry = session.getPersistenceContext().getEntry(object);
159 if ( entityEntry==null ) {
160 return isTransient(entityName, object, null, session);
161 }
162 else {
163 return entityEntry.isNullifiable(isEarlyInsert, session);
164 }
165
166 }
167
168 }
169
170 /**
171 * Is this instance persistent or detached?
172 * If <tt>assumed</tt> is non-null, don't hit the database to make the
173 * determination, instead assume that value; the client code must be
174 * prepared to "recover" in the case that this assumed result is incorrect.
175 */
176 public static boolean isNotTransient(String entityName, Object entity, Boolean assumed, SessionImplementor session)
177 throws HibernateException {
178 if (entity instanceof HibernateProxy) return true;
179 if ( session.getPersistenceContext().isEntryFor(entity) ) return true;
180 return !isTransient(entityName, entity, assumed, session);
181 }
182
183 /**
184 * Is this instance, which we know is not persistent, actually transient?
185 * If <tt>assumed</tt> is non-null, don't hit the database to make the
186 * determination, instead assume that value; the client code must be
187 * prepared to "recover" in the case that this assumed result is incorrect.
188 */
189 public static boolean isTransient(String entityName, Object entity, Boolean assumed, SessionImplementor session)
190 throws HibernateException {
191
192 if (entity==LazyPropertyInitializer.UNFETCHED_PROPERTY) {
193 // an unfetched association can only point to
194 // an entity that already exists in the db
195 return false;
196 }
197
198 // let the interceptor inspect the instance to decide
199 Boolean isUnsaved = session.getInterceptor().isTransient(entity);
200 if (isUnsaved!=null) return isUnsaved.booleanValue();
201
202 // let the persister inspect the instance to decide
203 EntityPersister persister = session.getEntityPersister(entityName, entity);
204 isUnsaved = persister.isTransient(entity, session);
205 if (isUnsaved!=null) return isUnsaved.booleanValue();
206
207 // we use the assumed value, if there is one, to avoid hitting
208 // the database
209 if (assumed!=null) return assumed.booleanValue();
210
211 // hit the database, after checking the session cache for a snapshot
212 Object[] snapshot = session.getPersistenceContext()
213 .getDatabaseSnapshot( persister.getIdentifier( entity, session.getEntityMode() ), persister );
214 return snapshot==null;
215
216 }
217
218 /**
219 * Return the identifier of the persistent or transient object, or throw
220 * an exception if the instance is "unsaved"
221 *
222 * Used by OneToOneType and ManyToOneType to determine what id value should
223 * be used for an object that may or may not be associated with the session.
224 * This does a "best guess" using any/all info available to use (not just the
225 * EntityEntry).
226 */
227 public static Serializable getEntityIdentifierIfNotUnsaved(
228 final String entityName,
229 final Object object,
230 final SessionImplementor session)
231 throws HibernateException {
232 if ( object == null ) {
233 return null;
234 }
235 else {
236 Serializable id = session.getContextEntityIdentifier( object );
237 if ( id == null ) {
238 // context-entity-identifier returns null explicitly if the entity
239 // is not associated with the persistence context; so make some
240 // deeper checks...
241 if ( isTransient(entityName, object, Boolean.FALSE, session) ) {
242 throw new TransientObjectException(
243 "object references an unsaved transient instance - save the transient instance before flushing: " +
244 (entityName == null ? session.guessEntityName( object ) : entityName)
245 );
246 }
247 id = session.getEntityPersister( entityName, object ).getIdentifier( object, session.getEntityMode() );
248 }
249 return id;
250 }
251 }
252
253 }