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.persister.entity;
26
27 import java.io.Serializable;
28 import java.util.ArrayList;
29 import java.util.HashMap;
30 import java.util.Iterator;
31 import java.util.Map;
32
33 import org.hibernate.AssertionFailure;
34 import org.hibernate.Hibernate;
35 import org.hibernate.HibernateException;
36 import org.hibernate.MappingException;
37 import org.hibernate.QueryException;
38 import org.hibernate.cache.access.EntityRegionAccessStrategy;
39 import org.hibernate.engine.Mapping;
40 import org.hibernate.engine.SessionFactoryImplementor;
41 import org.hibernate.engine.Versioning;
42 import org.hibernate.engine.ExecuteUpdateResultCheckStyle;
43 import org.hibernate.mapping.Column;
44 import org.hibernate.mapping.KeyValue;
45 import org.hibernate.mapping.PersistentClass;
46 import org.hibernate.mapping.Property;
47 import org.hibernate.mapping.Selectable;
48 import org.hibernate.mapping.Subclass;
49 import org.hibernate.mapping.Table;
50 import org.hibernate.sql.CaseFragment;
51 import org.hibernate.sql.SelectFragment;
52 import org.hibernate.type.Type;
53 import org.hibernate.util.ArrayHelper;
54
55 /**
56 * An <tt>EntityPersister</tt> implementing the normalized "table-per-subclass"
57 * mapping strategy
58 *
59 * @author Gavin King
60 */
61 public class JoinedSubclassEntityPersister extends AbstractEntityPersister {
62
63 // the class hierarchy structure
64 private final int tableSpan;
65 private final String[] tableNames;
66 private final String[] naturalOrderTableNames;
67 private final String[][] tableKeyColumns;
68 private final String[][] naturalOrderTableKeyColumns;
69 private final boolean[] naturalOrderCascadeDeleteEnabled;
70
71 private final String[] spaces;
72
73 private final String[] subclassClosure;
74
75 private final String[] subclassTableNameClosure;
76 private final String[][] subclassTableKeyColumnClosure;
77 private final boolean[] isClassOrSuperclassTable;
78
79 // properties of this class, including inherited properties
80 private final int[] naturalOrderPropertyTableNumbers;
81 private final int[] propertyTableNumbers;
82
83 // the closure of all properties in the entire hierarchy including
84 // subclasses and superclasses of this class
85 private final int[] subclassPropertyTableNumberClosure;
86
87 // the closure of all columns used by the entire hierarchy including
88 // subclasses and superclasses of this class
89 private final int[] subclassColumnTableNumberClosure;
90 private final int[] subclassFormulaTableNumberClosure;
91
92 // subclass discrimination works by assigning particular
93 // values to certain combinations of null primary key
94 // values in the outer join using an SQL CASE
95 private final Map subclassesByDiscriminatorValue = new HashMap();
96 private final String[] discriminatorValues;
97 private final String[] notNullColumnNames;
98 private final int[] notNullColumnTableNumbers;
99
100 private final String[] constraintOrderedTableNames;
101 private final String[][] constraintOrderedKeyColumnNames;
102
103 private final String discriminatorSQLString;
104
105 //INITIALIZATION:
106
107 public JoinedSubclassEntityPersister(
108 final PersistentClass persistentClass,
109 final EntityRegionAccessStrategy cacheAccessStrategy,
110 final SessionFactoryImplementor factory,
111 final Mapping mapping) throws HibernateException {
112
113 super( persistentClass, cacheAccessStrategy, factory );
114
115 // DISCRIMINATOR
116
117 final Object discriminatorValue;
118 if ( persistentClass.isPolymorphic() ) {
119 try {
120 discriminatorValue = new Integer( persistentClass.getSubclassId() );
121 discriminatorSQLString = discriminatorValue.toString();
122 }
123 catch (Exception e) {
124 throw new MappingException("Could not format discriminator value to SQL string", e );
125 }
126 }
127 else {
128 discriminatorValue = null;
129 discriminatorSQLString = null;
130 }
131
132 if ( optimisticLockMode() > Versioning.OPTIMISTIC_LOCK_VERSION ) {
133 throw new MappingException( "optimistic-lock=all|dirty not supported for joined-subclass mappings [" + getEntityName() + "]" );
134 }
135
136 //MULTITABLES
137
138 final int idColumnSpan = getIdentifierColumnSpan();
139
140 ArrayList tables = new ArrayList();
141 ArrayList keyColumns = new ArrayList();
142 ArrayList cascadeDeletes = new ArrayList();
143 Iterator titer = persistentClass.getTableClosureIterator();
144 Iterator kiter = persistentClass.getKeyClosureIterator();
145 while ( titer.hasNext() ) {
146 Table tab = (Table) titer.next();
147 KeyValue key = (KeyValue) kiter.next();
148 String tabname = tab.getQualifiedName(
149 factory.getDialect(),
150 factory.getSettings().getDefaultCatalogName(),
151 factory.getSettings().getDefaultSchemaName()
152 );
153 tables.add(tabname);
154 String[] keyCols = new String[idColumnSpan];
155 Iterator citer = key.getColumnIterator();
156 for ( int k=0; k<idColumnSpan; k++ ) {
157 keyCols[k] = ( (Column) citer.next() ).getQuotedName( factory.getDialect() );
158 }
159 keyColumns.add(keyCols);
160 cascadeDeletes.add( new Boolean( key.isCascadeDeleteEnabled() && factory.getDialect().supportsCascadeDelete() ) );
161 }
162 naturalOrderTableNames = ArrayHelper.toStringArray(tables);
163 naturalOrderTableKeyColumns = ArrayHelper.to2DStringArray(keyColumns);
164 naturalOrderCascadeDeleteEnabled = ArrayHelper.toBooleanArray(cascadeDeletes);
165
166 ArrayList subtables = new ArrayList();
167 ArrayList isConcretes = new ArrayList();
168 keyColumns = new ArrayList();
169 titer = persistentClass.getSubclassTableClosureIterator();
170 while ( titer.hasNext() ) {
171 Table tab = (Table) titer.next();
172 isConcretes.add( new Boolean( persistentClass.isClassOrSuperclassTable(tab) ) );
173 String tabname = tab.getQualifiedName(
174 factory.getDialect(),
175 factory.getSettings().getDefaultCatalogName(),
176 factory.getSettings().getDefaultSchemaName()
177 );
178 subtables.add(tabname);
179 String[] key = new String[idColumnSpan];
180 Iterator citer = tab.getPrimaryKey().getColumnIterator();
181 for ( int k=0; k<idColumnSpan; k++ ) {
182 key[k] = ( (Column) citer.next() ).getQuotedName( factory.getDialect() );
183 }
184 keyColumns.add(key);
185 }
186 subclassTableNameClosure = ArrayHelper.toStringArray(subtables);
187 subclassTableKeyColumnClosure = ArrayHelper.to2DStringArray(keyColumns);
188 isClassOrSuperclassTable = ArrayHelper.toBooleanArray(isConcretes);
189
190 constraintOrderedTableNames = new String[subclassTableNameClosure.length];
191 constraintOrderedKeyColumnNames = new String[subclassTableNameClosure.length][];
192 int currentPosition = 0;
193 for ( int i = subclassTableNameClosure.length - 1; i >= 0 ; i--, currentPosition++ ) {
194 constraintOrderedTableNames[currentPosition] = subclassTableNameClosure[i];
195 constraintOrderedKeyColumnNames[currentPosition] = subclassTableKeyColumnClosure[i];
196 }
197
198 tableSpan = naturalOrderTableNames.length;
199 tableNames = reverse(naturalOrderTableNames);
200 tableKeyColumns = reverse(naturalOrderTableKeyColumns);
201 reverse(subclassTableNameClosure, tableSpan);
202 reverse(subclassTableKeyColumnClosure, tableSpan);
203
204 spaces = ArrayHelper.join(
205 tableNames,
206 ArrayHelper.toStringArray( persistentClass.getSynchronizedTables() )
207 );
208
209 // Custom sql
210 customSQLInsert = new String[tableSpan];
211 customSQLUpdate = new String[tableSpan];
212 customSQLDelete = new String[tableSpan];
213 insertCallable = new boolean[tableSpan];
214 updateCallable = new boolean[tableSpan];
215 deleteCallable = new boolean[tableSpan];
216 insertResultCheckStyles = new ExecuteUpdateResultCheckStyle[tableSpan];
217 updateResultCheckStyles = new ExecuteUpdateResultCheckStyle[tableSpan];
218 deleteResultCheckStyles = new ExecuteUpdateResultCheckStyle[tableSpan];
219
220 PersistentClass pc = persistentClass;
221 int jk = tableSpan-1;
222 while (pc!=null) {
223 customSQLInsert[jk] = pc.getCustomSQLInsert();
224 insertCallable[jk] = customSQLInsert[jk] != null && pc.isCustomInsertCallable();
225 insertResultCheckStyles[jk] = pc.getCustomSQLInsertCheckStyle() == null
226 ? ExecuteUpdateResultCheckStyle.determineDefault( customSQLInsert[jk], insertCallable[jk] )
227 : pc.getCustomSQLInsertCheckStyle();
228 customSQLUpdate[jk] = pc.getCustomSQLUpdate();
229 updateCallable[jk] = customSQLUpdate[jk] != null && pc.isCustomUpdateCallable();
230 updateResultCheckStyles[jk] = pc.getCustomSQLUpdateCheckStyle() == null
231 ? ExecuteUpdateResultCheckStyle.determineDefault( customSQLUpdate[jk], updateCallable[jk] )
232 : pc.getCustomSQLUpdateCheckStyle();
233 customSQLDelete[jk] = pc.getCustomSQLDelete();
234 deleteCallable[jk] = customSQLDelete[jk] != null && pc.isCustomDeleteCallable();
235 deleteResultCheckStyles[jk] = pc.getCustomSQLDeleteCheckStyle() == null
236 ? ExecuteUpdateResultCheckStyle.determineDefault( customSQLDelete[jk], deleteCallable[jk] )
237 : pc.getCustomSQLDeleteCheckStyle();
238 jk--;
239 pc = pc.getSuperclass();
240 }
241 if ( jk != -1 ) {
242 throw new AssertionFailure( "Tablespan does not match height of joined-subclass hiearchy." );
243 }
244
245 // PROPERTIES
246
247 int hydrateSpan = getPropertySpan();
248 naturalOrderPropertyTableNumbers = new int[hydrateSpan];
249 propertyTableNumbers = new int[hydrateSpan];
250 Iterator iter = persistentClass.getPropertyClosureIterator();
251 int i=0;
252 while( iter.hasNext() ) {
253 Property prop = (Property) iter.next();
254 String tabname = prop.getValue().getTable().getQualifiedName(
255 factory.getDialect(),
256 factory.getSettings().getDefaultCatalogName(),
257 factory.getSettings().getDefaultSchemaName()
258 );
259 propertyTableNumbers[i] = getTableId(tabname, tableNames);
260 naturalOrderPropertyTableNumbers[i] = getTableId(tabname, naturalOrderTableNames);
261 i++;
262 }
263
264 // subclass closure properties
265
266 //TODO: code duplication with SingleTableEntityPersister
267
268 ArrayList columnTableNumbers = new ArrayList();
269 ArrayList formulaTableNumbers = new ArrayList();
270 ArrayList propTableNumbers = new ArrayList();
271
272 iter = persistentClass.getSubclassPropertyClosureIterator();
273 while ( iter.hasNext() ) {
274 Property prop = (Property) iter.next();
275 Table tab = prop.getValue().getTable();
276 String tabname = tab.getQualifiedName(
277 factory.getDialect(),
278 factory.getSettings().getDefaultCatalogName(),
279 factory.getSettings().getDefaultSchemaName()
280 );
281 Integer tabnum = new Integer( getTableId(tabname, subclassTableNameClosure) );
282 propTableNumbers.add(tabnum);
283
284 Iterator citer = prop.getColumnIterator();
285 while ( citer.hasNext() ) {
286 Selectable thing = (Selectable) citer.next();
287 if ( thing.isFormula() ) {
288 formulaTableNumbers.add(tabnum);
289 }
290 else {
291 columnTableNumbers.add(tabnum);
292 }
293 }
294
295 }
296
297 subclassColumnTableNumberClosure = ArrayHelper.toIntArray(columnTableNumbers);
298 subclassPropertyTableNumberClosure = ArrayHelper.toIntArray(propTableNumbers);
299 subclassFormulaTableNumberClosure = ArrayHelper.toIntArray(formulaTableNumbers);
300
301 // SUBCLASSES
302
303 int subclassSpan = persistentClass.getSubclassSpan() + 1;
304 subclassClosure = new String[subclassSpan];
305 subclassClosure[subclassSpan-1] = getEntityName();
306 if ( persistentClass.isPolymorphic() ) {
307 subclassesByDiscriminatorValue.put( discriminatorValue, getEntityName() );
308 discriminatorValues = new String[subclassSpan];
309 discriminatorValues[subclassSpan-1] = discriminatorSQLString;
310 notNullColumnTableNumbers = new int[subclassSpan];
311 final int id = getTableId(
312 persistentClass.getTable().getQualifiedName(
313 factory.getDialect(),
314 factory.getSettings().getDefaultCatalogName(),
315 factory.getSettings().getDefaultSchemaName()
316 ),
317 subclassTableNameClosure
318 );
319 notNullColumnTableNumbers[subclassSpan-1] = id;
320 notNullColumnNames = new String[subclassSpan];
321 notNullColumnNames[subclassSpan-1] = subclassTableKeyColumnClosure[id][0]; //( (Column) model.getTable().getPrimaryKey().getColumnIterator().next() ).getName();
322 }
323 else {
324 discriminatorValues = null;
325 notNullColumnTableNumbers = null;
326 notNullColumnNames = null;
327 }
328
329 iter = persistentClass.getSubclassIterator();
330 int k=0;
331 while ( iter.hasNext() ) {
332 Subclass sc = (Subclass) iter.next();
333 subclassClosure[k] = sc.getEntityName();
334 try {
335 if ( persistentClass.isPolymorphic() ) {
336 // we now use subclass ids that are consistent across all
337 // persisters for a class hierarchy, so that the use of
338 // "foo.class = Bar" works in HQL
339 Integer subclassId = new Integer( sc.getSubclassId() );//new Integer(k+1);
340 subclassesByDiscriminatorValue.put( subclassId, sc.getEntityName() );
341 discriminatorValues[k] = subclassId.toString();
342 int id = getTableId(
343 sc.getTable().getQualifiedName(
344 factory.getDialect(),
345 factory.getSettings().getDefaultCatalogName(),
346 factory.getSettings().getDefaultSchemaName()
347 ),
348 subclassTableNameClosure
349 );
350 notNullColumnTableNumbers[k] = id;
351 notNullColumnNames[k] = subclassTableKeyColumnClosure[id][0]; //( (Column) sc.getTable().getPrimaryKey().getColumnIterator().next() ).getName();
352 }
353 }
354 catch (Exception e) {
355 throw new MappingException("Error parsing discriminator value", e );
356 }
357 k++;
358 }
359
360 initLockers();
361
362 initSubclassPropertyAliasesMap(persistentClass);
363
364 postConstruct(mapping);
365
366 }
367
368 /*public void postInstantiate() throws MappingException {
369 super.postInstantiate();
370 //TODO: other lock modes?
371 loader = createEntityLoader(LockMode.NONE, CollectionHelper.EMPTY_MAP);
372 }*/
373
374 public String getSubclassPropertyTableName(int i) {
375 return subclassTableNameClosure[ subclassPropertyTableNumberClosure[i] ];
376 }
377
378 public Type getDiscriminatorType() {
379 return Hibernate.INTEGER;
380 }
381
382 public String getDiscriminatorSQLValue() {
383 return discriminatorSQLString;
384 }
385
386
387 public String getSubclassForDiscriminatorValue(Object value) {
388 return (String) subclassesByDiscriminatorValue.get(value);
389 }
390
391 public Serializable[] getPropertySpaces() {
392 return spaces; // don't need subclass tables, because they can't appear in conditions
393 }
394
395
396 protected String getTableName(int j) {
397 return naturalOrderTableNames[j];
398 }
399
400 protected String[] getKeyColumns(int j) {
401 return naturalOrderTableKeyColumns[j];
402 }
403
404 protected boolean isTableCascadeDeleteEnabled(int j) {
405 return naturalOrderCascadeDeleteEnabled[j];
406 }
407
408 protected boolean isPropertyOfTable(int property, int j) {
409 return naturalOrderPropertyTableNumbers[property]==j;
410 }
411
412 /**
413 * Load an instance using either the <tt>forUpdateLoader</tt> or the outer joining <tt>loader</tt>,
414 * depending upon the value of the <tt>lock</tt> parameter
415 */
416 /*public Object load(Serializable id, Object optionalObject, LockMode lockMode, SessionImplementor session)
417 throws HibernateException {
418
419 if ( log.isTraceEnabled() ) log.trace( "Materializing entity: " + MessageHelper.infoString(this, id) );
420
421 final UniqueEntityLoader loader = hasQueryLoader() ?
422 getQueryLoader() :
423 this.loader;
424 try {
425
426 final Object result = loader.load(id, optionalObject, session);
427
428 if (result!=null) lock(id, getVersion(result), result, lockMode, session);
429
430 return result;
431
432 }
433 catch (SQLException sqle) {
434 throw new JDBCException( "could not load by id: " + MessageHelper.infoString(this, id), sqle );
435 }
436 }*/
437
438 private static final void reverse(Object[] objects, int len) {
439 Object[] temp = new Object[len];
440 for (int i=0; i<len; i++) {
441 temp[i] = objects[len-i-1];
442 }
443 for (int i=0; i<len; i++) {
444 objects[i] = temp[i];
445 }
446 }
447
448 private static final String[] reverse(String[] objects) {
449 int len = objects.length;
450 String[] temp = new String[len];
451 for (int i=0; i<len; i++) {
452 temp[i] = objects[len-i-1];
453 }
454 return temp;
455 }
456
457 private static final String[][] reverse(String[][] objects) {
458 int len = objects.length;
459 String[][] temp = new String[len][];
460 for (int i=0; i<len; i++) {
461 temp[i] = objects[len-i-1];
462 }
463 return temp;
464 }
465
466 public String fromTableFragment(String alias) {
467 return getTableName() + ' ' + alias;
468 }
469
470 public String getTableName() {
471 return tableNames[0];
472 }
473
474 private static int getTableId(String tableName, String[] tables) {
475 for ( int j=0; j<tables.length; j++ ) {
476 if ( tableName.equals( tables[j] ) ) {
477 return j;
478 }
479 }
480 throw new AssertionFailure("Table " + tableName + " not found");
481 }
482
483 public void addDiscriminatorToSelect(SelectFragment select, String name, String suffix) {
484 if ( hasSubclasses() ) {
485 select.setExtraSelectList( discriminatorFragment(name), getDiscriminatorAlias() );
486 }
487 }
488
489 private CaseFragment discriminatorFragment(String alias) {
490 CaseFragment cases = getFactory().getDialect().createCaseFragment();
491
492 for ( int i=0; i<discriminatorValues.length; i++ ) {
493 cases.addWhenColumnNotNull(
494 generateTableAlias( alias, notNullColumnTableNumbers[i] ),
495 notNullColumnNames[i],
496 discriminatorValues[i]
497 );
498 }
499
500 return cases;
501 }
502
503 public String filterFragment(String alias) {
504 return hasWhere() ?
505 " and " + getSQLWhereString( generateFilterConditionAlias( alias ) ) :
506 "";
507 }
508
509 public String generateFilterConditionAlias(String rootAlias) {
510 return generateTableAlias( rootAlias, tableSpan-1 );
511 }
512
513 public String[] getIdentifierColumnNames() {
514 return tableKeyColumns[0];
515 }
516
517 public String[] toColumns(String alias, String propertyName) throws QueryException {
518
519 if ( ENTITY_CLASS.equals(propertyName) ) {
520 // This doesn't actually seem to work but it *might*
521 // work on some dbs. Also it doesn't work if there
522 // are multiple columns of results because it
523 // is not accounting for the suffix:
524 // return new String[] { getDiscriminatorColumnName() };
525
526 return new String[] { discriminatorFragment(alias).toFragmentString() };
527 }
528 else {
529 return super.toColumns(alias, propertyName);
530 }
531
532 }
533
534 protected int[] getPropertyTableNumbersInSelect() {
535 return propertyTableNumbers;
536 }
537
538 protected int getSubclassPropertyTableNumber(int i) {
539 return subclassPropertyTableNumberClosure[i];
540 }
541
542 public int getTableSpan() {
543 return tableSpan;
544 }
545
546 public boolean isMultiTable() {
547 return true;
548 }
549
550 protected int[] getSubclassColumnTableNumberClosure() {
551 return subclassColumnTableNumberClosure;
552 }
553
554 protected int[] getSubclassFormulaTableNumberClosure() {
555 return subclassFormulaTableNumberClosure;
556 }
557
558 protected int[] getPropertyTableNumbers() {
559 return naturalOrderPropertyTableNumbers;
560 }
561
562 protected String[] getSubclassTableKeyColumns(int j) {
563 return subclassTableKeyColumnClosure[j];
564 }
565
566 public String getSubclassTableName(int j) {
567 return subclassTableNameClosure[j];
568 }
569
570 public int getSubclassTableSpan() {
571 return subclassTableNameClosure.length;
572 }
573
574 protected boolean isClassOrSuperclassTable(int j) {
575 return isClassOrSuperclassTable[j];
576 }
577
578 public String getPropertyTableName(String propertyName) {
579 Integer index = getEntityMetamodel().getPropertyIndexOrNull(propertyName);
580 if ( index == null ) {
581 return null;
582 }
583 return tableNames[ propertyTableNumbers[ index.intValue() ] ];
584 }
585
586 public String[] getConstraintOrderedTableNameClosure() {
587 return constraintOrderedTableNames;
588 }
589
590 public String[][] getContraintOrderedTableKeyColumnClosure() {
591 return constraintOrderedKeyColumnNames;
592 }
593
594 public String getRootTableName() {
595 return naturalOrderTableNames[0];
596 }
597
598 public String getRootTableAlias(String drivingAlias) {
599 return generateTableAlias( drivingAlias, getTableId( getRootTableName(), tableNames ) );
600 }
601
602 public Declarer getSubclassPropertyDeclarer(String propertyPath) {
603 if ( "class".equals( propertyPath ) ) {
604 // special case where we need to force incloude all subclass joins
605 return Declarer.SUBCLASS;
606 }
607 return super.getSubclassPropertyDeclarer( propertyPath );
608 }
609 }