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 }