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