001: //$Id: PathExpressionParser.java 10829 2006-11-16 20:21:47Z steve.ebersole@jboss.com $
002: package org.hibernate.hql.classic;
003:
004: import java.util.LinkedList;
005: import java.util.Map;
006:
007: import org.hibernate.MappingException;
008: import org.hibernate.QueryException;
009: import org.hibernate.engine.JoinSequence;
010: import org.hibernate.hql.CollectionSubqueryFactory;
011: import org.hibernate.persister.collection.CollectionPropertyMapping;
012: import org.hibernate.persister.collection.QueryableCollection;
013: import org.hibernate.persister.entity.EntityPersister;
014: import org.hibernate.persister.entity.PropertyMapping;
015: import org.hibernate.persister.entity.Queryable;
016: import org.hibernate.sql.JoinFragment;
017: import org.hibernate.type.AssociationType;
018: import org.hibernate.type.CollectionType;
019: import org.hibernate.type.EntityType;
020: import org.hibernate.type.Type;
021: import org.hibernate.type.TypeFactory;
022:
023: /**
024: * Parses an expression of the form foo.bar.baz and builds up an expression
025: * involving two less table joins than there are path components.
026: */
027: public class PathExpressionParser implements Parser {
028:
029: //TODO: this class does too many things! we need a different
030: //kind of path expression parser for each of the diffferent
031: //ways in which path expressions can occur
032:
033: //We should actually rework this class to not implement Parser
034: //and just process path expressions in the most convenient way.
035:
036: //The class is now way to complex!
037:
038: private int dotcount;
039: private String currentName;
040: private String currentProperty;
041: private String oneToOneOwnerName;
042: private AssociationType ownerAssociationType;
043: private String[] columns;
044: private String collectionName;
045: private String collectionOwnerName;
046: private String collectionRole;
047: private final StringBuffer componentPath = new StringBuffer();
048: private Type type;
049: private final StringBuffer path = new StringBuffer();
050: private boolean ignoreInitialJoin;
051: private boolean continuation;
052: private int joinType = JoinFragment.INNER_JOIN; //default mode
053: private boolean useThetaStyleJoin = true;
054: private PropertyMapping currentPropertyMapping;
055: private JoinSequence joinSequence;
056:
057: private boolean expectingCollectionIndex;
058: private LinkedList collectionElements = new LinkedList();
059:
060: void setJoinType(int joinType) {
061: this .joinType = joinType;
062: }
063:
064: void setUseThetaStyleJoin(boolean useThetaStyleJoin) {
065: this .useThetaStyleJoin = useThetaStyleJoin;
066: }
067:
068: private void addJoin(String name, AssociationType joinableType)
069: throws QueryException {
070: try {
071: joinSequence.addJoin(joinableType, name, joinType,
072: currentColumns());
073: } catch (MappingException me) {
074: throw new QueryException(me);
075: }
076: }
077:
078: private void addJoin(String name, AssociationType joinableType,
079: String[] foreignKeyColumns) throws QueryException {
080: try {
081: joinSequence.addJoin(joinableType, name, joinType,
082: foreignKeyColumns);
083: } catch (MappingException me) {
084: throw new QueryException(me);
085: }
086: }
087:
088: String continueFromManyToMany(String entityName,
089: String[] joinColumns, QueryTranslatorImpl q)
090: throws QueryException {
091: start(q);
092: continuation = true;
093: currentName = q.createNameFor(entityName);
094: q.addType(currentName, entityName);
095: Queryable classPersister = q.getEntityPersister(entityName);
096: //QueryJoinFragment join = q.createJoinFragment(useThetaStyleJoin);
097: addJoin(currentName, TypeFactory.manyToOne(entityName),
098: joinColumns);
099: currentPropertyMapping = classPersister;
100: return currentName;
101: }
102:
103: public void ignoreInitialJoin() {
104: ignoreInitialJoin = true;
105: }
106:
107: public void token(String token, QueryTranslatorImpl q)
108: throws QueryException {
109:
110: if (token != null)
111: path.append(token);
112:
113: String alias = q.getPathAlias(path.toString());
114: if (alias != null) {
115: reset(q); //reset the dotcount (but not the path)
116: currentName = alias; //after reset!
117: currentPropertyMapping = q.getPropertyMapping(currentName);
118: if (!ignoreInitialJoin) {
119: JoinSequence ojf = q.getPathJoin(path.toString());
120: try {
121: joinSequence.addCondition(ojf.toJoinFragment(
122: q.getEnabledFilters(), true)
123: .toWhereFragmentString()); //after reset!
124: } catch (MappingException me) {
125: throw new QueryException(me);
126: }
127: // we don't need to worry about any condition in the ON clause
128: // here (toFromFragmentString), since anything in the ON condition
129: // is already applied to the whole query
130: }
131: } else if (".".equals(token)) {
132: dotcount++;
133: } else {
134: if (dotcount == 0) {
135: if (!continuation) {
136: if (!q.isName(token))
137: throw new QueryException("undefined alias: "
138: + token);
139: currentName = token;
140: currentPropertyMapping = q
141: .getPropertyMapping(currentName);
142: }
143: } else if (dotcount == 1) {
144: if (currentName != null) {
145: currentProperty = token;
146: } else if (collectionName != null) {
147: //processCollectionProperty(token, q.getCollectionPersister(collectionRole), collectionName);
148: continuation = false;
149: } else {
150: throw new QueryException("unexpected");
151: }
152: } else { // dotcount>=2
153:
154: // Do the corresponding RHS
155: Type propertyType = getPropertyType();
156:
157: if (propertyType == null) {
158: throw new QueryException("unresolved property: "
159: + path);
160: }
161:
162: if (propertyType.isComponentType()) {
163: dereferenceComponent(token);
164: } else if (propertyType.isEntityType()) {
165: if (!isCollectionValued())
166: dereferenceEntity(token,
167: (EntityType) propertyType, q);
168: } else if (propertyType.isCollectionType()) {
169: dereferenceCollection(token,
170: ((CollectionType) propertyType).getRole(),
171: q);
172:
173: } else {
174: if (token != null)
175: throw new QueryException("dereferenced: "
176: + path);
177: }
178:
179: }
180: }
181: }
182:
183: private void dereferenceEntity(String propertyName,
184: EntityType propertyType, QueryTranslatorImpl q)
185: throws QueryException {
186: //NOTE: we avoid joining to the next table if the named property is just the foreign key value
187:
188: //if its "id"
189: boolean isIdShortcut = EntityPersister.ENTITY_ID
190: .equals(propertyName)
191: && propertyType.isReferenceToPrimaryKey();
192:
193: //or its the id property name
194: final String idPropertyName;
195: try {
196: idPropertyName = propertyType
197: .getIdentifierOrUniqueKeyPropertyName(q
198: .getFactory());
199: } catch (MappingException me) {
200: throw new QueryException(me);
201: }
202: boolean isNamedIdPropertyShortcut = idPropertyName != null
203: && idPropertyName.equals(propertyName)
204: && propertyType.isReferenceToPrimaryKey();
205:
206: if (isIdShortcut || isNamedIdPropertyShortcut) {
207: // special shortcut for id properties, skip the join!
208: // this must only occur at the _end_ of a path expression
209: if (componentPath.length() > 0)
210: componentPath.append('.');
211: componentPath.append(propertyName);
212: } else {
213: String entityClass = propertyType.getAssociatedEntityName();
214: String name = q.createNameFor(entityClass);
215: q.addType(name, entityClass);
216: addJoin(name, propertyType);
217: if (propertyType.isOneToOne())
218: oneToOneOwnerName = currentName;
219: ownerAssociationType = propertyType;
220: currentName = name;
221: currentProperty = propertyName;
222: q.addPathAliasAndJoin(path.substring(0, path.toString()
223: .lastIndexOf('.')), name, joinSequence.copy());
224: componentPath.setLength(0);
225: currentPropertyMapping = q.getEntityPersister(entityClass);
226: }
227: }
228:
229: private void dereferenceComponent(String propertyName) {
230: if (propertyName != null) {
231: if (componentPath.length() > 0)
232: componentPath.append('.');
233: componentPath.append(propertyName);
234: }
235: }
236:
237: private void dereferenceCollection(String propertyName,
238: String role, QueryTranslatorImpl q) throws QueryException {
239: collectionRole = role;
240: QueryableCollection collPersister = q
241: .getCollectionPersister(role);
242: String name = q.createNameForCollection(role);
243: addJoin(name, collPersister.getCollectionType());
244: //if ( collPersister.hasWhere() ) join.addCondition( collPersister.getSQLWhereString(name) );
245: collectionName = name;
246: collectionOwnerName = currentName;
247: currentName = name;
248: currentProperty = propertyName;
249: componentPath.setLength(0);
250: currentPropertyMapping = new CollectionPropertyMapping(
251: collPersister);
252: }
253:
254: private String getPropertyPath() {
255: if (currentProperty == null) {
256: return EntityPersister.ENTITY_ID;
257: } else {
258: if (componentPath.length() > 0) {
259: return new StringBuffer().append(currentProperty)
260: .append('.').append(componentPath.toString())
261: .toString();
262: } else {
263: return currentProperty;
264: }
265: }
266: }
267:
268: private PropertyMapping getPropertyMapping() {
269: return currentPropertyMapping;
270: }
271:
272: private void setType() throws QueryException {
273: if (currentProperty == null) {
274: type = getPropertyMapping().getType();
275: } else {
276: type = getPropertyType();
277: }
278: }
279:
280: protected Type getPropertyType() throws QueryException {
281: String propertyPath = getPropertyPath();
282: Type propertyType = getPropertyMapping().toType(propertyPath);
283: if (propertyType == null) {
284: throw new QueryException(
285: "could not resolve property type: " + propertyPath);
286: }
287: return propertyType;
288: }
289:
290: protected String[] currentColumns() throws QueryException {
291: String propertyPath = getPropertyPath();
292: String[] propertyColumns = getPropertyMapping().toColumns(
293: currentName, propertyPath);
294: if (propertyColumns == null) {
295: throw new QueryException(
296: "could not resolve property columns: "
297: + propertyPath);
298: }
299: return propertyColumns;
300: }
301:
302: private void reset(QueryTranslatorImpl q) {
303: //join = q.createJoinFragment(useThetaStyleJoin);
304: dotcount = 0;
305: currentName = null;
306: currentProperty = null;
307: collectionName = null;
308: collectionRole = null;
309: componentPath.setLength(0);
310: type = null;
311: collectionName = null;
312: columns = null;
313: expectingCollectionIndex = false;
314: continuation = false;
315: currentPropertyMapping = null;
316: }
317:
318: public void start(QueryTranslatorImpl q) {
319: if (!continuation) {
320: reset(q);
321: path.setLength(0);
322: joinSequence = new JoinSequence(q.getFactory())
323: .setUseThetaStyle(useThetaStyleJoin);
324: }
325: }
326:
327: public void end(QueryTranslatorImpl q) throws QueryException {
328: ignoreInitialJoin = false;
329:
330: Type propertyType = getPropertyType();
331: if (propertyType != null && propertyType.isCollectionType()) {
332: collectionRole = ((CollectionType) propertyType).getRole();
333: collectionName = q.createNameForCollection(collectionRole);
334: prepareForIndex(q);
335: } else {
336: columns = currentColumns();
337: setType();
338: }
339:
340: //important!!
341: continuation = false;
342:
343: }
344:
345: private void prepareForIndex(QueryTranslatorImpl q)
346: throws QueryException {
347:
348: QueryableCollection collPersister = q
349: .getCollectionPersister(collectionRole);
350:
351: if (!collPersister.hasIndex())
352: throw new QueryException("unindexed collection before []: "
353: + path);
354: String[] indexCols = collPersister.getIndexColumnNames();
355: if (indexCols.length != 1)
356: throw new QueryException("composite-index appears in []: "
357: + path);
358: //String[] keyCols = collPersister.getKeyColumnNames();
359:
360: JoinSequence fromJoins = new JoinSequence(q.getFactory())
361: .setUseThetaStyle(useThetaStyleJoin).setRoot(
362: collPersister, collectionName).setNext(
363: joinSequence.copy());
364:
365: if (!continuation)
366: addJoin(collectionName, collPersister.getCollectionType());
367:
368: joinSequence.addCondition(collectionName + '.' + indexCols[0]
369: + " = "); //TODO: get SQL rendering out of here
370:
371: CollectionElement elem = new CollectionElement();
372: elem.elementColumns = collPersister
373: .getElementColumnNames(collectionName);
374: elem.elementType = collPersister.getElementType();
375: elem.isOneToMany = collPersister.isOneToMany();
376: elem.alias = collectionName;
377: elem.joinSequence = joinSequence;
378: collectionElements.addLast(elem);
379: setExpectingCollectionIndex();
380:
381: q.addCollection(collectionName, collectionRole);
382: q.addFromJoinOnly(collectionName, fromJoins);
383: }
384:
385: static final class CollectionElement {
386: Type elementType;
387: boolean isOneToMany;
388: String alias;
389: String[] elementColumns;
390: JoinSequence joinSequence;
391: StringBuffer indexValue = new StringBuffer();
392: }
393:
394: public CollectionElement lastCollectionElement() {
395: return (CollectionElement) collectionElements.removeLast();
396: }
397:
398: public void setLastCollectionElementIndexValue(String value) {
399: ((CollectionElement) collectionElements.getLast()).indexValue
400: .append(value);
401: }
402:
403: public boolean isExpectingCollectionIndex() {
404: return expectingCollectionIndex;
405: }
406:
407: protected void setExpectingCollectionIndex() throws QueryException {
408: expectingCollectionIndex = true;
409: }
410:
411: public JoinSequence getWhereJoin() {
412: return joinSequence;
413: }
414:
415: public String getWhereColumn() throws QueryException {
416: if (columns.length != 1) {
417: throw new QueryException(
418: "path expression ends in a composite value: "
419: + path);
420: }
421: return columns[0];
422: }
423:
424: public String[] getWhereColumns() {
425: return columns;
426: }
427:
428: public Type getWhereColumnType() {
429: return type;
430: }
431:
432: public String getName() {
433: return currentName == null ? collectionName : currentName;
434: }
435:
436: public String getCollectionSubquery(Map enabledFilters)
437: throws QueryException {
438: return CollectionSubqueryFactory.createCollectionSubquery(
439: joinSequence, enabledFilters, currentColumns());
440: }
441:
442: public boolean isCollectionValued() throws QueryException {
443: //TODO: is there a better way?
444: return collectionName != null
445: && !getPropertyType().isCollectionType();
446: }
447:
448: public void addAssociation(QueryTranslatorImpl q)
449: throws QueryException {
450: q.addJoin(getName(), joinSequence);
451: }
452:
453: public String addFromAssociation(QueryTranslatorImpl q)
454: throws QueryException {
455: if (isCollectionValued()) {
456: return addFromCollection(q);
457: } else {
458: q.addFrom(currentName, joinSequence);
459: return currentName;
460: }
461: }
462:
463: public String addFromCollection(QueryTranslatorImpl q)
464: throws QueryException {
465: Type collectionElementType = getPropertyType();
466:
467: if (collectionElementType == null) {
468: throw new QueryException(
469: "must specify 'elements' for collection valued property in from clause: "
470: + path);
471: }
472:
473: if (collectionElementType.isEntityType()) {
474: // an association
475: QueryableCollection collectionPersister = q
476: .getCollectionPersister(collectionRole);
477: Queryable entityPersister = (Queryable) collectionPersister
478: .getElementPersister();
479: String clazz = entityPersister.getEntityName();
480:
481: final String elementName;
482: if (collectionPersister.isOneToMany()) {
483: elementName = collectionName;
484: //allow index() function:
485: q.decoratePropertyMapping(elementName,
486: collectionPersister);
487: } else { //many-to-many
488: q.addCollection(collectionName, collectionRole);
489: elementName = q.createNameFor(clazz);
490: addJoin(elementName,
491: (AssociationType) collectionElementType);
492: }
493: q.addFrom(elementName, clazz, joinSequence);
494: currentPropertyMapping = new CollectionPropertyMapping(
495: collectionPersister);
496: return elementName;
497: } else {
498: // collections of values
499: q.addFromCollection(collectionName, collectionRole,
500: joinSequence);
501: return collectionName;
502: }
503:
504: }
505:
506: String getCollectionName() {
507: return collectionName;
508: }
509:
510: String getCollectionRole() {
511: return collectionRole;
512: }
513:
514: String getCollectionOwnerName() {
515: return collectionOwnerName;
516: }
517:
518: String getOneToOneOwnerName() {
519: return oneToOneOwnerName;
520: }
521:
522: AssociationType getOwnerAssociationType() {
523: return ownerAssociationType;
524: }
525:
526: String getCurrentProperty() {
527: return currentProperty;
528: }
529:
530: String getCurrentName() {
531: return currentName;
532: }
533:
534: public void fetch(QueryTranslatorImpl q, String entityName)
535: throws QueryException {
536: if (isCollectionValued()) {
537: q.setCollectionToFetch(getCollectionRole(),
538: getCollectionName(), getCollectionOwnerName(),
539: entityName);
540: } else {
541: q.addEntityToFetch(entityName, getOneToOneOwnerName(),
542: getOwnerAssociationType());
543: }
544: }
545: }
|