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.type;
26
27 import java.io.Serializable;
28 import java.sql.ResultSet;
29 import java.sql.SQLException;
30 import java.util.Map;
31
32 import org.dom4j.Element;
33 import org.dom4j.Node;
34 import org.hibernate.AssertionFailure;
35 import org.hibernate.EntityMode;
36 import org.hibernate.HibernateException;
37 import org.hibernate.MappingException;
38 import org.hibernate.engine.EntityUniqueKey;
39 import org.hibernate.engine.ForeignKeys;
40 import org.hibernate.engine.Mapping;
41 import org.hibernate.engine.PersistenceContext;
42 import org.hibernate.engine.SessionFactoryImplementor;
43 import org.hibernate.engine.SessionImplementor;
44 import org.hibernate.persister.entity.EntityPersister;
45 import org.hibernate.persister.entity.Joinable;
46 import org.hibernate.persister.entity.UniqueKeyLoadable;
47 import org.hibernate.proxy.HibernateProxy;
48 import org.hibernate.proxy.LazyInitializer;
49 import org.hibernate.tuple.ElementWrapper;
50 import org.hibernate.util.ReflectHelper;
51
52 /**
53 * Base for types which map associations to persistent entities.
54 *
55 * @author Gavin King
56 */
57 public abstract class EntityType extends AbstractType implements AssociationType {
58
59 private final String associatedEntityName;
60 protected final String uniqueKeyPropertyName;
61 protected final boolean isEmbeddedInXML;
62 private final boolean eager;
63 private final boolean unwrapProxy;
64
65 private transient Class returnedClass;
66
67 /**
68 * Constructs the requested entity type mapping.
69 *
70 * @param entityName The name of the associated entity.
71 * @param uniqueKeyPropertyName The property-ref name, or null if we
72 * reference the PK of the associated entity.
73 * @param eager Is eager fetching enabled.
74 * @param isEmbeddedInXML Should values of this mapping be embedded in XML modes?
75 * @param unwrapProxy Is unwrapping of proxies allowed for this association; unwrapping
76 * says to return the "implementation target" of lazy prooxies; typically only possible
77 * with lazy="no-proxy".
78 */
79 protected EntityType(
80 String entityName,
81 String uniqueKeyPropertyName,
82 boolean eager,
83 boolean isEmbeddedInXML,
84 boolean unwrapProxy) {
85 this.associatedEntityName = entityName;
86 this.uniqueKeyPropertyName = uniqueKeyPropertyName;
87 this.isEmbeddedInXML = isEmbeddedInXML;
88 this.eager = eager;
89 this.unwrapProxy = unwrapProxy;
90 }
91
92 /**
93 * An entity type is a type of association type
94 *
95 * @return True.
96 */
97 public boolean isAssociationType() {
98 return true;
99 }
100
101 /**
102 * Explicitly, an entity type is an entity type ;)
103 *
104 * @return True.
105 */
106 public final boolean isEntityType() {
107 return true;
108 }
109
110 /**
111 * {@inheritDoc}
112 */
113 public boolean isMutable() {
114 return false;
115 }
116
117 /**
118 * Generates a string representation of this type.
119 *
120 * @return string rep
121 */
122 public String toString() {
123 return getClass().getName() + '(' + getAssociatedEntityName() + ')';
124 }
125
126 /**
127 * For entity types, the name correlates to the associated entity name.
128 */
129 public String getName() {
130 return associatedEntityName;
131 }
132
133 /**
134 * Does this association foreign key reference the primary key of the other table?
135 * Otherwise, it references a property-ref.
136 *
137 * @return True if this association reference the PK of the associated entity.
138 */
139 public boolean isReferenceToPrimaryKey() {
140 return uniqueKeyPropertyName==null;
141 }
142
143 public String getRHSUniqueKeyPropertyName() {
144 return uniqueKeyPropertyName;
145 }
146
147 public String getLHSPropertyName() {
148 return null;
149 }
150
151 public String getPropertyName() {
152 return null;
153 }
154
155 /**
156 * The name of the associated entity.
157 *
158 * @return The associated entity name.
159 */
160 public final String getAssociatedEntityName() {
161 return associatedEntityName;
162 }
163
164 /**
165 * The name of the associated entity.
166 *
167 * @param factory The session factory, for resolution.
168 * @return The associated entity name.
169 */
170 public String getAssociatedEntityName(SessionFactoryImplementor factory) {
171 return getAssociatedEntityName();
172 }
173
174 /**
175 * Retrieves the {@link Joinable} defining the associated entity.
176 *
177 * @param factory The session factory.
178 * @return The associated joinable
179 * @throws MappingException Generally indicates an invalid entity name.
180 */
181 public Joinable getAssociatedJoinable(SessionFactoryImplementor factory) throws MappingException {
182 return ( Joinable ) factory.getEntityPersister( associatedEntityName );
183 }
184
185 /**
186 * This returns the wrong class for an entity with a proxy, or for a named
187 * entity. Theoretically it should return the proxy class, but it doesn't.
188 * <p/>
189 * The problem here is that we do not necessarily have a ref to the associated
190 * entity persister (nor to the session factory, to look it up) which is really
191 * needed to "do the right thing" here...
192 *
193 * @return The entiyt class.
194 */
195 public final Class getReturnedClass() {
196 if ( returnedClass == null ) {
197 returnedClass = determineAssociatedEntityClass();
198 }
199 return returnedClass;
200 }
201
202 private Class determineAssociatedEntityClass() {
203 try {
204 return ReflectHelper.classForName( getAssociatedEntityName() );
205 }
206 catch ( ClassNotFoundException cnfe ) {
207 return java.util.Map.class;
208 }
209 }
210
211 /**
212 * {@inheritDoc}
213 */
214 public Object nullSafeGet(ResultSet rs, String name, SessionImplementor session, Object owner)
215 throws HibernateException, SQLException {
216 return nullSafeGet( rs, new String[] {name}, session, owner );
217 }
218
219 /**
220 * {@inheritDoc}
221 */
222 public final Object nullSafeGet(
223 ResultSet rs,
224 String[] names,
225 SessionImplementor session,
226 Object owner) throws HibernateException, SQLException {
227 return resolve( hydrate(rs, names, session, owner), session, owner );
228 }
229
230 /**
231 * Two entities are considered the same when their instances are the same.
232 *
233 * @param x One entity instance
234 * @param y Another entity instance
235 * @param entityMode The entity mode.
236 * @return True if x == y; false otherwise.
237 */
238 public final boolean isSame(Object x, Object y, EntityMode entityMode) {
239 return x == y;
240 }
241
242 /**
243 * {@inheritDoc}
244 */
245 public int compare(Object x, Object y, EntityMode entityMode) {
246 return 0; //TODO: entities CAN be compared, by PK, fix this! -> only if/when we can extract the id values....
247 }
248
249 /**
250 * {@inheritDoc}
251 */
252 public Object deepCopy(Object value, EntityMode entityMode, SessionFactoryImplementor factory) {
253 return value; //special case ... this is the leaf of the containment graph, even though not immutable
254 }
255
256 /**
257 * {@inheritDoc}
258 */
259 public Object replace(
260 Object original,
261 Object target,
262 SessionImplementor session,
263 Object owner,
264 Map copyCache) throws HibernateException {
265 if ( original == null ) {
266 return null;
267 }
268 Object cached = copyCache.get(original);
269 if ( cached != null ) {
270 return cached;
271 }
272 else {
273 if ( original == target ) {
274 return target;
275 }
276 if ( session.getContextEntityIdentifier( original ) == null &&
277 ForeignKeys.isTransient( associatedEntityName, original, Boolean.FALSE, session ) ) {
278 final Object copy = session.getFactory().getEntityPersister( associatedEntityName )
279 .instantiate( null, session.getEntityMode() );
280 //TODO: should this be Session.instantiate(Persister, ...)?
281 copyCache.put( original, copy );
282 return copy;
283 }
284 else {
285 Object id = getIdentifier( original, session );
286 if ( id == null ) {
287 throw new AssertionFailure("non-transient entity has a null id");
288 }
289 id = getIdentifierOrUniqueKeyType( session.getFactory() )
290 .replace(id, null, session, owner, copyCache);
291 return resolve( id, session, owner );
292 }
293 }
294 }
295
296 /**
297 * {@inheritDoc}
298 */
299 public int getHashCode(Object x, EntityMode entityMode, SessionFactoryImplementor factory) {
300 EntityPersister persister = factory.getEntityPersister(associatedEntityName);
301 if ( !persister.canExtractIdOutOfEntity() ) {
302 return super.getHashCode(x, entityMode);
303 }
304
305 final Serializable id;
306 if (x instanceof HibernateProxy) {
307 id = ( (HibernateProxy) x ).getHibernateLazyInitializer().getIdentifier();
308 }
309 else {
310 id = persister.getIdentifier(x, entityMode);
311 }
312 return persister.getIdentifierType().getHashCode(id, entityMode, factory);
313 }
314
315 /**
316 * {@inheritDoc}
317 */
318 public boolean isEqual(Object x, Object y, EntityMode entityMode, SessionFactoryImplementor factory) {
319 EntityPersister persister = factory.getEntityPersister(associatedEntityName);
320 if ( !persister.canExtractIdOutOfEntity() ) {
321 return super.isEqual(x, y, entityMode);
322 }
323
324 Serializable xid;
325 if (x instanceof HibernateProxy) {
326 xid = ( (HibernateProxy) x ).getHibernateLazyInitializer()
327 .getIdentifier();
328 }
329 else {
330 xid = persister.getIdentifier(x, entityMode);
331 }
332
333 Serializable yid;
334 if (y instanceof HibernateProxy) {
335 yid = ( (HibernateProxy) y ).getHibernateLazyInitializer()
336 .getIdentifier();
337 }
338 else {
339 yid = persister.getIdentifier(y, entityMode);
340 }
341
342 return persister.getIdentifierType()
343 .isEqual(xid, yid, entityMode, factory);
344 }
345
346 /**
347 * {@inheritDoc}
348 */
349 public boolean isEmbeddedInXML() {
350 return isEmbeddedInXML;
351 }
352
353 /**
354 * {@inheritDoc}
355 */
356 public boolean isXMLElement() {
357 return isEmbeddedInXML;
358 }
359
360 /**
361 * {@inheritDoc}
362 */
363 public Object fromXMLNode(Node xml, Mapping factory) throws HibernateException {
364 if ( !isEmbeddedInXML ) {
365 return getIdentifierType(factory).fromXMLNode(xml, factory);
366 }
367 else {
368 return xml;
369 }
370 }
371
372 /**
373 * {@inheritDoc}
374 */
375 public void setToXMLNode(Node node, Object value, SessionFactoryImplementor factory) throws HibernateException {
376 if ( !isEmbeddedInXML ) {
377 getIdentifierType(factory).setToXMLNode(node, value, factory);
378 }
379 else {
380 Element elt = (Element) value;
381 replaceNode( node, new ElementWrapper(elt) );
382 }
383 }
384
385 public String getOnCondition(String alias, SessionFactoryImplementor factory, Map enabledFilters)
386 throws MappingException {
387 if ( isReferenceToPrimaryKey() ) { //TODO: this is a bit arbitrary, expose a switch to the user?
388 return "";
389 }
390 else {
391 return getAssociatedJoinable( factory ).filterFragment( alias, enabledFilters );
392 }
393 }
394
395 /**
396 * Resolve an identifier or unique key value
397 */
398 public Object resolve(Object value, SessionImplementor session, Object owner) throws HibernateException {
399 if ( isNotEmbedded( session ) ) {
400 return value;
401 }
402
403 if ( value == null ) {
404 return null;
405 }
406 else {
407 if ( isNull( owner, session ) ) {
408 return null; //EARLY EXIT!
409 }
410
411 if ( isReferenceToPrimaryKey() ) {
412 return resolveIdentifier( (Serializable) value, session );
413 }
414 else {
415 return loadByUniqueKey( getAssociatedEntityName(), uniqueKeyPropertyName, value, session );
416 }
417 }
418 }
419
420 public Type getSemiResolvedType(SessionFactoryImplementor factory) {
421 return factory.getEntityPersister( associatedEntityName ).getIdentifierType();
422 }
423
424 protected final Object getIdentifier(Object value, SessionImplementor session) throws HibernateException {
425 if ( isNotEmbedded(session) ) {
426 return value;
427 }
428
429 if ( isReferenceToPrimaryKey() ) {
430 return ForeignKeys.getEntityIdentifierIfNotUnsaved( getAssociatedEntityName(), value, session ); //tolerates nulls
431 }
432 else if ( value == null ) {
433 return null;
434 }
435 else {
436 EntityPersister entityPersister = session.getFactory().getEntityPersister( getAssociatedEntityName() );
437 Object propertyValue = entityPersister.getPropertyValue( value, uniqueKeyPropertyName, session.getEntityMode() );
438 // We now have the value of the property-ref we reference. However,
439 // we need to dig a little deeper, as that property might also be
440 // an entity type, in which case we need to resolve its identitifier
441 Type type = entityPersister.getPropertyType( uniqueKeyPropertyName );
442 if ( type.isEntityType() ) {
443 propertyValue = ( ( EntityType ) type ).getIdentifier( propertyValue, session );
444 }
445
446 return propertyValue;
447 }
448 }
449
450 protected boolean isNotEmbedded(SessionImplementor session) {
451 return !isEmbeddedInXML && session.getEntityMode()==EntityMode.DOM4J;
452 }
453
454 /**
455 * Get the identifier value of an instance or proxy.
456 * <p/>
457 * Intended only for loggin purposes!!!
458 *
459 * @param object The object from which to extract the identifier.
460 * @param persister The entity persister
461 * @param entityMode The entity mode
462 * @return The extracted identifier.
463 */
464 private static Serializable getIdentifier(Object object, EntityPersister persister, EntityMode entityMode) {
465 if (object instanceof HibernateProxy) {
466 HibernateProxy proxy = (HibernateProxy) object;
467 LazyInitializer li = proxy.getHibernateLazyInitializer();
468 return li.getIdentifier();
469 }
470 else {
471 return persister.getIdentifier( object, entityMode );
472 }
473 }
474
475 /**
476 * Generate a loggable representation of an instance of the value mapped by this type.
477 *
478 * @param value The instance to be logged.
479 * @param factory The session factory.
480 * @return The loggable string.
481 * @throws HibernateException Generally some form of resolution problem.
482 */
483 public String toLoggableString(Object value, SessionFactoryImplementor factory) {
484 if ( value == null ) {
485 return "null";
486 }
487
488 EntityPersister persister = factory.getEntityPersister( associatedEntityName );
489 StringBuffer result = new StringBuffer().append( associatedEntityName );
490
491 if ( persister.hasIdentifierProperty() ) {
492 final EntityMode entityMode = persister.guessEntityMode( value );
493 final Serializable id;
494 if ( entityMode == null ) {
495 if ( isEmbeddedInXML ) {
496 throw new ClassCastException( value.getClass().getName() );
497 }
498 id = ( Serializable ) value;
499 }
500 else {
501 id = getIdentifier( value, persister, entityMode );
502 }
503
504 result.append( '#' )
505 .append( persister.getIdentifierType().toLoggableString( id, factory ) );
506 }
507
508 return result.toString();
509 }
510
511 public abstract boolean isOneToOne();
512
513 /**
514 * Convenience method to locate the identifier type of the associated entity.
515 *
516 * @param factory The mappings...
517 * @return The identifier type
518 */
519 Type getIdentifierType(Mapping factory) {
520 return factory.getIdentifierType( getAssociatedEntityName() );
521 }
522
523 /**
524 * Convenience method to locate the identifier type of the associated entity.
525 *
526 * @param session The originating session
527 * @return The identifier type
528 */
529 Type getIdentifierType(SessionImplementor session) {
530 return getIdentifierType( session.getFactory() );
531 }
532
533 /**
534 * Determine the type of either (1) the identifier if we reference the
535 * associated entity's PK or (2) the unique key to which we refer (i.e.
536 * the property-ref).
537 *
538 * @param factory The mappings...
539 * @return The appropriate type.
540 * @throws MappingException Generally, if unable to resolve the associated entity name
541 * or unique key property name.
542 */
543 public final Type getIdentifierOrUniqueKeyType(Mapping factory) throws MappingException {
544 if ( isReferenceToPrimaryKey() ) {
545 return getIdentifierType(factory);
546 }
547 else {
548 Type type = factory.getReferencedPropertyType( getAssociatedEntityName(), uniqueKeyPropertyName );
549 if ( type.isEntityType() ) {
550 type = ( ( EntityType ) type).getIdentifierOrUniqueKeyType( factory );
551 }
552 return type;
553 }
554 }
555
556 /**
557 * The name of the property on the associated entity to which our FK
558 * refers
559 *
560 * @param factory The mappings...
561 * @return The appropriate property name.
562 * @throws MappingException Generally, if unable to resolve the associated entity name
563 */
564 public final String getIdentifierOrUniqueKeyPropertyName(Mapping factory)
565 throws MappingException {
566 if ( isReferenceToPrimaryKey() ) {
567 return factory.getIdentifierPropertyName( getAssociatedEntityName() );
568 }
569 else {
570 return uniqueKeyPropertyName;
571 }
572 }
573
574 protected abstract boolean isNullable();
575
576 /**
577 * Resolve an identifier via a load.
578 *
579 * @param id The entity id to resolve
580 * @param session The orginating session.
581 * @return The resolved identifier (i.e., loaded entity).
582 * @throws org.hibernate.HibernateException Indicates problems performing the load.
583 */
584 protected final Object resolveIdentifier(Serializable id, SessionImplementor session) throws HibernateException {
585 boolean isProxyUnwrapEnabled = unwrapProxy &&
586 session.getFactory()
587 .getEntityPersister( getAssociatedEntityName() )
588 .isInstrumented( session.getEntityMode() );
589
590 Object proxyOrEntity = session.internalLoad(
591 getAssociatedEntityName(),
592 id,
593 eager,
594 isNullable() && !isProxyUnwrapEnabled
595 );
596
597 if ( proxyOrEntity instanceof HibernateProxy ) {
598 ( ( HibernateProxy ) proxyOrEntity ).getHibernateLazyInitializer()
599 .setUnwrap( isProxyUnwrapEnabled );
600 }
601
602 return proxyOrEntity;
603 }
604
605 protected boolean isNull(Object owner, SessionImplementor session) {
606 return false;
607 }
608
609 /**
610 * Load an instance by a unique key that is not the primary key.
611 *
612 * @param entityName The name of the entity to load
613 * @param uniqueKeyPropertyName The name of the property defining the uniqie key.
614 * @param key The unique key property value.
615 * @param session The originating session.
616 * @return The loaded entity
617 * @throws HibernateException generally indicates problems performing the load.
618 */
619 public Object loadByUniqueKey(
620 String entityName,
621 String uniqueKeyPropertyName,
622 Object key,
623 SessionImplementor session) throws HibernateException {
624 final SessionFactoryImplementor factory = session.getFactory();
625 UniqueKeyLoadable persister = ( UniqueKeyLoadable ) factory.getEntityPersister( entityName );
626
627 //TODO: implement caching?! proxies?!
628
629 EntityUniqueKey euk = new EntityUniqueKey(
630 entityName,
631 uniqueKeyPropertyName,
632 key,
633 getIdentifierOrUniqueKeyType( factory ),
634 session.getEntityMode(),
635 session.getFactory()
636 );
637
638 final PersistenceContext persistenceContext = session.getPersistenceContext();
639 Object result = persistenceContext.getEntity( euk );
640 if ( result == null ) {
641 result = persister.loadByUniqueKey( uniqueKeyPropertyName, key, session );
642 }
643 return result == null ? null : persistenceContext.proxyFor( result );
644 }
645
646 }