1 //$Id: TableBinder.java 11223 2007-02-20 19:37:47Z epbernard $
2 package org.hibernate.cfg.annotations;
3
4 import java.util.ArrayList;
5 import java.util.Iterator;
6 import java.util.List;
7 import javax.persistence.UniqueConstraint;
8
9 import org.apache.commons.logging.Log;
10 import org.apache.commons.logging.LogFactory;
11 import org.hibernate.AnnotationException;
12 import org.hibernate.AssertionFailure;
13 import org.hibernate.annotations.Index;
14 import org.hibernate.annotations.common.util.StringHelper;
15 import org.hibernate.cfg.BinderHelper;
16 import org.hibernate.cfg.Ejb3JoinColumn;
17 import org.hibernate.cfg.ExtendedMappings;
18 import org.hibernate.cfg.IndexSecondPass;
19 import org.hibernate.mapping.Collection;
20 import org.hibernate.mapping.Column;
21 import org.hibernate.mapping.DependantValue;
22 import org.hibernate.mapping.JoinedSubclass;
23 import org.hibernate.mapping.PersistentClass;
24 import org.hibernate.mapping.Property;
25 import org.hibernate.mapping.SimpleValue;
26 import org.hibernate.mapping.Table;
27 import org.hibernate.mapping.ToOne;
28 import org.hibernate.mapping.Value;
29
30 /**
31 * Table related operations
32 *
33 * @author Emmanuel Bernard
34 */
35 public class TableBinder {
36 //TODO move it to a getter/setter strategy
37 private static Log log = LogFactory.getLog( TableBinder.class );
38 private String schema;
39 private String catalog;
40 private String name;
41 private boolean isAbstract;
42 private List<String[]> uniqueConstraints;
43 String constraints;
44 Table denormalizedSuperTable;
45 ExtendedMappings mappings;
46 private String ownerEntityTable;
47 private String associatedEntityTable;
48 private String propertyName;
49 private String ownerEntity;
50 private String associatedEntity;
51
52 public void setSchema(String schema) {
53 this.schema = schema;
54 }
55
56 public void setCatalog(String catalog) {
57 this.catalog = catalog;
58 }
59
60 public String getName() {
61 return name;
62 }
63
64 public void setName(String name) {
65 this.name = name;
66 }
67
68 public void setAbstract(boolean anAbstract) {
69 isAbstract = anAbstract;
70 }
71
72 public void setUniqueConstraints(UniqueConstraint[] uniqueConstraints) {
73 this.uniqueConstraints = TableBinder.buildUniqueConstraints( uniqueConstraints );
74 }
75
76 public void setConstraints(String constraints) {
77 this.constraints = constraints;
78 }
79
80 public void setDenormalizedSuperTable(Table denormalizedSuperTable) {
81 this.denormalizedSuperTable = denormalizedSuperTable;
82 }
83
84 public void setMappings(ExtendedMappings mappings) {
85 this.mappings = mappings;
86 }
87
88 // only bind association table currently
89 public Table bind() {
90 //logicalName only accurate for assoc table...
91 String unquotedOwnerTable = StringHelper.unquote( ownerEntityTable );
92 String unquotedAssocTable = StringHelper.unquote( associatedEntityTable );
93
94 String logicalName = mappings.getNamingStrategy()
95 .logicalCollectionTableName(
96 name,
97 unquotedOwnerTable,
98 unquotedAssocTable,
99 propertyName );
100 if ( StringHelper.isQuoted( ownerEntityTable ) || StringHelper.isQuoted( associatedEntityTable ) ) {
101 logicalName = StringHelper.quote( logicalName );
102 }
103 String extendedName;
104 if ( name != null ) {
105 extendedName = mappings.getNamingStrategy().tableName( name );
106 }
107 else {
108 extendedName = mappings.getNamingStrategy()
109 .collectionTableName(
110 ownerEntity,
111 unquotedOwnerTable,
112 associatedEntity,
113 unquotedAssocTable,
114 propertyName
115 );
116 if ( StringHelper.isQuoted( ownerEntityTable ) || StringHelper.isQuoted( associatedEntityTable ) ) {
117 extendedName = StringHelper.quote( extendedName );
118 }
119 }
120 return fillTable(
121 schema, catalog,
122 extendedName, logicalName, isAbstract, uniqueConstraints, constraints,
123 denormalizedSuperTable, mappings
124 );
125 }
126
127 public static Table fillTable(
128 String schema, String catalog, String realTableName, String logicalName, boolean isAbstract,
129 List uniqueConstraints, String constraints, Table denormalizedSuperTable, ExtendedMappings mappings
130 ) {
131 schema = BinderHelper.isDefault( schema ) ? mappings.getSchemaName() : schema;
132 catalog = BinderHelper.isDefault( catalog ) ? mappings.getCatalogName() : catalog;
133 Table table;
134 if ( denormalizedSuperTable != null ) {
135 table = mappings.addDenormalizedTable(
136 schema,
137 catalog,
138 realTableName,
139 isAbstract,
140 null, //subselect
141 denormalizedSuperTable
142 );
143 }
144 else {
145 table = mappings.addTable(
146 schema,
147 catalog,
148 realTableName,
149 null, //subselect
150 isAbstract
151 );
152 }
153 if ( uniqueConstraints != null && uniqueConstraints.size() > 0 ) {
154 mappings.addUniqueConstraints( table, uniqueConstraints );
155 }
156 if ( constraints != null ) table.addCheckConstraint( constraints );
157 //logicalName is null if we are in the second pass
158 if ( logicalName != null ) {
159 mappings.addTableBinding( schema, catalog, logicalName, realTableName, denormalizedSuperTable );
160 }
161 return table;
162 }
163
164 public static void bindFk(
165 PersistentClass referencedEntity, PersistentClass destinationEntity, Ejb3JoinColumn[] columns,
166 SimpleValue value,
167 boolean unique, ExtendedMappings mappings
168 ) {
169 PersistentClass associatedClass;
170 if ( destinationEntity != null ) {
171 //overidden destination
172 associatedClass = destinationEntity;
173 }
174 else {
175 associatedClass = columns[0].getPropertyHolder() == null ? null : columns[0].getPropertyHolder()
176 .getPersistentClass();
177 }
178 final String mappedByProperty = columns[0].getMappedBy();
179 if ( StringHelper.isNotEmpty( mappedByProperty ) ) {
180 /**
181 * Get the columns of the mapped-by property
182 * copy them and link the copy to the actual value
183 */
184 if ( log.isDebugEnabled() ) {
185 log.debug(
186 "Retrieving property " + associatedClass.getEntityName() + "." + mappedByProperty
187 );
188 }
189
190 final Property property = associatedClass.getRecursiveProperty( columns[0].getMappedBy() );
191 Iterator mappedByColumns;
192 if ( property.getValue() instanceof Collection ) {
193 Collection collection = ( (Collection) property.getValue() );
194 Value element = collection.getElement();
195 if ( element == null ) {
196 throw new AnnotationException(
197 "Illegal use of mappedBy on both sides of the relationship: "
198 + associatedClass.getEntityName() + "." + mappedByProperty
199 );
200 }
201 mappedByColumns = element.getColumnIterator();
202 }
203 else {
204 mappedByColumns = property.getValue().getColumnIterator();
205 }
206 while ( mappedByColumns.hasNext() ) {
207 Column column = (Column) mappedByColumns.next();
208 columns[0].overrideSqlTypeIfNecessary( column );
209 columns[0].linkValueUsingAColumnCopy( column, value );
210 }
211 }
212 else if ( columns[0].isImplicit() ) {
213 /**
214 * if columns are implicit, then create the columns based on the
215 * referenced entity id columns
216 */
217 Iterator idColumns;
218 if ( referencedEntity instanceof JoinedSubclass ) {
219 idColumns = ( (JoinedSubclass) referencedEntity ).getKey().getColumnIterator();
220 }
221 else {
222 idColumns = referencedEntity.getIdentifier().getColumnIterator();
223 }
224 while ( idColumns.hasNext() ) {
225 Column column = (Column) idColumns.next();
226 columns[0].overrideSqlTypeIfNecessary( column );
227 columns[0].linkValueUsingDefaultColumnNaming( column, referencedEntity, value );
228 }
229 }
230 else {
231 int fkEnum = Ejb3JoinColumn.checkReferencedColumnsType( columns, referencedEntity, mappings );
232
233 if ( Ejb3JoinColumn.NON_PK_REFERENCE == fkEnum ) {
234 String referencedPropertyName;
235 if ( value instanceof ToOne ) {
236 referencedPropertyName = ( (ToOne) value ).getReferencedPropertyName();
237 }
238 else if ( value instanceof DependantValue ) {
239 String propertyName = columns[0].getPropertyName();
240 if ( propertyName != null ) {
241 Collection collection = (Collection) referencedEntity.getRecursiveProperty( propertyName )
242 .getValue();
243 referencedPropertyName = collection.getReferencedPropertyName();
244 }
245 else {
246 throw new AnnotationException( "SecondaryTable JoinColumn cannot reference a non primary key" );
247 }
248
249 }
250 else {
251 throw new AssertionFailure(
252 "Do a property ref on an unexpected Value type: "
253 + value.getClass().getName()
254 );
255 }
256 if ( referencedPropertyName == null ) {
257 throw new AssertionFailure(
258 "No property ref found while expected"
259 );
260 }
261 Property synthProp = referencedEntity.getRecursiveProperty( referencedPropertyName );
262 if ( synthProp == null ) {
263 throw new AssertionFailure(
264 "Cannot find synthProp: " + referencedEntity.getEntityName() + "." + referencedPropertyName
265 );
266 }
267 linkJoinColumnWithValueOverridingNameIfImplicit(
268 referencedEntity, synthProp.getColumnIterator(), columns, value
269 );
270
271 }
272 else {
273 if ( Ejb3JoinColumn.NO_REFERENCE == fkEnum ) {
274 //implicit case, we hope PK and FK columns are in the same order
275 if ( columns.length != referencedEntity.getIdentifier().getColumnSpan() ) {
276 throw new AnnotationException(
277 "A Foreign key refering " + referencedEntity.getEntityName()
278 + " from " + associatedClass.getEntityName()
279 + " has the wrong number of column. should be " + referencedEntity.getIdentifier()
280 .getColumnSpan()
281 );
282 }
283 linkJoinColumnWithValueOverridingNameIfImplicit(
284 referencedEntity,
285 referencedEntity.getIdentifier().getColumnIterator(),
286 columns,
287 value
288 );
289 }
290 else {
291 //explicit referencedColumnName
292 Iterator idColItr = referencedEntity.getKey().getColumnIterator();
293 org.hibernate.mapping.Column col;
294 Table table = referencedEntity.getTable(); //works cause the pk has to be on the primary table
295 if ( ! idColItr.hasNext() ) log.debug( "No column in the identifier!" );
296 while ( idColItr.hasNext() ) {
297 boolean match = false;
298 //for each PK column, find the associated FK column.
299 col = (org.hibernate.mapping.Column) idColItr.next();
300 for ( Ejb3JoinColumn joinCol : columns ) {
301 String referencedColumn = joinCol.getReferencedColumn();
302 referencedColumn = mappings.getPhysicalColumnName( referencedColumn, table );
303 if ( referencedColumn.equals( col.getName() ) ) {
304 //proper join column
305 if ( joinCol.isNameDeferred() ) {
306 joinCol.linkValueUsingDefaultColumnNaming(
307 col, referencedEntity, value
308 );
309 }
310 else {
311 joinCol.linkWithValue( value );
312 }
313 joinCol.overrideSqlTypeIfNecessary( col );
314 match = true;
315 break;
316 }
317 }
318 if ( !match ) {
319 throw new AnnotationException(
320 "Column name " + col.getName() + " of "
321 + referencedEntity.getEntityName() + " not found in JoinColumns.referencedColumnName"
322 );
323 }
324 }
325 }
326 }
327 }
328 value.createForeignKey();
329 if ( unique == true ) {
330 createUniqueConstraint( value );
331 }
332 }
333
334 private static void linkJoinColumnWithValueOverridingNameIfImplicit(
335 PersistentClass referencedEntity, Iterator columnIterator, Ejb3JoinColumn[] columns, SimpleValue value
336 ) {
337 for ( Ejb3JoinColumn joinCol : columns ) {
338 Column synthCol = (Column) columnIterator.next();
339 if ( joinCol.isNameDeferred() ) {
340 //this has to be the default value
341 joinCol.linkValueUsingDefaultColumnNaming( synthCol, referencedEntity, value );
342 }
343 else {
344 joinCol.linkWithValue( value );
345 }
346 joinCol.overrideSqlTypeIfNecessary( synthCol );
347 }
348 }
349
350 public static void createUniqueConstraint(Value value) {
351 Iterator iter = value.getColumnIterator();
352 ArrayList cols = new ArrayList();
353 while ( iter.hasNext() ) {
354 cols.add( iter.next() );
355 }
356 value.getTable().createUniqueKey( cols );
357 }
358
359 public static void addIndexes(Table hibTable, Index[] indexes, ExtendedMappings mappings) {
360 for ( Index index : indexes ) {
361 //no need to handle inSecondPass here since it is only called from EntityBinder
362 mappings.addSecondPass(
363 new IndexSecondPass( hibTable, index.name(), index.columnNames(), mappings )
364 );
365 }
366 }
367
368 public static List<String[]> buildUniqueConstraints(UniqueConstraint[] constraintsArray) {
369 List<String[]> result = new ArrayList<String[]>();
370 if ( constraintsArray.length != 0 ) {
371 for ( UniqueConstraint uc : constraintsArray ) {
372 result.add( uc.columnNames() );
373 }
374 }
375 return result;
376 }
377
378 public void setDefaultName(
379 String ownerEntity, String ownerEntityTable, String associatedEntity, String associatedEntityTable,
380 String propertyName
381 ) {
382 this.ownerEntity = ownerEntity;
383 this.ownerEntityTable = ownerEntityTable;
384 this.associatedEntity = associatedEntity;
385 this.associatedEntityTable = associatedEntityTable;
386 this.propertyName = propertyName;
387 this.name = null;
388 }
389 }