001: // $Id: FromElement.java 10851 2006-11-21 17:38:43Z steve.ebersole@jboss.com $
002: package org.hibernate.hql.ast.tree;
003:
004: import java.util.LinkedList;
005: import java.util.List;
006:
007: import org.hibernate.QueryException;
008: import org.hibernate.engine.JoinSequence;
009: import org.hibernate.hql.QueryTranslator;
010: import org.hibernate.hql.CollectionProperties;
011: import org.hibernate.hql.antlr.SqlTokenTypes;
012: import org.hibernate.hql.ast.util.ASTUtil;
013: import org.hibernate.hql.ast.HqlSqlWalker;
014: import org.hibernate.persister.collection.QueryableCollection;
015: import org.hibernate.persister.entity.EntityPersister;
016: import org.hibernate.persister.entity.PropertyMapping;
017: import org.hibernate.persister.entity.Queryable;
018: import org.hibernate.type.EntityType;
019: import org.hibernate.type.Type;
020: import org.hibernate.util.StringHelper;
021:
022: import org.apache.commons.logging.Log;
023: import org.apache.commons.logging.LogFactory;
024:
025: /**
026: * Represents a single mapped class mentioned in an HQL FROM clause. Each
027: * class reference will have the following symbols:
028: * <ul>
029: * <li>A class name - This is the name of the Java class that is mapped by Hibernate.</li>
030: * <li>[optional] an HQL alias for the mapped class.</li>
031: * <li>A table name - The name of the table that is mapped to the Java class.</li>
032: * <li>A table alias - The alias for the table that will be used in the resulting SQL.</li>
033: * </ul>
034: * <br>
035: * User: josh<br>
036: * Date: Dec 6, 2003<br>
037: * Time: 10:28:17 AM<br>
038: */
039: public class FromElement extends HqlSqlWalkerNode implements
040: DisplayableNode {
041: private static final Log log = LogFactory.getLog(FromElement.class);
042:
043: private String className;
044: private String classAlias;
045: private String tableAlias;
046: private String collectionTableAlias;
047: private FromClause fromClause;
048: private boolean includeSubclasses = true;
049: private boolean collectionJoin = false;
050: private FromElement origin;
051: private String[] columns;
052: private String role;
053: private boolean fetch;
054: private boolean isAllPropertyFetch;
055: private boolean filter = false;
056: private int sequence = -1;
057: private boolean useFromFragment = false;
058: private boolean initialized = false;
059: private FromElementType elementType;
060: private boolean useWhereFragment = true;
061: private List destinations = new LinkedList();
062: private boolean manyToMany = false;
063: private String withClauseFragment = null;
064: private String withClauseJoinAlias;
065: private boolean dereferencedBySuperclassProperty;
066: private boolean dereferencedBySubclassProperty;
067:
068: public FromElement() {
069: }
070:
071: public String getCollectionSuffix() {
072: return elementType.getCollectionSuffix();
073: }
074:
075: public void setCollectionSuffix(String suffix) {
076: elementType.setCollectionSuffix(suffix);
077: }
078:
079: public void initializeCollection(FromClause fromClause,
080: String classAlias, String tableAlias) {
081: doInitialize(fromClause, tableAlias, null, classAlias, null,
082: null);
083: initialized = true;
084: }
085:
086: public void initializeEntity(FromClause fromClause,
087: String className, EntityPersister persister,
088: EntityType type, String classAlias, String tableAlias) {
089: doInitialize(fromClause, tableAlias, className, classAlias,
090: persister, type);
091: this .sequence = fromClause.nextFromElementCounter();
092: initialized = true;
093: }
094:
095: private void doInitialize(FromClause fromClause, String tableAlias,
096: String className, String classAlias,
097: EntityPersister persister, EntityType type) {
098: if (initialized) {
099: throw new IllegalStateException("Already initialized!!");
100: }
101: this .fromClause = fromClause;
102: this .tableAlias = tableAlias;
103: this .className = className;
104: this .classAlias = classAlias;
105: this .elementType = new FromElementType(this , persister, type);
106: // Register the FromElement with the FROM clause, now that we have the names and aliases.
107: fromClause.registerFromElement(this );
108: if (log.isDebugEnabled()) {
109: log.debug(fromClause + " : " + className + " ("
110: + (classAlias == null ? "no alias" : classAlias)
111: + ") -> " + tableAlias);
112: }
113: }
114:
115: public EntityPersister getEntityPersister() {
116: return elementType.getEntityPersister();
117: }
118:
119: public Type getDataType() {
120: return elementType.getDataType();
121: }
122:
123: public Type getSelectType() {
124: return elementType.getSelectType();
125: }
126:
127: public Queryable getQueryable() {
128: return elementType.getQueryable();
129: }
130:
131: public String getClassName() {
132: return className;
133: }
134:
135: public String getClassAlias() {
136: return classAlias;
137: //return classAlias == null ? className : classAlias;
138: }
139:
140: private String getTableName() {
141: Queryable queryable = getQueryable();
142: return (queryable != null) ? queryable.getTableName()
143: : "{none}";
144: }
145:
146: public String getTableAlias() {
147: return tableAlias;
148: }
149:
150: /**
151: * Render the identifier select, but in a 'scalar' context (i.e. generate the column alias).
152: *
153: * @param i the sequence of the returned type
154: * @return the identifier select with the column alias.
155: */
156: String renderScalarIdentifierSelect(int i) {
157: return elementType.renderScalarIdentifierSelect(i);
158: }
159:
160: void checkInitialized() {
161: if (!initialized) {
162: throw new IllegalStateException(
163: "FromElement has not been initialized!");
164: }
165: }
166:
167: /**
168: * Returns the identifier select SQL fragment.
169: *
170: * @param size The total number of returned types.
171: * @param k The sequence of the current returned type.
172: * @return the identifier select SQL fragment.
173: */
174: String renderIdentifierSelect(int size, int k) {
175: return elementType.renderIdentifierSelect(size, k);
176: }
177:
178: /**
179: * Returns the property select SQL fragment.
180: *
181: * @param size The total number of returned types.
182: * @param k The sequence of the current returned type.
183: * @return the property select SQL fragment.
184: */
185: String renderPropertySelect(int size, int k) {
186: return elementType.renderPropertySelect(size, k,
187: isAllPropertyFetch);
188: }
189:
190: String renderCollectionSelectFragment(int size, int k) {
191: return elementType.renderCollectionSelectFragment(size, k);
192: }
193:
194: String renderValueCollectionSelectFragment(int size, int k) {
195: return elementType.renderValueCollectionSelectFragment(size, k);
196: }
197:
198: public FromClause getFromClause() {
199: return fromClause;
200: }
201:
202: /**
203: * Returns true if this FromElement was implied by a path, or false if this FROM element is explicitly declared in
204: * the FROM clause.
205: *
206: * @return true if this FromElement was implied by a path, or false if this FROM element is explicitly declared
207: */
208: public boolean isImplied() {
209: return false; // This is an explicit FROM element.
210: }
211:
212: /**
213: * Returns additional display text for the AST node.
214: *
215: * @return String - The additional display text.
216: */
217: public String getDisplayText() {
218: StringBuffer buf = new StringBuffer();
219: buf.append("FromElement{");
220: appendDisplayText(buf);
221: buf.append("}");
222: return buf.toString();
223: }
224:
225: protected void appendDisplayText(StringBuffer buf) {
226: buf
227: .append(isImplied() ? (isImpliedInFromClause() ? "implied in FROM clause"
228: : "implied")
229: : "explicit");
230: buf.append(",").append(
231: isCollectionJoin() ? "collection join"
232: : "not a collection join");
233: buf.append(",").append(
234: fetch ? "fetch join" : "not a fetch join");
235: buf.append(",").append(
236: isAllPropertyFetch ? "fetch all properties"
237: : "fetch non-lazy properties");
238: buf.append(",classAlias=").append(getClassAlias());
239: buf.append(",role=").append(role);
240: buf.append(",tableName=").append(getTableName());
241: buf.append(",tableAlias=").append(getTableAlias());
242: FromElement origin = getRealOrigin();
243: buf.append(",origin=").append(
244: origin == null ? "null" : origin.getText());
245: buf.append(",colums={");
246: if (columns != null) {
247: for (int i = 0; i < columns.length; i++) {
248: buf.append(columns[i]);
249: if (i < columns.length) {
250: buf.append(" ");
251: }
252: }
253: }
254: buf.append(",className=").append(className);
255: buf.append("}");
256: }
257:
258: public int hashCode() {
259: return super .hashCode();
260: }
261:
262: public boolean equals(Object obj) {
263: return super .equals(obj);
264: }
265:
266: public void setJoinSequence(JoinSequence joinSequence) {
267: elementType.setJoinSequence(joinSequence);
268: }
269:
270: public JoinSequence getJoinSequence() {
271: return elementType.getJoinSequence();
272: }
273:
274: public void setIncludeSubclasses(boolean includeSubclasses) {
275: if (isDereferencedBySuperclassOrSubclassProperty()) {
276: if (!includeSubclasses && log.isTraceEnabled()) {
277: log.trace("attempt to disable subclass-inclusions",
278: new Exception("stack-trace source"));
279: }
280: }
281: this .includeSubclasses = includeSubclasses;
282: }
283:
284: public boolean isIncludeSubclasses() {
285: return includeSubclasses;
286: }
287:
288: public boolean isDereferencedBySuperclassOrSubclassProperty() {
289: return dereferencedBySubclassProperty
290: || dereferencedBySuperclassProperty;
291: }
292:
293: public String getIdentityColumn() {
294: checkInitialized();
295: String table = getTableAlias();
296: if (table == null) {
297: throw new IllegalStateException("No table alias for node "
298: + this );
299: }
300: String[] cols;
301: String propertyName;
302: if (getEntityPersister() != null
303: && getEntityPersister().getEntityMetamodel() != null
304: && getEntityPersister().getEntityMetamodel()
305: .hasNonIdentifierPropertyNamedId()) {
306: propertyName = getEntityPersister()
307: .getIdentifierPropertyName();
308: } else {
309: propertyName = EntityPersister.ENTITY_ID;
310: }
311: if (getWalker().getStatementType() == HqlSqlWalker.SELECT) {
312: cols = getPropertyMapping(propertyName).toColumns(table,
313: propertyName);
314: } else {
315: cols = getPropertyMapping(propertyName).toColumns(
316: propertyName);
317: }
318: String result = StringHelper.join(", ", cols);
319: return cols.length == 1 ? result : "(" + result + ")";
320: }
321:
322: public void setCollectionJoin(boolean collectionJoin) {
323: this .collectionJoin = collectionJoin;
324: }
325:
326: public boolean isCollectionJoin() {
327: return collectionJoin;
328: }
329:
330: public void setRole(String role) {
331: this .role = role;
332: }
333:
334: public void setQueryableCollection(
335: QueryableCollection queryableCollection) {
336: elementType.setQueryableCollection(queryableCollection);
337: }
338:
339: public QueryableCollection getQueryableCollection() {
340: return elementType.getQueryableCollection();
341: }
342:
343: public void setColumns(String[] columns) {
344: this .columns = columns;
345: }
346:
347: public void setOrigin(FromElement origin, boolean manyToMany) {
348: this .origin = origin;
349: this .manyToMany = manyToMany;
350: origin.addDestination(this );
351: if (origin.getFromClause() == this .getFromClause()) {
352: // TODO: Figure out a better way to get the FROM elements in a proper tree structure.
353: // If this is not the destination of a many-to-many, add it as a child of the origin.
354: if (manyToMany) {
355: ASTUtil.appendSibling(origin, this );
356: } else {
357: if (!getWalker().isInFrom()
358: && !getWalker().isInSelect()) {
359: getFromClause().addChild(this );
360: } else {
361: origin.addChild(this );
362: }
363: }
364: } else if (!getWalker().isInFrom()) {
365: // HHH-276 : implied joins in a subselect where clause - The destination needs to be added
366: // to the destination's from clause.
367: getFromClause().addChild(this ); // Not sure if this is will fix everything, but it works.
368: } else {
369: // Otherwise, the destination node was implied by the FROM clause and the FROM clause processor
370: // will automatically add it in the right place.
371: }
372: }
373:
374: public boolean isManyToMany() {
375: return manyToMany;
376: }
377:
378: private void addDestination(FromElement fromElement) {
379: destinations.add(fromElement);
380: }
381:
382: public List getDestinations() {
383: return destinations;
384: }
385:
386: public FromElement getOrigin() {
387: return origin;
388: }
389:
390: public FromElement getRealOrigin() {
391: if (origin == null) {
392: return null;
393: }
394: if (origin.getText() == null || "".equals(origin.getText())) {
395: return origin.getRealOrigin();
396: }
397: return origin;
398: }
399:
400: public Type getPropertyType(String propertyName, String propertyPath) {
401: return elementType.getPropertyType(propertyName, propertyPath);
402: }
403:
404: public String[] toColumns(String tableAlias, String path,
405: boolean inSelect) {
406: return elementType.toColumns(tableAlias, path, inSelect);
407: }
408:
409: public String[] toColumns(String tableAlias, String path,
410: boolean inSelect, boolean forceAlias) {
411: return elementType.toColumns(tableAlias, path, inSelect,
412: forceAlias);
413: }
414:
415: public PropertyMapping getPropertyMapping(String propertyName) {
416: return elementType.getPropertyMapping(propertyName);
417: }
418:
419: public void setFetch(boolean fetch) {
420: this .fetch = fetch;
421: // Fetch can't be used with scroll() or iterate().
422: if (fetch && getWalker().isShallowQuery()) {
423: throw new QueryException(
424: QueryTranslator.ERROR_CANNOT_FETCH_WITH_ITERATE);
425: }
426: }
427:
428: public boolean isFetch() {
429: return fetch;
430: }
431:
432: public int getSequence() {
433: return sequence;
434: }
435:
436: public void setFilter(boolean b) {
437: filter = b;
438: }
439:
440: public boolean isFilter() {
441: return filter;
442: }
443:
444: public boolean useFromFragment() {
445: checkInitialized();
446: // If it's not implied or it is implied and it's a many to many join where the target wasn't found.
447: return !isImplied() || this .useFromFragment;
448: }
449:
450: public void setUseFromFragment(boolean useFromFragment) {
451: this .useFromFragment = useFromFragment;
452: }
453:
454: public boolean useWhereFragment() {
455: return useWhereFragment;
456: }
457:
458: public void setUseWhereFragment(boolean b) {
459: useWhereFragment = b;
460: }
461:
462: public void setCollectionTableAlias(String collectionTableAlias) {
463: this .collectionTableAlias = collectionTableAlias;
464: }
465:
466: public String getCollectionTableAlias() {
467: return collectionTableAlias;
468: }
469:
470: public boolean isCollectionOfValuesOrComponents() {
471: return elementType.isCollectionOfValuesOrComponents();
472: }
473:
474: public boolean isEntity() {
475: return elementType.isEntity();
476: }
477:
478: public void setImpliedInFromClause(boolean flag) {
479: throw new UnsupportedOperationException(
480: "Explicit FROM elements can't be implied in the FROM clause!");
481: }
482:
483: public boolean isImpliedInFromClause() {
484: return false; // Since this is an explicit FROM element, it can't be implied in the FROM clause.
485: }
486:
487: public void setInProjectionList(boolean inProjectionList) {
488: // Do nothing, eplicit from elements are *always* in the projection list.
489: }
490:
491: public boolean inProjectionList() {
492: return !isImplied() && isFromOrJoinFragment();
493: }
494:
495: public boolean isFromOrJoinFragment() {
496: return getType() == SqlTokenTypes.FROM_FRAGMENT
497: || getType() == SqlTokenTypes.JOIN_FRAGMENT;
498: }
499:
500: public boolean isAllPropertyFetch() {
501: return isAllPropertyFetch;
502: }
503:
504: public void setAllPropertyFetch(boolean fetch) {
505: isAllPropertyFetch = fetch;
506: }
507:
508: public String getWithClauseFragment() {
509: return withClauseFragment;
510: }
511:
512: public String getWithClauseJoinAlias() {
513: return withClauseJoinAlias;
514: }
515:
516: public void setWithClauseFragment(String withClauseJoinAlias,
517: String withClauseFragment) {
518: this .withClauseJoinAlias = withClauseJoinAlias;
519: this .withClauseFragment = withClauseFragment;
520: }
521:
522: public boolean hasCacheablePersister() {
523: if (getQueryableCollection() != null) {
524: return getQueryableCollection().hasCache();
525: } else {
526: return getQueryable().hasCache();
527: }
528: }
529:
530: public void handlePropertyBeingDereferenced(Type propertySource,
531: String propertyName) {
532: if (getQueryableCollection() != null
533: && CollectionProperties
534: .isCollectionProperty(propertyName)) {
535: // propertyName refers to something like collection.size...
536: return;
537: }
538: if (propertySource.isComponentType()) {
539: // property name is a sub-path of a component...
540: return;
541: }
542:
543: Queryable persister = getQueryable();
544: if (persister != null) {
545: try {
546: Queryable.Declarer propertyDeclarer = persister
547: .getSubclassPropertyDeclarer(propertyName);
548: if (log.isTraceEnabled()) {
549: log.trace("handling property dereference ["
550: + persister.getEntityName() + " ("
551: + getClassAlias() + ") -> " + propertyName
552: + " (" + propertyDeclarer + ")]");
553: }
554: if (propertyDeclarer == Queryable.Declarer.SUBCLASS) {
555: dereferencedBySubclassProperty = true;
556: includeSubclasses = true;
557: } else if (propertyDeclarer == Queryable.Declarer.SUPERCLASS) {
558: dereferencedBySuperclassProperty = true;
559: }
560: } catch (QueryException ignore) {
561: // ignore it; the incoming property could not be found so we
562: // cannot be sure what to do here. At the very least, the
563: // safest is to simply not apply any dereference toggling...
564:
565: }
566: }
567: }
568:
569: public boolean isDereferencedBySuperclassProperty() {
570: return dereferencedBySuperclassProperty;
571: }
572:
573: public boolean isDereferencedBySubclassProperty() {
574: return dereferencedBySubclassProperty;
575: }
576: }
|