Save This Page
Home » hibernate-distribution-3.3.1.GA-dist » org.hibernate » cfg » [javadoc | source]
    1   //$Id: BinderHelper.java 11186 2007-02-10 07:27:14Z epbernard $
    2   package org.hibernate.cfg;
    3   
    4   import java.util.ArrayList;
    5   import java.util.Collections;
    6   import java.util.HashMap;
    7   import java.util.HashSet;
    8   import java.util.Iterator;
    9   import java.util.List;
   10   import java.util.Map;
   11   import java.util.Set;
   12   import java.util.StringTokenizer;
   13   import java.util.Properties;
   14   
   15   import org.hibernate.AnnotationException;
   16   import org.hibernate.AssertionFailure;
   17   import org.hibernate.MappingException;
   18   import org.hibernate.id.PersistentIdentifierGenerator;
   19   import org.hibernate.id.MultipleHiLoPerTableGenerator;
   20   import org.hibernate.cfg.annotations.TableBinder;
   21   import org.hibernate.mapping.Collection;
   22   import org.hibernate.mapping.Column;
   23   import org.hibernate.mapping.Component;
   24   import org.hibernate.mapping.Join;
   25   import org.hibernate.mapping.PersistentClass;
   26   import org.hibernate.mapping.Property;
   27   import org.hibernate.mapping.Table;
   28   import org.hibernate.mapping.ToOne;
   29   import org.hibernate.mapping.Value;
   30   import org.hibernate.mapping.SimpleValue;
   31   import org.hibernate.mapping.IdGenerator;
   32   import org.hibernate.util.StringHelper;
   33   
   34   /**
   35    * @author Emmanuel Bernard
   36    */
   37   public class BinderHelper {
   38   
   39   	public static final String ANNOTATION_STRING_DEFAULT = "";
   40   
   41   	private BinderHelper() {
   42   	}
   43   
   44   	static {
   45   		Set<String> primitiveNames = new HashSet<String>();
   46   		primitiveNames.add( byte.class.getName() );
   47   		primitiveNames.add( short.class.getName() );
   48   		primitiveNames.add( int.class.getName() );
   49   		primitiveNames.add( long.class.getName() );
   50   		primitiveNames.add( float.class.getName() );
   51   		primitiveNames.add( double.class.getName() );
   52   		primitiveNames.add( char.class.getName() );
   53   		primitiveNames.add( boolean.class.getName() );
   54   		PRIMITIVE_NAMES = Collections.unmodifiableSet( primitiveNames );
   55   	}
   56   
   57   	public static final Set<String> PRIMITIVE_NAMES;
   58   
   59   	/**
   60   	 * create a property copy reusing the same value
   61   	 */
   62   	public static Property shallowCopy(Property property) {
   63   		Property clone = new Property();
   64   		clone.setCascade( property.getCascade() );
   65   		clone.setInsertable( property.isInsertable() );
   66   		clone.setLazy( property.isLazy() );
   67   		clone.setName( property.getName() );
   68   		clone.setNodeName( property.getNodeName() );
   69   		clone.setNaturalIdentifier( property.isNaturalIdentifier() );
   70   		clone.setOptimisticLocked( property.isOptimisticLocked() );
   71   		clone.setOptional( property.isOptional() );
   72   		clone.setPersistentClass( property.getPersistentClass() );
   73   		clone.setPropertyAccessorName( property.getPropertyAccessorName() );
   74   		clone.setSelectable( property.isSelectable() );
   75   		clone.setUpdateable( property.isUpdateable() );
   76   		clone.setValue( property.getValue() );
   77   		return clone;
   78   	}
   79   
   80   	public static void createSyntheticPropertyReference(
   81   			Ejb3JoinColumn[] columns,
   82   			PersistentClass ownerEntity,
   83   			PersistentClass associatedEntity,
   84   			Value value,
   85   			boolean inverse, ExtendedMappings mappings
   86   	) {
   87   		//associated entity only used for more precise exception, yuk!
   88   		if ( columns[0].isImplicit() || StringHelper.isNotEmpty( columns[0].getMappedBy() ) ) return;
   89   		int fkEnum = Ejb3JoinColumn.checkReferencedColumnsType( columns, ownerEntity, mappings );
   90   		PersistentClass associatedClass = columns[0].getPropertyHolder() != null ?
   91   				columns[0].getPropertyHolder().getPersistentClass() :
   92   				null;
   93   		if ( Ejb3JoinColumn.NON_PK_REFERENCE == fkEnum ) {
   94   			/**
   95   			 * Create a synthetic property to refer to including an
   96   			 * embedded component value containing all the properties
   97   			 * mapped to the referenced columns
   98   			 * We need to shallow copy those properties to mark them
   99   			 * as non insertable / non updatable
  100   			 */
  101   			StringBuilder propertyNameBuffer = new StringBuilder( "_" );
  102   			propertyNameBuffer.append( associatedClass.getEntityName().replace( '.', '_' ) );
  103   			propertyNameBuffer.append( "_" ).append( columns[0].getPropertyName() );
  104   			String syntheticPropertyName = propertyNameBuffer.toString();
  105   			//find properties associated to a certain column
  106   			Object columnOwner = findColumnOwner( ownerEntity, columns[0].getReferencedColumn(), mappings );
  107   			List<Property> properties = findPropertiesByColumns( columnOwner, columns, mappings );
  108   			//create an embeddable component
  109   			Property synthProp = null;
  110   			if ( properties != null ) {
  111   				//todo how about properties.size() == 1, this should be much simpler
  112   				Component embeddedComp = columnOwner instanceof PersistentClass ?
  113   						new Component( (PersistentClass) columnOwner ) :
  114   						new Component( (Join) columnOwner );
  115   				embeddedComp.setEmbedded( true );
  116   				embeddedComp.setNodeName( syntheticPropertyName );
  117   				embeddedComp.setComponentClassName( embeddedComp.getOwner().getClassName() );
  118   				for ( Property property : properties ) {
  119   					Property clone = BinderHelper.shallowCopy( property );
  120   					clone.setInsertable( false );
  121   					clone.setUpdateable( false );
  122   					clone.setNaturalIdentifier( false );
  123   					embeddedComp.addProperty( clone );
  124   				}
  125   				synthProp = new Property();
  126   				synthProp.setName( syntheticPropertyName );
  127   				synthProp.setNodeName(syntheticPropertyName);
  128   				synthProp.setPersistentClass( ownerEntity );
  129   				synthProp.setUpdateable( false );
  130   				synthProp.setInsertable( false );
  131   				synthProp.setValue( embeddedComp );
  132   				synthProp.setPropertyAccessorName( "embedded" );
  133   				ownerEntity.addProperty( synthProp );
  134   				//make it unique
  135   				TableBinder.createUniqueConstraint( embeddedComp );
  136   			}
  137   			else {
  138   				//TODO use a ToOne type doing a second select
  139   				StringBuilder columnsList = new StringBuilder();
  140   				columnsList.append( "referencedColumnNames(" );
  141   				for ( Ejb3JoinColumn column : columns ) {
  142   					columnsList.append( column.getReferencedColumn() ).append( ", " );
  143   				}
  144   				columnsList.setLength( columnsList.length() - 2 );
  145   				columnsList.append( ") " );
  146   
  147   				if ( associatedEntity != null ) {
  148   					//overidden destination
  149   					columnsList.append( "of " )
  150   							.append( associatedEntity.getEntityName() )
  151   							.append( "." )
  152   							.append( columns[0].getPropertyName() )
  153   							.append( " " );
  154   				}
  155   				else {
  156   					if ( columns[0].getPropertyHolder() != null ) {
  157   						columnsList.append( "of " )
  158   								.append( columns[0].getPropertyHolder().getEntityName() )
  159   								.append( "." )
  160   								.append( columns[0].getPropertyName() )
  161   								.append( " " );
  162   					}
  163   				}
  164   				columnsList.append( "referencing " )
  165   						.append( ownerEntity.getEntityName() )
  166   						.append( " not mapped to a single property" );
  167   				throw new AnnotationException( columnsList.toString() );
  168   			}
  169   
  170   			/**
  171   			 * creating the property ref to the new synthetic property
  172   			 */
  173   			if ( value instanceof ToOne ) {
  174   				( (ToOne) value ).setReferencedPropertyName( syntheticPropertyName );
  175   				mappings.addUniquePropertyReference( ownerEntity.getEntityName(), syntheticPropertyName );
  176   			}
  177   			else if ( value instanceof Collection ) {
  178   				( (Collection) value ).setReferencedPropertyName( syntheticPropertyName );
  179   				//not unique because we could create a mtm wo association table
  180   				mappings.addPropertyReference( ownerEntity.getEntityName(), syntheticPropertyName );
  181   			}
  182   			else {
  183   				throw new AssertionFailure(
  184   						"Do a property ref on an unexpected Value type: "
  185   								+ value.getClass().getName()
  186   				);
  187   			}
  188   			mappings.addPropertyReferencedAssociation(
  189   					( inverse ? "inverse__" : "" ) + associatedClass.getEntityName(),
  190   					columns[0].getPropertyName(),
  191   					syntheticPropertyName
  192   			);
  193   		}
  194   	}
  195   
  196   
  197   	private static List<Property> findPropertiesByColumns(
  198   			Object columnOwner, Ejb3JoinColumn[] columns,
  199   			ExtendedMappings mappings
  200   	) {
  201   		Map<Column, Set<Property>> columnsToProperty = new HashMap<Column, Set<Property>>();
  202   		List<Column> orderedColumns = new ArrayList<Column>( columns.length );
  203   		Table referencedTable = null;
  204   		if ( columnOwner instanceof PersistentClass ) {
  205   			referencedTable = ( (PersistentClass) columnOwner ).getTable();
  206   		}
  207   		else if ( columnOwner instanceof Join ) {
  208   			referencedTable = ( (Join) columnOwner ).getTable();
  209   		}
  210   		else {
  211   			throw new AssertionFailure(
  212   					columnOwner == null ?
  213   							"columnOwner is null" :
  214   							"columnOwner neither PersistentClass nor Join: " + columnOwner.getClass()
  215   			);
  216   		}
  217   		//build the list of column names
  218   		for ( Ejb3JoinColumn column1 : columns ) {
  219   			Column column = new Column(
  220   					mappings.getPhysicalColumnName( column1.getReferencedColumn(), referencedTable )
  221   			);
  222   			orderedColumns.add( column );
  223   			columnsToProperty.put( column, new HashSet<Property>() );
  224   		}
  225   		boolean isPersistentClass = columnOwner instanceof PersistentClass;
  226   		Iterator it = isPersistentClass ?
  227   				( (PersistentClass) columnOwner ).getPropertyIterator() :
  228   				( (Join) columnOwner ).getPropertyIterator();
  229   		while ( it.hasNext() ) {
  230   			matchColumnsByProperty( (Property) it.next(), columnsToProperty );
  231   		}
  232   		if (isPersistentClass) {
  233   			matchColumnsByProperty( ( (PersistentClass) columnOwner ).getIdentifierProperty(), columnsToProperty );
  234   		}
  235   
  236   		//first naive implementation
  237   		//only check 1 columns properties
  238   		//TODO make it smarter by checking correctly ordered multi column properties
  239   		List<Property> orderedProperties = new ArrayList<Property>();
  240   		for ( Column column : orderedColumns ) {
  241   			boolean found = false;
  242   			for ( Property property : columnsToProperty.get( column ) ) {
  243   				if ( property.getColumnSpan() == 1 ) {
  244   					orderedProperties.add( property );
  245   					found = true;
  246   					break;
  247   				}
  248   			}
  249   			if ( !found ) return null; //have to find it the hard way
  250   		}
  251   		return orderedProperties;
  252   	}
  253   
  254   	private static void matchColumnsByProperty(Property property, Map<Column, Set<Property>> columnsToProperty) {
  255   		if ( property == null ) return;
  256   		if ( "noop".equals( property.getPropertyAccessorName() )
  257   				|| "embedded".equals( property.getPropertyAccessorName() ) ) {
  258   			return;
  259   		}
  260   // FIXME cannot use subproperties becasue the caller needs top level properties
  261   //		if ( property.isComposite() ) {
  262   //			Iterator subProperties = ( (Component) property.getValue() ).getPropertyIterator();
  263   //			while ( subProperties.hasNext() ) {
  264   //				matchColumnsByProperty( (Property) subProperties.next(), columnsToProperty );
  265   //			}
  266   //		}
  267   		else {
  268   			Iterator columnIt = property.getColumnIterator();
  269   			while ( columnIt.hasNext() ) {
  270   				Object column = columnIt.next(); //can be a Formula so we don't cast
  271   				//noinspection SuspiciousMethodCalls
  272   				if ( columnsToProperty.containsKey( column ) ) {
  273   					columnsToProperty.get( column ).add( property );
  274   				}
  275   			}
  276   		}
  277   	}
  278   
  279   	/**
  280   	 * Retrieve the property by path in a recursive way, including IndetifierProperty in the loop
  281   	 * If propertyName is null or empty, the IdentifierProperty is returned
  282   	 */
  283   	public static Property findPropertyByName(PersistentClass associatedClass, String propertyName) {
  284   		Property property = null;
  285   		Property idProperty = associatedClass.getIdentifierProperty();
  286   		String idName = idProperty != null ? idProperty.getName() : null;
  287   		try {
  288   			if ( propertyName == null
  289   					|| propertyName.length() == 0
  290   					|| propertyName.equals( idName ) ) {
  291   				//default to id
  292   				property = idProperty;
  293   			}
  294   			else {
  295   				if ( propertyName.indexOf( idName + "." ) == 0 ) {
  296   					property = idProperty;
  297   					propertyName = propertyName.substring( idName.length() + 1 );
  298   				}
  299   				StringTokenizer st = new StringTokenizer( propertyName, ".", false );
  300   				while ( st.hasMoreElements() ) {
  301   					String element = (String) st.nextElement();
  302   					if ( property == null ) {
  303   						property = associatedClass.getProperty( element );
  304   					}
  305   					else {
  306   						if ( ! property.isComposite() ) return null;
  307   						property = ( (Component) property.getValue() ).getProperty( element );
  308   					}
  309   				}
  310   			}
  311   		}
  312   		catch (MappingException e) {
  313   			try {
  314   				//if we do not find it try to check the identifier mapper
  315   				if ( associatedClass.getIdentifierMapper() == null ) return null;
  316   				StringTokenizer st = new StringTokenizer( propertyName, ".", false );
  317   				while ( st.hasMoreElements() ) {
  318   					String element = (String) st.nextElement();
  319   					if ( property == null ) {
  320   						property = associatedClass.getIdentifierMapper().getProperty( element );
  321   					}
  322   					else {
  323   						if ( ! property.isComposite() ) return null;
  324   						property = ( (Component) property.getValue() ).getProperty( element );
  325   					}
  326   				}
  327   			}
  328   			catch (MappingException ee) {
  329   				return null;
  330   			}
  331   		}
  332   		return property;
  333   	}
  334   
  335   	public static String getRelativePath(PropertyHolder propertyHolder, String propertyName) {
  336   		if ( propertyHolder == null ) return propertyName;
  337   		String path = propertyHolder.getPath();
  338   		String entityName = propertyHolder.getPersistentClass().getEntityName();
  339   		if ( path.length() == entityName.length() ) {
  340   			return propertyName;
  341   		}
  342   		else {
  343   			return StringHelper.qualify( path.substring( entityName.length() + 1 ), propertyName );
  344   		}
  345   	}
  346   
  347   	/**
  348   	 * Find the column owner (ie PersistentClass or Join) of columnName.
  349   	 * If columnName is null or empty, persistentClass is returned
  350   	 */
  351   	public static Object findColumnOwner(
  352   			PersistentClass persistentClass, String columnName, ExtendedMappings mappings
  353   	) {
  354   		if ( StringHelper.isEmpty( columnName ) ) {
  355   			return persistentClass; //shortcut for implicit referenced column names
  356   		}
  357   		PersistentClass current = persistentClass;
  358   		Object result = null;
  359   		boolean found = false;
  360   		do {
  361   			result = current;
  362   			Table currentTable = current.getTable();
  363   			try {
  364   				mappings.getPhysicalColumnName( columnName, currentTable );
  365   				found = true;
  366   			}
  367   			catch (MappingException me) {
  368   				//swallow it
  369   			}
  370   			Iterator joins = current.getJoinIterator();
  371   			while ( ! found && joins.hasNext() ) {
  372   				result = joins.next();
  373   				currentTable = ( (Join) result ).getTable();
  374   				try {
  375   					mappings.getPhysicalColumnName( columnName, currentTable );
  376   					found = true;
  377   				}
  378   				catch (MappingException me) {
  379   					//swallow it
  380   				}
  381   			}
  382   			current = current.getSuperclass();
  383   		}
  384   		while ( !found && current != null );
  385   		return found ? result : null;
  386   	}
  387   
  388   	/** apply an id generator to a SimpleValue */
  389   	public static void makeIdGenerator(
  390   			SimpleValue id, String generatorType, String generatorName, ExtendedMappings mappings,
  391   			Map<String, IdGenerator> localGenerators
  392   	) {
  393   		Table table = id.getTable();
  394   		table.setIdentifierValue( id );
  395   		//generator settings
  396   		id.setIdentifierGeneratorStrategy( generatorType );
  397   		Properties params = new Properties();
  398   		//always settable
  399   		params.setProperty(
  400   				PersistentIdentifierGenerator.TABLE, table.getName()
  401   		);
  402   
  403   		if ( id.getColumnSpan() == 1 ) {
  404   			params.setProperty(
  405   					PersistentIdentifierGenerator.PK,
  406   					( (org.hibernate.mapping.Column) id.getColumnIterator().next() ).getName()
  407   			);
  408   		}
  409   		if ( ! isDefault( generatorName ) ) {
  410   			//we have a named generator
  411   			IdGenerator gen = mappings.getGenerator( generatorName, localGenerators );
  412   			if ( gen == null ) {
  413   				throw new AnnotationException( "Unknown Id.generator: " + generatorName );
  414   			}
  415   			//This is quite vague in the spec but a generator could override the generate choice
  416   			String identifierGeneratorStrategy = gen.getIdentifierGeneratorStrategy();
  417   			//yuk! this is a hack not to override 'AUTO' even if generator is set
  418   			final boolean avoidOverriding =
  419   					identifierGeneratorStrategy.equals( "identity" )
  420   							|| identifierGeneratorStrategy.equals( "seqhilo" )
  421   							|| identifierGeneratorStrategy.equals( MultipleHiLoPerTableGenerator.class.getName() );
  422   			if ( generatorType == null || ! avoidOverriding ) {
  423   				id.setIdentifierGeneratorStrategy( identifierGeneratorStrategy );
  424   			}
  425   			//checkIfMatchingGenerator(gen, generatorType, generatorName);
  426   			Iterator genParams = gen.getParams().entrySet().iterator();
  427   			while ( genParams.hasNext() ) {
  428   				Map.Entry elt = (Map.Entry) genParams.next();
  429   				params.setProperty( (String) elt.getKey(), (String) elt.getValue() );
  430   			}
  431   		}
  432   		if ( "assigned".equals( generatorType ) ) id.setNullValue( "undefined" );
  433   		id.setIdentifierGeneratorProperties( params );
  434   	}
  435   
  436   	public static boolean isDefault(String annotationString) {
  437   		return annotationString != null && annotationString.length() == 0;
  438   		//equivalent to (but faster) ANNOTATION_STRING_DEFAULT.equals( annotationString );
  439   	}
  440   }

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