Save This Page
Home » hibernate-distribution-3.3.1.GA-dist » org.hibernate » type » [javadoc | source]
    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   }

Save This Page
Home » hibernate-distribution-3.3.1.GA-dist » org.hibernate » type » [javadoc | source]